Repository navigation
fix(auth): #940 review follow-ups — split group_ceiling.go, deny malformed requested amount under a cap - #954
Conversation
Pure move of constraintsCover, listCovers and amountCovers from group_ceiling.go into grant_ceiling_cover.go (same package, bodies and comments unchanged) so group_ceiling.go is back under the 500-line guideline (519 -> 448). No behavior change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
matchPurchaseAmountConstraint only validated the stored cap. A NaN requested amount compares false against the cap and a negative one sits below it, so either passed a capped permission. When a cap applies, a negative, NaN or infinite requested amount now denies. With no cap (permMax == 0) the amount dimension stays unrestricted, matching a permission with no constraints at all, which never reaches the matcher. reqMax is computed server-side, so this is defense in depth; no caller changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cristim
left a comment
There was a problem hiding this comment.
Independent review verdict: CLEAN at 332ed90
Reviewer: fresh Claude agent (Sonnet, high effort), not the author, cold brief. The PR body was treated as a claim. Coordinator: claude-cc-reviewer.
Blocking findings: none. The change is a strict tightening. For valid requested amounts (finite, >= 0) every outcome matches the base. The only newly denied inputs are a negative, NaN or ±Inf request under a cap > 0, which previously got through for NaN and negatives.
Non-blocking notes (optional follow-ups):
internal/auth/purchase_amount_cap.go:41: themath.IsInfbranch of the requested-amount guard is not pinned by a test. It only matters for a stored cap of+Inf, whichinvalidPurchaseAmountCapdoes not reject. A test with cap+Infand request+Infwould pin it, or+Infcaps could be rejected at validation.- The
math.IsNaNbranch is redundant (NaN <= capis false), but it is harmless defense in depth.
What was attacked and how (local macOS; unit/mock evidence only, no DB, cloud account, purchase or deploy):
-
Refactor commit: I diffed the hunk removed from
group_ceiling.goagainstgrant_ceiling_cover.go.constraintsCover,listCoversandamountCoversare verbatim; only the new file header differs. -
Callers: the only production caller is
matchConstraints(internal/auth/service_group.go:408, viacheckPermissionConstraints). I traced every producer of the requested amount:- the purchase constraint sets (
handler_purchases.go:2788, with the zero-commitment guard); - the USD exchange path (
handler_ri_exchange.go:1090, 2512); - the non-USD Azure path, which sends
math.MaxFloat64. That is still denied under a finite cap and still allowed under aMaxFloat64cap, as before. Its follow-up probe sends0. - Revoke, which passes no amount.
Cap
0means no cap everywhere (and-0.0 == 0). NaN and negative caps are still rejected first byinvalidPurchaseAmountCap. - the purchase constraint sets (
-
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null go test ./internal/auth/... ./internal/api/... -count=1: 4246 passed, 0 failed. There were 3 pre-existing SKIPs unrelated to this change (a federation IaC tfvars case and two four-eyes revoke TODOs).go vet ./internal/auth/...was clean. -
Fail-before: with the base
purchase_amount_cap.gorestored in a scratch copy,TestMatchPurchaseAmountConstraint_MalformedRequestedAmountfails: -1, -0.01, -MaxFloat64, -Inf and NaN all get through. -
8 hand mutation probes: 5 were killed (
<=to<,== 0to!= 0, dropping the guard,v < 0tov <= 0, droppingv < 0). 3 survived, all non-blocking: dropping IsNaN, dropping IsInf, and== 0to<= 0(equivalent, because negative caps are caught earlier).
CodeRabbit: skipped. It is rate-limited and the on-demand review is billable. The gate permits this because exact-revision local verification plus this independent review cover the SHA.
Remaining gate items before merge:
- CI:
mainis currently red because ofTestScheduler_ListRecommendations_StaleCacheKicksBackgroundRefresh, which #960 fixes. This PR's Unit and Integration jobs inherit that failure. After #960 merges, please rebase onto main. An emptygit range-diffagainst 332ed90 keeps this verdict. - Labels: #953 has no triage labels, so there is nothing to mirror yet. A maintainer needs to label #953 (the body suggests #83's set), or state that none applies.
|
#960 has merged (2ef2f5d), which fixes the red |
|
Thanks @cristim. I merged 🤖 Generated with Claude Code |
|
Closing and reopening to refresh CI on the current main merge ref: main changed this PR's packages after its last CI run. The branch is untouched. (claude-cc-pr-merge-3) |
Merge gate record: #954 at 32030f0
Merged by claude-cc-pr-merge-3 with |
Closes #953. Follow-up to #940 (issue #83), which merged at 7dc1891 before these two commits landed on its branch. They are cherry-picked unchanged onto current main (b49b494).
Changes
1.
refactor(auth): move the grant-ceiling coverage helpers into their own file.constraintsCover,listCoversandamountCoversmove verbatim frominternal/auth/group_ceiling.goto the newinternal/auth/grant_ceiling_cover.go. There is no behavior change. A diff of the removed lines against the new file's body is identical.group_ceiling.gogoes from 519 to 448 lines. The new file is 77 lines.2.
fix(auth): deny a malformed requested amount when a spend cap applies. The change is ininternal/auth/purchase_amount_cap.go.math.MaxFloat64, which is finite, so its behavior is unchanged.The operator note (review note 3) already lives in #940's description under "Behavior notes".
Verification
These are unit and mock tests only, with no real database, cloud account, purchase or deploy. Tests ran with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null, because this machine's local AWS config is invalid.TestMatchPurchaseAmountConstraint_MalformedRequestedAmountfailed before the fix with 10 assertion failures: -1, -0.01, -MaxFloat64, -Inf and NaN were each allowed under caps of 1000 and MaxFloat64. It passes after the fix, and the hardening(auth): negative MaxPurchaseAmount constraint is treated as 'no constraint' instead of deny-all #83 tests still pass.go build ./...passed andgo vet ./internal/auth/...was clean.go test ./internal/auth/... ./internal/api/... -count=1: ok.gocyclo -over 10is silent on the three changed non-test files.scripts/test-git-secrets-allowlist.shgives 42 PASS, 0 FAIL, and a git-secrets scan of the changed files is clean.Maintainer: #953 has no triage labels yet. Suggested: mirror #83's labels (
type/security,priority/p3,severity/low,effort/s,impact/internal,urgency/eventually,triaged).🤖 Generated with Claude Code
Summary by CodeRabbit