x402-surface-check 0.2.10 → 0.2.11
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 +3 -0
- package/bin/x402-surface-check.mjs +19 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ npx --yes x402-surface-check --endpoint --method POST https://x402.rpc.ankr.com/
|
|
|
27
27
|
- Browser CORS allowance for `X-PAYMENT`
|
|
28
28
|
- Over-broad public method surfaces
|
|
29
29
|
- Auth, validation, and free/trial responses that appear before a payment challenge, without piling on missing-field findings when no challenge was actually returned
|
|
30
|
+
- Operational health/status endpoints, without treating expected free health checks as paid-route failures
|
|
30
31
|
- Object-valued document metadata such as facilitator objects, without `[object Object]` report artifacts
|
|
31
32
|
|
|
32
33
|
## Public Proof
|
|
@@ -36,6 +37,8 @@ Recent public no-payment checks have found and verified real launch fixes:
|
|
|
36
37
|
- TensorFeed: parameter-required premium routes moved behind canonical x402 V2 challenges, then verified clean. https://github.com/solana-foundation/pay-skills/pull/68#issuecomment-4455360068
|
|
37
38
|
- x402jp: weather routes that returned 500 now return structured Base x402 challenges. https://github.com/solana-foundation/pay-skills/pull/58#issuecomment-4455401355
|
|
38
39
|
- Spraay: resource echo and browser payment-header behavior verified clean. https://github.com/solana-foundation/pay-skills/pull/60#issuecomment-4455519760
|
|
40
|
+
- UZPROOF: schemes-style Solana x402 challenge and browser payment-header behavior verified clean. https://github.com/solana-foundation/pay-skills/pull/28#issuecomment-4455613892
|
|
41
|
+
- HYRE Agent: OpenAPI-declared prices found 10x below live 402 challenge prices. https://github.com/solana-foundation/pay-skills/pull/19#issuecomment-4455641258
|
|
39
42
|
- Agent Trust Bench: live discovery URL and browser-compatibility notes for adversarial agent-payment resources. https://github.com/solana-foundation/pay-skills/pull/23#issuecomment-4455484414
|
|
40
43
|
|
|
41
44
|
Field notes and browser version: https://tateprograms.com/x402-surface-check.html
|
|
@@ -501,11 +501,21 @@ function looksLikePlaceholderPayTo(payTo) {
|
|
|
501
501
|
return false
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
+
function entryKey(entry) {
|
|
505
|
+
return `${entry.method ?? 'POST'} ${entry.url}`
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function looksLikeOperationalHealthEndpoint(result) {
|
|
509
|
+
const value = `${result.name ?? ''} ${new URL(result.url).pathname}`.toLowerCase()
|
|
510
|
+
return /(^|[/_\s-])(health|healthz|ready|readiness|live|liveness|status)([/_\s-]|$)/.test(value)
|
|
511
|
+
}
|
|
512
|
+
|
|
504
513
|
function findingList(documentResult, challengeResults, preflightResults, entries) {
|
|
505
514
|
const document = documentResult.body.json ?? {}
|
|
506
515
|
const findings = []
|
|
507
516
|
const networks = valueList(document.networks)
|
|
508
517
|
const challengeNetworks = new Set()
|
|
518
|
+
const challengesByEntry = new Map(challengeResults.map(result => [entryKey(result), result]))
|
|
509
519
|
|
|
510
520
|
if (documentResult.status < 200 || documentResult.status >= 300) {
|
|
511
521
|
findings.push(`P1 - Document returned HTTP ${documentResult.status}; expected a successful JSON response.`)
|
|
@@ -526,7 +536,9 @@ function findingList(documentResult, challengeResults, preflightResults, entries
|
|
|
526
536
|
|
|
527
537
|
if (result.status !== 402) {
|
|
528
538
|
if (result.status >= 200 && result.status < 300) {
|
|
529
|
-
|
|
539
|
+
if (!looksLikeOperationalHealthEndpoint(result)) {
|
|
540
|
+
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.`)
|
|
541
|
+
}
|
|
530
542
|
}
|
|
531
543
|
else if (result.status === 400 || result.status === 422) {
|
|
532
544
|
findings.push(`P1 - ${result.name} returned validation HTTP ${result.status} before a payment challenge for a no-payment ${result.method ?? 'POST'} probe.`)
|
|
@@ -569,9 +581,14 @@ function findingList(documentResult, challengeResults, preflightResults, entries
|
|
|
569
581
|
}
|
|
570
582
|
|
|
571
583
|
for (const result of preflightResults) {
|
|
584
|
+
const challengeResult = challengesByEntry.get(entryKey(result))
|
|
585
|
+
if (!challengeResult || !hasPaymentChallenge(challengeResult)) continue
|
|
572
586
|
const allowed = result.headers['access-control-allow-headers'] ?? ''
|
|
573
587
|
if (allowed !== '*' && !/x-payment/i.test(allowed)) {
|
|
574
|
-
|
|
588
|
+
const observed = result.status >= 400
|
|
589
|
+
? `HTTP ${result.status}; allow headers: ${allowed || 'none'}`
|
|
590
|
+
: `allow headers: ${allowed || 'none'}`
|
|
591
|
+
findings.push(`P1 - ${result.name} CORS preflight does not allow X-PAYMENT; observed ${observed}.`)
|
|
575
592
|
}
|
|
576
593
|
const allowedMethods = result.headers['access-control-allow-methods'] ?? ''
|
|
577
594
|
if (/delete|put|patch/i.test(allowedMethods)) {
|