x402-surface-check 0.2.23 → 0.2.24

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
@@ -33,6 +33,7 @@ npx --yes x402-surface-check --strict-cache https://api.example.com/openapi.json
33
33
  - HTTPS resource URLs and stable resource metadata
34
34
  - Browser CORS allowance for the requesting origin and `X-PAYMENT`, including the actual 402 challenge response
35
35
  - Cache-Control posture on no-payment challenge responses, with warnings for explicitly cacheable payment gates and optional strict-cache findings for missing policy headers
36
+ - Payment-enforcement headers on `200` responses, so public telemetry/free-trial endpoints do not accidentally advertise enforced x402 while returning content before a challenge
36
37
  - Grouped finding summaries for repeated route-wide issues, so large manifests keep the patch order readable
37
38
  - Contextual reference guides for CORS, cache policy, Worker gates, resource echo, validation/auth ordering, and the May 2026 x402 attack-control map
38
39
  - Over-broad public method surfaces
@@ -51,6 +52,7 @@ Recent public no-payment checks have found and verified real launch fixes:
51
52
  - HYRE Agent: OpenAPI-declared prices found 10x below live 402 challenge prices. https://github.com/solana-foundation/pay-skills/pull/19#issuecomment-4455641258
52
53
  - anchor-x402: multi-rail x402 challenges verified, with browser preflight blockers isolated before merge. https://github.com/solana-foundation/pay-skills/pull/47#issuecomment-4455678163
53
54
  - Agent Trust Bench: three no-payment passes converged on zero findings after discovery, browser preflight, cache, and resource-echo fixes. https://github.com/solana-foundation/pay-skills/pull/23#issuecomment-4467597309
55
+ - SolSentry: x402 stats endpoint flagged for returning `200` content while headers advertised payment enforcement and browser preflight omitted `X-PAYMENT`. https://github.com/solsentry/solsentry-app/issues/2
54
56
  - Solrouter: private LLM inference route verified with HTTPS resource-binding and price-alignment notes. https://github.com/solana-foundation/pay-skills/pull/39#issuecomment-4455800060
55
57
  - Tetrac: Solana market-data payment gates verified, with browser payment-header preflight blocker isolated. https://github.com/solana-foundation/pay-skills/pull/32#issuecomment-4455923744
56
58
 
@@ -710,6 +710,24 @@ function cachePolicy(headers = {}) {
710
710
  return headers['cache-control'] ?? headers.cacheControl ?? ''
711
711
  }
712
712
 
713
+ function paymentSignalHeaders(headers = {}) {
714
+ return [
715
+ 'x-payment-required',
716
+ 'x-payment-enforce',
717
+ 'x-price-usdc',
718
+ 'x-payment-address',
719
+ 'x-payment-network',
720
+ 'x-payment-token',
721
+ 'x-payment-protocol',
722
+ ].filter(name => headers[name] !== undefined && headers[name] !== '')
723
+ }
724
+
725
+ function advertisesPaymentEnforcement(headers = {}) {
726
+ const required = String(headers['x-payment-required'] ?? '').toLowerCase() === 'true'
727
+ const enforced = String(headers['x-payment-enforce'] ?? '').toLowerCase() === 'true'
728
+ return required || enforced || (required && paymentSignalHeaders(headers).length > 1)
729
+ }
730
+
713
731
  function looksExplicitlyCacheable(headers = {}) {
714
732
  const policy = cachePolicy(headers)
715
733
  if (!policy) return false
@@ -755,6 +773,9 @@ function findingList(documentResult, challengeResults, preflightResults, entries
755
773
  if (!looksLikeOperationalHealthEndpoint(result)) {
756
774
  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.`)
757
775
  }
776
+ if (advertisesPaymentEnforcement(result.headers)) {
777
+ findings.push(`P2 - ${result.name} returned ${result.status} content while payment headers advertise enforcement (${paymentSignalHeaders(result.headers).join(', ')}); either return a 402 before content or document this endpoint as public telemetry.`)
778
+ }
758
779
  }
759
780
  else if (result.status === 400 || result.status === 422) {
760
781
  findings.push(`P1 - ${result.name} returned validation HTTP ${result.status} before a payment challenge for a no-payment ${result.method ?? 'POST'} probe.`)
@@ -807,7 +828,7 @@ function findingList(documentResult, challengeResults, preflightResults, entries
807
828
 
808
829
  for (const result of preflightResults) {
809
830
  const challengeResult = challengesByEntry.get(entryKey(result))
810
- if (!challengeResult || !hasPaymentChallenge(challengeResult)) continue
831
+ if (!challengeResult || (!hasPaymentChallenge(challengeResult) && !advertisesPaymentEnforcement(challengeResult.headers))) continue
811
832
  const allowedOrigin = result.headers['access-control-allow-origin'] ?? ''
812
833
  if (!allowedOrigin) {
813
834
  findings.push(`P1 - ${result.name} CORS preflight does not allow the requesting origin; observed allow-origin: none.`)
@@ -864,6 +885,9 @@ function groupedFindingLabel(finding) {
864
885
  if (/payment challenge response did not expose Cache-Control/.test(finding)) {
865
886
  return 'P3 - Payment challenge responses do not expose Cache-Control in strict cache mode.'
866
887
  }
888
+ if (/content while payment headers advertise enforcement/.test(finding)) {
889
+ return 'P2 - Payment headers advertise enforcement on a 200 response.'
890
+ }
867
891
  return null
868
892
  }
869
893
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-surface-check",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
4
4
  "description": "No-payment x402 public-surface checker for manifests, OpenAPI specs, and HTTP 402 challenges.",
5
5
  "type": "module",
6
6
  "bin": {