Repository navigation
Validate the members file fully before an access change writes it - #126
Merged
Merged
Conversation
apply() only ran the per-entry schema on the current file, wrote the new file, and then built the view, which runs the cross-file checks (an id in two entries, an admin with no entry). A hand edit that failed those checks left the file written but the view and audit record unchanged, with a generic error for the admin. Run the startup cross-checks on the current file and build the new view before writing, so the change is refused and nothing is written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Needs a human reviewer (AGENTS.md rule 14): this changes
src/services/access-store.ts.What was wrong
FileAccessStore.apply()checked the currentmembers.yamlonly against the per-entry schema (parseMembers). It then wrote the new file and only after that calledbuildAccessConfig, which runs the cross-file checks (an id in two entries, an admin with no members entry).Example: someone hand-edits
members.yamlso the samediscord:<id>is in two entries, or removes the admin's entry. The schema passes. An admin runs/admin level set:members.yamlis renamed into place (with a.bak),buildAccessConfigthrowsAccessConfigError(not user-facing, and its message includes the file path),access.changedaudit log or breadcrumb is written,This broke the guarantee in
docs/identity-and-access.md("If any step fails, both the file and the view are unchanged") and AGENTS.md rule 10 (every change is audited).What changed
#parseCurrentnow also runsbuildAccessConfig(this.#admins, entries, paths), the same checks as at startup. AnAccessConfigErrorfrom it maps to the existingAccessStoreError: "The members file is invalid, so nothing was changed. Fix it, then run /admin reload." It is logged asaccess.file_invalid.#writeFile, and swapped in only after the write succeeds. With the current file cross-checked, our own edit can't fail here (adding a new entry only happens when the id isn't already present), but if it ever does, nothing is written.docs/identity-and-access.mdsteps 1 and 3 now describe this. There is a CHANGELOGFixedentry.Testing
src/services/access-store.test.tscovers both a duplicate id across entries and an admin with no entry. For each one it tries a new-personset-tier, an existing-personset-tierand aset-capabilities, and asserts that each is refused with anAccessStoreError(no ID in the message), the file is byte-for-byte unchanged, there is no.bak, the view is the same object, and nothing is audited.main: the error is anAccessConfigError, not anAccessStoreError. Both pass with the fix.just checkpasses (lint, typecheck, tests, coverage thresholds).🤖 Generated with Claude Code