Skip to content

feat(coderd): accept delegated API key ID from in-process aibridge callers#25625

Merged
dannykopping merged 2 commits into
mainfrom
dk/accept-delegated-key-id
May 25, 2026
Merged

feat(coderd): accept delegated API key ID from in-process aibridge callers#25625
dannykopping merged 2 commits into
mainfrom
dk/accept-delegated-key-id

Conversation

@dannykopping

@dannykopping dannykopping commented May 22, 2026

Copy link
Copy Markdown
Contributor

Allows an api_key_id to be passed from a trusted in-memory transport (currently: chatd) to aibridged for use in authenticating LLM requests.

This value can only be passed via context, and all users of the in-memory transport must provide it.

It can be used in conjunction with BYOK headers.

…llers

aibridged's in-memory transport now requires callers to attach a delegated
API key ID via aibridge.WithDelegatedAPIKeyID, enforced at the RoundTrip
boundary. The handler uses it to authenticate the request as the named user
without the key secret; IsAuthorized validates only liveness for this path.
Orthogonal to BYOK: a delegated request still forwards user-supplied LLM
credentials and strips the Coder governance token.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

dannykopping commented May 22, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Review posted | Chat
Requested: 2026-05-22 15:50 UTC by @dannykopping

Review history
  • R1 (2026-05-22): 11 reviewers, 1 Nit, 2 Note, 1 P2, 6 P3, COMMENT. Review
  • R2 (2026-05-25): 5 reviewers, 2 Nit, 2 Note, 1 P2, 7 P3, APPROVE. Review

deep-review v0.5.0 | Round 2 | ef3f95a..ef81de8

Last posted: Round 2, 12 findings (1 P2, 7 P3, 2 Nit, 2 Note), APPROVE. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Author fixed (ef81de8) http.go:70 authMode log field omits delegation origin R1 Kurapika P3, Kite P3, Knov P3, Mafuuu P3, Meruem Nit Yes
CRF-2 P2 Author fixed (ef81de8) factory.go:36 Doc claims "revoked" but no revocation check exists R1 Gon P2, Leorio P3 Yes
CRF-3 P3 Author fixed (ef81de8) factory.go:48 DelegatedAPIKeyIDFromContext doc says "whether one was set" but empty returns false R1 Gon P3, Leorio Nit Yes
CRF-4 P3 Author fixed (ef81de8) http.go:72 Empty SessionKey on delegated path creates MCP proxy with empty Bearer token R1 Meruem P3, Knov Note, Mafuuu Note Yes
CRF-5 P3 Author fixed (ef81de8) aibridgedserver_test.go:206 TestAuthorization_Delegated missing deleted-user and system-user test cases R1 Bisky Yes
CRF-6 P3 Author fixed (ef81de8) aibridgedserver.go:577 ErrInvalidKey conflates "both fields set" with "malformed key" R1 Leorio Yes
CRF-7 P3 Author contested; panel closed R2 (3/3 accept) aibridgedserver.go:579 No format validation on delegated key_id R1 Hisoka Yes
CRF-8 Note Author accepted R2 (Note, resolved in GitHub UI) aibridged.proto:139 key/key_id not oneof; mutual exclusivity runtime-enforced R1 Meruem P3, Kurapika Note, Kite Nit, Knov Note, Hisoka Note, Pariston Nit Yes
CRF-9 Note Author fixed (ef81de8) aibridgedserver.go:579 key_id bypass security depends on in-memory transport (architectural) R1 Hisoka P2, Meruem Note, Kite Note, Knov Note Yes
CRF-10 Nit Author contested; panel closed R2 (2/2 accept) PR description "currently: chatd" but chatd doesn't use this yet R1 Mafu-san Yes
CRF-11 Nit Open aibridgedserver.go:557 Broken godoc link [aibridged.MemTransportPipe] in SECURITY comment R2 Netero Nit, Mafu-san P3 Yes
CRF-12 P3 Open mcp.go:86 No test for MCP proxy skip on delegated requests R2 Bisky Yes

