x402-surface-check 0.2.18 → 0.2.20

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
@@ -15,7 +15,7 @@ npx --yes x402-surface-check --endpoint --method POST --body '{"prompt":"price C
15
15
 
16
16
  ## What It Checks
17
17
 
18
- - Manifest endpoint discovery from `items[]`, `endpoints[]`, `resources[]`, `x402Endpoints`, category arrays, resource strings, and OpenAPI paths
18
+ - Manifest endpoint discovery from `items[]`, `endpoints[]`, `resources[]`, `x402Endpoints`, category arrays, raw resource URL strings, method-prefixed resource strings, and OpenAPI paths
19
19
  - Linked discovery documents via `discovery_url`, `discoveryUrl`, `resources_url`, `resourcesUrl`, or manifest-level OpenAPI links
20
20
  - OpenAPI `servers[]` base-path preservation, so `/paths` are probed through the documented gateway rather than the domain root
21
21
  - OpenAPI query/path examples, JSON request-body examples, nested request schemas, local `$ref` request schemas, and explicit direct-endpoint bodies for safer no-payment probes
@@ -30,6 +30,7 @@ npx --yes x402-surface-check --endpoint --method POST --body '{"prompt":"price C
30
30
  - Testnet or staging rails such as Base Sepolia and Solana devnet
31
31
  - HTTPS resource URLs and stable resource metadata
32
32
  - 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
33
34
  - Grouped finding summaries for repeated route-wide issues, so large manifests keep the patch order readable
34
35
  - Over-broad public method surfaces
35
36
  - Auth, validation, and free/trial responses that appear before a payment challenge, without piling on missing-field findings when no challenge was actually returned
@@ -394,8 +394,8 @@ function endpointEntries(document, sourceUrl, limit) {
394
394
  for (const resource of document.resources ?? []) {
395
395
  if (typeof resource === 'string') {
396
396
  const match = resource.match(/^(GET|POST|PUT|PATCH|DELETE)\s+(\S+)/i)
397
- if (!match) continue
398
- const [, method, rawPath] = match
397
+ const method = match?.[1] ?? 'GET'
398
+ const rawPath = match?.[2] ?? resource
399
399
  const url = rawPath.startsWith('http')
400
400
  ? rawPath
401
401
  : new URL(rawPath, document.baseUrl ?? sourceUrl).toString()
@@ -701,6 +701,17 @@ function looksLikePlaceholderPayTo(payTo) {
701
701
  return false
702
702
  }
703
703
 
704
+ function cachePolicy(headers = {}) {
705
+ return headers['cache-control'] ?? headers.cacheControl ?? ''
706
+ }
707
+
708
+ function looksExplicitlyCacheable(headers = {}) {
709
+ const policy = cachePolicy(headers)
710
+ if (!policy) return false
711
+ if (/\b(no-store|private|no-cache)\b/i.test(policy)) return false
712
+ return /\b(public|s-maxage|max-age\s*=)\b/i.test(policy)
713
+ }
714
+
704
715
  function entryKey(entry) {
705
716
  return `${entry.method ?? 'POST'} ${entry.url}`
706
717
  }
@@ -781,6 +792,9 @@ function findingList(documentResult, challengeResults, preflightResults, entries
781
792
  if (!summary.resourceUrl || !summary.extraResource) {
782
793
  findings.push(`P2 - ${result.name} challenge does not repeat the resource URL in both resource.url and accepts[0].extra.resource/resource.`)
783
794
  }
795
+ if (looksExplicitlyCacheable(result.headers)) {
796
+ 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
+ }
784
798
  }
785
799
 
786
800
  for (const result of preflightResults) {
@@ -862,6 +876,9 @@ function formatMarkdown(report) {
862
876
  const preflightRows = report.preflights.map(result => {
863
877
  return `| ${result.name} | ${result.method ?? 'POST'} | ${result.status} | ${result.headers['access-control-allow-origin'] ?? '-'} | ${result.headers['access-control-allow-headers'] ?? '-'} | ${result.headers['access-control-allow-methods'] ?? '-'} |`
864
878
  })
879
+ const cacheRows = report.challenges.map(result => {
880
+ return `| ${result.name} | ${result.method ?? 'POST'} | ${result.status} | ${cachePolicy(result.headers) || '-'} |`
881
+ })
865
882
  const findingSummary = groupedFindingSummary(report.findings)
866
883
 
867
884
  return [
@@ -896,6 +913,12 @@ function formatMarkdown(report) {
896
913
  '| --- | --- | --- | --- | --- | --- |',
897
914
  ...(preflightRows.length ? preflightRows : ['| - | - | - | - | - | - |']),
898
915
  '',
916
+ '## Cache Policy Map',
917
+ '',
918
+ '| Endpoint | Method | HTTP | Cache-Control |',
919
+ '| --- | --- | --- | --- |',
920
+ ...(cacheRows.length ? cacheRows : ['| - | - | - | - |']),
921
+ '',
899
922
  ...(findingSummary.length ? [
900
923
  '## Finding Summary',
901
924
  '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-surface-check",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "No-payment x402 public-surface checker for manifests, OpenAPI specs, and HTTP 402 challenges.",
5
5
  "type": "module",
6
6
  "bin": {