sec(auth): carve out money-verb wildcards at grant and check - #2069
Conversation
The admin:* carve-out set is keyed on exact (action, resource) pairs, but enforcement treats a stored resource of "*" as matching every resource. An admin could therefore write execute:*, approve-any:* or retry-any:* onto a group, join it, or mint an API key with it, and every holder then passed the execute / approve-any / retry-any checks for purchases and ri-exchange. Replace the five exact-pair lookups with one predicate, coversCarvedOut, that asks enforcement's own matcher (checkPermissionMatch) whether the permission would satisfy any carved-out pair. The grant ceiling, the self-membership guard, API-key validation, the key/owner intersection and both enforcement matchers now agree on what the set covers. Closes #1901 Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01Fu9uWjxtDFx5HDKeMRt1jC
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Essentials Run ID: 📒 Files selected for processing (7)
Included review availability: 3 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour. 📝 WalkthroughWalkthroughThe change adds wildcard-aware carve-out matching for authorization checks. Grant ceilings, group permissions, self-membership, API-key creation, and effective permissions now enforce carved-out money actions. Regression tests cover wildcard and explicit permission cases. ChangesWildcard carve-out enforcement
Estimated code review effort: 3 (Moderate) | ~20 minutes Severity of issue fixed: High Merge Risk: ⚪ Minimal · up to Wildcard money permissions no longer bypass separation-of-duties protections across group, membership, API-key, and effective-permission paths. The change is ready to merge. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
What
Closes #1901.
The separation-of-duties carve-out that stops an admin granting money verbs was keyed on exact
(action, resource)pairs, while enforcement treats a storedResource == "*"as matching every resource. An admin holding onlyadmin:*could therefore create a group carryingexecute:*,approve-any:*andretry-any:*: the ceiling looked up the wildcard form, found nothing, and allowed the write. Every member of that group then passed the concreteexecute:purchases,approve-any:purchases,retry-any:purchasesandexecute:ri-exchangechecks.The fix adds one predicate,
coversCarvedOut(perm), which asks enforcement's own matcher whether a permission would satisfy any carved-out pair, and replaces all five exact-pair lookups with it. Grant-time guards and enforcement now agree by construction rather than by both being written correctly.Source diff is +21/-5 across four files. The rest is tests.
The four guards
The issue named three. A fourth was found while planning, and it is the one that matters for already-issued credentials.
group_ceiling.go:85service_user.go:551types.go:167service_group.go:332Verification
Each guard has a regression test that was observed failing before the fix and passing after. The refusal tests stub no write on the mock, so a missing guard panics on an unexpected
CreateGroup,UpdateUserorCreateAPIKeyrather than passing quietly.An independent reviewer ran ten mutants of the predicate against the auth suite. Nine were killed. The survivor is the mirror branch in
grantCeilingAllows, which the code documents as unreachable for carved-out pairs becausecheckGrantCeilingrefuses them first, so it is an equivalent mutant rather than a coverage gap.go build ./...,go vetgo test ./internal/auth/go test ./internal/api/go test ./internal/...gocyclo -over 10on the touched filesNo legitimate grant regresses. Every up-migration was checked: the only wildcard-resource permission ever seeded is
{admin,*}. Purchaser seeds the three concretepurchasespairs and RI Exchanger seedsexecute:ri-exchange, so nothing in a default deployment relies on the wildcard form.Operator action required after merge
This fix does not neutralise data already written through the bypass. A group that already stores
execute:*keeps granting the money verbs to its members, and narrowing it through the API is now refused, so it must be corrected in SQL or the group deleted. Find affected rows with:API keys behave differently from groups and need no SQL: a stored wildcard key whose owner is an admin loses the
executeverb entirely at use time rather than being narrowed toexecute:purchases. That is fail-closed and intended, but a key that worked yesterday can stop executing, so it is worth knowing before someone reports it as a regression.Scope
This closes the wildcard forms only. LeanerCloud/cloud-commitments-platform#226 remains open and is the other route to spend authority: membership writes have no ceiling, so an admin can still create a user directly inside the Purchaser group, or use
update:usersto move an account into it. That is a different defect and is not addressed here.Summary by CodeRabbit
Bug Fixes
Tests