x402-surface-check 0.2.22 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,7 @@ No-payment CLI for checking x402 launch surfaces before a real agent spends.
5
5
  It accepts an x402 manifest or OpenAPI URL, derives public endpoints, sends no-payment probes, checks browser preflight behavior, and returns a Markdown patch queue. It never sends `X-PAYMENT`, never signs, and never attempts a paid call.
6
6
 
7
7
  npm: https://www.npmjs.com/package/x402-surface-check
8
+ Attack-map field note: https://tateprograms.com/x402-attack-map-2026.html
8
9
 
9
10
  ```bash
10
11
  npx --yes x402-surface-check https://api.example.com/.well-known/x402
@@ -32,8 +33,9 @@ npx --yes x402-surface-check --strict-cache https://api.example.com/openapi.json
32
33
  - HTTPS resource URLs and stable resource metadata
33
34
  - Browser CORS allowance for the requesting origin and `X-PAYMENT`, including the actual 402 challenge response
34
35
  - Cache-Control posture on no-payment challenge responses, with warnings for explicitly cacheable payment gates and optional strict-cache findings for missing policy headers
36
+ - Payment-enforcement headers on `200` responses, so public telemetry/free-trial endpoints do not accidentally advertise enforced x402 while returning content before a challenge
35
37
  - Grouped finding summaries for repeated route-wide issues, so large manifests keep the patch order readable
36
- - Contextual reference guides for CORS, cache policy, Worker gates, resource echo, validation/auth ordering, and launch controls
38
+ - Contextual reference guides for CORS, cache policy, Worker gates, resource echo, validation/auth ordering, and the May 2026 x402 attack-control map
37
39
  - Over-broad public method surfaces
38
40
  - Auth, validation, and free/trial responses that appear before a payment challenge, without piling on missing-field findings when no challenge was actually returned
39
41
  - Operational health/status endpoints, without treating expected free health checks as paid-route failures
@@ -49,7 +51,8 @@ Recent public no-payment checks have found and verified real launch fixes:
49
51
  - UZPROOF: schemes-style Solana x402 challenge and browser payment-header behavior verified clean. https://github.com/solana-foundation/pay-skills/pull/28#issuecomment-4455613892
50
52
  - HYRE Agent: OpenAPI-declared prices found 10x below live 402 challenge prices. https://github.com/solana-foundation/pay-skills/pull/19#issuecomment-4455641258
51
53
  - anchor-x402: multi-rail x402 challenges verified, with browser preflight blockers isolated before merge. https://github.com/solana-foundation/pay-skills/pull/47#issuecomment-4455678163
52
- - Agent Trust Bench: linked discovery URL and browser-compatibility notes verified clean for adversarial agent-payment resources. https://github.com/solana-foundation/pay-skills/pull/23#issuecomment-4455722170
54
+ - Agent Trust Bench: three no-payment passes converged on zero findings after discovery, browser preflight, cache, and resource-echo fixes. https://github.com/solana-foundation/pay-skills/pull/23#issuecomment-4467597309
55
+ - SolSentry: x402 stats endpoint flagged for returning `200` content while headers advertised payment enforcement and browser preflight omitted `X-PAYMENT`. https://github.com/solsentry/solsentry-app/issues/2
53
56
  - Solrouter: private LLM inference route verified with HTTPS resource-binding and price-alignment notes. https://github.com/solana-foundation/pay-skills/pull/39#issuecomment-4455800060
54
57
  - Tetrac: Solana market-data payment gates verified, with browser payment-header preflight blocker isolated. https://github.com/solana-foundation/pay-skills/pull/32#issuecomment-4455923744
55
58
 
@@ -710,6 +710,24 @@ function cachePolicy(headers = {}) {
710
710
  return headers['cache-control'] ?? headers.cacheControl ?? ''
711
711
  }
712
712
 
713
+ function paymentSignalHeaders(headers = {}) {
714
+ return [
715
+ 'x-payment-required',
716
+ 'x-payment-enforce',
717
+ 'x-price-usdc',
718
+ 'x-payment-address',
719
+ 'x-payment-network',
720
+ 'x-payment-token',
721
+ 'x-payment-protocol',
722
+ ].filter(name => headers[name] !== undefined && headers[name] !== '')
723
+ }
724
+
725
+ function advertisesPaymentEnforcement(headers = {}) {
726
+ const required = String(headers['x-payment-required'] ?? '').toLowerCase() === 'true'
727
+ const enforced = String(headers['x-payment-enforce'] ?? '').toLowerCase() === 'true'
728
+ return required || enforced || (required && paymentSignalHeaders(headers).length > 1)
729
+ }
730
+
713
731
  function looksExplicitlyCacheable(headers = {}) {
714
732
  const policy = cachePolicy(headers)
715
733
  if (!policy) return false
@@ -755,6 +773,9 @@ function findingList(documentResult, challengeResults, preflightResults, entries
755
773
  if (!looksLikeOperationalHealthEndpoint(result)) {
756
774
  findings.push(`P3 - ${result.name} returned ${result.status} without a payment challenge for a no-payment ${result.method ?? 'POST'} probe; document this as free/trial access or move the 402 challenge before content.`)
757
775
  }
776
+ if (advertisesPaymentEnforcement(result.headers)) {
777
+ findings.push(`P2 - ${result.name} returned ${result.status} content while payment headers advertise enforcement (${paymentSignalHeaders(result.headers).join(', ')}); either return a 402 before content or document this endpoint as public telemetry.`)
778
+ }
758
779
  }
759
780
  else if (result.status === 400 || result.status === 422) {
760
781
  findings.push(`P1 - ${result.name} returned validation HTTP ${result.status} before a payment challenge for a no-payment ${result.method ?? 'POST'} probe.`)
@@ -807,7 +828,7 @@ function findingList(documentResult, challengeResults, preflightResults, entries
807
828
 
808
829
  for (const result of preflightResults) {
809
830
  const challengeResult = challengesByEntry.get(entryKey(result))
810
- if (!challengeResult || !hasPaymentChallenge(challengeResult)) continue
831
+ if (!challengeResult || (!hasPaymentChallenge(challengeResult) && !advertisesPaymentEnforcement(challengeResult.headers))) continue
811
832
  const allowedOrigin = result.headers['access-control-allow-origin'] ?? ''
812
833
  if (!allowedOrigin) {
813
834
  findings.push(`P1 - ${result.name} CORS preflight does not allow the requesting origin; observed allow-origin: none.`)
@@ -864,6 +885,9 @@ function groupedFindingLabel(finding) {
864
885
  if (/payment challenge response did not expose Cache-Control/.test(finding)) {
865
886
  return 'P3 - Payment challenge responses do not expose Cache-Control in strict cache mode.'
866
887
  }
888
+ if (/content while payment headers advertise enforcement/.test(finding)) {
889
+ return 'P2 - Payment headers advertise enforcement on a 200 response.'
890
+ }
867
891
  return null
868
892
  }
869
893
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-surface-check",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "description": "No-payment x402 public-surface checker for manifests, OpenAPI specs, and HTTP 402 challenges.",
5
5
  "type": "module",
6
6
  "bin": {