Contested and acknowledged

CRF-7 (P3, aibridgedserver.go:579) - No format validation on delegated key_id

  • Finding: The delegated path passes the raw key_id string to GetAPIKeyByID without the length validation that SplitAPIToken applies on the standard path. Malformed input produces ErrUnknownKey instead of ErrInvalidKey.
  • Author defense: "No, let's not leak the implementation details of the keys here." Adding a length check would expose the internal key-format constraint (10 chars) to the delegated caller interface.
  • Panel closure (R2, 3/3): Hisoka traced the consequence: one wasted DB roundtrip, no security impact, delegated callers are trusted in-process code. Mafuuu confirmed: error becomes ErrUnauthorized/403 at HTTP boundary regardless; coupling delegation to 10-char format leaks implementation detail. Pariston: the delegated interface should treat key_id as opaque.

CRF-10 (Nit, PR description) - "currently: chatd" but chatd doesn't use this yet

  • Finding: PR description says "(currently: chatd)" but no production caller exists.
  • Author defense: "The chatd usage is coming soon."
  • Panel closure (R2, 2/2): Mafuuu: PR description nit about forward-looking language, not re-raising. Pariston: code is feature-complete and ready for integration.

CRF-8 (Note, aibridged.proto:139) - key/key_id not oneof

  • Finding: Mutual exclusivity of key and key_id enforced at runtime, not schema.
  • Author accepted: Resolved thread in GitHub UI without reply. Note requires no action; current approach is defensible.

Round log

Round 1

Panel. 1 P2, 6 P3, 1 Nit, 2 Note. Reviewed against ef3f95a..2cda32c.

Round 2

Churn guard: 7 addressed, 1 accepted (Note), 2 contested. Panel: 5 reviewers (Bisky, Hisoka, Mafuuu, Mafu-san, Pariston). CRF-7 closed (3/3 accept). CRF-10 closed (2/2 accept). 1 P3, 1 Nit new. All R1 fixes verified. Reviewed against ef3f95a..ef81de8.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Kurapika security
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-layered mechanism for delegated API key authentication. The trust boundary design is sound: context values are unexported types unreachable from HTTP, the transport enforces the invariant before the handler runs, the DRPC Authorizer sits behind MemTransportPipe. Test suite covers the security-critical negative cases (secret-hash-mismatch-ignored, empty-identity rejection, both-fields-set, BYOK orthogonality end-to-end).

1 P2, 6 P3, 1 Nit.

The P2 is a factual error in an exported doc comment (claims "revoked" but no revocation check exists). The P3s are observability, test coverage, error disambiguation, and format validation. No functional bugs found.

The full panel confirmed the security model is architecturally correct. Hisoka noted the IsAuthorized RPC has no way to know whether it was called over in-memory or network transport. If the DRPC service is ever exposed over a network, key_id becomes user impersonation with just a 10-character ID. Current architecture makes this safe; a // SECURITY comment on the RPC documenting the transport invariant would protect future refactors.

Proto uses two separate string fields rather than oneof. Discussed by 7 reviewers. Consensus: oneof would be more idiomatic but proto3 oneof scalar quirks exist, and the runtime enforcement with explicit both-set rejection is arguably better than oneof's silent last-wins. Current approach is defensible.

Comment verbosity pattern noted by Gon: "the caller never has the secret" appears 4 times across the diff, and several local comments restate what the code already shows. The minimum-draft suggestions are sound.

