Skip to content

Runtime adapter IDs are globally first-claim and need an ownership/recovery contract #370

Description

@coygeek

Summary

POST /v1/adapters/{adapterID}/ticket claims a runtime adapter ID as a side effect of minting the first ticket for that ID. The handler accepts any authenticated requester, validates only the DNS-style shape of adapterID, and writes a durable RuntimeAdapterIdentityRecord keyed only by adapterID. After that first claim, later ticket or registered-lease attempts by another owner or organization are rejected as conflicts.

This creates a global first-claim namespace for runtime adapter IDs. A low-privileged authenticated user who can guess or learn an intended adapter ID can pre-claim it and prevent the intended operator from binding a registered lease or connecting a runtime adapter under that ID until an administrator repairs the stored identity record.

Scope status: in_scope. This is a reportable Crabbox control-plane boundary because an authenticated user can persist owner/org state for a global adapter namespace and block another owner or organization from using that adapter ID.

The supported impact is availability and namespace-control denial. Source review did not support the stronger claim that the attacker can receive another tenant's already-registered workspace relay traffic: the registration path rejects mismatched identities before a victim lease can bind to the squatted adapter ID, and the relay path rejects cross-owner requests.

Affected Components

  • Checked commit: d3d1891aafa7213e7b76380465a75da8d20fb64d
  • Component: Cloudflare Worker coordinator runtime adapter flow
  • Affected files:
    • worker/src/fleet.ts
    • worker/src/runtime-adapter-relay.ts

Attack Path

Attacker role: any authenticated coordinator user.

Prerequisites:

  • The attacker has a valid user token for the coordinator.
  • The attacker can guess, learn, or race an adapter ID before the intended operator claims it.
  • The intended operator later tries to use the same adapter ID for a registered lease or runtime adapter agent.

Steps:

  1. The attacker sends POST /v1/adapters/<adapterID>/ticket with a valid authenticated request body such as {}.
  2. createRuntimeAdapterTicket sees no existing runtime-adapter-identity:<adapterID> record and stores the attacker's owner and org for that adapter ID.
  3. The intended operator later mints a ticket or registers a lease for the same adapter ID.
  4. Ticket creation fails with adapter_id_conflict, or lease registration fails with runtime_adapter_conflict, because the stored identity belongs to the attacker.
  5. The intended operator cannot use that adapter ID without administrator intervention or choosing a different adapter ID.

Impact

An authenticated user can:

  1. Choose an unclaimed DNS-style runtime adapter ID.
  2. Call the ticket endpoint with their own owner/org context.
  3. Persist a RuntimeAdapterIdentityRecord for that global adapter ID.
  4. Cause later attempts by another owner/org to use the same adapter ID to fail with adapter_id_conflict or runtime_adapter_conflict.

This can deny use of predictable or documented adapter IDs, deployment-convention names, or IDs leaked through configuration/history. The impact lasts until the stored identity is removed or an operator chooses a different adapter ID.

Severity Assessment

CVSS Assessment

Metric v3.1 v4.0
Score 3.1 / 10.0 2.3 / 10.0
Severity Low Low
Vector CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N
Calculator CVSS v3.1 Calculator CVSS v4.0 Calculator

Rationale: the attack is network-reachable by an authenticated low-privileged user and requires no victim interaction, but the attacker must know or predict an adapter ID before the legitimate owner claims it. The validated direct impact is low availability loss for workflows that depend on that specific runtime adapter ID. Confidentiality and integrity of an already-bound victim workspace were not proven.

Recommended Remediation

Avoid creating global adapter ownership as an incidental side effect of ticket issuance. Possible fixes:

  1. Bind claims to a registered lease or registration generation. Require the ticket request to reference an existing lease registration for the same owner/org before creating the identity.
  2. Generate high-entropy adapter IDs server-side instead of accepting arbitrary predictable names as durable global identities.
  3. Add an explicit claim/reclaim flow with appropriate authorization, auditability, and operator recovery.
  4. Support deployment-reserved adapter IDs so known operator IDs cannot be claimed by ordinary users.

