Repository navigation
fix(api): answer 400 for non-UUID account ids on the ladder routes - #1437
Conversation
PUT /api/ladder/configs and GET /api/ladder/{runs,tranches} pass a
caller-supplied account id straight to requireAccountAccess, which looked
it up without validating it. cloud_accounts.id is a UUID column, so
Postgres rejected the value with a syntax error and handleRequestError
turned it into a 500 with an error-level log line.
requireAccountAccess now refuses a non-UUID id with a 400 ClientError
before touching the store. That covers the ladder write and both
timeline reads and any future caller; valid UUIDs (found, missing or out
of scope) keep their current answers. The ladder tests that used
"acct-1"/"ghost" ids now use UUIDs.
Refs LeanerCloud#1430
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Opened at head b96468e: requireAccountAccess refuses a non-UUID account id with a 400 before the store lookup; fail-before/pass-after and local check results are in the PR body. |
Fresh CI: the previous run's Security Scanning failed on a transient Trivy database download 404 from mirror.gcr.io. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rC3. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 1 approving independent review(s) on |
Closes #1430
Root cause
requireAccountAccess(internal/api/scoping.go) passed the account id straight toGetCloudAccount.cloud_accounts.idis a UUID column, so a non-UUID value made Postgres fail withinvalid input syntax for type uuid. The store returned that as a plain error, andhandleRequestErrorturned it into a 500 logged at error level. The account handlers runvalidateUUIDon their path ids first. The ladder routes don't, because they take the id from the request body (PUT /api/ladder/configs,cloud_account_id) or the query string (GET /api/ladder/runsand/api/ladder/tranches,account_id).Fix
requireAccountAccessnow refuses a non-UUID id with a 400ClientError("invalid account id: must be a valid UUID") before it touches the store. The sameuuidRegexbacksvalidateUUID. This covers the ladder config write, both timeline reads, and any future caller. Valid UUIDs keep their current answers (found, missing or out of scope). The other callers already pass validated path ids or stored tranche account ids, so they don't change.The ladder handler tests that used
"acct-1"/"ghost"as account ids now use UUID constants. Each test still asserts what it asserted before.Verification
All local runs, Linux, Go 1.26.9 toolchain (go1.26.10 auto-selected):
TestUpsertLadderConfig_NonUUIDAccountIDIs400(PUT /api/ladder/configs) andTestLadderTimelineNonUUIDAccountIDIs400(subtests for/api/ladder/tranchesand/api/ladder/runs, driven throughNewRouter(h).Route). The mock store'sGetCloudAccountanswers a non-UUID id the way Postgres does, with a plainpq: invalid input syntax for type uuiderror. Each test asserts a 400ClientError, thatGetCloudAccountwas never called with the bad id, and that nothing reachedUpsertLadderConfigor the timeline store.internal/api/scoping.gorestored fromorigin/main,go test -short -run NonUUID ./internal/api/fails all three cases withgot *fmt.wrapError: accounts: pq: invalid input syntax for type uuid: "not-a-uuid", which is the 500 path.go build ./...,go vet ./...,go vet -tags integration ./...,(cd tests/e2e && go vet -tags e2e ./...): clean.golangci-lintv2.10.1 on./internal/api/, with and without--build-tags integration: 0 issues.gocyclo -over 10 internal/api/scoping.go: nothing reported.go test -race -short -count=1 ./internal/api/: ok.go test -race -tags integration -count=1 ./internal/api/: ok (656s; the full package, not only the CI-selected integration tests)secrets-scan.shon the changed files: "clean; planted key flagged".scripts/test-git-secrets-allowlist.sh: 183 PASS, 0 FAIL.Not verified locally: a real Postgres answering a non-UUID id. The mock reproduces its error shape, and the new guard returns before any store call, so the database is never reached.
🤖 Generated with Claude Code