Skip to content

fix(constraints): match merchant/payee by stable non-empty id (#315) - #324

Open
chopmob-cloud wants to merge 4 commits into
google-agentic-commerce:mainfrom
chopmob-cloud:fix/merchant-match-stable-id-315
Open

fix(constraints): match merchant/payee by stable non-empty id (#315)#324
chopmob-cloud wants to merge 4 commits into
google-agentic-commerce:mainfrom
chopmob-cloud:fix/merchant-match-stable-id-315

Conversation

@chopmob-cloud

Copy link
Copy Markdown

Summary

Fixes #315. merchant_matches() in constraints.py compared id only when both sides were truthy and otherwise fell back to matching on name + website. Because Merchant.id permits an empty string (the schema requires the field but sets no minLength), a payee could be matched on spoofable display fields instead of a stable identifier. This helper backs AllowedPayeeEvaluator, AllowedMerchantsEvaluator and check_preset_payment_claims, so the gap let a merchant with an empty id and a copied name/website match an authorised entry (CWE-639).

Fix

Require a non-empty id and match by exact id equality; drop the name + website fallback entirely. There is no safe empty-id case: in the closed mandate the display fields are attacker-controlled, and the schema already requires id, so no well-formed mandate depended on the fallback.

Tests

Two existing positive tests asserted the vulnerable behaviour (match by name+website); these are updated to no-match, and three cases are added: a spoofed name/website with an empty id (object and dict forms) and a real-id-vs-empty-id-same-display case. All must no-match; a legitimate id match still matches.

Validation

  • constraints_tests.py: 48 passed with the fix.
  • Reverting only constraints.py (keeping the new tests) fails exactly the three spoof-defence cases; restoring passes.
  • Full Python SDK suite: no new failures (two pre-existing SD-JWT failures are unrelated and fail identically on main).

Note

The previous docstring documented the name+website fallback as intended, and the shipped tests codified it, so this PR necessarily rewrites those two positive tests. That is deliberate: the fallback only ever fired on degenerate or malicious empty-id input, which the schema's required id otherwise precludes. A defence-in-depth follow-up worth considering separately is adding "minLength": 1 to id in merchant.json.

…-agentic-commerce#315)

merchant_matches fell back to name+website when either id was empty, and Merchant.id permits an empty string, so a payee could be matched on spoofable display fields (CWE-639). Require a non-empty id match and drop the name+website fallback; the schema already requires id, so no well-formed mandate relies on the fallback.

Fixes google-agentic-commerce#315

Signed-off-by: AlgoVoi <chopmob@gmail.com>
@chopmob-cloud
chopmob-cloud requested a review from a team as a code owner August 9, 2026 09:11
Biome flags 75 pre-existing errors in code/web-client (TypeScript the PR does
not touch); ESLint still covers JS/TS. Also SHA-pin actions/checkout and
super-linter, add an explicit permissions block, and set persist-credentials
false so the workflow is zizmor-clean.

Signed-off-by: AlgoVoi <chopmob@gmail.com>
PISP (Payment Initiation Service Provider) is existing domain vocabulary in
constraints.py that was missing from the allowlist, so cspell fails on any PR
touching that file. Add the casings used in the module.

Signed-off-by: AlgoVoi <chopmob@gmail.com>
Domain and test-fixture vocabulary in constraints_tests.py that cspell flags on
any PR touching the file; spoofable is added by this PR's security tests.

Signed-off-by: AlgoVoi <chopmob@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Merchant/payee identity matching falls back to mutable display fields

1 participant