x402-surface-check 0.2.17 → 0.2.19
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 +2 -1
- package/bin/x402-surface-check.mjs +48 -2
- package/package.json +1 -1
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
|
+
- Grouped finding summaries for repeated route-wide issues, so large manifests keep the patch order readable
|
|
33
34
|
- Over-broad public method surfaces
|
|
34
35
|
- Auth, validation, and free/trial responses that appear before a payment challenge, without piling on missing-field findings when no challenge was actually returned
|
|
35
36
|
- Operational health/status endpoints, without treating expected free health checks as paid-route failures
|
|
@@ -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
|
-
|
|
398
|
-
const
|
|
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()
|
|
@@ -814,6 +814,45 @@ function findingList(documentResult, challengeResults, preflightResults, entries
|
|
|
814
814
|
return findings
|
|
815
815
|
}
|
|
816
816
|
|
|
817
|
+
function groupedFindingLabel(finding) {
|
|
818
|
+
if (/402 challenge response does not allow the requesting origin/.test(finding)) {
|
|
819
|
+
return 'P1 - Actual 402 challenge responses do not allow the requesting origin; browser clients cannot read payment requirements.'
|
|
820
|
+
}
|
|
821
|
+
if (/CORS preflight does not allow the requesting origin/.test(finding)) {
|
|
822
|
+
return 'P1 - CORS preflight does not allow the requesting origin.'
|
|
823
|
+
}
|
|
824
|
+
if (/CORS preflight does not allow X-PAYMENT/.test(finding)) {
|
|
825
|
+
return 'P1 - CORS preflight does not allow X-PAYMENT.'
|
|
826
|
+
}
|
|
827
|
+
if (/challenge does not repeat the resource URL/.test(finding)) {
|
|
828
|
+
return 'P2 - Challenge accept legs do not repeat the resource URL for reconciliation.'
|
|
829
|
+
}
|
|
830
|
+
if (/returned validation HTTP \d+ before a payment challenge/.test(finding)) {
|
|
831
|
+
return 'P1 - Routes return validation before a payment challenge.'
|
|
832
|
+
}
|
|
833
|
+
if (/returned auth HTTP \d+ before a payment challenge/.test(finding)) {
|
|
834
|
+
return 'P2 - Routes return auth before a payment challenge.'
|
|
835
|
+
}
|
|
836
|
+
if (/challenge advertises staging\/test network/.test(finding)) {
|
|
837
|
+
return 'P2 - Challenges advertise staging or test networks.'
|
|
838
|
+
}
|
|
839
|
+
if (/challenge advertises placeholder-looking payTo/.test(finding)) {
|
|
840
|
+
return 'P1 - Challenges advertise placeholder-looking payTo recipients.'
|
|
841
|
+
}
|
|
842
|
+
return null
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function groupedFindingSummary(findings) {
|
|
846
|
+
const counts = new Map()
|
|
847
|
+
for (const finding of findings) {
|
|
848
|
+
const label = groupedFindingLabel(finding)
|
|
849
|
+
if (label) counts.set(label, (counts.get(label) ?? 0) + 1)
|
|
850
|
+
}
|
|
851
|
+
return [...counts.entries()]
|
|
852
|
+
.filter(([, count]) => count > 1)
|
|
853
|
+
.map(([label, count]) => `- ${count} endpoints: ${label}`)
|
|
854
|
+
}
|
|
855
|
+
|
|
817
856
|
function formatMarkdown(report) {
|
|
818
857
|
const document = report.document.body.json ?? {}
|
|
819
858
|
const challengeRows = report.challenges.map(result => {
|
|
@@ -823,6 +862,7 @@ function formatMarkdown(report) {
|
|
|
823
862
|
const preflightRows = report.preflights.map(result => {
|
|
824
863
|
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'] ?? '-'} |`
|
|
825
864
|
})
|
|
865
|
+
const findingSummary = groupedFindingSummary(report.findings)
|
|
826
866
|
|
|
827
867
|
return [
|
|
828
868
|
'# x402 Public Surface Check',
|
|
@@ -856,6 +896,12 @@ function formatMarkdown(report) {
|
|
|
856
896
|
'| --- | --- | --- | --- | --- | --- |',
|
|
857
897
|
...(preflightRows.length ? preflightRows : ['| - | - | - | - | - | - |']),
|
|
858
898
|
'',
|
|
899
|
+
...(findingSummary.length ? [
|
|
900
|
+
'## Finding Summary',
|
|
901
|
+
'',
|
|
902
|
+
...findingSummary,
|
|
903
|
+
'',
|
|
904
|
+
] : []),
|
|
859
905
|
'## Findings',
|
|
860
906
|
'',
|
|
861
907
|
...(report.findings.length ? report.findings.map(item => `- ${item}`) : ['- No obvious launch-readiness findings from the public no-payment probes.']),
|