Skip to content

[Bug]: Python SDK drops PaymentInstrument extensions and matches allowed instruments by ID only #299

Open

Description

@zesttec

Version

AP2 v0.2.0 / main e1ea56d

Problem

1. Type-specific properties are silently dropped

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.

Relevant log output

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

  1. added theissue type on Jul 15, 2026
  2. shibutatsu commented on Jul 21, 2026

    @shibutatsu

    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?

  3. zesttec commented on Jul 21, 2026

    @zesttec
    Author

    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.

  4. shibutatsu commented on Aug 13, 2026

    @shibutatsu

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions