What happened?
Description
AP2 closed Checkout Mandates contain a merchant-signed checkout_jwt and a checkout_hash that binds the mandate to that checkout.
The Python SDK's CheckoutMandateChain.verify() accepts expected_checkout_hash as optional. If it is omitted, the SDK evaluates checkout constraints against the supplied checkout_jwt but does not check the closed mandate's checkout_hash against an independently expected checkout hash.
The Python Merchant MCP sample passes checkout_jwt=chain.closed_mandate.checkout_jwt but does not pass expected_checkout_hash. It then decodes that embedded JWT payload and continues checkout processing.
Impact
A merchant verifier can validate checkout constraints against checkout data carried inside the closed mandate, rather than binding the mandate to an independently created merchant checkout session.
This weakens the protection against manipulated or unrelated checkout data being used to complete an order.
Steps to Reproduce
-
Review code/sdk/schemas/ap2/checkout_mandate.json:20-28. checkout_jwt is the merchant-signed checkout payload, and checkout_hash uniquely identifies that JWT.
-
Review code/sdk/python/ap2/sdk/checkout_mandate_chain.py:44-100. expected_checkout_hash is optional and checkout_hash is checked only when that value is provided.
-
Review code/sdk/python/ap2/sdk/checkout_mandate_chain.py:112-125. The SDK extracts checkout data by decoding the JWT payload.
-
Review code/samples/python/src/roles/merchant_agent_mcp/server.py:872-883. The Merchant sample verifies the checkout mandate using checkout_jwt from the closed mandate, without passing expected_checkout_hash.
-
Review code/samples/python/src/roles/merchant_agent_mcp/server.py:900-944. The sample decodes the embedded checkout JWT payload and continues checkout processing from that data.
Relevant log output
Code of Conduct
What happened?
Description
AP2 closed Checkout Mandates contain a merchant-signed
checkout_jwtand acheckout_hashthat binds the mandate to that checkout.The Python SDK's
CheckoutMandateChain.verify()acceptsexpected_checkout_hashas optional. If it is omitted, the SDK evaluates checkout constraints against the suppliedcheckout_jwtbut does not check the closed mandate'scheckout_hashagainst an independently expected checkout hash.The Python Merchant MCP sample passes
checkout_jwt=chain.closed_mandate.checkout_jwtbut does not passexpected_checkout_hash. It then decodes that embedded JWT payload and continues checkout processing.Impact
A merchant verifier can validate checkout constraints against checkout data carried inside the closed mandate, rather than binding the mandate to an independently created merchant checkout session.
This weakens the protection against manipulated or unrelated checkout data being used to complete an order.
Steps to Reproduce
Review
code/sdk/schemas/ap2/checkout_mandate.json:20-28.checkout_jwtis the merchant-signed checkout payload, andcheckout_hashuniquely identifies that JWT.Review
code/sdk/python/ap2/sdk/checkout_mandate_chain.py:44-100.expected_checkout_hashis optional andcheckout_hashis checked only when that value is provided.Review
code/sdk/python/ap2/sdk/checkout_mandate_chain.py:112-125. The SDK extracts checkout data by decoding the JWT payload.Review
code/samples/python/src/roles/merchant_agent_mcp/server.py:872-883. The Merchant sample verifies the checkout mandate usingcheckout_jwtfrom the closed mandate, without passingexpected_checkout_hash.Review
code/samples/python/src/roles/merchant_agent_mcp/server.py:900-944. The sample decodes the embedded checkout JWT payload and continues checkout processing from that data.Relevant log output
Code of Conduct