Summary
MockAuthService.grantPermissionsScoped (internal/api/mocks_test.go:403-417) wires both HasPermissionAPI and HasPermissionForConstraintsAPI to the same closure:
decide := func(action, resource string) bool {
return authCtx.HasPermission(action, resource)
}
m.On("HasPermissionAPI", ...).Return(decide, nil).Maybe()
// The SEC-01 execution-time constraint check. A principal holding only
// admin:* satisfies any constraint set for the verbs it holds, and holds
// none of the carved-out verbs, so the same decision function applies.
m.On("HasPermissionForConstraintsAPI", ...).Return(decide, nil).Maybe()
decide takes (action, resource) and never looks at the constraint arguments. So the constraint dimension — providers, regions, MaxPurchaseAmount — is not modelled at all: every handler test that appears to exercise it is measuring the mock.
The comment justifying it is now false
The quoted justification holds only while no test principal holds a carved-out verb. PR #1758 introduces exactly such a principal: execute:ri-exchange joins adminCarvedOuts, and membership in the seeded RI Exchanger group (migration 000096) grants it explicitly.
A stale justification is worse than none — the next reader stops at the comment and concludes the shortcut is sound.
Evidence (execution-verified)
Measured against real permissionsAllow in internal/auth, compared to what the mock's decide closure answers for the same principal:
held: execute:ri-exchange constrained to Providers: ["aws"]
request: Providers: ["azure"]
production (permissionsAllow): false
mock (decide closure): true
divergent: true
Production refuses the cross-provider request; the mock allows it. Any handler test asserting a constraint refusal for such a principal is green regardless of whether the handler enforces constraints at all.
This is the third divergence in this mock today — the pattern is the design, not three bugs
| # |
divergence |
status |
| 1 |
grantAdmin stubbed the authorization decision, answering true for carved-out verbs production denies |
fixed in #1744 |
| 2 |
empty constraintSets: production fails closed, the mock answered true |
fixed in #1744 |
| 3 |
this one: HasPermissionForConstraintsAPI discards the constraint arguments entirely |
open |
Three in one day in one mock is a signal about its shape: after #1744 it models the permission set faithfully (via the real AuthContext.HasPermission) and the constraint answer not at all. The two halves of the authorization decision are held to different standards, and the constraint half has no backing implementation.
That changes what a fix should look like. Patching the third case in isolation invites a fourth. The direction that removes the class is to make HasPermissionForConstraintsAPI delegate to the same real code path production uses — Service.permissionsAllow(perms, action, resource, constraints) — so the mock models the principal rather than the answer, exactly as #1744 did for the permission half.
Suggested work
- Wire
HasPermissionForConstraintsAPI to real constraint evaluation rather than to decide(action, resource).
- Delete or correct the justification comment at
mocks_test.go:410-412.
- Add a regression test that a constrained holder of a carved-out verb is refused a request outside its constraints, and confirm it fails against the current stub.
Expect fallout: handler tests that currently pass because constraints are ignored may start failing, and each such failure is a genuine gap worth triaging rather than papering over.
References
Found during independent adversarial review of PR #1758.
Summary
MockAuthService.grantPermissionsScoped(internal/api/mocks_test.go:403-417) wires bothHasPermissionAPIandHasPermissionForConstraintsAPIto the same closure:decidetakes(action, resource)and never looks at the constraint arguments. So the constraint dimension — providers, regions,MaxPurchaseAmount— is not modelled at all: every handler test that appears to exercise it is measuring the mock.The comment justifying it is now false
The quoted justification holds only while no test principal holds a carved-out verb. PR #1758 introduces exactly such a principal:
execute:ri-exchangejoinsadminCarvedOuts, and membership in the seeded RI Exchanger group (migration 000096) grants it explicitly.A stale justification is worse than none — the next reader stops at the comment and concludes the shortcut is sound.
Evidence (execution-verified)
Measured against real
permissionsAllowininternal/auth, compared to what the mock'sdecideclosure answers for the same principal:Production refuses the cross-provider request; the mock allows it. Any handler test asserting a constraint refusal for such a principal is green regardless of whether the handler enforces constraints at all.
This is the third divergence in this mock today — the pattern is the design, not three bugs
grantAdminstubbed the authorization decision, answeringtruefor carved-out verbs production deniesconstraintSets: production fails closed, the mock answeredtrueHasPermissionForConstraintsAPIdiscards the constraint arguments entirelyThree in one day in one mock is a signal about its shape: after #1744 it models the permission set faithfully (via the real
AuthContext.HasPermission) and the constraint answer not at all. The two halves of the authorization decision are held to different standards, and the constraint half has no backing implementation.That changes what a fix should look like. Patching the third case in isolation invites a fourth. The direction that removes the class is to make
HasPermissionForConstraintsAPIdelegate to the same real code path production uses —Service.permissionsAllow(perms, action, resource, constraints)— so the mock models the principal rather than the answer, exactly as #1744 did for the permission half.Suggested work
HasPermissionForConstraintsAPIto real constraint evaluation rather than todecide(action, resource).mocks_test.go:410-412.Expect fallout: handler tests that currently pass because constraints are ignored may start failing, and each such failure is a genuine gap worth triaging rather than papering over.
References
grantAdmindecision stub)AssertNotCalledis vacuous on every mock type(action, resource)a handler asks for; this one is about the constraint arguments being discarded)Found during independent adversarial review of PR #1758.