Skip to content

[Bug]: Allowed payment instruments are matched by id without binding type #320

Description

@mh-yu

What happened?

Description

AP2 defines payment.allowed_payment_instruments as an allowed array of Payment Instrument objects. A Payment Instrument contains both id and type, and type identifies the instrument category.

The Python SDK's AllowedPaymentInstrumentEvaluator checks only allowed.id == instrument.id. It does not compare type.

As a result, a closed mandate using {id: "shared-id", type: "bank_account"} can satisfy an open mandate that allowed {id: "shared-id", type: "card"}.

Impact

The mandate signatures and chain may still verify, but the payment-instrument authorization check is under-bound.

The practical impact depends on whether Payment Instrument IDs are guaranteed to be globally unique across instrument types, providers, rails, and account namespaces. If IDs are scoped or can repeat across types, the SDK can accept a closed mandate selecting a payment instrument type different from the one authorized by the user.

Downstream payment may still fail if the Credential Provider or payment rail performs stricter checks. The issue is that the SDK verifier can incorrectly pass the authorization constraint before those downstream checks.

Steps to Reproduce

  1. Review docs/ap2/payment_mandate.md:102-117. The closed mandate's payment_instrument must be present in the open mandate's allowed array.

  2. Review docs/ap2/specification.md:389-394 and code/sdk/schemas/ap2/types/payment_instrument.json:8-24. Payment Instrument objects include both id and type, and type identifies the instrument category.

  3. Review code/sdk/schemas/ap2/open_payment_mandate.json:198-219. allowed is an array of Payment Instrument objects, not an array of IDs.

  4. Review code/sdk/python/ap2/sdk/constraints.py:232-251. AllowedPaymentInstrumentEvaluator.evaluate() returns success when any allowed instrument has the same id as the closed mandate's instrument.

  5. Review code/sdk/python/ap2/tests/constraints_tests.py:377-410. Existing tests cover same-ID/same-type success and different-ID failure, but not same-ID/different-type rejection.

Relevant log output

Code of Conduct

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

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