fix(gate-7): the auth token may be the FIRST segment too (.github#360) - #368
Merged
Conversation
#353 relaxed WHERE the auth token may sit in a guard-helper name, but left the segment before it mandatory -- `[A-Z][A-Za-z0-9_]*` with no `?` -- so the token could never be the first segment after the is/has/can/may prefix. The most conventional per-object guard names in the fleet are exactly that shape, and gate-7 reported every method delegating to one as an unguarded IDOR, before AND after #353. Making that segment repeatable and optional admits the token in any position: hasPermission canAccess isOwner isAllowed mayAccess hasAccess x -> ok canUserAccessAgent hasOwnerPermissionForRun isAdmin canEditPermission canViewOwner hasACLAccess assertOwner ok -> ok canRender hasChanges canUserModifyAgent hasPendingRevision isVisible canDelete hasItems x -> x THE TOKEN SET IS UNCHANGED and a token is still REQUIRED. The newly admitted names are exactly {is,has,can,may} + token + optional object noun. Widening this to "any is/has/can/may method" would let gate-7 clear real IDORs -- strictly worse than the false positives it removes -- so `canRender`/`hasChanges` must stay non-guards, and there is now a unit test that says so by name. BLAST RADIUS: NONE, and this is measured, not argued. The new pattern is a STRICT SUPERSET of the old: 400,000 fuzzed identifiers over the prefix/token grammar produced ZERO names the old regex matched and the new one does not. So this change can only turn gate-7 findings green; it cannot redden any repo. EVIDENCE, observed red-then-green rather than asserted: test_gate7_verb_object_guards.sh before: the hasPermission()/canAccess() controller produced 2 findings, carried as a live KNOWN DEFECT after: 0 findings, and the same fixture still goes RED (2) under the pre-#360 regex test_check_no_admin_idor.py 95 tests pass; the new test_the_auth_token_may_be_the_first_segment FAILS on the old regex ("hasPermission should be a guard name") The #353 anti-dead-test arm demanded its own update when the regex was reshaped -- "Re-derive the revert and update it here rather than deleting this arm" -- which is what a suite that refuses to rot looks like. Done, and it still proves clean/ goes red (3) under the pre-#353 regex. Corrected for the record, in the code comment where it will be read: the #353 commit message says `canAccess` matched. Measured against BOTH regexes, it never did. #353 remains a clean widening; its message overstated the scope.
This was referenced Aug 11, 2026
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.
Closes #360.
#353relaxed where the auth token may sit in a guard-helper name, but leftthe segment before it mandatory —
[A-Z][A-Za-z0-9_]*with no?— so thetoken could never be the first segment after the
is|has|can|mayprefix. Themost conventional per-object guard names in the fleet are exactly that shape, and
gate-7 reported every method delegating to one as an unguarded IDOR, before
AND after
#353.hasPermissioncanAccessisOwnerisAllowedmayAccesshasAccesscanUserAccessAgenthasOwnerPermissionForRunisAdmincanEditPermissioncanViewOwnerhasACLAccessassertOwnercanRenderhasChangescanUserModifyAgenthasPendingRevisionisVisiblecanDeletehasItemsThe token set is unchanged and a token is still REQUIRED. The newly admitted
names are exactly
{is,has,can,may}+ token + optional object noun. Widening thisto "any
is/has/can/maymethod" would let gate-7 clear real IDORs —strictly worse than the false positives it removes — so
canRender/hasChangesmust stay non-guards, and there is now a unit test saying so by name
(
test_360_did_not_widen_into_silence).Blast radius: NONE, and it is measured rather than argued
The new pattern is a strict superset of the old. 400,000 fuzzed identifiers
over the prefix/token grammar produced zero names the old regex matched and
the new one does not. This change can therefore only turn gate-7 findings green;
it cannot redden any repo, which is why it is sequenced first.
Observed red, then green — not asserted
test_gate7_verb_object_guards.shshort-guard armtest_check_no_admin_idor.pytest_the_auth_token_may_be_the_first_segmentfails on the old regex withAssertionError: hasPermission should be a guard nameThe
#353anti-dead-test arm demanded its own update the moment the regex wasreshaped — "Re-derive the revert and update it here rather than deleting this
arm" — which is what a suite that refuses to rot looks like. Done, and it still
proves
clean/goes red (3) under the pre-#353regex.One correction carried into the code
The
#353commit message sayscanAccessmatched. Measured against bothregexes, it never did.
#353remains a clean widening — nothing it used to matchwas lost — but its message overstated the scope, and the comment above the regex
is now where that stops being repeated. Same for
canUserModifyAgent, whichSHARED-LESSONS listed as fixed by
#353and which correctly still does not match:"Modify" is not an auth token.
ShellCheck clean (
koalaman/shellcheck:stableagainst the repo's own.shellcheckrc).