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
This page summarizes the current platform enforcement model for integrators and operators.Actors and trust boundaries
Your API must verify Bearer tokens independently. X-Duct-Proxy is a hint, not proof of identity.
Token model
Confirmations (side-effects)
Side-effect actions return ask from the permission gate. Agents receive 202 confirmation_required with an approval_url. After approval, retry with the issued execution_token.
Operator session (default). DUCT_APPROVAL_MODE=operator_session — a signed-in dashboard operator who owns the shell approves via the confirmation page or dashboard JSON API. This proves an operator account session, not end-user presence.
Link holder (opt-in). DUCT_APPROVAL_MODE=link_holder — approval uses a browser-bound httpOnly approval-nonce cookie. The approval_url is opaque (no nonce in the URL or status poll). This proves channel possession only — optional CAPTCHA can be enabled. CLI API keys do not qualify as operator approval credentials.
Audit records which factor class gated each decision (operator_session, link_holder, webauthn, policy_only). Duct does not infer a verified human from HTML or cookies alone.
Approval trust by caller type
How approval is proven depends on caller profile and deployment mode. Integrators requiring provable human-in-the-loop must choose the right case — not all paths guarantee an accountable human reviewed the action.
Case 6 — explicit non-guarantee
When the caller is an autonomous agent with no delegated user context and no enforced possession factor, Duct still blocks silent mutation (consent + confirmation tokens), but cannot guarantee that a specific accountable human reviewed the action. Do not rely on agent-only confirmation polling alone for provable HITL — use Cases 1–2 (or future Case 4 WebAuthn).Cross-shell calls
Cross-shell agent traffic must use the intershell API (/v1/intershell/invoke, /v1/intershell/message, and related endpoints). Direct POST /v1/shells/:id/invoke or POST /v1/shells/:id/message where the caller shell differs from the target returns 403 use_intershell_endpoint. Same-shell agent tokens, embeddable-shell bypass, and delegation tokens on same-target invoke are unchanged.
Intershell calls are gated on intershell_enabled, registry_visibility: public, explicit agentAccessible: true, optional allowedCallers, and the consent matrix. The same gates apply on HTTP and WebSocket transports.
Details: Inter-Shell Protocol.
Execution ambiguity (outcome_unknown)
After your upstream API accepts a side-effect but before Duct durably records idempotency completion, retries with the same Idempotency-Key return 409 outcome_unknown (DUCT_OUTCOME_UNKNOWN, retryable: true) instead of dispatching a blind second mutation. Consumed execution tokens without a replayable idempotency entry follow the same rule. Integrators should backoff and retry — or reconcile with your upstream using the idempotency key.
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. In production, ALLOW_PRIVATE_UPSTREAM must remain unset so upstream targets cannot include private or loopback addresses.
Residual risk
A manifestbaseUrl 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 intent 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.