x402-surface-check 0.2.26 → 0.2.27

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
@@ -37,6 +37,7 @@ npx --yes x402-surface-check --strict-cache https://api.example.com/openapi.json
37
37
  - Browser CORS allowance for the requesting origin and `X-PAYMENT`, including the actual 402 challenge response
38
38
  - Cache-Control posture on no-payment challenge responses, with P1 warnings for explicitly cacheable payment gates and optional strict-cache findings for missing policy headers
39
39
  - Payment-enforcement headers on `200` responses, so public telemetry/free-trial endpoints do not accidentally advertise enforced x402 while returning content before a challenge
40
+ - Credential-like query params and URL userinfo inside public registry/discovery endpoint URLs, reported with redacted values so provider API keys and tokens are not repeated in scan output
40
41
  - Grouped finding summaries for repeated route-wide issues, so large manifests keep the patch order readable
41
42
  - Contextual reference guides for CORS, cache policy, Worker gates, resource echo, validation/auth ordering, and the May 2026 x402 attack-control map
42
43
  - Over-broad public method surfaces
@@ -811,6 +811,55 @@ function looksLikeLocalResourceUrl(value) {
811
811
  }
812
812
  }
813
813
 
814
+ const secretQueryParamPattern =
815
+ /^(?:access[_-]?token|api[_-]?key|auth|authorization|bearer|client[_-]?secret|code|key|password|private[_-]?key|secret|session|sig|signature|token|jwt)$/i
816
+
817
+ function redactedCredentialUrl(value) {
818
+ if (!/^https?:\/\//i.test(String(value ?? ''))) return null
819
+ try {
820
+ const url = new URL(value)
821
+ let changed = false
822
+
823
+ if (url.username || url.password) {
824
+ if (url.username) url.username = 'REDACTED'
825
+ if (url.password) url.password = 'REDACTED'
826
+ changed = true
827
+ }
828
+
829
+ for (const [name] of Array.from(url.searchParams.entries())) {
830
+ if (secretQueryParamPattern.test(name)) {
831
+ url.searchParams.set(name, 'REDACTED')
832
+ changed = true
833
+ }
834
+ }
835
+
836
+ return changed ? url.toString() : null
837
+ }
838
+ catch {
839
+ return null
840
+ }
841
+ }
842
+
843
+ function publicUrlCredentialFindings(value, path = 'document', depth = 0) {
844
+ if (depth > 8 || value === null || value === undefined) return []
845
+ if (typeof value === 'string') {
846
+ const redacted = redactedCredentialUrl(value)
847
+ return redacted
848
+ ? [`P2 - Public document exposes credential-like URL material at ${path}: ${redacted}. Move provider tokens, signatures, sessions, or API keys out of registry-visible endpoint URLs.`]
849
+ : []
850
+ }
851
+ if (Array.isArray(value)) {
852
+ return value.flatMap((item, index) => publicUrlCredentialFindings(item, `${path}[${index}]`, depth + 1))
853
+ }
854
+ if (typeof value === 'object') {
855
+ return Object.entries(value).flatMap(([key, item]) => {
856
+ const safeKey = /^[a-zA-Z_$][\w$-]*$/.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`
857
+ return publicUrlCredentialFindings(item, `${path}${safeKey}`, depth + 1)
858
+ })
859
+ }
860
+ return []
861
+ }
862
+
814
863
  function cachePolicy(headers = {}) {
815
864
  return headers['cache-control'] ?? headers.cacheControl ?? ''
816
865
  }
@@ -863,6 +912,9 @@ function findingList(documentResult, challengeResults, preflightResults, entries
863
912
  if (!documentResult.body.json) {
864
913
  findings.push(`P1 - Document did not return parseable JSON; content begins: ${documentResult.body.text.slice(0, 80).replace(/\s+/g, ' ')}.`)
865
914
  }
915
+ else {
916
+ findings.push(...publicUrlCredentialFindings(document))
917
+ }
866
918
 
867
919
  if (entries.length === 0) {
868
920
  findings.push('P1 - Document does not expose any manifest, OpenAPI, item, category, or resource endpoints for no-payment probes.')
@@ -1059,6 +1111,10 @@ function referenceGuides(findings) {
1059
1111
  add('x402 Surface Check notes', 'https://tateprograms.com/x402-surface-check.html')
1060
1112
  add('x402 Attack Map 2026', 'https://tateprograms.com/x402-attack-map-2026.html')
1061
1113
  }
1114
+ if (/credential-like URL material|provider tokens|API keys|registry-visible endpoint URLs/i.test(text)) {
1115
+ add('x402 Metadata Filter', 'https://tateprograms.com/x402-metadata-filter.html')
1116
+ add('Agent Commerce Gate', 'https://tateprograms.com/agent-commerce-gate.html')
1117
+ }
1062
1118
  return guides.map(guide => `- ${guide.label}: ${guide.url}`)
1063
1119
  }
1064
1120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-surface-check",
3
- "version": "0.2.26",
3
+ "version": "0.2.27",
4
4
  "description": "No-payment x402 public-surface checker for manifests, OpenAPI specs, and HTTP 402 challenges.",
5
5
  "type": "module",
6
6
  "bin": {