Repository navigation
Open mandate constraints: contains is dropped by codegen, so the checkout binding the schema requires is never enforced #356
Description
Summary
open_payment_mandate.json requires the constraints array to contain a
payment.reference, and open_checkout_mandate.json to contain a line_items.
Neither reaches the generated model or any runtime check, so an open payment
mandate with no checkout binding is created, chain verified, and constraint
checked with zero violations.
I am filing this as a question rather than a fix, because the schema rule and
another part of the specification look inconsistent, and which one is wrong is
your call. Details at the end.
The mechanical part
Walking every validation keyword in code/sdk/schemas against
code/sdk/python/ap2/sdk/generated, contains is the only enforceable keyword
in the vocabulary that does not survive generation:
| keyword | occurrences | survives |
|---|---|---|
required |
35 | yes, Field(...) |
const |
24 | yes, Literal |
enum |
9 | yes, Enum |
format |
7 | yes, AnyUrl |
pattern |
2 | yes, constr(pattern=) |
minimum |
2 | yes, conint(ge=) |
minItems |
1 | yes, min_length |
exclusiveMinimum |
1 | yes, PositiveInt |
contains |
2 | no |
Regenerating with code/sdk/schemas/generate.py reproduces the drop byte for
byte, so this is deterministic generator behaviour rather than a stale artifact.
Reproduction
An open payment mandate with an empty constraints array, which is the shape
chain_tests.py itself constructs:
open_payment_mandate, constraints: []
jsonschema against the shipped schema : INVALID
"[] does not contain items matching the given schema" (keyword: contains)
OpenPaymentMandate.model_validate : ACCEPTED
End to end through the SDK, a verifier that supplies the expected checkout
binding still gets nothing back, because check_payment_constraints builds an
evaluator per constraint found, so an absent constraint means
open_checkout_hash is never read.
Why the binding matters
docs/ap2/security_and_privacy_considerations.md, under Manipulated
Checkout:
The Payment Mandate MUST contain a reference to its associated Checkout. This
is viatransaction_idfor closed Payment Mandates and the
mandate.payment.referenceconstraint for open ones.
Three things that cut against simply enforcing it
These are the reason I am not opening a pull request.
- Your own fixtures mostly omit it. Twelve files construct an
OpenPaymentMandate, 47 sites in total. Onlyconstraints_tests.pyand the
shopping_agent_v2sample referencePaymentReferenceat all. Nine test
files construct open mandates with none. payment.agent_recurrencelooks incompatible with the rule as written.
docs/ap2/payment_mandate.md:51describes it as conditions for the agent to
reuse one mandate multiple times, and a mandate reused many times cannot
bind to the digest of a single checkout. Thecontainsrule is
unconditional.- Enforcement would therefore fail a substantial part of the suite, including
the recurrence tests, which cannot be updated without first deciding 1 and 2.
Which way would you like it
Three options as I see them, and I have no stake in which:
- enforce
containsin the SDK, and update the fixtures - make the rule conditional, so it does not apply when
payment.agent_recurrenceis present - drop
containsfrom the schema and amend the security document
Happy to open the pull request for whichever you prefer.
Related
- Constraint evaluation is presence-driven: a withheld disclosure silently disables enforcement (+ budget.max unit is undocumented) #339 raises the runtime half of this, that constraint evaluation is presence
driven. It notes the underlying reports were closed through the vulnerability
programme as intended behaviour. This issue is about something that issue does
not cover: the schema itself declares the constraint mandatory, and the
generator drops the keyword that says so. - fix(sdk): enforce Payment Mandate closed checkout-binding by default in chain verify #330 is mine and still open. It quotes the same sentence from the same threat
section and touches the same verify path, but for the closed side. I checked
its branch, and it does not close this hole. Fold this into that PR if you
would rather handle them together.
Activity