"There are no second chances when you do not know the identity of your opponent, but you also cannot win without taking a risk." ~ Pariston

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/aibridged/http.go
Comment thread coderd/aibridge/factory.go Outdated
Comment thread coderd/aibridge/factory.go Outdated
Comment thread coderd/aibridged/http.go
Comment thread coderd/aibridgedserver/aibridgedserver_test.go
Comment thread coderd/aibridgedserver/aibridgedserver.go
Comment thread coderd/aibridgedserver/aibridgedserver.go
Comment thread coderd/aibridged/proto/aibridged.proto
Comment thread coderd/aibridged/proto/aibridged.proto
Comment thread coderd/aibridgedserver/aibridgedserver.go
- http.go: add auth_delegated + transport source to the request logger
- aibridge/factory.go: correct doc to describe the actual liveness checks
- aibridged/mcp.go: skip Coder MCP proxy when no session key (delegated)
- aibridgedserver: introduce ErrAmbiguousAuth for both-fields-set case
- aibridgedserver: document the in-memory transport invariant on
  IsAuthorized
- aibridgedserver_test: cover deleted/system user on the delegated path

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 7 addressed R1 findings verified by the panel. Fixes are clean and proportional.

CRF-7 (contested, no format validation on delegated key_id): panel closed 3/3. Hisoka, Mafuuu, and Pariston accept the author's defense. The delegated interface should treat key_id as opaque; coupling it to the 10-char format leaks an implementation detail across an abstraction boundary. Consequence of a malformed ID is one wasted DB roundtrip and ErrUnknownKey, which becomes 403 at the HTTP layer regardless.

CRF-10 (contested, "currently: chatd"): panel closed 2/2. Forward-looking language in a PR description is not worth blocking.

1 P3, 1 Nit new this round. Both are in the fix commit, neither blocks approval.

The security model holds up well under adversarial probing. Hisoka verified the three-layer defense (transport guard, Go context type safety, in-memory DRPC pipe) and could not construct a scenario where a network request reaches the delegated path. Pariston confirmed the design choice (delegated auth with liveness check) over four alternative framings. Test suite covers the load-bearing properties.

"I tried to build a case against this PR and could not." ~ Pariston

🤖 This review was automatically generated with Coder Agents.

// SECURITY: when in.KeyId is set (the "delegated" path), this method trusts the
// caller's claim of identity and skips the key-secret check. This is safe only
// because the DRPCServer is reachable solely via the in-process
// [aibridged.MemTransportPipe]; the handler itself cannot tell whether it was

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit [CRF-11] Broken godoc link: [aibridged.MemTransportPipe] references a symbol that does not exist. The actual function is drpcsdk.MemTransportPipe() (called at coderd/aibridged.go:61). Since this SECURITY comment's entire purpose is to help future developers trace the trust boundary, the cross-reference should be accurate.

"Writing a security-critical cross-reference without verification, in a comment whose entire purpose is accurate documentation of a trust invariant." ~ Mafu-san

Fix: replace with [drpcsdk.MemTransportPipe] or describe the transport in prose. (Netero Nit, Mafu-san P3)

🤖

Comment thread coderd/aibridged/mcp.go
// secret and so cannot authenticate against the Coder MCP server.
// Skip the proxy in that case rather than attempting a connection
// with an empty bearer token, which will fail upstream.
if req.SessionKey == "" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 [CRF-12] The SessionKey == "" guard was added to fix CRF-4, but no test exercises this branch. The integration tests use pool mocks that bypass MCP; mcp_internal_test.go only tests regex compilation; pool_test.go always passes SessionKey: "key".

If someone reverts this guard, delegated requests would silently attempt MCP proxy creation with empty bearer tokens. The upstream server would reject (401), and the proxy factory would log a warning and proceed without the Coder MCP server. Not catastrophic, but a regression in fix-commit behavior that would go undetected.

(Bisky P3)

🤖

@dannykopping dannykopping marked this pull request as ready for review May 25, 2026 08:54
@dannykopping dannykopping requested a review from johnstcn May 25, 2026 08:54
@dannykopping dannykopping merged commit eddd4a8 into main May 25, 2026
32 checks passed
@dannykopping dannykopping deleted the dk/accept-delegated-key-id branch May 25, 2026 09:08
@github-actions github-actions Bot locked and limited conversation to collaborators May 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants