x402-surface-check 0.2.20 → 0.2.22

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
@@ -11,6 +11,7 @@ npx --yes x402-surface-check https://api.example.com/.well-known/x402
11
11
  npx --yes x402-surface-check https://api.example.com/openapi.json report.md
12
12
  npx --yes x402-surface-check --endpoint --method POST https://x402.rpc.ankr.com/eth
13
13
  npx --yes x402-surface-check --endpoint --method POST --body '{"prompt":"price CPI"}' https://api.example.com/paid-post
14
+ npx --yes x402-surface-check --strict-cache https://api.example.com/openapi.json
14
15
  ```
15
16
 
16
17
  ## What It Checks
@@ -30,8 +31,9 @@ npx --yes x402-surface-check --endpoint --method POST --body '{"prompt":"price C
30
31
  - Testnet or staging rails such as Base Sepolia and Solana devnet
31
32
  - HTTPS resource URLs and stable resource metadata
32
33
  - Browser CORS allowance for the requesting origin and `X-PAYMENT`, including the actual 402 challenge response
33
- - Cache-Control posture on no-payment challenge responses, with warnings for explicitly cacheable payment gates
34
+ - Cache-Control posture on no-payment challenge responses, with warnings for explicitly cacheable payment gates and optional strict-cache findings for missing policy headers
34
35
  - 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
35
37
  - Over-broad public method surfaces
36
38
  - Auth, validation, and free/trial responses that appear before a payment challenge, without piling on missing-field findings when no challenge was actually returned
37
39
  - Operational health/status endpoints, without treating expected free health checks as paid-route failures
@@ -65,6 +67,7 @@ x402-surface-check --endpoint --method POST <paid-endpoint-url> [output.md]
65
67
  --body-file <p> Read JSON request body for direct endpoint mode from a file
66
68
  --origin <url> Origin to use for browser-style CORS preflight
67
69
  --limit <n> Maximum endpoints to probe, default 6
70
+ --strict-cache Flag missing Cache-Control on no-payment 402 responses
68
71
  --json Print JSON instead of Markdown
69
72
  --help Show usage
70
73
  --version Show package version
@@ -75,6 +78,7 @@ Environment variables are also supported:
75
78
  ```bash
76
79
  X402_CHECK_ORIGIN=https://example.com x402-surface-check https://api.example.com/openapi.json
77
80
  X402_CHECK_LIMIT=12 x402-surface-check https://api.example.com/.well-known/x402
