Containment & Governance
The Containment extension hardens INK against abuse by adding transport-scoped delegation, discovery minimization and per-correlation handshake budgets.
Transport-Bound Authorization
Delegation tokens can be scoped to specific transport channels via the allowedTransports constraint. Transport scoping is a containment-extension mechanism and is specified here, not in the authorization grant or chain, whose attenuation works on the opaque scope tokens instead.
Standard transport identifiers:
| Transport | Description |
|---|---|
ink_http | Standard HTTPS INK endpoints |
ink_ws | WebSocket connections |
extension_api | Browser/app extension API calls |
voice | In-app voice channels |
line_phone | PSTN telephony |
human_review_queue | Queued for human review |
Transport-bound authorization is enforced on both INK HTTP and extension API surfaces, a token scoped to ["ink_http"] cannot be used to call extension API endpoints and vice versa.
Messages arriving on a transport not in the token’s allowedTransports are rejected with transport_scope_violation.
Version-Gated Migration
- v0.3+ tokens (with
tokenVersionfield): omittedallowedTransportsdefaults to["ink_http"](least privilege) - Legacy tokens (no
tokenVersion): default to the permissive set["ink_http", "extension_api", "voice", "line_phone"]during a migration window, matching the transports that existed before transport scoping. Once the window closes they default to["ink_http"]only, the same as a v0.3+ token
The window is a deployment parameter, not a protocol constant. The spec sets it at 90 days from the deploy that introduced transport scoping, or earlier once audit logs show no transport_scope_violation events from legacy tokens for 14 consecutive days, and the end date lives in deployment config. Whether any given deployment is still inside its window is that deployment’s fact to publish, not something a counterparty can infer from this page.
Capability-Gated Discovery
Agent Cards support four visibility levels that control what unauthenticated requests can see. See Agent Card. Visibility for the full table.
When an agent’s visibility is network_only or capability_gated, unauthenticated GET /ink/v1/:agentId/agent.json returns a redacted card that confirms the agent exists and supports INK but strips all sensitive fields.
Redacted Card
The redacted card includes only:
agentId, identitydisplayName, human-readable namesupportsInk: true, protocol support flagdiscoveryMode: "authenticate_for_details", instructs the caller to authenticatevisibility, the card’s visibility levelupdatedAt, freshness timestamp
Capabilities, endpoints, keys, availability and profile data are all stripped.
Authenticated Query Flow
Denial reasons: unknown_requester, insufficient_trust, not_connected.
The difference between network_only and capability_gated:
network_onlygrants the full card to any authenticated INK peercapability_gatedfilters by relationship tier, only peers meeting a trust threshold receive the full card
Handshake Flood Resistance
Per-correlation budgets and per-sender rate limits prevent handshake amplification attacks. These limits are enforced on all handshake ingress paths (INK HTTP and extension API).
Sender Map Limits
The senders map that tracks per-sender state is bounded so attacker-driven growth cannot exhaust memory. When the cap is reached the least-recently-used sender entry is evicted. The cap itself is not a protocol requirement: the containment Phase 1 spec requires a bound and pins only the correlation-tracking bound (10,000 active correlations, LRU on overflow). The reference HandshakeBudgetTracker defaults to 1,000 sender entries and 10,000 correlations, both configurable. Pick your own bound; do not implement 1,000 as a requirement.
Per-Correlation Budgets
Each correlationId (handshake session) has bounded state. These limits are normative: containment Phase 1 §3.1 says a recipient MUST enforce them and fixes each value.
| Limit | Value | Description |
|---|---|---|
| Max challenges | 3 | Maximum network.tulpa.challenge messages per correlation |
| Max rejections | 1 | Terminal, no further messages accepted |
| Max resolutions | 1 | Terminal, no further messages accepted |
| Max transitions | 5 | Total state transitions (intent + challenges + resolution) |
| TTL | The intent’s expiresAt or 24h, whichever is shorter | After expiry no further handshake messages are accepted |
Terminal states: network.tulpa.rejection and network.tulpa.resolution are terminal, no further messages are accepted for that correlationId.
Per-Sender Rate Limits
Per-sender limits are a SHOULD, and the values below are recommended defaults rather than protocol requirements (containment Phase 1 §3.4). An implementation MAY adjust them by trust tier, for example granting connected peers more headroom. The reference tracker ships these values and exposes each one as configuration.
Sliding window counters per sender principal:
| Limit | Recommended default |
|---|---|
| New intents per minute | 10 |
| New intents per hour | 60 |
| Handshake messages per minute | 30 |
Violation Behavior
The first budget violation for a given sender returns a typed rejection with a backoffHint:
{ "protocol": "ink/0.1", "type": "network.tulpa.rejection", "reason": "handshake_budget_exhausted", "backoffHint": { "retryAfterSeconds": 60, "backoffClass": "sender" }, "nonce": "<base64url>", "timestamp": "..."}Subsequent violations from the same sender are silently dropped, no response is sent, preventing amplification.
Containment Rejection Reasons
| Reason | Description |
|---|---|
handshake_budget_exhausted | Per-correlation budget exceeded |
counterparty_cooldown | Recipient broadly rate-limiting |
sender_rate_limited | Per-sender sliding window exceeded |
delegation_budget_exhausted | Delegation issuance limit hit |
transport_scope_violation | Transport not in delegation token scope |
See Error Codes for HTTP status mappings.
Governance Advertisement
Agents advertise containment parameters in their Agent Card’s governance block. See Agent Card. Governance.
Audit Events
Containment events are logged to the agent’s hash-chained audit log:
| Event | Description |
|---|---|
transport_scope_violation | Message rejected for transport mismatch |
handshake_rate_limited | Per-sender rate limit triggered |
handshake_budget_exhausted | Per-correlation budget exceeded |
discovery_query_received | Authenticated card query received |
discovery_query_granted | Full card returned to authenticated requester |
discovery_query_denied | Card query denied |
See Audit Trail. Event Types for the full list.