Potential payment verification issue in paid resource gate
Hi, I noticed a possible payment-flow issue while reviewing the current repository state. This is a conservative report based on the visible code path, and I may be missing deployment-specific guards outside this repository.
Reviewed HEAD: 85f3eb6b61b9
What I observed
In the visible payment path, I observed:
ucp-proxy is a real UCP checkout/payment proxy. The MCP schema comments require idempotency-key for complete/cancel, but neither REST nor MCP handlers validate or persist an idempotency key, and CompleteCheckout directly calls WooCommerce /checkout. Replaying complete_checkout can therefore re-submit the payment/order path instead of being deduplicated.
Relevant code locations:
internal/handler/checkout.go:95-141
internal/handler/mcp.go:84-100
internal/woocommerce/client.go:426-533
README.md:72-93
Relevant code excerpts
internal/handler/checkout.go:95-119
95 // handleCompleteCheckout submits payment and finalizes the checkout.
96 // POST /checkout-sessions/{id}/complete
97 func (h *Handler) handleCompleteCheckout(w http.ResponseWriter, r *http.Request) {
98 ctx := r.Context()
99 checkoutID := r.PathValue("id")
100
101 if checkoutID == "" {
102 h.writeCheckoutError(w, "", model.NewValidationError("id", "checkout ID required"))
103 return
104 }
105
106 var req model.CheckoutSubmitRequest
107 if err := decodeJSON(r, &req); err != nil {
108 h.writeCheckoutError(w, checkoutID, err)
109 return
110 }
111
112 // Validate payment submission: at least one instrument with selected=true
113 if len(req.Payment.Instruments) == 0 {
114 h.writeCheckoutError(w, checkoutID, model.NewValidationError("payment.instruments", "at least one instrument required"))
115 return
116 }
117 selectedInstrument := req.Payment.SelectedInstrument()
118 if selectedInstrument == nil {
119 h.writeCheckoutError(w, checkoutID, model.NewValidationError("payment.instruments", "one instrument must have selected=true"))
internal/handler/mcp.go:84-100
84 // CompleteCheckoutInput is the input schema for complete_checkout tool.
85 // Per spec: idempotency-key is required in meta for complete_checkout.
86 type CompleteCheckoutInput struct {
87 Meta MCPMeta `json:"meta" jsonschema:"request metadata (idempotency-key required),required"`
88 ID string `json:"id" jsonschema:"checkout ID,required"`
89 Checkout CompleteCheckoutPayload `json:"checkout" jsonschema:"checkout data,required"`
90 }
91
92 // CompleteCheckoutPayload contains the checkout completion data.
93 type CompleteCheckoutPayload struct {
94 Payment *model.Payment `json:"payment,omitempty" jsonschema:"payment instruments"`
95 }
96
97 // CancelCheckoutInput is the input schema for cancel_checkout tool.
98 // Per spec: idempotency-key is required in meta for cancel_checkout.
99 type CancelCheckoutInput struct {
100 Meta MCPMeta `json:"meta" jsonschema:"request metadata (idempotency-key required),required"`
internal/woocommerce/client.go:426-450
426 // CompleteCheckout submits payment and finalizes the checkout.
427 func (c *Client) CompleteCheckout(ctx context.Context, checkoutID string, req *model.CheckoutSubmitRequest) (*model.Checkout, error) {
428 cartToken := ExtractCartToken(checkoutID)
429
430 // Check for products requiring escalation
431 if c.hasEscalationConfig() {
432 cart, _, err := c.getCartViaMutation(ctx, cartToken, nil)
433 if err != nil {
434 return nil, fmt.Errorf("checking cart for escalation: %w", err)
435 }
436 if matches := c.findEscalationMatches(cart); len(matches) > 0 {
437 return c.buildEscalationResponseForProducts(ctx, checkoutID, cart, cartToken, matches)
438 }
439 }
440
441 // Find selected payment instrument
442 instrument := req.Payment.SelectedInstrument()
443 if instrument == nil {
444 return nil, model.NewValidationError("payment", "no payment instrument selected")
445 }
446
447 wcReq := &WooCheckoutRequest{}
448
449 // Route based on credential type (not handler_id - proxy is PSP-agnostic)
450 // Credential type is the contract between tokenization and processing.
README.md:72-93
72 - **Stateless**: session tokens encoded in checkout IDs. No server-side session storage.
73 - **Per-tenant**: one proxy instance per merchant. Scales to zero when idle.
74 - **Multi-transport**: same checkout logic exposed via REST and MCP (Model Context Protocol).
75 - **Pluggable adapters**: each platform adapter translates UCP → native API calls.
76 - **Capability negotiation**: agents provide their profile URL, proxy intersects capabilities.
77
78 ## API Overview
79
80 | Endpoint | Description |
81 | --------------------------------------- | ----------------------------------------------------- |
82 | `GET /.well-known/ucp` | Discovery profile (capabilities, transport endpoints) |
83 | `POST /checkout-sessions` | Create checkout from line items |
84 | `GET /checkout-sessions/{id}` | Get current checkout state |
85 | `PUT /checkout-sessions/{id}` | Update checkout (full state replacement) |
86 | `POST /checkout-sessions/{id}/complete` | Submit payment and finalize order |
87 | `DELETE /checkout-sessions/{id}` | Cancel checkout |
88 | `POST /mcp` | MCP transport (JSON-RPC over HTTP) |
89
90 **Capability Negotiation:** All checkout operations require agent profile for capability intersection:
91 - **REST**: `UCP-Agent: profile="https://agent.example/profile"` header
92 - **MCP**: `meta.ucp-agent.profile` field in request params
93
Why this may matter
This may let a request pass the payment gate without the payment state intended by the server.
Suggested check
Consider making the paid-resource path depend on server-trusted payment requirements and a completed payment state. In particular, re-check recipient/payTo, amount, asset, network, nonce/idempotency, resource binding, and settlement result at the exact point where the protected API/tool/content is released.
Conservative caveat
I only reviewed the code visible in this repository at the HEAD above. If deployment-specific middleware or an upstream service enforces the missing binding/settlement invariant, this may already be mitigated there.
Potential payment verification issue in paid resource gate
Hi, I noticed a possible payment-flow issue while reviewing the current repository state. This is a conservative report based on the visible code path, and I may be missing deployment-specific guards outside this repository.
Reviewed HEAD:
85f3eb6b61b9What I observed
In the visible payment path, I observed:
Relevant code locations:
internal/handler/checkout.go:95-141internal/handler/mcp.go:84-100internal/woocommerce/client.go:426-533README.md:72-93Relevant code excerpts
internal/handler/checkout.go:95-119internal/handler/mcp.go:84-100internal/woocommerce/client.go:426-450README.md:72-93Why this may matter
This may let a request pass the payment gate without the payment state intended by the server.
Suggested check
Consider making the paid-resource path depend on server-trusted payment requirements and a completed payment state. In particular, re-check recipient/payTo, amount, asset, network, nonce/idempotency, resource binding, and settlement result at the exact point where the protected API/tool/content is released.
Conservative caveat
I only reviewed the code visible in this repository at the HEAD above. If deployment-specific middleware or an upstream service enforces the missing binding/settlement invariant, this may already be mitigated there.