Repository navigation
fix(api): keep enabled and aws_is_org_root when an account update omits them - #1554
Conversation
…ts them
PUT /api/accounts/{id} built the stored row from the request alone, so a
body without an `enabled` or `aws_is_org_root` key silently disabled the
account and cleared its org-root flag (audit finding A02-013). An API
client or Terraform provider renaming an account or rotating its role
would stop the scheduler from scanning it, with no error.
AWSIsOrgRoot on CloudAccountRequest is now a *bool like Enabled, and
updateAccount falls back to the stored value of each flag when the
request omits it or sends null. An explicit false still clears it.
Create keeps its defaults (both false when absent). openapi.yaml
documents the new update semantics.
Refs LeanerCloud#151
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 59 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
govulncheck in the Docker image build now fails on these two advisories, both fixed in x/net v0.62.0. 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: rG3. 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. |
|
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: rH1. 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: 2 approving independent review(s) on |
Root cause
PUT /api/accounts/{id}(updateAccount) built the stored row from the request alone throughcloudAccountFromRequest, copying onlyID,CreatedAtandCreatedByfrom the existing row.CloudAccountRequest.Enabledwas already a*bool, but a nil pointer still mapped tofalse.AWSIsOrgRootwas a plainbool, so "omitted" couldn't be told apart from "false" at all. An API client or Terraform provider that sent a PUT with a name and credentials but noenabledkey therefore disabled the account without any error, and the same PUT clearedaws_is_org_root. The scheduler then skipped the account. The web UI always sendsenabled, which hid this from UI users. This is audit finding A02-013 in the issue.Fix
internal/api/handler_accounts.go:CloudAccountRequest.AWSIsOrgRootis now*bool, likeEnabled.cloudAccountFromRequestapplies it only when the request sets it.keepOmittedAccountFlagshelper, called only fromupdateAccount, copies the storedEnabledandAWSIsOrgRootwhen the request omits them or sendsnull.falsestill clears either flag.createAccountand registration approval still treat an absent flag asfalse. Registration approval still overridesenabledfrom its credential check, andcreateSelfAccountstill forcesenabled=true.internal/api/openapi.yaml: theupdateAccountdescription no longer says "enabled defaults to false". Both properties are now markednullableand documented as "create: absent means false; update: absent or null keeps the stored value".Consumers: the CLI and MCP sibling checkouts (
cloud-commitments-cliat c07edc53,cloud-commitments-mcpat d49fb20) never call/api/accountsand never referenceaws_is_org_root(grep, read-only), so nothing depended on the old clearing behaviour.frontend/src/settings.tsalways sendsenabled, and sendsaws_is_org_rootfor AWS accounts, so UI saves behave as before.Refs #151
Concurrency
No DB or store change.
UpdateCloudAccountis still a whole-row overwrite, so two concurrent PUTs to one account are last-writer-wins on every column, the same as on main. With this change, a PUT that omitsenabledwrites back the value it read a moment earlier. If a concurrent PUT flipsenabledin between, the omitting PUT can undo that flip, just as it would already undo a concurrent rename. Before this change the omitting PUT always wrotefalse, which is strictly worse. Making the fallback atomic would need aCOALESCE-style store signature (*boolparameters inUpdateCloudAccount) or optimistic concurrency onupdated_at. That is a wider change than this finding, so it is not done here; this note is so a reviewer can decide.Verification
Unit/mock evidence. The handler is exercised through the real
updateAccount/createAccountcode against a capturing config-store mock that records the row passed toUpdateCloudAccount/CreateCloudAccount. This bug is in the request-to-row mapping, so no Postgres test was added. The SQL already writes whatever value the handler hands it.New tests in
internal/api/handler_accounts_update_keep_test.go:TestUpdateAccount_OmittedFlagsKeepStoredValuescovers seven cases: omitted keeps true, omitted keeps false,nullkeeps true, explicit false clears, explicit true sets, and each flag omitted while the other is explicit false.TestUpdateAccount_ExplicitFalseAndOmittedDivergefollows the issue's verification rule: a realfalseand a genuine absence through the same path must store different values.TestCreateAccount_FlagDefaultsUnchangedchecks that omitted andnullgive false/false on create, and explicit true gives true/true.Fail-before, on main 7b86e38 with only the test file added:
Pass-after, on this branch: all three tests and all 10 subtests pass.
Other commands run on this branch, all clean (run with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true):go build ./...go vet ./...go vet -tags integration ./...go test ./internal/api/ -count=1: ok (51s)go test ./internal/mocks/ -count=1: okgolangci-lint run --allow-parallel-runners ./internal/api/(v2.10.1): 0 issuesgocyclo -over 10 -ignore "_test\.go" internal/api/handler_accounts.go: no outputCombined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree:
queue/platform@400d256b= origin/main7b86e386plus these heads, merged in this order:Linux, Go 1.26.9,
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers. The full suite ran onf7c48020, which differs from this tree only by test-only commits on fix/issue-30, fix/issue-713 and fix/issue-263 that replace placeholder account-number literals with named constants:go build ./...,go vet ./...,go vet -tags integration ./...and the fullgo test -race -short -count=1 ./....--build-tags integration.gocyclo -over 10is silent on the changed non-test files.-tags integration -count=1):internal/analytics,internal/api,internal/auth,internal/config,internal/database/postgres/migrations,internal/purchase,internal/schedulerandinternal/serverall pass. Migrations 000149, 000150 and 000151 apply in order on this tree.approval-details,settings-accounts,api) pass (221 tests).accountkey); fixed with named constants. Re-run on this tree: git-secrets, using the repo'sscripts/setup-git-secrets.shpatterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged.scripts/test-git-secrets-allowlist.sh: 183 PASS, 0 FAIL..tftest.hclfiles were not run locally (CI runs them). Nothing was applied to any cloud.Out of scope
assembleLadderPlanfabricating a $0existing_usd_hr), which is held in fix(server): report the ladder plan's net existing commitment #1461.No frontend change is needed. The account form (
frontend/src/settings.ts) always sendsenabled, and it sendsaws_is_org_rootfor AWS accounts, so UI saves behave exactly as before. For Azure and GCP accounts the form omitsaws_is_org_root, which now keeps the stored value (normally false) instead of forcing it to false.Maintainer: please mirror the issue's triage labels on this PR:
priority/p2,severity/high,urgency/this-quarter,impact/many,effort/l,type/bug,triaged.🤖 Generated with Claude Code