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: f0ecbaae6159
What I observed
In the visible payment path, I observed:
One-time x402 proof signs only tx_hash and redemption state is keyed only by tx_hash; resource/path not bound to authorization
Relevant code locations:
core/rust/src/middleware/mod.rs
core/rust/src/middleware/one_time_payment/verify.rs
core/rust/src/middleware/one_time_payment/state.rs
core/rust/src/middleware/one_time_payment/utils.rs
Relevant code excerpts
core/rust/src/middleware/mod.rs:18-42
18 pub use state::MiddlewareState;
19 #[cfg(not(target_arch = "wasm32"))]
20 pub use types::{MiddlewareConfig, Scheme, SchemeConfig};
21
22 #[cfg(not(target_arch = "wasm32"))]
23 use crate::{
24 error::AuthError,
25 middleware::{
26 one_time_payment::{types::OneTimePaymentConfig, verify::verify_tx},
27 payment_channel::{
28 types::{PaymentChannel, PaymentChannelConfig},
29 utils::modify_headers_axum,
30 verify::verify_and_update_channel,
31 },
32 stream_payment::{
33 types::{StreamsConfig, CFA_V1_FORWARDER_ADDRESS},
34 verify::verify_stream,
35 },
36 types::{PaymentHeader, PaymentPayload},
37 utils::{
38 get_current_time, parse_channel_payload, parse_onetime_payload, parse_stream_payload,
39 },
40 },
41 };
42
core/rust/src/middleware/one_time_payment/verify.rs:12-36
12 types::{OneTimePayment, OneTimePaymentConfig, SignedPaymentTx, ABS_WINDOW_SEC},
13 utils::create_tx_message,
14 },
15 utils::get_current_time,
16 },
17 };
18
19 // For one time payment verification
20 pub async fn verify_tx(
21 signed_tx: SignedPaymentTx,
22 config: OneTimePaymentConfig,
23 ) -> Result<(OneTimePayment, bool), AuthError> {
24 // creating the message
25 let reconstructed_message = create_tx_message(signed_tx.tx_hash);
26 println!("Message: 0x{}", hex::encode(&reconstructed_message));
27
28 let signature = signed_tx.signature;
29 println!("Signature: 0x{}", hex::encode(&signature.as_bytes()));
30
31 // recovering the address from the signature
32 let recovered = match signature.recover_address_from_msg(reconstructed_message) {
33 Ok(address) => address,
34 Err(_) => return Err(AuthError::InvalidSignature),
35 };
36 println!("Recovered address: {}", recovered);
core/rust/src/middleware/one_time_payment/state.rs:28-52
28 }
29
30 pub async fn invalidate(&self, tx_hash: FixedBytes<32>) {
31 let mut payments = self.payments.write().await;
32 payments.remove(&tx_hash);
33 }
34
35 // Additional helper methods for one-time payment logic
36 pub async fn increment_redemptions(&self, tx_hash: FixedBytes<32>) -> Option<u32> {
37 let mut payments = self.payments.write().await;
38 if let Some(payment) = payments.get_mut(&tx_hash) {
39 payment.redemptions += 1;
40 Some(payment.redemptions)
41 } else {
42 None
43 }
44 }
45
46 pub async fn set_first_redeemed(&self, tx_hash: FixedBytes<32>, timestamp: u64) -> bool {
47 let mut payments = self.payments.write().await;
48 if let Some(payment) = payments.get_mut(&tx_hash) {
49 if payment.first_reedemed == 0 {
50 payment.first_reedemed = timestamp;
51 true
52 } else {
core/rust/src/middleware/one_time_payment/utils.rs:25-49
25 })
26 .and_then(|bytes| {
27 PrimitiveSignature::try_from(bytes.as_slice()).map_err(|_| {
28 println!("Failed: Signature conversion");
29 AuthError::InvalidSignature
30 })
31 })?;
32
33 let tx_hash = headers
34 .get("X-Transaction")
35 .ok_or(AuthError::MissingHeaders)?
36 .to_str()
37 .map_err(|_| {
38 AuthError::InvalidHeaders(
39 "X-TxHash header contains invalid UTF-8 characters".to_string(),
40 )
41 })?;
42
43 let tx_hash = hex::decode(tx_hash).map_err(|_| {
44 println!("Failed: Message decode");
45 AuthError::InvalidTransaction("Tx hash decode failed".to_string())
46 })?;
47
48 let signed_tx = SignedPaymentTx {
49 signature,
Why this may matter
For agent-facing payment code, untrusted requests that can drive a server-held wallet or signing flow can turn the service itself into the payer or actor.
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:
f0ecbaae6159What I observed
In the visible payment path, I observed:
Relevant code locations:
core/rust/src/middleware/mod.rscore/rust/src/middleware/one_time_payment/verify.rscore/rust/src/middleware/one_time_payment/state.rscore/rust/src/middleware/one_time_payment/utils.rsRelevant code excerpts
core/rust/src/middleware/mod.rs:18-42core/rust/src/middleware/one_time_payment/verify.rs:12-36core/rust/src/middleware/one_time_payment/state.rs:28-52core/rust/src/middleware/one_time_payment/utils.rs:25-49Why this may matter
For agent-facing payment code, untrusted requests that can drive a server-held wallet or signing flow can turn the service itself into the payer or actor.
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.