Require the per-bottle identity token at /resolve (retire source-IP-only attribution) #366

Open
opened 2026-07-13 19:22:13 -04:00 by didericis-claude · 0 comments
Collaborator

Context

The consolidated gateway (PRD 0070) attributes each request to a bottle by source IP, with a per-bottle identity token as application-layer defense-in-depth. Today that token is optional: PolicyResolver.resolve / resolve_bottle_id accept identity_token="", and POST /resolve falls back to source-IP-only attribution when it's absent (see the transitional notes in bot_bottle/policy_resolver.py and git_http_backend.resolve_sandbox_root).

This issue tracks flipping the token to mandatory in the consolidated end state.

Why it's optional now

Identity-token delivery is still a PRD 0070 open question — "exactly how the per-bottle token is placed where the agent can present it but not swap in another bottle's." Until that mechanism exists, the agent has no token to send, so requiring it would make consolidated mode dead-on-arrival. Source-IP-only is the transitional bridge:

  • Firecracker — sound by construction (/31 point-to-point TAP + bot_bottle_fc nft table make the source IP unforgeable), so the token is belt-and-suspenders there.
  • Docker / weaker backends — the token is genuinely load-bearing; source-IP anti-spoof is weaker.

Why require it (the end state)

Defense-in-depth only counts if it's enforced. Leaving the token optional means a misconfigured /31+nft (or a weaker backend) silently degrades to "source IP is the sole attributor" with no second factor. Requiring it keeps the app-layer proof always-on and fail-closed.

The change

  • POST /resolve (orchestrator/control_plane.py): reject an empty identity_token — return 403 (unattributed), same fail-closed path as an unknown IP. Remove the source-IP-only branch.
  • PolicyResolver: drop the identity_token="" default so callers must pass one; keep the 403 -> None contract.
  • Data plane (egress_addon, git_http_backend): already read the agent-injected x-bot-bottle-identity header — ensure they always forward it and that a missing token denies.
  • Remove the "transitional" caveats from the docstrings once done.

Dependency / sequencing

Blocked on the identity-token delivery mechanism (how the agent obtains + presents an unforgeable, non-cross-leakable per-bottle token). Do this with the launch-integration slice that wires delivery — enforcing it standalone before delivery exists just breaks the consolidated path.

References

  • PR #365 (multi-tenant git-http backend) review thread — the original ask.
  • PRD 0070 "Open questions" -> identity-token delivery.
  • Related: #351 (orchestrator), #355 (SecretProvider).
## Context The consolidated gateway (PRD 0070) attributes each request to a bottle by source IP, with a per-bottle **identity token** as application-layer defense-in-depth. Today that token is **optional**: `PolicyResolver.resolve` / `resolve_bottle_id` accept `identity_token=""`, and `POST /resolve` falls back to source-IP-only attribution when it's absent (see the transitional notes in `bot_bottle/policy_resolver.py` and `git_http_backend.resolve_sandbox_root`). This issue tracks flipping the token to **mandatory** in the consolidated end state. ## Why it's optional now Identity-token **delivery** is still a PRD 0070 open question — *"exactly how the per-bottle token is placed where the agent can present it but not swap in another bottle's."* Until that mechanism exists, the agent has no token to send, so requiring it would make consolidated mode dead-on-arrival. Source-IP-only is the transitional bridge: - **Firecracker** — sound *by construction* (`/31` point-to-point TAP + `bot_bottle_fc` nft table make the source IP unforgeable), so the token is belt-and-suspenders there. - **Docker / weaker backends** — the token is genuinely load-bearing; source-IP anti-spoof is weaker. ## Why require it (the end state) Defense-in-depth only counts if it's **enforced**. Leaving the token optional means a misconfigured `/31`+nft (or a weaker backend) silently degrades to "source IP is the sole attributor" with no second factor. Requiring it keeps the app-layer proof always-on and fail-closed. ## The change - **`POST /resolve`** (`orchestrator/control_plane.py`): reject an empty `identity_token` — return `403` (unattributed), same fail-closed path as an unknown IP. Remove the source-IP-only branch. - **`PolicyResolver`**: drop the `identity_token=""` default so callers must pass one; keep the `403 -> None` contract. - **Data plane** (`egress_addon`, `git_http_backend`): already read the agent-injected `x-bot-bottle-identity` header — ensure they always forward it and that a missing token denies. - Remove the "transitional" caveats from the docstrings once done. ## Dependency / sequencing Blocked on the **identity-token delivery** mechanism (how the agent obtains + presents an unforgeable, non-cross-leakable per-bottle token). Do this **with the launch-integration slice** that wires delivery — enforcing it standalone before delivery exists just breaks the consolidated path. ## References - PR #365 (multi-tenant git-http backend) review thread — the original ask. - PRD 0070 "Open questions" -> identity-token delivery. - Related: #351 (orchestrator), #355 (SecretProvider).
didericis added the Kind/Security label 2026-07-14 03:47:32 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: didericis/bot-bottle#366