Skip to main content
Securing an agent isn’t one thing. It’s layers: auth at the edge to keep strangers out, RBAC to scope what each caller can run, request isolation so users can’t see each other’s data, schema enforcement so even a misbehaving agent can’t write where it shouldn’t, sandboxes for tools that touch the outside world, and human approval for actions that should never run unsupervised.
LayerWhat it stopsMechanism
AuthenticationUnauthenticated requestsJWT validation at the middleware
AuthorizationAuthorized but out-of-scope requestsRBAC scopes on every endpoint
Request isolationCross-user and cross-session leakageSessions and memory scoped per user_id
Schema enforcementThe agent writing where it shouldn’tRead-only roles, schema-scoped engines
Tool sandboxingTools doing damage outside their laneSandboxes, allowlists, path scoping
Human approvalIrreversible actions running unsupervisedrequires_confirmation, @approval (see Human Approval)
AgentOS gives you defaults at every layer. The rest of this page covers each one and where you’d want to override the default. Network-layer controls (rate limiting, WAF, IP allowlists, mTLS) live at your reverse proxy or API gateway, not here.

Authentication

A production AgentOS sits behind JWT-validating middleware. Every request carries a token signed by the control plane; your service verifies it with a public key.
from agno.os import AgentOS

agent_os = AgentOS(
    agents=[agent],
    db=db,
    authorization=True,
)
# Or set RUNTIME_ENV=prd and JWT_VERIFICATION_KEY=...
When authorization=True, every endpoint except /health and /openapi.json requires a valid JWT.

Setup flow

  1. Deploy AgentOS with authorization=True and JWT_VERIFICATION_KEY set. The app crash-loops until the key is valid. That’s expected.
  2. Open os.agno.comAdd OSLive → paste your URL.
  3. Go to SettingsGenerate key pair.
  4. Copy the public key. Paste into your env (JWT_VERIFICATION_KEY).
  5. Redeploy.
The control plane keeps the private key. Your service only ever sees the public key. JWTs are signed by the control plane and verified by your service. This is the flow Dash and Coda walk through end-to-end.

Custom JWT configuration

If you’re issuing JWTs from your own auth provider, pass an AuthorizationConfig:
from agno.os import AgentOS, AuthorizationConfig

agent_os = AgentOS(
    agents=[agent],
    db=db,
    authorization=True,
    authorization_config=AuthorizationConfig(
        verification_keys=["public-key-1", "public-key-2"],   # rotation
        algorithm="RS256",
        scopes_claim="scopes",
        user_id_claim="sub",
    ),
)
verification_keys is a list, so you can rotate keys without downtime. Old tokens validate against the old key, new tokens against the new one. Drop the old key once tokens have aged out.

Authorization (RBAC scopes)

Every JWT carries a scopes claim. Endpoints are gated on scopes.
ScopeGrants
agents:readList agents and read their config
agents:<id>:runRun a specific agent
agents:*:runRun any agent
teams:<id>:runRun a specific team
teams:*:runRun any team
workflows:<id>:runRun a specific workflow
workflows:*:runRun any workflow
agent_os:adminFull access including session, memory, and trace queries
A typical end-user gets agents:my-agent:run. An internal service gets agent_os:admin. The control plane mints both with appropriate scopes. See RBAC for the full setup.

Request isolation

Every request runs in its own context. State doesn’t bleed between users, agents, or sessions.
GuaranteeHow
No cross-user stateSessions and memory are scoped per user_id. The JWT’s user_id_claim decides who’s asking.
No cross-session stateHistory is scoped per (user_id, session_id). Two open sessions for the same user stay independent.
No tool side-effect leakageTool calls run in-process within a single request’s lifecycle. No shared mutable state.
No model-level mixingEach LLM call carries only the messages for the current run. No global prompt caching across users.
This is the baseline for multi-tenant deployments. Combined with agent_os:admin for service-level callers, it gives you per-user data isolation without a separate per-tenant runtime.

Schema enforcement

For agents that touch databases, API-level auth isn’t enough. A model can decide to write DROP TABLE users regardless of what its instructions say. Stop it at the engine, not the prompt.
  • Read-only roles. PostgreSQL connections opened with default_transaction_read_only=on reject any write attempt no matter what SQL the model generates. This is what Dash uses on the Analyst agent.
  • Schema-scoped writes. A SQLAlchemy event listener that blocks DDL/DML targeting forbidden schemas. Dash uses this on the Engineer agent: writes go to the dash schema, never public.
These are infrastructure guardrails, not prompt instructions. They hold even if the model goes off-script.

Tool sandboxing

Most tools are pure functions. The ones that aren’t (HTTP calls, shell commands, code execution, file I/O) need their own guardrails.
Tool categoryRecommended guard
Database queriesRead-only role, schema scoping
HTTP requestsAllowlist of hosts, no localhost
Shell or code executionSandbox (subprocess in container, separate worktree, strict ulimits)
File I/OPath scoping (one root directory, no ..)
Money or irreversible actionsrequires_confirmation=True + @approval(type="required") (see Human Approval)
Coda uses a git worktree sandbox for code-writing tools. The Coder agent can write anything it likes, but only inside an isolated worktree. Main is never touched.

Human approval

The last layer. Some actions should never run unsupervised — refunds, deletes, prod deploys, anything irreversible. AgentOS pauses the run, surfaces the pending approval, resumes when a human signs off. Two patterns: requires_confirmation for the user asking the agent, @approval for a designated admin with policy authority. Full details on the Human Approval page.

Defaults

ConcernDefault behavior
Auth in productionRUNTIME_ENV=prd enables JWT validation
Auth in devRUNTIME_ENV=dev skips JWT validation so you can iterate
User isolationSessions and memory keyed on user_id from the JWT
Trace tamperingTraces are append-only; no API to mutate past spans
Schedule abuseScheduler tools require agent_os:admin scope to create system-level schedules

Next

Interfaces →