Repository navigation
Conversation
…e to be permitted matchStringListConstraints was any-match (containsAny): a permission scoped to account A matched a request naming A and B. It was safe only because every caller passes a single-value list, which nothing enforced. Switch it to all-match via containsAll (exact comparison, fails closed on an empty request list), mirroring matchAllRegionsConstraint. For the single-value requests every current caller sends, the result is the same. Empty-list "no restriction" semantics are unchanged. Closes LeanerCloud#138 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Comment |
|
VERDICT: APPROVE 28f283b Reviewed: full diff Issue #138 asks for all-match (or a len==1 assertion) on AccountIDs/Providers/Services. The PR switches Findings (none blocking):
Out of scope, pre-existing, and called out in the PR body: on the non-strict path an empty request list still matches a constrained permission (03-L6). Attacked:
Local results (fresh |
|
Independent review 2 (claude-fleet, cold): APPROVE 28f283b Findings:
Against the issue: the PR takes the issue's second option (all-match) and applies it to all three dimensions, not only AccountIDs. That fixes the root cause in the shared matcher instead of adding per-call-site Attacked:
Verified locally, on a
Reviewed by an independent agent with no author context (second reviewer). Review only: nothing was pushed, and the GitHub review state is unchanged. |
Root cause
matchStringListConstraints(internal/auth/service_group.go) enforces theAccountIDs,ProvidersandServicesconstraint dimensions for every non-strict permission check. It was any-match (containsAny): when both lists were non-empty, one shared value was enough. A permission scoped to account A therefore matched a request naming accounts A and B. It was safe only because every current caller passes a single-value request list, and nothing enforced that. TheRegionsdimension had already been moved to the all-matchmatchAllRegionsConstraintfor this reason (Azure RI exchange target locations).Fix
containsAnyis replaced bycontainsAll(internal/auth/service_helpers.go). It returns true only when the requested list is non-empty and every requested value is in the allowed list. Comparison stays exact (case-sensitive, no trimming), and an empty requested list returns false.matchStringListConstraintsnow callscontainsAll, so all three dimensions are all-match, mirroringmatchAllRegionsConstraint. The empty-list semantics (03-L6) are unchanged: an empty permission list or an empty request list still means "no restriction on this dimension".PermissionConstraints(handler_ri_exchange.go,handler_purchases.gopurchaseConstraintSets,handler_purchases_revoke.go,handler_marketplace.go,handler_ladder*.go,handler_config.go). Each passes at most one value for AccountIDs, Providers and Services, and all-match gives the same result as any-match for one value. TheStrictScopepath already used the all-matchlistCoversand is untouched.containsAnyas the enforcement matcher:grant_ceiling_cover.go(thelistCoversrationale; exact all-match enforcement keeps the "ceiling never looser than enforcement" argument valid),group_ceiling.go, and the doc comment onTestMatchConstraints_RegionsRequireEveryRequestedRegion.Verification
All evidence is from unit tests that exercise the real matcher and
PermissionsAllowForConstraintSets(no mocks in the decision path). No real-cloud or DB coverage applies: the change is a pure in-memory matcher. Tests ran withAWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/nullbecause this machine's default AWS profile is invalid.New tests (
internal/auth/constraint_all_match_test.go):TestMatchConstraints_StringListsRequireEveryRequestedValuecovers each of AccountIDs, Providers and Services throughmatchConstraintson a non-strict request. Cases: a partially permitted multi-value request is denied, including when the permitted value is listed second; a fully permitted subset is allowed; duplicates are allowed; a single value is allowed or denied as before; casing and whitespace still do not match; a blank extra value is denied; an unconstrained permission and a request that omits the dimension still match.TestPermissionsAllow_MultiAccountRequestDeniedOutsideScoperuns the scenario from the issue through the exportedPermissionsAllowForConstraintSets: a permission scoped toacct-a/aws/ec2allows the single-value request and denies a request widened on each dimension.TestContainsAll(replacesTestContainsAny) also pins the fail-closed empty-request case and exact comparison.Fail before (new tests run against the unchanged matcher on
origin/mainba38120):Pass after, plus the CI-equivalent checks:
Overlap with open PRs
permissionsAllowinservice_group.go(around lines 323-345). This PR editsmatchStringListConstraintsand thematchAllRegionsConstraintdoc comment (around lines 411-460). The hunks don't overlap and should merge cleanly. The two changes compose: once fix(auth): enforce constraints on an admin-scoped permission on every check #1096 lands, an{admin, *}permission's AccountIDs/Providers/Services constraints are also all-match.internal/apiand builds single-value strict constraint sets, so it is unaffected.internal/api/handler_ri_exchange_azure_test.go. Its comments refer tomatchAllRegionsConstraint(not renamed here) and to "containsAny's any-match rule" as a historical regression, which is still accurate.group_ceiling.gonear line 376 and removes tests near line 806 ofservice_group_test.go. This PR's edits there are one comment line near 229 and the region test's doc comment near 1436, so they don't overlap.Out of scope
StrictScope/listCovers, which deny this case.docs/audits/codebase-audit-2026-09-02.mdflags it as a separate concern, and changing it would be a wider behavior change than this issue asks for.listCovers(the grant ceiling) relies on exact enforcement there.Closes #138
A maintainer, please mirror the issue's triage labels onto this PR:
triaged,priority/p3,severity/low,urgency/eventually,impact/few,effort/xs,type/security.🤖 Generated with Claude Code