Documentation

Threat Model

How Duct enforces safety between humans, agents, peer shells, and your upstream APIs — and what integrators must still verify on their side.

Operator reference

The canonical long-form document lives at docs/threat-model.md in the monorepo. This page summarizes the enforcement model after the 2026-07 security remediation pass.

Actors and trust boundaries

End user
Trusted for their own session; approves side-effects in the shell UI.
Your backend
Trusted by you — holds DUCT_SECRET_KEY and user sessions.
Agent (at_)
Untrusted automation — cannot act as a user without a verified consent token.
Peer shell
Untrusted cross-tenant caller — subject to intershell gates and delegation.
agent-proxy
Primary policy enforcement point before your upstream APIs.
Modality router / LLM
Untrusted computation — deterministic guards apply before tool execution.

Your API must verify Bearer tokens independently. X-Duct-Proxy is a hint, not proof of identity.

Token model

shell_token
Shell identity for agents/services — typically 1 hour TTL.
duct_delegated
User session delegated to the shell. Default TTL 60s; doc examples often use 300s. Always verify with expectedShellId.
uct_
User consent for agent-scoped actions. Verified whenever any gate relied on consent.
dt_
Cross-shell delegation — 60s TTL, single-use per jti (IS-36).
Deeplink JWT
Browser handoff — verify server-side; enforce jti replay protection in production.

Confirmations (side-effects)

Agents receive 202 confirmation_required with an approval_url for a human. Approve/deny requires an approval_nonce embedded only in that URL — not returned to the agent. The execution_token is delivered on the approval page HTML only.

Intershell invariants (IS-*)

Cross-shell calls are gated on intershell_enabled, registry_visibility: public, explicit agentAccessible: true, optional allowedCallers, and the consent matrix. The same gates apply on /invoke for cross-shell agent callers — not only on /intershell/message.

IS-5
Call-chain HMAC required when a signing key is configured; max depth 5.
IS-17
Max one cross-shell hop per human chat turn.
IS-18
30 req/min per caller→target; 120 req/min per target inbound.
IS-19
Bidirectional audit on caller and target.
IS-36
Delegation tokens single-use per verified jti.
IS-14 / IS-49
Cross-shell results tagged [EXTERNAL DATA]; tags stripped from user-visible synthesis.

Details: Inter-Shell Protocol.

SSRF posture

Upstream fetches resolve DNS once, reject private/loopback/metadata addresses, pin the resolved IP, and use redirect: manual with per-hop re-validation. Production must run with ALLOW_PRIVATE_UPSTREAM unset. Local Docker enables private upstreams for developer convenience only.

Residual risk

A manifest baseUrl pointing at a public attacker origin can still harvest delegated tokens. Treat manifest push as a privileged operation and monitor baseUrl changes.

Prompt injection

Cross-shell payloads are tagged as external data. Deterministic guards in the modality router enforce hop caps and tool policy — not prompt text alone. Synthesis is terminal: the shell ends the turn after synthesis. Side-effects still require human confirmation even if routing misfires.

Integrator checklist

Secret key
DUCT_SECRET_KEY stays server-side — symmetric HS256 signing secret, never in client bundles.
Delegated tokens
verifyDuctToken with expectedShellId in middleware.
Consent tokens
POST /v1/verify/consent-token for uct_ when partner agents call user-scoped APIs.
Deeplinks
JTI replay store (Redis) in production.
Manual embeds
Validate postMessage origin and source.