aws-samples / aws-samples/sample-agentcore-cloudfront-x402-payments

Potential atomicity issue: payment is settled before paid content retrieval succeeds

Open
#28 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
25
Forks
11
PR merge metrics
No merged PRs in 30d

Description

# Potential atomicity issue: payment is settled before paid content retrieval succeeds

Hi, I noticed a possible payment-flow issue in the current repository state. This is a conservative report based on the current code path, and I may be missing deployment-specific guards outside this repository.

Repository: `https://github.com/aws-samples/sample-agentcore-cloudfront-x402-payments`
Reviewed HEAD: `36fc3ef`

## What I observed

The Lambda@Edge verifier settles the payment first and only then retrieves/generates the paid content. If content retrieval fails after settlement, the outer catch path returns a 500 payment-processing error after the payment may already have been settled.

Relevant code excerpts:

`seller-infrastructure/lib/lambda-edge/payment-verifier.ts:792-815`

```text
792 // Settle payment with facilitator
793 const settlementStartTime = Date.now();
794 const settlement = await settlePaymentWithFacilitator(
795 paymentPayload,
796 paymentRequirement,
797 logger
798 );
...
801 if (!settlement.success) {
...
809 return createErrorResponse(
810 '402',
811 'Payment Required',
812 'Settlement Failed',
813 `Payment settlement failed: ${settlement.errorReason}`
814 );
815 }
```

`seller-infrastructure/lib/lambda-edge/payment-verifier.ts:817-836`

```text
817 // Payment verified and settled - return dynamic content
818 logger.incrementCounter(MetricName.PAYMENT_SETTLED);
...
828 logger.info('Payment settled successfully', {
829 payer,
830 transactionHash: settlement.transaction,
831 amount: paymentRequirement.amount,
832 network: paymentRequirement.network,
833 });
834
835 // Get dynamic content from content manager
836 const content = await contentManager.getContent(uri);
```

`seller-infrastructure/lib/lambda-edge/payment-verifier.ts:854-887`

```text
854 return {
855 status: '200',
856 statusDescription: 'OK',
...
874 body: JSON.stringify(content),
875 };
876 } catch (error) {
877 logger.error('Unexpected error processing payment', error);
878 logger.incrementCounter(MetricName.PAYMENT_FAILED);
...
881 return createErrorResponse(
882 '500',
883 'Internal Server Error',
884 'Payment Processing Error',
885 'Failed to process payment'
886 );
887 }
```

## Why this may matter

This ordering can create a charged-without-content state: settlement succeeds, but content lookup/generation fails afterward. The request then receives a 500 error rather than the paid resource or an explicit recoverable entitlement.

## Suggested check

Consider checking content availability before settlement, or persisting a durable entitlement/receipt before returning the error path so a user can retry content delivery without paying again. If content generation can fail, settlement and content delivery may need a two-phase or recoverable state.

## Conservative caveat

If `contentManager.getContent(uri)` is guaranteed not to fail for all paid resources, impact may be lower. I am reporting this because the current code settles before the paid content sink has succeeded.

Contributor guide

Open the contributing guide

Research direction

Start in seller-infrastructure/lib/lambda-edge/payment-verifier.ts around lines 792-887 and trace the settlement, contentManager.getContent(uri), and catch paths. Reproduce or inspect the failure sequence where retrieval fails after settlement, then define a recoverable behavior that prevents a charged-without-content result; done means the payment and delivery outcomes are handled consistently.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
backend, cloud, payments
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.