fix(auth): exempt scaffold_classify from scope enforcement (#51)#52
Merged
Conversation
scaffold_classify is zero-cost heuristic keyword matching — no LLM call, no backend state mutation, no quota consumption. Requiring 'generate' (or even 'read') scope was blocking it for tokens minted without explicit scope (e.g. pre-#28 OAuth clients, API keys from older edge-auth versions). Root cause: RISK_REQUIRED_SCOPES['READ_ONLY'] = ['read', 'generate'], so any READ_ONLY tool silently 401s/403s for tokens with scopes:[]. scaffold_classify, scaffold_status, and image_list_models are the only tools where this is wrong — they carry no privilege worth protecting. Fix: introduce SCOPE_EXEMPT_TOOLS set. Tools in this set skip the RISK_REQUIRED_SCOPES check entirely. Any valid authenticated bearer token can call them; unauthenticated requests are still rejected at the auth layer before we reach scope enforcement. Also updates two tests that were asserting the wrong tool name (they used scaffold_status, which is now also exempt, to verify scope blocking; both now use scaffold_create which is LOCAL_MUTATION and correctly requires scope). Co-Authored-By: Claude Sonnet 4.6 <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.
What broke
scaffold_classifywas returning a scope enforcement error (surfaced as 401 by some MCP clients) for any bearer token that lacked'read'or'generate'scope — including:scopefrom the initiate (and got empty grants)Root cause
RISK_REQUIRED_SCOPES['READ_ONLY'] = ['read', 'generate']. EveryREAD_ONLYtool, includingscaffold_classify, was blocked forscopes: []tokens. The C-1b remediation was correct for mutation tools but too aggressive for zero-cost, side-effect-free operations.Fix
Introduce
SCOPE_EXEMPT_TOOLS— a set of tools callable by any valid authenticated bearer token, regardless of scope grant. Criteria:baseCost=0, no external calls, no state mutation, pure read/heuristic.Tools added to the exempt set:
scaffold_classify— keyword matching against the aegis-intents deck, no LLM, no quotascaffold_status— health/version probeimage_list_models— static model catalogimage_check_jobis NOT exempt — it reads tenant-scoped job state and should keep requiring'read'scope.Unauthenticated requests are still rejected at the auth layer before scope enforcement is reached — this does not weaken security, it removes friction for a tool that has nothing to protect.
Tests
gateway.test.ts: fixed "denies all tool calls when token has no scopes" to usescaffold_create(LOCAL_MUTATION); added two newscaffold_classifyscope-exempt assertions (no scopes, read-only scope)gateway-legacy-scope.test.ts: same fix — swappedscaffold_statusforscaffold_createin the empty-scope blocking testAll 185 tests pass.
🤖 Generated with Claude Code