Security Model¶
AgenticAPI implements defense-in-depth for safe agent code execution. Every piece of code an LLM generates passes through multiple safety layers before execution.
Authentication¶
AgenticAPI provides HTTP authentication schemes following FastAPI's patterns:
Security Schemes¶
| Scheme | What it extracts |
|---|---|
APIKeyHeader(name="X-API-Key") |
API key from a request header |
APIKeyQuery(name="api_key") |
API key from a query parameter |
HTTPBearer() |
Bearer token from Authorization: Bearer <token> |
HTTPBasic() |
Username/password from Authorization: Basic <base64> |
Authenticator¶
Combines a scheme with a verify function:
from agenticapi.security import APIKeyHeader, Authenticator, AuthUser
scheme = APIKeyHeader(name="X-API-Key")
async def verify(credentials):
if credentials.credentials in VALID_KEYS:
return AuthUser(user_id="u1", username="alice", roles=["admin"])
return None # -> AuthenticationError (401)
auth = Authenticator(scheme=scheme, verify=verify)
# Per-endpoint
@app.agent_endpoint(name="orders", auth=auth)
# Or app-wide default
app = AgenticApp(auth=auth)
AuthUser¶
Available in handlers via context.auth_user:
@app.agent_endpoint(name="orders", auth=auth)
async def handler(intent, context):
user = context.auth_user # AuthUser(user_id, username, roles, scopes, metadata)
if "admin" not in user.roles:
raise AuthorizationError("Admin role required")
Layer 1: Prompt Design¶
User input is XML-escaped before inclusion in LLM prompts using html.escape() to prevent prompt injection attacks.
Files: runtime/prompts/code_generation.py, runtime/prompts/intent_parsing.py
Layer 2: Static AST Analysis¶
Generated code is parsed into an AST and checked for dangerous patterns:
- Forbidden imports: configurable allow/deny lists, handles multi-line and submodule imports
- eval/exec detection: both direct calls (
eval()) and attribute calls (builtins.eval()) - Dynamic imports:
__import__()via name or attribute access - Dangerous builtins:
compile,globals,locals,vars,breakpoint,help,getattr,setattr,delattr - File I/O:
open()via direct or attribute calls - Syntax errors: code that can't be parsed is rejected
File: harness/sandbox/static_analysis.py
Layer 3: Policy Evaluation¶
Four policy types evaluate generated code:
| Policy | What it checks |
|---|---|
CodePolicy |
Import allowlist/denylist, eval/exec, dynamic imports, network access, max lines |
DataPolicy |
SQL table/column access controls, DDL prevention, restricted columns (with quoted identifier support), result row limits |
ResourcePolicy |
Loop depth, large range() detection, recursion warnings |
RuntimePolicy |
AST node count (complexity), line count |
DataPolicy strips SQL comments (--, /* */) before write detection to prevent bypass.
Layer 4: Approval Workflow¶
Write operations can require human approval:
ApprovalWorkflow(rules=[
ApprovalRule(
name="write_gate",
require_for_actions=["write", "execute"],
approvers=["admin"],
timeout_seconds=1800,
),
])
State transitions: PENDING -> APPROVED | REJECTED | EXPIRED | ESCALATED
Resolution uses asyncio.Lock to prevent race conditions on concurrent resolve() calls.
Layer 5: Process Sandbox¶
- Code is base64-encoded for safe transport to the subprocess (no repr() injection)
- Execution in isolated subprocess via
asyncio.create_subprocess_exec(noshell=True) - Timeout enforcement with
asyncio.wait_for - Pre-populated namespace includes
datadict with tool results (no direct tool access) - Temp files cleaned up in
finallyblock with null-safety guard
Layer 6: Post-Execution Validation¶
- ResourceMonitor: checks CPU, memory, wall time against limits
- OutputSizeMonitor: rejects oversized output (default 1MB)
- OutputTypeValidator: ensures JSON-serializable results
- ReadOnlyValidator: warns if read intents produce write-like output
Layer 7: Audit Trail¶
Every execution is recorded as an ExecutionTrace containing intent, generated code, policy evaluations, execution result, duration, and errors. AuditRecorder has bounded storage (max_traces=10000) to prevent memory exhaustion.
File Upload Security¶
File uploads are accepted via multipart/form-data when a handler declares an UploadedFiles parameter. The intent field is extracted from the form data and processed through the normal intent pipeline.
- Uploaded files are held in memory as
bytes— no temp files written to disk - The
UploadFiledataclass exposesfilename,content_type,content, andsize - File content is not passed to the LLM or sandbox — handlers process files directly
- Maximum file size: 50MB per file (returns HTTP 413 if exceeded)
python-multipartdependency handles multipart parsing
MCP Security¶
When exposing endpoints via MCP (enable_mcp=True), the same harness pipeline applies — MCP tool calls go through intent parsing, policy evaluation, and sandbox execution. Only explicitly opted-in endpoints are exposed.
Known Limitations (Phase 1)¶
ProcessSandboxprovides process-level isolation, not kernel-level (ContainerSandbox is Phase 2)- AST analysis detects known patterns; sophisticated obfuscation may bypass it
- Sessions, audit traces, and approval requests are in-memory (not persistent)
- API keys must be provided via environment variables, never hardcoded
- MCP transport does not yet support authentication/authorization
- Approval resolution is programmatic only (no built-in admin HTTP endpoint)
- File uploads are limited to 50MB per file (returns 413 if exceeded); additional validation should be added in handler logic