Security model
The four guard families that make adk-rs secure by default, what trips them, and how to opt out deliberately.
adk-rs is secure by default: a handful of guards trip whenever behaviour would be unsafe — sending credentials over plaintext HTTP, exposing an unauthenticated control plane to the network, writing attacker-controlled paths, or running model-generated code with host privileges. Each guard has exactly one deliberate opt-out, and the crate as a whole is #![forbid(unsafe_code)].
1. HTTPS-only credentials
Every credential-bearing outbound client routes its destination through transport_security::require_secure_url(url, field). The rule: the URL must be https:// (case-insensitive) or a loopback http:// host. Anything else is rejected at construction time, before any request is built.
- Who checks: the provider clients (
Gemini,Anthropic,OpenAi),RestApiTool(OpenAPI tools), the MCP HTTP transport, the A2A client when its extra headers look credential-bearing (Authorization,Cookie,Proxy-Authorization,x-api*,x-auth*— the classification is the public helpertransport_security::header_looks_credential_bearing(name)), and A2A push-notification webhook URLs. - Loopback exemption:
localhost, any127.0.0.0/8address, and[::1]pass — this is what lets tests and local mocks (e.g. wiremock, Ollama) work over plain HTTP. - No parser differential:
is_secure_urlparses with the WHATWGurlcrate — the same parser reqwest uses — so the validated host is exactly the host the request goes to.http://127.0.0.1@evil.example.comis rejected, and so ishttp://evil.com\@localhost/: in WHATWG http(s) URLs the\terminates the authority, so the real host isevil.com. Unknown schemes (ftp://,file://, scheme-less strings) always fail. - No redirect leaks: every credential-bearing outbound client disables HTTP redirects, because reqwest re-sends custom headers like
x-api-keyto redirect targets — the three provider clients (and their embedders), the MCP HTTP transport, the A2A client and its agent-card fetch,RestApiTool, the A2A push notifier, and the OAuth token clients. - Secret-safe errors: the error message names the offending field (e.g.
OpenAiConfig.base_url), never the URL itself, because URLs can carry secrets in their userinfo.
2. Loopback-only dev servers
Both serve entry points — the HTTP server (adk_rs::server::serve_with) and the A2A bridge (adk_rs::a2a::serve_with) — refuse to bind a non-loopback address when no auth token is configured. The error is immediate and explicit; nothing listens.
- Opt-out 1 (preferred): configure a bearer token —
AppState::with_bearer_token(...)for the HTTP server,A2aServerConfig::with_bearer_token(...)for A2A, or--auth-token/ADK_WEB_TOKENon the CLI. Every request must then carryAuthorization: Bearer <token>. - Opt-out 2 (deliberate): pass
ServeOptions { dangerously_allow_unauthenticated_remote: true }(CLI:--dangerously-allow-unauthenticated-remote). The name is the warning. - Never silent: even with auth configured, binding a non-loopback interface logs a
warnline describing exactly who can now reach the agents.
Token comparison is constant-time: the middleware XOR-folds the byte difference across the full token rather than short-circuiting, so timing cannot leak a prefix match. (Length mismatch returns early — token length is fixed per deployment, not a secret.) Failed auth returns 401 with a WWW-Authenticate: Bearer header.
3. Filesystem artifact path sanitization
FileArtifactService (artifacts) builds paths as <root>/<app>/<user>/<session>/<filename>/vNNNNNN.json — four components that can all be attacker-influenced through the HTTP API. Each component is passed through sanitize, which:
- replaces every character that is not alphanumeric,
_,-, or.with_, - rewrites dot-only components (
.,..,..., …) to_, so..segments can never climb out of the artifact root, and - collapses empty input to
_, becausePath::join("")is a no-op that would silently merge two adjacent components.
There is no opt-out: sanitization is unconditional on the filesystem backend. If you need raw names, store them in artifact metadata, not in the path.
4. Locked-down container code execution
ContainerCodeExecutor (feature code-exec-docker, see Code execution) runs each call in a fresh ephemeral container with a hardened docker run argv by default:
| Flag | Default | Relax with |
|---|---|---|
--network=none | no outbound network | with_extra_args (deliberate) |
--read-only + --tmpfs=/tmp:rw,exec,size=64m | read-only rootfs, small writable /tmp | with_extra_args |
--memory / --memory-swap | 256m (swap pinned to the same value) | with_memory("1g") |
--cpus | 1.0 | with_cpus("0.5") |
--pids-limit | 128 (fork-bomb cap) | with_pids_limit(n) |
--user | 65534:65534 (nobody, never root) | with_user("uid:gid") |
--cap-drop=ALL + --security-opt=no-new-privileges | on | the drop_capabilities field |
Every cap is a typed with_* builder method, so loosening the sandbox is an explicit, reviewable line of code. The argv builder (build_run_args) is public so tests can assert the policy. By contrast, LocalCodeExecutor is subprocess isolation only — the crate documents it as not a security boundary.
What trips, at a glance
| You do this | What happens | Deliberate opt-out |
|---|---|---|
Point a provider / RestApiTool / MCP / A2A client with credentials at http://api.example.com | Error::Config at construction | Use HTTPS or a loopback proxy |
serve on 0.0.0.0 with no token | Error::Config, nothing binds | Bearer token, or dangerously_allow_unauthenticated_remote |
Register an A2A push webhook at a public http:// URL | JSON-RPC -32602 INVALID_PARAMS | Use an HTTPS webhook receiver |
Send filename: "../../etc/cron.d/x" to the artifact API | Component rewritten to _; write stays under the root | None |
| Run model-emitted code in Docker | No network, read-only rootfs, nobody user, resource caps | Typed with_* builders |
- HTTP server —
ServeOptionsand the bearer middleware in context. - A2A protocol — the second server with the same bind policy.
- Code execution — executor APIs and retry behaviour.
- Auth — credential storage and OAuth flows (a separate concern from transport security).