81
+ X402_STRICT_CACHE=1 x402-surface-check https://api.example.com/.well-known/x402
78
82
  ```
79
83
 
80
84
  ## Scope
@@ -22,6 +22,7 @@ Options:
22
22
  --body-file <p> Read JSON request body for direct endpoint mode from a file
23
23
  --origin <url> Origin to use for browser-style CORS preflight
24
24
  --limit <n> Maximum endpoints to probe, default ${defaultLimit}
25
+ --strict-cache Flag missing Cache-Control on no-payment 402 responses
25
26
  --json Print JSON instead of Markdown
26
27
  --help Show this help
27
28
  --version Show package version
@@ -38,6 +39,7 @@ function parseArgs(argv) {
38
39
  body: process.env.X402_CHECK_BODY,
39
40
  bodyFile: process.env.X402_CHECK_BODY_FILE,
40
41
  outputPath: '',
42
+ strictCache: process.env.X402_STRICT_CACHE === '1',
41
43
  url: '',
42
44
  }
43
45
 
@@ -55,6 +57,9 @@ function parseArgs(argv) {
55
57
  else if (arg === '--endpoint') {
56
58
  args.endpoint = true
57
59
  }
60
+ else if (arg === '--strict-cache') {
61
+ args.strictCache = true
62
+ }
58
63
  else if (arg === '--method') {
59
64
  args.method = String(argv[index + 1] ?? '').toUpperCase()
60
65
  index += 1
@@ -721,7 +726,7 @@ function looksLikeOperationalHealthEndpoint(result) {
721
726
  return /(^|[/_\s-])(health|healthz|ready|readiness|live|liveness|status)([/_\s-]|$)/.test(value)
722
727
  }
723
728
 
724
- function findingList(documentResult, challengeResults, preflightResults, entries) {
729
+ function findingList(documentResult, challengeResults, preflightResults, entries, options = {}) {
725
730
  const document = documentResult.body.json ?? {}
726
731
  const findings = []
727
732
  const networks = valueList(document.networks)
@@ -795,6 +800,9 @@ function findingList(documentResult, challengeResults, preflightResults, entries
795
800
  if (looksExplicitlyCacheable(result.headers)) {
796
801
  findings.push(`P2 - ${result.name} payment challenge response is explicitly cacheable (${cachePolicy(result.headers)}); paid routes should use no-store/private cache policy or bypass shared caches.`)
797
802
  }
803
+ else if (options.strictCache && !cachePolicy(result.headers)) {
804
+ findings.push(`P3 - ${result.name} payment challenge response did not expose Cache-Control; for payment-gated routes, document or send no-store/private cache policy and confirm paid 200 responses are never shared-cacheable.`)
805
+ }
798
806
  }
799
807
 
800
808
  for (const result of preflightResults) {
@@ -853,6 +861,9 @@ function groupedFindingLabel(finding) {
853
861
  if (/challenge advertises placeholder-looking payTo/.test(finding)) {
854
862
  return 'P1 - Challenges advertise placeholder-looking payTo recipients.'
855
863
  }
864
+ if (/payment challenge response did not expose Cache-Control/.test(finding)) {
865
+ return 'P3 - Payment challenge responses do not expose Cache-Control in strict cache mode.'
866
+ }
856
867
  return null
857
868
  }
858
869
 
@@ -867,6 +878,29 @@ function groupedFindingSummary(findings) {
867
878
  .map(([label, count]) => `- ${count} endpoints: ${label}`)
868
879
  }
869
880
 
881
+ function referenceGuides(findings) {
882
+ const guides = []
883
+ const add = (label, url) => {
884
+ if (!guides.some(guide => guide.url === url)) guides.push({ label, url })
885
+ }
886
+ const text = findings.join('\n')
887
+ if (/CORS|402 challenge response does not allow the requesting origin|X-PAYMENT/i.test(text)) {
888
+ add('x402 CORS Fix', 'https://tateprograms.com/x402-cors-fix.html')
889
+ add('Cloudflare x402 Worker Starter', 'https://tateprograms.com/cloudflare-x402-worker.html')
890
+ }
891
+ if (/cacheable|Cache-Control|cache policy|shared caches/i.test(text)) {
892
+ add('Cloudflare x402 Worker Starter', 'https://tateprograms.com/cloudflare-x402-worker.html')
893
+ add('x402 Attack Map 2026', 'https://tateprograms.com/x402-attack-map-2026.html')
894
+ }
895
+ if (/validation HTTP \d+ before a payment challenge|auth HTTP \d+ before a payment challenge|replay|idempotency/i.test(text)) {
896
+ add('x402 Launch Checklist', 'https://tateprograms.com/x402-launch-checklist.html')
897
+ }
898
+ if (/resource URL|resource echo|accepts\[0\]\.extra\.resource/i.test(text)) {
899
+ add('x402 Surface Check notes', 'https://tateprograms.com/x402-surface-check.html')
900
+ }
901
+ return guides.map(guide => `- ${guide.label}: ${guide.url}`)
902
+ }
903
+
870
904
  function formatMarkdown(report) {
871
905
  const document = report.document.body.json ?? {}
872
906
  const challengeRows = report.challenges.map(result => {
@@ -880,6 +914,7 @@ function formatMarkdown(report) {
880
914
  return `| ${result.name} | ${result.method ?? 'POST'} | ${result.status} | ${cachePolicy(result.headers) || '-'} |`
881
915
  })
882
916
  const findingSummary = groupedFindingSummary(report.findings)
917
+ const guides = referenceGuides(report.findings)
883
918
 
884
919
  return [
885
920
  '# x402 Public Surface Check',
@@ -929,6 +964,12 @@ function formatMarkdown(report) {
929
964
  '',
930
965
  ...(report.findings.length ? report.findings.map(item => `- ${item}`) : ['- No obvious launch-readiness findings from the public no-payment probes.']),
931
966
  '',
967
+ ...(guides.length ? [
968
+ '## Reference Guides',
969
+ '',
970
+ ...guides,
971
+ '',
972
+ ] : []),
932
973
  ].join('\n')
933
974
  }
934
975
 
@@ -983,7 +1024,9 @@ async function runCheck(options) {
983
1024
  preflights,
984
1025
  sourceDocument,
985
1026
  }
986
- report.findings = findingList(document, challenges, preflights, entries)
1027
+ report.findings = findingList(document, challenges, preflights, entries, {
1028
+ strictCache: options.strictCache,
1029
+ })
987
1030
  return report
988
1031
  }
989
1032
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-surface-check",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "description": "No-payment x402 public-surface checker for manifests, OpenAPI specs, and HTTP 402 challenges.",
5
5
  "type": "module",
6
6
  "bin": {