Skip to content

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:

TransportDescription
ink_httpStandard HTTPS INK endpoints
ink_wsWebSocket connections
extension_apiBrowser/app extension API calls
voiceIn-app voice channels
line_phonePSTN telephony
human_review_queueQueued 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 tokenVersion field): omitted allowedTransports defaults 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, identity
  • displayName, human-readable name
  • supportsInk: true, protocol support flag
  • discoveryMode: "authenticate_for_details", instructs the caller to authenticate
  • visibility, the card’s visibility level
  • updatedAt, freshness timestamp

Capabilities, endpoints, keys, availability and profile data are all stripped.

Authenticated Query Flow

Diagram

Denial reasons: unknown_requester, insufficient_trust, not_connected.

The difference between network_only and capability_gated:

  • network_only grants the full card to any authenticated INK peer
  • capability_gated filters 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.

LimitValueDescription
Max challenges3Maximum network.tulpa.challenge messages per correlation
Max rejections1Terminal, no further messages accepted
Max resolutions1Terminal, no further messages accepted
Max transitions5Total state transitions (intent + challenges + resolution)
TTLThe intent’s expiresAt or 24h, whichever is shorterAfter 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:

LimitRecommended default
New intents per minute10
New intents per hour60
Handshake messages per minute30

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

ReasonDescription
handshake_budget_exhaustedPer-correlation budget exceeded
counterparty_cooldownRecipient broadly rate-limiting
sender_rate_limitedPer-sender sliding window exceeded
delegation_budget_exhaustedDelegation issuance limit hit
transport_scope_violationTransport 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:

EventDescription
transport_scope_violationMessage rejected for transport mismatch
handshake_rate_limitedPer-sender rate limit triggered
handshake_budget_exhaustedPer-correlation budget exceeded
discovery_query_receivedAuthenticated card query received
discovery_query_grantedFull card returned to authenticated requester
discovery_query_deniedCard query denied

See Audit Trail. Event Types for the full list.