For a minimal hardening step, ensure that createRuntimeAdapterTicket cannot create a new identity unless the requester proves control over the lease or registration that will use the adapter ID.

Validation

Validation method: source trace, with focused review of the ticket, registration, relay, and ID-validation paths.

Relevant code:

  • worker/src/fleet.ts:797-805 routes /v1/adapters/{adapterID}/ticket, /agent, and /proxy requests to the runtime adapter handlers.

  • worker/src/runtime-adapter-relay.ts:1 and worker/src/runtime-adapter-relay.ts:31-33 define validRuntimeAdapterID as a DNS-style syntax check only. It does not reserve names, require generated IDs, or scope IDs by owner/org.

  • worker/src/fleet.ts:5627-5697 implements createRuntimeAdapterTicket. On first use, it reads runtimeAdapterIdentityKey(adapterID), and if no identity exists, writes:

    { adapterID, owner, org, createdAt }
    

    The owner and org are taken from the current request. There is no lease-binding requirement, admin gate, generated-token requirement, or deployment reservation check before the write.

  • worker/src/fleet.ts:5660-5670 rejects only when an identity already exists for a different owner/org. This confirms the first requester wins the adapter ID.

  • worker/src/fleet.ts:3619-3644 requires registered leases that specify a runtime adapter binding to find an existing identity whose owner and org match the lease registration request. If the ID was pre-claimed by another user, registration fails with runtime_adapter_conflict.

  • worker/src/fleet.ts:5699-5753 only accepts a runtime adapter agent when the consumed ticket matches the already-claimed identity, so a later user cannot connect an agent to the squatted ID with their own credentials.

  • worker/src/fleet.ts:5832-5863 rejects relay attempts when the identity owner/org does not match the request or explicit lease scope, returning runtime_adapter_forbidden.

  • worker/test/fleet.test.ts:5378-5467 already encodes the current intended behavior: the lease registration initially fails as runtime_adapter_unclaimed, succeeds after the same owner mints a ticket, and fails as runtime_adapter_conflict when another owner has claimed a different adapter ID.

Counterevidence considered:

  • The relay checks prevent arbitrary cross-tenant use of a claimed adapter ID. This reduces the finding to denial of use for the contested adapter ID rather than interception of a victim's established workspace traffic.
  • Runtime adapter workspace delete reconciliation is lease-scoped and does not provide a self-service reclaim path for a different owner whose desired adapter ID was pre-claimed.
  • Existing tests appear to treat claim-before-registration as normal behavior, so this may be an intentional product decision. The security issue is that the claim is global, durable, and available to any authenticated user without proof that the requester controls the deployment or registered lease that will use the name.

Focused runtime verification was attempted with:

npm test --prefix worker -- --run worker/test/fleet.test.ts -t "persists an immutable runtime adapter binding"

The isolated source copy did not have Worker dependencies installed, so the command failed before running Vitest with sh: vitest: command not found. Installing dependencies would write into crabbox/, which is outside the verifier write boundary, so validation stayed with source and existing test inspection.

Add tests covering the security boundary rather than only the current first-claim behavior:

  • A user should not be able to create a runtime adapter identity for an arbitrary adapter ID without an associated registered lease, registration generation, or other ownership proof.
  • If adapter IDs remain user-selectable, a pre-claim by one owner should not permanently block a different owner from using an adapter ID that is reserved or otherwise assigned to that owner.
  • Cross-owner relay attempts should continue to fail, preserving the existing runtime_adapter_forbidden guard.

Suggested verification after a fix:

npm test --prefix worker -- --run worker/test/fleet.test.ts worker/test/runtime-adapter-relay.test.ts

Disclosure Recommendation

  • Recommended route: public GitHub issue
  • Reason: the finding is a bounded authenticated namespace-squatting issue. It does not expose secrets, provide code execution, or include exploit-ready infrastructure details.
  • Sensitive details withheld from public issue: none

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal priority bug or improvement with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.hardeningDefense-in-depth or safety improvement that must preserve supported functionalityimpact:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions