You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Python SDK drops PaymentInstrument extensions and matches allowed instruments by ID only #299
AP2 permits a Payment Instrument type to define additional properties. [Specification]The x402 sample attempts to pass payee_address and facilitator to the generic PaymentInstrument model. [Sample]
The generated model defines only id, type, and description; its current Pydantic behavior accepts but discards the extra fields. [Model] Signed claims are created using model_dump(), so the discarded fields are absent from the signed Payment Mandate. [Signing path]
After verification, the x402 CP tries to read the missing payee_address. The resulting AttributeError causes it to replace both the destination with an environment/default address and the signed amount with the fixed value 1250. [Fallback]
Consequently, the sample can construct its EIP-3009 authorization from fallback values rather than the corresponding verified Mandate data. This should be an SDK/sample correctness and integrity issue.
2. Allowed-instrument matching is underspecified and ID-only
AP2 requires the closed payment_instrument to be present in the authorized allowed array, but it does not define the matching fields or the uniqueness namespace of id. [Constraint] [Schema]
The Python evaluator compares only id. It therefore accepts an allowed {id: "same", type: "card"} and a closed {id: "same", type: "bank"}. [Evaluator]
At minimum, this permits inconsistent signed instrument metadata. If type selects the payment profile, or IDs are scoped by type or Credential Provider, a different profile or instrument could satisfy the constraint.
Suggested fix
Provide a validated extension mechanism or registered per-type models, and preserve their fields through parsing and signing.
In the x402 sample, always use the verified payment_amount; define and validate the destination field and fail closed when it is absent.
Define the namespace and matching semantics of PaymentInstrument.id.
Require at least (type, id) matching or delegate comparison to the instrument profile.
Add regression tests for extension-field round trips and same-ID/different-type rejection.
Independent regression data point: we published an experimental, non-normative offline suite at https://github.com/shibutatsu/pulse-ap2-x402-conformance, pinned to AP2 e1ea56db72a6385bce3e5c1112b3a56ce60acb43 and x402 67b1ba0a7abbd7907a28fa624670872532e0eae9 / 2.19.0.
The AP2 pipeline uses a fixture-only x402-preserving Pydantic model because the generated PaymentInstrument drops extra fields, then re-verifies the signed chain and requires full id/type/x402-extension equality. Negative fixtures fail closed on missing or unknown instrument extensions and on amount/payee divergence.
This is not a proposed AP2 model fix and does not supersede #300 or #301. Would a small upstream regression fixture for the remaining extension-preservation item be useful, and should it target schema generation or the x402 sample tests?
Independent regression data point: we published an experimental, non-normative offline suite at https://github.com/shibutatsu/pulse-ap2-x402-conformance, pinned to AP2 e1ea56db72a6385bce3e5c1112b3a56ce60acb43 and x402 67b1ba0a7abbd7907a28fa624670872532e0eae9 / 2.19.0.
The AP2 pipeline uses a fixture-only x402-preserving Pydantic model because the generated PaymentInstrument drops extra fields, then re-verifies the signed chain and requires full id/type/x402-extension equality. Negative fixtures fail closed on missing or unknown instrument extensions and on amount/payee divergence.
This is not a proposed AP2 model fix and does not supersede #300 or #301. Would a small upstream regression fixture for the remaining extension-preservation item be useful, and should it target schema generation or the x402 sample tests?
Thanks for sharing this. Yes, I think a small upstream regression fixture would help. If choosing one location, I’d put it at the schema/generated-model boundary, since that’s where the remaining issue occurs. Ideally, it would use the actual generated PaymentInstrument and verify that a supported type-specific field survives:
model construction and model_dump();
nesting inside a PaymentMandate; and
signing, verification, and typed re-parsing.
The key expectation is that supported extension fields survive end to end, while unsupported fields are rejected clearly instead of being silently dropped.
An x402 sample test would also be useful as follow-up integration coverage once the x402-specific field shape and destination/payee semantics are settled. If adding only one test now, I’d prioritize the generated-model test because it targets the root issue directly. #300 addresses the verified amount handling, and #301 addresses (type, id) matching.
I implemented the generated-model regression you suggested in #333.
The test uses the actual generated PaymentInstrument, nests it in a PaymentMandate, signs and verifies the mandate, then performs typed re-parsing. A type-specific extension field is required to survive each step. The accompanying schema and generated-model changes preserve extension fields instead of silently dropping them.
The targeted Python tests and the full SDK suite pass locally. Spellcheck also passes on the PR. The remaining GitHub checks are the contributor CLA and a repository-wide lint failure in files outside the three-file change; I left the details on the PR.
Marking #333 ready for review so the implementation is linked back to this request.
Activity
shibutatsu commentedon Jul 21, 2026
Independent regression data point: we published an experimental, non-normative offline suite at https://github.com/shibutatsu/pulse-ap2-x402-conformance, pinned to AP2
e1ea56db72a6385bce3e5c1112b3a56ce60acb43and x40267b1ba0a7abbd7907a28fa624670872532e0eae9/ 2.19.0.The AP2 pipeline uses a fixture-only x402-preserving Pydantic model because the generated
PaymentInstrumentdrops extra fields, then re-verifies the signed chain and requires full id/type/x402-extension equality. Negative fixtures fail closed on missing or unknown instrument extensions and on amount/payee divergence.This is not a proposed AP2 model fix and does not supersede #300 or #301. Would a small upstream regression fixture for the remaining extension-preservation item be useful, and should it target schema generation or the x402 sample tests?
zesttec commentedon Jul 21, 2026
Thanks for sharing this. Yes, I think a small upstream regression fixture would help. If choosing one location, I’d put it at the schema/generated-model boundary, since that’s where the remaining issue occurs. Ideally, it would use the actual generated PaymentInstrument and verify that a supported type-specific field survives:
The key expectation is that supported extension fields survive end to end, while unsupported fields are rejected clearly instead of being silently dropped.
An x402 sample test would also be useful as follow-up integration coverage once the x402-specific field shape and destination/payee semantics are settled. If adding only one test now, I’d prioritize the generated-model test because it targets the root issue directly. #300 addresses the verified amount handling, and #301 addresses (type, id) matching.
fix(sdk): match allowed payment instruments by (type, id), not id alone
fix(samples): authorize the verified mandate amount in x402 credentia…
fix(sdk): preserve type-specific PaymentInstrument extension fields t…
shibutatsu commentedon Aug 13, 2026
I implemented the generated-model regression you suggested in #333.
The test uses the actual generated
PaymentInstrument, nests it in aPaymentMandate, signs and verifies the mandate, then performs typed re-parsing. A type-specific extension field is required to survive each step. The accompanying schema and generated-model changes preserve extension fields instead of silently dropping them.The targeted Python tests and the full SDK suite pass locally. Spellcheck also passes on the PR. The remaining GitHub checks are the contributor CLA and a repository-wide lint failure in files outside the three-file change; I left the details on the PR.
Marking #333 ready for review so the implementation is linked back to this request.