x402-surface-check 0.2.35 → 0.2.36
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 +17 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ npx --yes x402-surface-check --strict-proof https://api.example.com/openapi.json
|
|
|
30
30
|
- No-payment HTTP 402 challenge shape
|
|
31
31
|
- x402 v1 and v2 price fields, including `accepts[]` and `schemes[]` challenge arrays
|
|
32
32
|
- MPP `WWW-Authenticate: Payment` and x402 V2 `WWW-Authenticate: X402 requirements=...` challenges
|
|
33
|
+
- MPP descriptor-only 402s that advertise discovery headers but do not return a machine-readable payment retry challenge
|
|
33
34
|
- Atomic-unit `amount` / `maxAmountRequired` fields, plus legacy decimal `amount` + `token` x402 v1 challenges
|
|
34
35
|
- `asset` or token metadata, `network`, and `payTo`
|
|
35
36
|
- OpenAPI-declared `x-payment-info.price.amount` drift versus the live 402 challenge price
|
|
@@ -1084,6 +1084,17 @@ function hasMppRetryChallenge(result) {
|
|
|
1084
1084
|
return challengeAccepts(result).some(accept => String(accept.scheme ?? '').toLowerCase() === 'mpp')
|
|
1085
1085
|
}
|
|
1086
1086
|
|
|
1087
|
+
function mppDiscoveryHeaders(headers = {}) {
|
|
1088
|
+
return [
|
|
1089
|
+
'x-mpp-descriptor',
|
|
1090
|
+
'x-agent-card',
|
|
1091
|
+
].filter(name => headers[name] !== undefined && headers[name] !== '')
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function advertisesMppDiscovery(result) {
|
|
1095
|
+
return mppDiscoveryHeaders(result.headers).length > 0
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1087
1098
|
function allowsAnyHeader(headerValue = '', names = []) {
|
|
1088
1099
|
return names.some(name => headerListAllows(headerValue, name))
|
|
1089
1100
|
}
|
|
@@ -1160,6 +1171,10 @@ function findingList(documentResult, challengeResults, preflightResults, entries
|
|
|
1160
1171
|
}
|
|
1161
1172
|
|
|
1162
1173
|
if (!hasChallenge) {
|
|
1174
|
+
const descriptorHeaders = mppDiscoveryHeaders(result.headers)
|
|
1175
|
+
if (result.status === 402 && descriptorHeaders.length > 0) {
|
|
1176
|
+
findings.push(`P1 - ${result.name} returns 402 and advertises MPP discovery via ${descriptorHeaders.join(', ')}, but does not include a WWW-Authenticate: Payment challenge or machine-readable body challenge; autonomous callers cannot construct a paid retry from this response alone.`)
|
|
1177
|
+
}
|
|
1163
1178
|
continue
|
|
1164
1179
|
}
|
|
1165
1180
|
|
|
@@ -1238,7 +1253,7 @@ function findingList(documentResult, challengeResults, preflightResults, entries
|
|
|
1238
1253
|
|
|
1239
1254
|
for (const result of preflightResults) {
|
|
1240
1255
|
const challengeResult = challengesByEntry.get(entryKey(result))
|
|
1241
|
-
if (!challengeResult || (!hasPaymentChallenge(challengeResult) && !advertisesPaymentEnforcement(challengeResult.headers))) continue
|
|
1256
|
+
if (!challengeResult || (!hasPaymentChallenge(challengeResult) && !advertisesPaymentEnforcement(challengeResult.headers) && !advertisesMppDiscovery(challengeResult))) continue
|
|
1242
1257
|
const allowedOrigin = result.headers['access-control-allow-origin'] ?? ''
|
|
1243
1258
|
if (!allowedOrigin) {
|
|
1244
1259
|
findings.push(`P1 - ${result.name} CORS preflight does not allow the requesting origin; observed allow-origin: none.`)
|
|
@@ -1250,7 +1265,7 @@ function findingList(documentResult, challengeResults, preflightResults, entries
|
|
|
1250
1265
|
: `allow headers: ${allowed || 'none'}`
|
|
1251
1266
|
findings.push(`P1 - ${result.name} CORS preflight does not allow a known x402 retry header (X-PAYMENT or PAYMENT-SIGNATURE); observed ${observed}.`)
|
|
1252
1267
|
}
|
|
1253
|
-
if (hasMppRetryChallenge(challengeResult) && !headerListAllows(allowed, 'authorization')) {
|
|
1268
|
+
if ((hasMppRetryChallenge(challengeResult) || advertisesMppDiscovery(challengeResult)) && !headerListAllows(allowed, 'authorization')) {
|
|
1254
1269
|
const observed = result.status >= 400
|
|
1255
1270
|
? `HTTP ${result.status}; allow headers: ${allowed || 'none'}`
|
|
1256
1271
|
: `allow headers: ${allowed || 'none'}`
|