Repository navigation
fix(accounts): scrub account_requests[] and request_account like account rows - #3604
Merged
Merged
Conversation
…unt rows A resolved AdCP 3.3 account request embeds a full Account. list_accounts served account_requests[] raw next to the scrubbed account rows, and request_account had no response builder at all, so billing_entity.bank, governance_agents[].authentication, notification_configs[].authentication .credentials and destination_billing_entity reached the buyer and the idempotency replay cache. listAccountsResponse now scrubs each account_requests[] item, and request_account gets a framework wrap that scrubs `request` on both arms. The embedded account goes through stripAccountWriteOnlyFields; `requested` loses the same credential fields. The account scrubber also drops governance_agents[].authentication, which raw list_accounts / sync_accounts / get_media_buys handlers could otherwise echo. Inputs are copied, not mutated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — security fix scrubs write-only account credentials from list_accounts and request_account responses.
What I checked:
- Credential scrub (billing_entity.bank, governance_agents[].authentication, notification_configs[].authentication.credentials, destination_billing_entity) applied to account_requests[] and request_account responses, consistent with the existing account-row scrub precedent.
- Both raw and platform paths funnel through one TOOL_META wrap (create-adcp-server.ts:3573);
stripAccountCredentialFields(responses.ts:75) copies and never mutates. - Replay path caches the wrapped/scrubbed payload; test asserts no secrets leak on replay with validation off.
candidates[]uncovered but AccountCandidate carries no credential fields — not a gap.- Changeset present (
patch) — defensible for a leak bugfix matching the prior account-row scrub precedent.
No Critical/High/Medium findings. high_risk false, gated_paths false, no author team gate. Rows 1–8 do not fire → row 9 approve.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security fix
A resolved AdCP 3.3
AccountRequestembeds a fullAccount. The SDK scrubbedlist_accountsaccounts[]rows but servedaccount_requests[]raw, andrequest_accounthad no response builder at all (wrap: null→genericResponse). Whatever the seller kept on the account went to the buyer and into the idempotency replay cache:billing_entity.bankgovernance_agents[].authentication(the seller's bearer forcheck_governance)notification_configs[].authentication.credentials(webhook signing secrets)destination_billing_entity(request-only; must not appear in account read models)requested, if a handler spread the inbound request into itAffected
request_account,createAdcpServerandcreateAdcpServerFromPlatformlistAccountshandler returningaccount_requestsaccounts.listRequests→account_requests: requests.itemsStable 16.x does not serve account requests.
Fix (wire-emit boundary,
src/lib/server/responses.ts)listAccountsResponsenow scrubs eachaccount_requests[]item.requestAccountResponsewrap forrequest_account(TOOL_META) scrubsrequeston both the completed and thestatus: 'failed'arm.accountgoes throughstripAccountWriteOnlyFields, the same scrubberaccounts[]rows use.requesteddrops the same credential fields (no identity projection).stripAccountWriteOnlyFieldsnow also dropsgovernance_agents[].authentication. The v6 platform path already projected rows to{ url }, but rawcreateAdcpServerlist_accounts/sync_accounts/get_media_buyshandlers could still echo it.I put the scrub in the response builders, not in
from-platform.ts. That way one change covers both the v5 handler-bag path and the v6 platform path, and the cached replay payload is the scrubbed one. This mirrors adcontextprotocol/adcp-client-python#1820. The TS SDK has noCREDENTIAL_BEARING_METHODS-style list (the task-registry and webhook paths don't key scrubbing off tool names), so there was nothing to addrequest_accountto.Tests
test/lib/account-requests-seller.test.js› account requests never echo write-only account fields:account_requests[]item and onerequest_accountresponse, each carrying a bank account, a governance credential, a webhook credential anddestination_billing_entityonaccountandrequested. All four are stripped, the idempotent replay is clean, and the store record is unchanged.createAdcpServerraw handlers: same checks for anaccounts[]row, anaccount_requests[]item, and the failedrequest_accountarm.mainwithout thesrc/change (verified by stash + rebuild).Local checks
tsc --noEmit,npm run lint,npm run lint:boundaries: pass. Prettier on changed files: pass.npm run test:lib:fast: everything passes except the live-Postgres suites (ECONNREFUSED, no DB locally).check:package-size: within budget.🤖 Generated with Claude Code