x402-surface-check 0.2.19 → 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 +1 -0
- package/bin/x402-surface-check.mjs +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
|
@@ -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
|
'',
|