Security¶
Cognition is designed to run untrusted agent workloads in multi-tenant environments. Security controls are applied at every layer — network, process, filesystem, and API. This document describes each control, where it is implemented, and how to configure it.
Multi-Tenant Session Scoping¶
Implemented in server/app/api/scoping.py.
Session scoping is Cognition's multi-tenancy mechanism. When enabled, every session carries a set of key-value pairs (its effective_scope), and API requests must supply matching headers. Scopes prevent one tenant's sessions from being visible or accessible to another tenant.
Scope keys are builder-defined — Cognition does not hardcode a vocabulary. Choose keys that match your application's tenancy model (e.g. user, tenant, project, env).
How It Works¶
COGNITION_SCOPING_ENABLED=trueactivates scope enforcement.COGNITION_SCOPE_KEYSdefines which key names are required (default:["user"]).- For each key in
scope_keys, the request must include anx-cognition-scope-{key}header. - Missing headers return
403 Forbiddenimmediately — fail-closed. - When listing sessions, results are filtered to only sessions whose scope values match the request headers.
- The resulting
effective_scopedict propagates throughCognitionContext→ LangGraphruntime.context→ middleware → tools.
Configuration¶
Usage¶
# Create a session scoped to user + project
curl -X POST http://localhost:8000/sessions \
-H "Content-Type: application/json" \
-H "X-Cognition-Scope-User: alice" \
-H "X-Cognition-Scope-Project: proj-123" \
-d '{"title": "My session"}'
# List sessions — only returns sessions for this user+project
curl http://localhost:8000/sessions \
-H "X-Cognition-Scope-User: alice" \
-H "X-Cognition-Scope-Project: proj-123"
A request without the required scope headers returns:
Scope Matching Logic¶
SessionScope.matches(other_scopes) checks that every key in the session's scope has a matching value in the request's scope. Sessions with no scopes are only visible when scoping is disabled.
Sandbox Isolation¶
Implemented in server/app/agent/sandbox_backend.py and server/app/execution/backend.py.
No shell=True¶
Every command executed by the agent goes through shlex.split() followed by subprocess.run() with shell=False. This eliminates shell injection vulnerabilities — a command like ; rm -rf / cannot be executed because the shell metacharacters are passed as literal argument strings.
Protected Paths¶
CognitionLocalSandboxBackend maintains a list of protected paths that cannot be modified by the agent. By default, .cognition/ is protected. Any write operation (file write, file delete, directory creation inside a protected path) is blocked before execution.
Docker Isolation¶
When COGNITION_SANDBOX_BACKEND=docker, code runs in a Docker container with all available Linux security controls applied:
| Control | Setting | Effect |
|---|---|---|
| Capabilities | cap_drop: ALL |
All Linux capabilities removed |
| Privilege escalation | no-new-privileges: true |
Processes cannot gain privileges |
| Root filesystem | read_only: true |
Cannot write outside allowed paths |
| Writable mounts | tmpfs:/tmp, tmpfs:/home |
Only temp directories are writable |
| Network | network_mode: none (default) |
No outbound or inbound network access |
| Memory | 512m (default) |
Hard memory ceiling |
| CPU | 1.0 (default) |
CPU quota |
The container is created from cognition-sandbox:latest, a minimal image without unnecessary tools. See Deployment for building the sandbox image.
Tool Security¶
Implemented in server/app/agent/middleware.py:ToolSecurityMiddleware.
Trust Model¶
Tool source code (both file-discovered and API-registered) executes with full Python privileges inside the sandbox backend. Cognition does not perform AST scanning or Python-level restrictions on tool code — these were removed as they were bypassable via reflection and created a false sense of security.
The real security boundaries are:
| Boundary | Mechanism |
|---|---|
| API authorization | Gateway/proxy layer — Cognition assumes authenticated callers |
| Per-name tool blocking | ToolSecurityMiddleware — COGNITION_BLOCKED_TOOLS blocklist enforced at call time |
| Process isolation | Docker sandbox backend — container per session |
| Network isolation | Docker network_mode=none |
| Filesystem isolation | CognitionLocalSandboxBackend protected paths |
| Memory isolation | LangGraph Store namespaces scoped per tenant via CognitionContext.effective_scope |
POST /tools (API-registered tools) executes arbitrary Python with full privileges. Restrict this endpoint to authorized administrators at the Gateway/proxy layer.
For a detailed explanation, see AGENTS.md — Tool Security Trust Model.
Tool Namespace Allowlist¶
Tool imports are validated against a set of trusted namespaces before the agent starts. This prevents agent definitions from loading arbitrary Python code by specifying a malicious import path.
If a tool's dotted path does not start with a trusted namespace, it is rejected at agent creation time. An empty trusted_tool_namespaces list disables the check (all namespaces allowed — suitable only for development).
Tool Blocklist¶
ToolSecurityMiddleware intercepts every tool call before execution. If the tool name is in the blocked list, the call returns an error ToolMessage without executing the tool.
The blocked call returns:
MCP Remote-Only Policy¶
Implemented in server/app/agent/mcp_client.py:McpServerConfig.
MCP (Model Context Protocol) tool servers must be remote HTTP/HTTPS servers. Stdio-based MCP servers (which would spawn a local subprocess) are not supported:
@field_validator("url")
def validate_url(cls, v: str) -> str:
if not v.startswith(("http://", "https://")):
raise ValueError("MCP server URL must be HTTP or HTTPS (no stdio)")
return v
This policy ensures MCP tool servers cannot be used to execute arbitrary local processes.
Additional MCP security measures:
- Header redaction — GET /mcp-servers returns empty headers dicts to prevent credential leakage
- File-managed immutability — servers from .cognition/config.yaml cannot be modified via API (409 on mutation)
- Scope injection — X-Cognition-Scope-* headers are automatically added to MCP requests via ToolCallInterceptor
- Tool name prefixing — tool_name_prefix=True on MultiServerMCPClient prevents tool name collisions
A2A Protocol Boundary¶
The A2A (Agent-to-Agent) protocol adapter is a Cognition protocol surface, not an app-layer concern.
Security boundary: A2A requests must include X-Cognition-Scope-* headers. Per-agent card discovery at /a2a/{agent_name}/.well-known/agent-card.json, root discovery, and JSON-RPC endpoints are scope-filtered — only agents visible in the caller's scope are discoverable and invocable. Dynamically registered agents remain bound to the scope used at creation time, so callers must provide the same trusted scope headers for discovery and invocation.
Builder responsibility: Cognition does not perform end-user authentication on A2A requests. Authorization must be handled at the gateway/proxy layer before requests reach Cognition. The a2a_exposed flag on AgentDefinition controls which agents are visible — set it explicitly to True only for agents intended for external A2A access.
Global disable: Set COGNITION_A2A_ENABLED=false to prevent the A2A protocol surface from being mounted at all. When disabled, the endpoints do not exist and GET /capabilities reports a2a: false.
Rate Limiting¶
Implemented in server/app/rate_limiter.py.
The rate limiter uses a token bucket algorithm with one bucket per scope key (or per IP address when scoping is disabled). Buckets refill continuously at the configured rate.
| Setting | Variable | Default |
|---|---|---|
| Requests per minute | COGNITION_RATE_LIMIT_PER_MINUTE |
60 |
| Burst allowance | COGNITION_RATE_LIMIT_BURST |
10 |
The burst parameter allows short-lived traffic spikes above the per-minute rate. Once the burst allowance is exhausted, requests are throttled until the bucket refills.
Exceeded limits return:
HTTP/1.1 429 Too Many Requests
Retry-After: 4
{"error": "Rate limit exceeded", "code": "RATE_LIMITED"}
Buckets are keyed on the scope value (e.g. user:alice) when scoping is enabled, or on the client IP address otherwise. Inactive buckets are cleaned up every 5 minutes.
CORS¶
Implemented in server/app/main.py via FastAPI's CORSMiddleware.
All CORS settings are configurable without code changes:
COGNITION_CORS_ORIGINS=["https://app.example.com", "https://admin.example.com"]
COGNITION_CORS_METHODS=["GET", "POST", "PATCH", "DELETE"]
COGNITION_CORS_HEADERS=["Content-Type", "Authorization", "X-Cognition-Scope-User"]
COGNITION_CORS_ALLOW_CREDENTIALS=true
In development, COGNITION_CORS_ORIGINS=["*"] is acceptable. In production, restrict to known origins.
Security Headers¶
server/app/api/middleware.py:SecurityHeadersMiddleware adds the following headers to every response:
| Header | Value |
|---|---|
X-Content-Type-Options |
nosniff |
X-Frame-Options |
DENY |
X-XSS-Protection |
1; mode=block |
These prevent MIME sniffing, clickjacking, and reflected XSS attacks in browser contexts.
Secrets Management¶
- API keys and credentials must be set via environment variables or
.envfiles — never in YAML config files committed to version control. - The
GET /configendpoint returns sanitized configuration:SecretStrfields are masked as**redacted**. - The
.envfile is listed in.gitignoreby default. - The
COGNITION_OPENAI_API_KEYand similar secret settings use Pydantic'sSecretStrtype so they never appear in logs or error messages.
Production Security Checklist¶
- [ ] Set
COGNITION_SCOPING_ENABLED=trueand configureCOGNITION_SCOPE_KEYS(builder-defined keys matching your tenancy model) - [ ] Set
COGNITION_SANDBOX_BACKEND=docker - [ ] Set
COGNITION_DOCKER_NETWORK=none - [ ] Restrict
POST /toolsto authorized administrators at the Gateway/proxy layer - [ ] Set
COGNITION_TRUSTED_TOOL_NAMESPACESto your allowed namespaces - [ ] Set
COGNITION_CORS_ORIGINSto your specific frontend domains - [ ] Set
COGNITION_RATE_LIMIT_PER_MINUTEappropriate for your load - [ ] Never commit API keys; use
.envor secrets management (Vault, AWS Secrets Manager) - [ ] Run the sandbox image from a minimal, audited base image
- [ ] Set
COGNITION_PROTECTED_PATHSto include any sensitive directories - [ ] Review which agents have
a2a_exposed: true— only expose agents intended for external A2A access - [ ] Restrict
/mcp-serversCRUD to authorized administrators (MCP server headers contain credentials)