Repository navigation
🤖 fix: fail closed on a missing CoderMCPServer access group - #366
Merged
Merged
Conversation
When a name in spec.access.groups does not resolve in Coder (GroupNotFound), the ACL sync now still removes every entry that the list does not name, by the group names in Coder's ACL answer, and every user entry. It adds nothing, so access never widens while a group is missing. A generation that also disables the server sends one PATCH with only enabled: false. The rest of the generation and the missing groups follow once the groups exist. The access.groups field doc says so, and the dormant CRD is regenerated. Fixes #313 Refs #155 Change-Id: I9a98f53a214e5ed6049b01abf64e703015600a8b Signed-off-by: Thomas Kosiewski <tk@coder.com>
Member
Author
|
@codex security review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
🛡️ Codex Security ReviewSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a group in
spec.access.groupsdoes not exist in Coder, the dormantCoderMCPServerreconciler now fails closed. It still removes the access of every group that the list does not name, adds no group, and disables the server if the same generation disables it. The kind is not registered, so users see no change.Fixes #313
Background
Before this PR, a missing group (
GroupNotFound) stopped the sync before it changed anything (fail static, plan 4.8 item 3). A broader group of the earlier generation, such asEveryone, then kept read access to the enabled config until the new group existed. That also held when the same generation disabled the server. The maintainer decided Option B (fail closed) on #313, to match the fail-closed rule of #330 and #360.How it works
syncMCPServerACLPass(the ACL helper of 🤖 feat: add the CoderMCPServer ACL helper #294): when a name does not resolve, it still reads the ACL. It keeps only the group entries whose namesaccess.groupslists, matched by the group names in Coder's ACL answer.Everyoneis matched by its ID, which is the organization ID. It removes every other group entry and every user entry, adds nothing, and returnsGroupNotFound.syncMCPServer: when that remove-only pass before the config PATCH returnsGroupNotFound, and the sparse PATCH of this generation setsenabled: false, the reconciler sends one PATCH that holds onlyenabled: false(disableForMissingGroup). It checks the answer's ID, organization, slug andenabled. A failed PATCH waits 30 s.GroupNotFoundresyncs every 10 minutes. Once the group exists, the next sync applies the rest of the generation and adds the missing groups.A new config with a missing group stays disabled with the nonce, as before. Its default
Everyoneentry now goes too.The
access.groupsfield doc describes the behavior, and the dormant CRD is regenerated. The plan's M9 how-to row repeats it.Validation
TestMCPServerACLGroupNotFound: for loose matches only, one missing name, lowercaseeveryone, and a rejected search, the sync givesGroupNotFoundwith one ACL read and one remove-only ACL write. The defaultEveryoneentry and the user entry go, and no group is added.TestMCPServerACLMissingGroupNeverWidens: the ACL holdsEveryone,dataandanalytics. A sync fordata,data-scienceandmissingkeepsdata, removesEveryoneandanalytics, and does not adddata-sciencealthough it resolves. The next sync writes nothing. Oncemissingexists, the sync addsdata-scienceandmissing.TestCoderMCPServerApplyMissingGroupFailsClosed: an applied config readable byEveryoneanddatagets a generation withdata,ops(missing) and a new description, with and withoutenabled: false. The reconcile givesGroupNotFound, the ACL becomesdata, and the description waits. Withenabled: false, the writes are the ACL removal and then{"enabled":false}. Onceopsexists, the generation applies.TestCoderMCPServerApplyGroupNotFound: a new config with a missing group still gets no config PATCH. ItsEveryoneentry goes.TestMCPServerACLRejectsNamesOutsideThePattern: a name outside the pattern still sends no search for it.Everyonewhen it is not listed, keeping every entry in the by-name pass, and no disable PATCH. One change survives: dropping the!byNameguard on the add loop, because the resolver returns no IDs when a name is missing. I kept the guard, so the rule does not depend on that detail.make verify-vendor,make build,make lint,GOFLAGS=-mod=vendor go tool govulncheck ./...,make test,make test-integration,go test -race ./internal/controller/, andmake docs-checkpass.make codegen,make manifests,make build-installerandmake docs-referenceleave no diff.Size
163 lines added and 14 removed. 5 changed lines are regenerated (the dormant CRD). The rest are hand-written: 29 changed lines in the ACL helper, 25 in the apply step, 5 in the field doc, and 113 in tests.
Refs #155
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$195.76