Skip to content

🤖 fix: check a CoderAIProvider's provider ID before Coder calls, and trust only Coder's own delete 404 - #325

Merged
ThomasK33 merged 1 commit into
mainfrom
ai-provider-id-check
Oct 14, 2026
Merged

ThomasK33 merged 1 commit into
mainfrom
ai-provider-id-check

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Two hardening fixes for the dormant CoderAIProvider controller, from the #312 final assessment. Both gate activation (#315). The controller is still not registered, so users see no change.

Fixes #323
Refs #154

1. status.providerID must be a canonical UUID before any Coder call

The controller puts status.providerID into /api/v2/ai/providers/{id}. That route also accepts a provider name, and the SDK builds the URL with URL.Parse, which resolves .. segments: /api/v2/ai/providers/../../users/x becomes /api/v2/users/x (checked with the Go standard library). A forged value could therefore send a request with the operator's site-owner token to another Coder route. Only the controller's own role writes the status subresource, so this needs cluster-admin-level access today.

checkAIProviderID now runs before every GET, PATCH or DELETE that uses the ID: at the start of the sync path (syncAIProvider), the credential disable path (disableWithoutConsent) and the delete path (deleteInCoder). Anything other than a canonical, non-nil, lowercase UUID fails with assertion failed: AI provider <ns>/<name> has status.providerID "<value>", which is not a canonical UUID, and no Coder request is sent. The finalizer stays. While the control plane is being deleted, the existing one-attempt rule still releases the finalizer and logs the error.

2. A delete trusts only Coder's own 404

ReadBodyAsError turns any non-2xx answer into a *codersdk.Error, including an HTML 404 from a proxy or a wrong base URL, so the delete path could release the finalizer while the provider stays in Coder. The delete path now treats a 404 as "gone" only when the answer carries Coder's fixed not-found message (Resource not found or you do not have access to this resource, httpapi.ResourceNotFound at coderd/httpapi/httpapi.go:149-155 in v2.37.3, which the provider routes answer through writeAIProviderError at coderd/ai_providers.go:636-638). An unknown route answers Route not found. instead, so a wrong base URL that still reaches some Coder does not count either. That applies to the DELETE by ID and to the name lookup for an unconfirmed create. Any other 404 waits with CoderUnavailable and retries every 60 seconds.

Tests

  • TestCoderAIProviderForgedProviderID: on the delete, sync and disable paths, five forged values (../../users/<uuid>, a name, braces, upper case, the nil UUID) each fail the assertion with zero Coder requests, and the finalizer stays.
  • TestCoderAIProviderDeleteNotFoundFromProxy: a DELETE answered with an HTML 404, or a JSON 404 with another message, keeps the finalizer (CoderUnavailable, 60 s); the next reconcile with Coder's own answer deletes and releases.
  • TestCoderAIProviderDeletePendingCreateNotFoundFromProxy: the same for the name lookup of an unconfirmed create.

Mutation checks (each one breaks a named test, then I reverted it): no check on the delete path, no check on the sync path, no check on the disable path, a non-canonical form accepted, the nil UUID accepted, any 404 trusted on DELETE, any 404 trusted on the lookup.

Local gates on the pushed tree, exit 0 each: make verify-vendor, make test, make test-integration, make build, make lint, go tool govulncheck ./..., go test -race ./internal/controller/, and make codegen manifests build-installer docs-reference with no diff.


Generated with mux • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $107.36

…trust only Coder's own delete 404

status.providerID goes into /api/v2/ai/providers/{id}, which also takes a
name, and the SDK resolves ".." segments. The sync, disable and delete
paths now fail an assertion before any Coder request unless the value is a
canonical UUID. The delete path also releases the finalizer on a 404 only
when the answer carries Coder's fixed not-found message, so an HTML 404 from
a proxy no longer leaks the provider.

Fixes #323

Change-Id: I7080fde6ce8329872f072efd01f3e0e992889a75
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T05:25:40.839863Z b56ab12 PR opened
🔒 Security Review ✅ Completed 2026-10-14T05:27:14.881788Z b56ab12 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: b56ab12a3e

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33
ThomasK33 added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit 3410010 Oct 14, 2026
10 checks passed
@ThomasK33
ThomasK33 deleted the ai-provider-id-check branch October 14, 2026 05:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CoderAIProvider: validate status.providerID as a UUID before Coder calls, and prove a delete 404 comes from Coder

1 participant