@blamejs/blamejs-shop 0.4.3 → 0.4.5
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/CHANGELOG.md +4 -0
- package/README.md +8 -7
- package/lib/admin.js +376 -0
- package/lib/asset-manifest.json +5 -5
- package/lib/storefront.js +296 -9
- package/lib/vendor/MANIFEST.json +23 -23
- package/lib/vendor/blamejs/.pinact.yaml +1 -1
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/SECURITY.md +1 -1
- package/lib/vendor/blamejs/api-snapshot.json +15 -2
- package/lib/vendor/blamejs/index.js +5 -1
- package/lib/vendor/blamejs/lib/auth/jar.js +190 -28
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +213 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +115 -101
- package/lib/vendor/blamejs/lib/http-client.js +3 -4
- package/lib/vendor/blamejs/lib/lro.js +3 -4
- package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -10
- package/lib/vendor/blamejs/lib/middleware/health.js +1 -4
- package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +3 -6
- package/lib/vendor/blamejs/lib/validate-opts.js +34 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jar.test.js +226 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +122 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +104 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +127 -0
- package/package.json +1 -1
- package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +0 -165
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Layer 0 — b.auth.jar
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* Layer 0 — b.auth.jar: RFC 9101 JWT-Secured Authorization Request.
|
|
4
|
+
*
|
|
5
|
+
* parse (server side) verifies the client-signed request object via
|
|
6
|
+
* verifyExternal (mandatory alg allowlist), pins iss + client_id + aud,
|
|
7
|
+
* refuses nested request / request_uri, and returns the authorization
|
|
8
|
+
* parameters. Request objects are signed inline with a classical key
|
|
9
|
+
* (RS256) to mirror a real OAuth client.
|
|
10
|
+
*
|
|
11
|
+
* build (client side) mints the request object via b.auth.jws.sign and
|
|
12
|
+
* round-trips through parse as the verifying oracle across RS256 / PS256 /
|
|
13
|
+
* ES256 / EdDSA; the anti-nesting, reserved-claim, required-param,
|
|
14
|
+
* alg-key-mismatch, none-refusal, and exp-window paths are asserted.
|
|
9
15
|
*/
|
|
10
16
|
|
|
11
17
|
var helpers = require("../helpers");
|
|
@@ -163,6 +169,212 @@ async function testTypEnforcement() {
|
|
|
163
169
|
check("parse: absent typ refused (strict)", eAbsent && eAbsent.code === "auth-jar/bad-typ");
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
// ---- b.auth.jar.build (RFC 9101 client side) ----
|
|
173
|
+
|
|
174
|
+
// Per-alg keypair generators. Public half as a JWK (kid c1) so jar.parse
|
|
175
|
+
// verifies the build output against it; private half as a KeyObject for the
|
|
176
|
+
// signer.
|
|
177
|
+
function _ecPair(curve, alg) {
|
|
178
|
+
var kp = nodeCrypto.generateKeyPairSync("ec", { namedCurve: curve });
|
|
179
|
+
var jwk = Object.assign(kp.publicKey.export({ format: "jwk" }), { kid: "c1", use: "sig", alg: alg });
|
|
180
|
+
return { privateKey: kp.privateKey, jwk: jwk };
|
|
181
|
+
}
|
|
182
|
+
function _rsaSigPair(alg) {
|
|
183
|
+
var kp = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
|
|
184
|
+
var jwk = Object.assign(kp.publicKey.export({ format: "jwk" }), { kid: "c1", use: "sig", alg: alg });
|
|
185
|
+
return { privateKey: kp.privateKey, jwk: jwk };
|
|
186
|
+
}
|
|
187
|
+
function _edPair() {
|
|
188
|
+
var kp = nodeCrypto.generateKeyPairSync("ed25519");
|
|
189
|
+
var jwk = Object.assign(kp.publicKey.export({ format: "jwk" }), { kid: "c1", use: "sig", alg: "EdDSA" });
|
|
190
|
+
return { privateKey: kp.privateKey, jwk: jwk };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function _buildParams(over) {
|
|
194
|
+
var p = {
|
|
195
|
+
response_type: "code",
|
|
196
|
+
client_id: CLIENT,
|
|
197
|
+
redirect_uri: "https://app.example.com/cb",
|
|
198
|
+
scope: "openid profile",
|
|
199
|
+
state: "xyz-state",
|
|
200
|
+
nonce: "n-0S6_WzA2Mj",
|
|
201
|
+
};
|
|
202
|
+
if (over) { var k = Object.keys(over); for (var i = 0; i < k.length; i++) p[k[i]] = over[k[i]]; }
|
|
203
|
+
return p;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// build → parse round-trip across every classical alg family, using the
|
|
207
|
+
// in-repo jar.parse as the verifying oracle.
|
|
208
|
+
async function testBuildRoundTrip() {
|
|
209
|
+
var cases = [
|
|
210
|
+
{ name: "RS256", pair: _rsaSigPair("RS256"), alg: "RS256" },
|
|
211
|
+
{ name: "PS256", pair: _rsaSigPair("PS256"), alg: "PS256" },
|
|
212
|
+
{ name: "ES256", pair: _ecPair("P-256", "ES256"), alg: "ES256" },
|
|
213
|
+
{ name: "EdDSA", pair: _edPair(), alg: "EdDSA" },
|
|
214
|
+
];
|
|
215
|
+
for (var i = 0; i < cases.length; i++) {
|
|
216
|
+
var c = cases[i];
|
|
217
|
+
var ro = b.auth.jar.build(_buildParams(), {
|
|
218
|
+
clientId: CLIENT, audience: AS, key: c.pair.privateKey, alg: c.alg, kid: "c1",
|
|
219
|
+
});
|
|
220
|
+
var hdr = JSON.parse(Buffer.from(ro.split(".")[0], "base64url").toString("utf8"));
|
|
221
|
+
check("build[" + c.name + "]: header typ is oauth-authz-req+jwt", hdr.typ === "oauth-authz-req+jwt");
|
|
222
|
+
check("build[" + c.name + "]: header alg matches the key", hdr.alg === c.alg);
|
|
223
|
+
check("build[" + c.name + "]: header carries kid", hdr.kid === "c1");
|
|
224
|
+
var out = await b.auth.jar.parse(ro, _parseOpts({ algorithms: [c.alg], jwks: [c.pair.jwk] }));
|
|
225
|
+
check("build[" + c.name + "]: round-trips through parse — params preserved",
|
|
226
|
+
out.params.response_type === "code" && out.params.redirect_uri === "https://app.example.com/cb" &&
|
|
227
|
+
out.params.scope === "openid profile" && out.params.state === "xyz-state");
|
|
228
|
+
check("build[" + c.name + "]: parse sees iss=clientId + aud=AS",
|
|
229
|
+
out.claims.iss === CLIENT && out.claims.aud === AS);
|
|
230
|
+
check("build[" + c.name + "]: builder minted jti + nbf + iat + exp",
|
|
231
|
+
typeof out.claims.jti === "string" && typeof out.claims.nbf === "number" &&
|
|
232
|
+
typeof out.claims.iat === "number" && typeof out.claims.exp === "number");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// alg inferred from the key when no explicit alg is supplied.
|
|
237
|
+
async function testBuildAlgInference() {
|
|
238
|
+
var ed = _edPair();
|
|
239
|
+
var ro = b.auth.jar.build(_buildParams(), { clientId: CLIENT, audience: AS, key: ed.privateKey, kid: "c1" });
|
|
240
|
+
var hdr = JSON.parse(Buffer.from(ro.split(".")[0], "base64url").toString("utf8"));
|
|
241
|
+
check("build: Ed25519 key infers EdDSA alg (no explicit alg)", hdr.alg === "EdDSA");
|
|
242
|
+
var out = await b.auth.jar.parse(ro, _parseOpts({ algorithms: ["EdDSA"], jwks: [ed.jwk] }));
|
|
243
|
+
check("build: inferred-alg object verifies", out.params.response_type === "code");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// anti-nesting — params carrying request / request_uri refused at build.
|
|
247
|
+
async function testBuildAntiNesting() {
|
|
248
|
+
var ec = _ecPair("P-256", "ES256");
|
|
249
|
+
var e1 = null;
|
|
250
|
+
try { b.auth.jar.build(_buildParams({ request: "ey.x.y" }), { clientId: CLIENT, audience: AS, key: ec.privateKey }); }
|
|
251
|
+
catch (e) { e1 = e; }
|
|
252
|
+
check("build: nested request refused (RFC 9101 §4)", e1 && e1.code === "auth-jar/nested-request");
|
|
253
|
+
var e2 = null;
|
|
254
|
+
try { b.auth.jar.build(_buildParams({ request_uri: "https://evil/ro" }), { clientId: CLIENT, audience: AS, key: ec.privateKey }); }
|
|
255
|
+
catch (e) { e2 = e; }
|
|
256
|
+
check("build: nested request_uri refused", e2 && e2.code === "auth-jar/nested-request");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// reserved-collision + required-param + client_id agreement refusals.
|
|
260
|
+
async function testBuildClaimRules() {
|
|
261
|
+
var ec = _ecPair("P-256", "ES256");
|
|
262
|
+
var opts = { clientId: CLIENT, audience: AS, key: ec.privateKey };
|
|
263
|
+
var e1 = null;
|
|
264
|
+
try { b.auth.jar.build(_buildParams({ iss: "evil" }), opts); } catch (e) { e1 = e; }
|
|
265
|
+
check("build: params.iss collision refused (builder owns iss)", e1 && e1.code === "auth-jar/reserved-claim");
|
|
266
|
+
var e2 = null;
|
|
267
|
+
try { b.auth.jar.build(_buildParams({ exp: 123 }), opts); } catch (e) { e2 = e; }
|
|
268
|
+
check("build: params.exp collision refused (builder mints exp)", e2 && e2.code === "auth-jar/reserved-claim");
|
|
269
|
+
var e3 = null;
|
|
270
|
+
var noRt = _buildParams(); delete noRt.response_type;
|
|
271
|
+
try { b.auth.jar.build(noRt, opts); } catch (e) { e3 = e; }
|
|
272
|
+
check("build: missing response_type refused (RFC 9101 §4)", e3 && e3.code === "auth-jar/missing-required-param");
|
|
273
|
+
var e4 = null;
|
|
274
|
+
try { b.auth.jar.build(_buildParams({ client_id: "different" }), opts); } catch (e) { e4 = e; }
|
|
275
|
+
check("build: params.client_id != opts.clientId refused", e4 && e4.code === "auth-jar/client-id-mismatch");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// alg-key mismatch + `none` refusal (delegated to the jws signer).
|
|
279
|
+
async function testBuildAlgRefusals() {
|
|
280
|
+
var ec = _ecPair("P-256", "ES256");
|
|
281
|
+
var e1 = null;
|
|
282
|
+
// a P-256 key cannot produce an RS256 signature.
|
|
283
|
+
try { b.auth.jar.build(_buildParams(), { clientId: CLIENT, audience: AS, key: ec.privateKey, alg: "RS256" }); }
|
|
284
|
+
catch (e) { e1 = e; }
|
|
285
|
+
check("build: alg incompatible with key refused (ES key + RS256)",
|
|
286
|
+
e1 && e1.code === "auth-jwt-external/sign-alg-key-mismatch");
|
|
287
|
+
var e2 = null;
|
|
288
|
+
try { b.auth.jar.build(_buildParams(), { clientId: CLIENT, audience: AS, key: ec.privateKey, alg: "none" }); }
|
|
289
|
+
catch (e) { e2 = e; }
|
|
290
|
+
check("build: alg 'none' refused", e2 && e2.code === "auth-jwt-external/sign-alg-refused");
|
|
291
|
+
var e3 = null;
|
|
292
|
+
try { b.auth.jar.build(_buildParams(), { clientId: CLIENT, audience: AS, key: ec.privateKey, alg: "HS256" }); }
|
|
293
|
+
catch (e) { e3 = e; }
|
|
294
|
+
check("build: HMAC alg refused", e3 && e3.code === "auth-jwt-external/sign-alg-refused");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// exp window honored — default 5m, operator override respected, and an
|
|
298
|
+
// already-expired window is refused by parse.
|
|
299
|
+
async function testBuildExpWindow() {
|
|
300
|
+
var ec = _ecPair("P-256", "ES256");
|
|
301
|
+
var defaultRo = b.auth.jar.build(_buildParams(), { clientId: CLIENT, audience: AS, key: ec.privateKey });
|
|
302
|
+
var defaultClaims = JSON.parse(Buffer.from(defaultRo.split(".")[1], "base64url").toString("utf8"));
|
|
303
|
+
var defaultTtl = defaultClaims.exp - defaultClaims.iat;
|
|
304
|
+
check("build: default exp window is 5 minutes", defaultTtl === 300);
|
|
305
|
+
|
|
306
|
+
var shortRo = b.auth.jar.build(_buildParams(), {
|
|
307
|
+
clientId: CLIENT, audience: AS, key: ec.privateKey, kid: "c1", expiresInMs: 60 * 1000 });
|
|
308
|
+
var shortClaims = JSON.parse(Buffer.from(shortRo.split(".")[1], "base64url").toString("utf8"));
|
|
309
|
+
check("build: expiresInMs override honored (60s)", shortClaims.exp - shortClaims.iat === 60);
|
|
310
|
+
|
|
311
|
+
// The minted window is a live exp parse enforces: a fresh object inside
|
|
312
|
+
// the window verifies, and the same object handed to parse with a NEGATIVE
|
|
313
|
+
// skew that pushes "now" past the exp is refused on the expired path —
|
|
314
|
+
// proves the builder's exp reaches the verifier and is checked, not cosmetic.
|
|
315
|
+
var liveRo = b.auth.jar.build(_buildParams(), {
|
|
316
|
+
clientId: CLIENT, audience: AS, key: ec.privateKey, kid: "c1", expiresInMs: 60 * 1000 });
|
|
317
|
+
var liveOut = await b.auth.jar.parse(liveRo, _parseOpts({ algorithms: ["ES256"], jwks: [ec.jwk], clockSkewMs: 0 }));
|
|
318
|
+
check("build: object inside its exp window verifies", liveOut.params.response_type === "code");
|
|
319
|
+
var eExp = null;
|
|
320
|
+
try {
|
|
321
|
+
await b.auth.jar.parse(liveRo, _parseOpts({ algorithms: ["ES256"], jwks: [ec.jwk], clockSkewMs: -120 * 1000 }));
|
|
322
|
+
} catch (e) { eExp = e; }
|
|
323
|
+
check("build: exp is enforced by parse (skew past exp refuses)", eExp && /expired/.test(eExp.code || ""));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// config-time opt validation.
|
|
327
|
+
async function testBuildValidation() {
|
|
328
|
+
var ec = _ecPair("P-256", "ES256");
|
|
329
|
+
var bads = [
|
|
330
|
+
[function () { return b.auth.jar.build(null, { clientId: CLIENT, audience: AS, key: ec.privateKey }); }, "auth-jar/bad-params"],
|
|
331
|
+
[function () { return b.auth.jar.build(_buildParams(), { audience: AS, key: ec.privateKey }); }, "auth-jar/bad-client-id"],
|
|
332
|
+
[function () { return b.auth.jar.build(_buildParams(), { clientId: CLIENT, key: ec.privateKey }); }, "auth-jar/bad-audience"],
|
|
333
|
+
[function () { return b.auth.jar.build(_buildParams(), { clientId: CLIENT, audience: AS }); }, "auth-jar/no-key"],
|
|
334
|
+
[function () { return b.auth.jar.build(_buildParams(), { clientId: CLIENT, audience: AS, key: ec.privateKey, bogus: 1 }); }, null],
|
|
335
|
+
[function () { return b.auth.jar.build(_buildParams(), { clientId: CLIENT, audience: AS, key: ec.privateKey, expiresInMs: -5 }); }, "auth-jar/bad-expiry"],
|
|
336
|
+
];
|
|
337
|
+
var ok = true;
|
|
338
|
+
for (var i = 0; i < bads.length; i++) {
|
|
339
|
+
var caught = null;
|
|
340
|
+
try { bads[i][0](); } catch (e) { caught = e; }
|
|
341
|
+
var expected = bads[i][1];
|
|
342
|
+
var pass = expected === null ? (caught !== null) : (caught && caught.code === expected);
|
|
343
|
+
if (!pass) { ok = false; check("build validation case " + i + " expected " + expected + " got " + (caught && caught.code), false); }
|
|
344
|
+
}
|
|
345
|
+
check("build: malformed args throw the right codes", ok);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// A verified-but-hostile request object carrying a `__proto__` claim
|
|
349
|
+
// (JSON.parse materializes it as an own key; JSON.stringify round-trips
|
|
350
|
+
// it) must not graft onto the returned params object's prototype chain
|
|
351
|
+
// (CWE-1321). The signature is the client's own — signing hostile claim
|
|
352
|
+
// names is exactly what a malicious-but-registered client would do.
|
|
353
|
+
async function testProtoPollutionClaimInert() {
|
|
354
|
+
var payload = JSON.parse(JSON.stringify({
|
|
355
|
+
iss: CLIENT,
|
|
356
|
+
aud: AS,
|
|
357
|
+
client_id: CLIENT,
|
|
358
|
+
response_type: "code",
|
|
359
|
+
iat: _nowSec(),
|
|
360
|
+
exp: _nowSec() + 120,
|
|
361
|
+
}).replace("\"response_type\"", "\"__proto__\":{\"polluted\":true},\"response_type\""));
|
|
362
|
+
check("fixture: payload carries __proto__ as an own key",
|
|
363
|
+
Object.prototype.hasOwnProperty.call(payload, "__proto__"));
|
|
364
|
+
var jar = _signRs256(KEYS.privateKey,
|
|
365
|
+
{ alg: "RS256", typ: "oauth-authz-req+jwt", kid: "c1" }, payload);
|
|
366
|
+
var out = await b.auth.jar.parse(jar, _parseOpts());
|
|
367
|
+
check("parse: __proto__ claim does not graft onto params' prototype",
|
|
368
|
+
Object.getPrototypeOf(out.params) === Object.prototype &&
|
|
369
|
+
out.params.polluted === undefined);
|
|
370
|
+
check("parse: __proto__ not copied as an own params key",
|
|
371
|
+
!Object.prototype.hasOwnProperty.call(out.params, "__proto__"));
|
|
372
|
+
check("parse: Object.prototype untouched",
|
|
373
|
+
Object.prototype.polluted === undefined);
|
|
374
|
+
check("parse: legitimate params still returned",
|
|
375
|
+
out.params.response_type === "code");
|
|
376
|
+
}
|
|
377
|
+
|
|
166
378
|
async function run() {
|
|
167
379
|
await testRoundTrip();
|
|
168
380
|
await testAntiNesting();
|
|
@@ -170,6 +382,14 @@ async function run() {
|
|
|
170
382
|
await testAlgConfusionDelegated();
|
|
171
383
|
await testValidation();
|
|
172
384
|
await testTypEnforcement();
|
|
385
|
+
await testBuildRoundTrip();
|
|
386
|
+
await testBuildAlgInference();
|
|
387
|
+
await testBuildAntiNesting();
|
|
388
|
+
await testBuildClaimRules();
|
|
389
|
+
await testBuildAlgRefusals();
|
|
390
|
+
await testBuildExpWindow();
|
|
391
|
+
await testBuildValidation();
|
|
392
|
+
await testProtoPollutionClaimInert();
|
|
173
393
|
}
|
|
174
394
|
|
|
175
395
|
module.exports = { run: run };
|
|
@@ -2618,12 +2618,48 @@ async function testNoDuplicateCodeBlocks() {
|
|
|
2618
2618
|
{
|
|
2619
2619
|
mode: "family-subset",
|
|
2620
2620
|
files: [
|
|
2621
|
-
"lib/auth/oauth.js:buildClientAttestation",
|
|
2622
2621
|
"lib/guard-filename.js:verifyExtractionPath",
|
|
2623
2622
|
"lib/hal.js:resource",
|
|
2623
|
+
"lib/validate-opts.js:assignOwnEnumerable",
|
|
2624
2624
|
"lib/vault-aad.js:_canonicalize",
|
|
2625
2625
|
],
|
|
2626
|
-
reason: "v0.13.13 — coincidental token shingle of the generic split-then-walk-segments / walk-own-keys idiom (`x.split(sep)` or `Object.keys(x)` then `for (...) { var seg = ...; if (...) throw/continue }`). guard-filename verifyExtractionPath walks path components refusing per-segment Windows-extraction hazards (reserved names / NTFS-ADS / trailing-dot); hal.js:resource builds a HAL resource by walking link/embedded keys; vault-aad.js:_canonicalize canonicalizes AAD key-value segments;
|
|
2626
|
+
reason: "v0.13.13, membership refreshed v0.14.22 — coincidental token shingle of the generic split-then-walk-segments / walk-own-keys idiom (`x.split(sep)` or `Object.keys(x)` then `for (...) { var seg = ...; if (...) throw/continue }`). guard-filename verifyExtractionPath walks path components refusing per-segment Windows-extraction hazards (reserved names / NTFS-ADS / trailing-dot); hal.js:resource builds a HAL resource by walking link/embedded keys; vault-aad.js:_canonicalize canonicalizes AAD key-value segments; validate-opts.js:assignOwnEnumerable IS the extraction of the proto-safe claim-merge shape (oauth.buildClientAttestation / jar.build / jws sign all compose it now) — its own body necessarily carries the walk shape the family shares. Unrelated remaining domains (path safety / hypermedia link assembly / crypto AAD canonicalization / the shared merge helper itself) — nothing further to extract.",
|
|
2627
|
+
},
|
|
2628
|
+
{
|
|
2629
|
+
mode: "family-subset",
|
|
2630
|
+
files: [
|
|
2631
|
+
"lib/api-key.js:_validateIssueOpts",
|
|
2632
|
+
"lib/audit-daily-review.js:create",
|
|
2633
|
+
"lib/auth/jar.js:build",
|
|
2634
|
+
"lib/compliance-sanctions-fetcher.js:create",
|
|
2635
|
+
"lib/fdx.js:consentReceipt",
|
|
2636
|
+
"lib/http-client-cache.js:create",
|
|
2637
|
+
"lib/http-client.js:_validateDownloadOpts",
|
|
2638
|
+
"lib/mail-arc-sign.js:sign",
|
|
2639
|
+
"lib/middleware/dpop.js:create",
|
|
2640
|
+
"lib/outbox.js:create",
|
|
2641
|
+
"lib/self-update.js:_validateVerifyOpts",
|
|
2642
|
+
"lib/static.js:_validateCreateOpts",
|
|
2643
|
+
"lib/tcpa-10dlc.js:recordConsent",
|
|
2644
|
+
"lib/vault/seal-pem-file.js:sealPemFile",
|
|
2645
|
+
"lib/vex.js:document",
|
|
2646
|
+
"lib/watcher.js:_validateOpts",
|
|
2647
|
+
"lib/web-push-vapid.js:buildVapidAuthHeader",
|
|
2648
|
+
],
|
|
2649
|
+
reason: "Config-time validateOpts cascade family — `validateOpts(opts, KEYS, label)` followed by per-field requireNonEmptyString / optionalPositiveInt / optionalFunction checks at a factory or builder entry point. jar.js:build joined in v0.14.22 (the RFC 9101 request-object builder validates clientId / audience / key / expiresInMs before minting). Each member emits its own error class and code namespace over a different opts vocabulary; the shared helper (validateOpts) is the extraction, and consolidating the cascades past the call boundary would surface the wrong error code for operator typos.",
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
mode: "family-subset",
|
|
2653
|
+
files: [
|
|
2654
|
+
"lib/auth/dpop.js:buildProof",
|
|
2655
|
+
"lib/auth/dpop.js:verify",
|
|
2656
|
+
"lib/auth/fido-mds3.js:_verifyJws",
|
|
2657
|
+
"lib/auth/jwt-external.js:_signCompactJws",
|
|
2658
|
+
"lib/auth/jwt-external.js:verifyExternal",
|
|
2659
|
+
"lib/auth/oauth.js:_verifyAttestationJws",
|
|
2660
|
+
"lib/auth/oauth.js:verifyIdToken",
|
|
2661
|
+
],
|
|
2662
|
+
reason: "Compact-JWS assemble/verify family — the three-segment base64url split/join + alg-params dispatch + nodeCrypto sign/verify shape shared by every classical JOSE touchpoint. jwt-external.js:_signCompactJws joined in v0.14.22 as the PROMOTED canonical signer (oauth's attestation signer now composes it; dpop keeps its own RFC 9449-specific proof assembly with htm/htu/ath claims, and the verify members each pin different header gates — typ/jwk/x5c/kid — that the spec assigns per protocol). The residual shingle is the irreducible JWS wire format itself; further consolidation would couple per-protocol header policy into one function with a mode flag, which the gate-contract discipline forbids.",
|
|
2627
2663
|
},
|
|
2628
2664
|
{
|
|
2629
2665
|
mode: "family-subset",
|
|
@@ -6157,10 +6193,10 @@ var KNOWN_ANTIPATTERNS = [
|
|
|
6157
6193
|
// converts each call site, drops the allowlist entries, and
|
|
6158
6194
|
// flips the detector from "documentation" to "enforce".
|
|
6159
6195
|
//
|
|
6160
|
-
// The allowlist below is the survey ground truth
|
|
6161
|
-
//
|
|
6162
|
-
// file here pre-floor-bump requires
|
|
6163
|
-
//
|
|
6196
|
+
// The allowlist below is the survey ground truth — each entry
|
|
6197
|
+
// names the map and the on-miss factory it covers. Adding a new
|
|
6198
|
+
// file here pre-floor-bump requires the same per-site annotation
|
|
6199
|
+
// so the sweep stays mechanical.
|
|
6164
6200
|
//
|
|
6165
6201
|
// The catalog catches both variants via a pair of sibling entries:
|
|
6166
6202
|
// A. `var X = M.get(k); if (!X) { ... .set(k, ...) ... }` — this
|
|
@@ -6179,7 +6215,7 @@ var KNOWN_ANTIPATTERNS = [
|
|
|
6179
6215
|
// where the `.get(...)` and `.set(...)` name DIFFERENT maps is
|
|
6180
6216
|
// covered by the allowlist + reason.
|
|
6181
6217
|
id: "map-get-or-insert-pre-node-26",
|
|
6182
|
-
primitive: "Map.prototype.getOrInsertComputed(key, factory) (Node 26+); pre-floor-bump call sites are allowlisted with
|
|
6218
|
+
primitive: "Map.prototype.getOrInsertComputed(key, factory) (Node 26+); pre-floor-bump call sites are allowlisted below with per-site map+factory annotations — the floor-bump sweep walks the allowlist",
|
|
6183
6219
|
// Variant A only — `var X = M.get(k); if (!X) { ... M.set(k, ...) ... }`.
|
|
6184
6220
|
// Variant B (`if (!M.has(k)) { ... M.set(k, ...) ... }`) is caught
|
|
6185
6221
|
// by the sibling `map-has-then-set-pre-node-26` entry below; one
|
|
@@ -6204,16 +6240,15 @@ var KNOWN_ANTIPATTERNS = [
|
|
|
6204
6240
|
],
|
|
6205
6241
|
// Strong-dup allowlists added with v0.12.7 archive substrate
|
|
6206
6242
|
// — see KNOWN_CLUSTERS additions below for structural reasons.
|
|
6207
|
-
reason: "Node 26 ships Map.prototype.getOrInsertComputed(key, factory) — a single-lookup get-or-insert that replaces the two-step `var v = m.get(k); if (!v) { v = factory(); m.set(k, v); }` pattern. The sweep is deferred to the Node 26 floor-bump (eligible Oct 2026); engines.node is `>=24` today. Allowlist above is the survey ground truth
|
|
6243
|
+
reason: "Node 26 ships Map.prototype.getOrInsertComputed(key, factory) — a single-lookup get-or-insert that replaces the two-step `var v = m.get(k); if (!v) { v = factory(); m.set(k, v); }` pattern. The sweep is deferred to the Node 26 floor-bump (eligible Oct 2026); engines.node is `>=24` today. Allowlist above is the survey ground truth (map + factory annotated per entry). New code post-this-patch trips the detector — either wait for the floor bump, or add the call site to the allowlist with the same per-site annotation in the same patch. When the floor moves, the bump commit walks the allowlist, rewrites each call site, drops the allowlist + flips the detector to enforce.",
|
|
6208
6244
|
},
|
|
6209
6245
|
{
|
|
6210
6246
|
// Companion to `map-get-or-insert-pre-node-26` — same Node-26
|
|
6211
6247
|
// migration target, different syntactic variant. Catches the
|
|
6212
6248
|
// `if (!M.has(k)) { ... M.set(k, factory); ... }` shape (no
|
|
6213
|
-
// intermediate `var X = M.get(k)` binding). See the sibling entry
|
|
6214
|
-
// and memory/specs/node-26-map-getorinsert-migration.md.
|
|
6249
|
+
// intermediate `var X = M.get(k)` binding). See the sibling entry.
|
|
6215
6250
|
id: "map-has-then-set-pre-node-26",
|
|
6216
|
-
primitive: "Map.prototype.getOrInsertComputed(key, factory) (Node 26+); pre-floor-bump call sites are allowlisted with
|
|
6251
|
+
primitive: "Map.prototype.getOrInsertComputed(key, factory) (Node 26+); pre-floor-bump call sites are allowlisted below with per-site map+factory annotations — the floor-bump sweep walks the allowlist",
|
|
6217
6252
|
regex: /if\s*\(\s*!\s*\w+\.has\s*\([^)]+\)\s*\)\s*\{[\s\S]{0,300}?\.set\s*\(/,
|
|
6218
6253
|
allowlist: [
|
|
6219
6254
|
"lib/websocket-channels.js", // channelToConns (Map<channel, Set<conn>>) — Set factory; cluster-shared race window
|
|
@@ -6222,9 +6257,9 @@ var KNOWN_ANTIPATTERNS = [
|
|
|
6222
6257
|
//
|
|
6223
6258
|
// - mail-greylist.js memoryStore.put runs `data.set(key, ...)`
|
|
6224
6259
|
// unconditionally (always overwrites the value); the if-block
|
|
6225
|
-
// manages an evict-oldest sidecar `insertionOrder`.
|
|
6226
|
-
//
|
|
6227
|
-
//
|
|
6260
|
+
// manages an evict-oldest sidecar `insertionOrder`.
|
|
6261
|
+
// getOrInsertComputed only runs the factory on miss — wrong
|
|
6262
|
+
// semantics for an always-write; the sidecar logic stays.
|
|
6228
6263
|
// - dsr.js memoryTicketStore.update is a presence assertion:
|
|
6229
6264
|
// `if (!byId.has(id)) throw new DsrError(...)` followed by
|
|
6230
6265
|
// `byId.set(id, ...)` outside the if-block (it's an UPDATE,
|
|
@@ -6349,6 +6384,53 @@ var KNOWN_ANTIPATTERNS = [
|
|
|
6349
6384
|
allowlist: [],
|
|
6350
6385
|
reason: "Codex P2 on v0.14.21 PR #301 — bulkId cross-references appear in operation paths as well as operation data (RFC 7644 §3.7.2); a planner that scans only data leaves path-referencing operations unordered and lets the literal bulkId:<id> token reach the resource adapter. Any file collecting data refs must collect and substitute path refs too.",
|
|
6351
6386
|
},
|
|
6387
|
+
{
|
|
6388
|
+
// Copying keys from one object to another with a raw bracket-assign
|
|
6389
|
+
// loop (`out[keys[i]] = src[keys[i]]`) writes attacker-chosen
|
|
6390
|
+
// property names when the source is parsed input: an own
|
|
6391
|
+
// `__proto__` key (JSON.parse materializes one) hits the
|
|
6392
|
+
// Object.prototype setter and grafts onto the target's prototype
|
|
6393
|
+
// chain (CWE-1321). validate-opts.assignOwnEnumerable is the
|
|
6394
|
+
// composing primitive — it skips __proto__/constructor/prototype
|
|
6395
|
+
// and takes a reserved-keys array, which also expresses the
|
|
6396
|
+
// filtered-copy variants (build the skip list, then copy).
|
|
6397
|
+
id: "raw-key-copy-loop-bypasses-assign-own-enumerable",
|
|
6398
|
+
primitive: "validateOpts.assignOwnEnumerable(target, source, reservedKeys) — prototype-safe own-enumerable key copy",
|
|
6399
|
+
regex: /\[\s*keys\[\w+\]\s*\]\s*=\s*[\w$.]+\[\s*keys\[\w+\]\s*\]/,
|
|
6400
|
+
skipCommentLines: true,
|
|
6401
|
+
allowlist: [
|
|
6402
|
+
// canonicalize() builds the hash input for persisted rowHash
|
|
6403
|
+
// chains — altering which keys are copied (sentinel skips) would
|
|
6404
|
+
// change historical hash inputs and break verifyChain on existing
|
|
6405
|
+
// rows. Row keys are schema-fixed audit columns, not parsed
|
|
6406
|
+
// remote input.
|
|
6407
|
+
"lib/audit-chain.js",
|
|
6408
|
+
// omit()/partial() are schema-shape transforms that map values
|
|
6409
|
+
// (`.optional()`) and track key arrays in the same pass; shapes
|
|
6410
|
+
// are boot-time operator literals, not parsed input.
|
|
6411
|
+
"lib/safe-schema.js",
|
|
6412
|
+
],
|
|
6413
|
+
reason: "CodeQL js/remote-property-injection (high) on v0.14.22 PR #302 — jar.parse copied verified-JWT claim keys into the returned params object with a raw bracket-assign loop; a hostile request object carrying a `__proto__` claim grafts onto params' prototype chain. Every key-copy loop in lib/ composes assignOwnEnumerable; genuinely-different bodies carry an allowlist entry with the structural reason.",
|
|
6414
|
+
},
|
|
6415
|
+
{
|
|
6416
|
+
// A JWS/JWT builder that accepts caller-supplied extra
|
|
6417
|
+
// protected-header members must refuse the two members that change
|
|
6418
|
+
// what the signature is computed over: `b64` (RFC 7797 — unencoded
|
|
6419
|
+
// payload changes the signing input) and `crit` (RFC 7515 §4.1.11 —
|
|
6420
|
+
// promises the producer implements every extension it names). A
|
|
6421
|
+
// builder that copies them through while base64url-encoding the
|
|
6422
|
+
// payload mints a self-inconsistent JWS: a compliant verifier
|
|
6423
|
+
// derives a different signing input or refuses the critical header.
|
|
6424
|
+
// The `requires` companion is satisfied by the refusal branch
|
|
6425
|
+
// naming 'b64' somewhere in the same file.
|
|
6426
|
+
id: "jose-header-passthrough-without-b64-crit-refusal",
|
|
6427
|
+
primitive: "refuse own 'b64'/'crit' members on any caller-supplied JOSE protected-header object before signing",
|
|
6428
|
+
regex: /assignOwnEnumerable\s*\(\s*\{\s*\}\s*,\s*opts\.header/,
|
|
6429
|
+
requires: /["']b64["']/,
|
|
6430
|
+
skipCommentLines: true,
|
|
6431
|
+
allowlist: [],
|
|
6432
|
+
reason: "Codex P2 on v0.14.22 PR #302 — jws.sign reserved alg/typ/kid but passed every other caller header member into the protected header; `{ b64: false, crit: [\"b64\"] }` produced a compact JWS whose payload was base64url-encoded and signed as such while the header claimed RFC 7797 unencoded-payload semantics. Any caller-header pass-through must name-refuse b64/crit until those semantics are actually implemented.",
|
|
6433
|
+
},
|
|
6352
6434
|
{
|
|
6353
6435
|
// v0.10.15 — `zlib.gunzipSync` / `zlib.createGunzip` /
|
|
6354
6436
|
// `zlib.brotliDecompress` without an output-size cap is the
|
|
@@ -10792,6 +10874,31 @@ function testCompliancePostureCoverage() {
|
|
|
10792
10874
|
// container-build smoke workflow: if WIKI_PORT is set to X in
|
|
10793
10875
|
// examples/wiki/Dockerfile, the workflow's `-p host:container` map +
|
|
10794
10876
|
// curl host MUST also reference X.
|
|
10877
|
+
// Internal working notes (planning documents, scratch output, session
|
|
10878
|
+
// residue) live outside the repository — a tracked file under memory/,
|
|
10879
|
+
// notes/, or a .scratch* path ships internal planning narrative to
|
|
10880
|
+
// everyone who clones the repo. v0.14.22 removed the one such file that
|
|
10881
|
+
// had been committed (a migration planning note, added v0.11.2); this
|
|
10882
|
+
// gate refuses any recurrence at commit time instead of at code review.
|
|
10883
|
+
function testNoTrackedInternalNotes() {
|
|
10884
|
+
var out;
|
|
10885
|
+
try {
|
|
10886
|
+
// Local require mirrors the bootstrap wrapper at the top of this
|
|
10887
|
+
// file — child_process is only touched on the two paths that talk
|
|
10888
|
+
// to the host (re-exec + this git query).
|
|
10889
|
+
out = require("node:child_process").execFileSync(
|
|
10890
|
+
"git", ["ls-files", "memory", "notes", ".scratch", ".scratch-*"],
|
|
10891
|
+
{ stdio: ["ignore", "pipe", "ignore"] }
|
|
10892
|
+
).toString().trim();
|
|
10893
|
+
} catch (_e) {
|
|
10894
|
+
// Not a git checkout (npm tarball / exported tree) — nothing to gate.
|
|
10895
|
+
return;
|
|
10896
|
+
}
|
|
10897
|
+
check("no tracked internal-notes files (memory/ notes/ .scratch*)" +
|
|
10898
|
+
(out ? " — found: " + out.split("\n").join(", ") : ""),
|
|
10899
|
+
out === "");
|
|
10900
|
+
}
|
|
10901
|
+
|
|
10795
10902
|
function testWikiPortAgreesAcrossArtifacts() {
|
|
10796
10903
|
var bad = [];
|
|
10797
10904
|
var dockerfile;
|
|
@@ -11787,6 +11894,7 @@ async function run() {
|
|
|
11787
11894
|
// WIKI_PORT default must match the release-container.yml smoke
|
|
11788
11895
|
// step's port mapping + curl host.
|
|
11789
11896
|
testWikiPortAgreesAcrossArtifacts();
|
|
11897
|
+
testNoTrackedInternalNotes();
|
|
11790
11898
|
testWikiStopGraceExceedsShutdownBudget();
|
|
11791
11899
|
testOrchestratorRegistryReadsTenantScoped();
|
|
11792
11900
|
testErrorCodesNamespacedKebab();
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.auth.jwt.verifyExternal — classical-alg JWT verifier
|
|
3
|
+
* b.auth.jwt.verifyExternal — classical-alg JWT verifier — and
|
|
4
|
+
* b.auth.jws.sign, its signer counterpart.
|
|
4
5
|
*
|
|
5
|
-
* Covers: surface; algorithms required (no defaults — alg-confusion
|
|
6
|
+
* Covers verify: surface; algorithms required (no defaults — alg-confusion
|
|
6
7
|
* defense); HMAC/none refused; alg-not-allowed rejected; missing
|
|
7
8
|
* key-source rejected; conflicting key-source rejected; valid RS256
|
|
8
9
|
* round-trip with kid match; aud/iss/exp claim validation.
|
|
10
|
+
*
|
|
11
|
+
* Covers sign: ES256 / EdDSA / RS256 round-trip back through verifyExternal;
|
|
12
|
+
* alg derived from the key; none/HMAC/alg-key-mismatch refused; a
|
|
13
|
+
* caller-supplied header.alg cannot override the signer-derived alg;
|
|
14
|
+
* header.b64 / header.crit refused (RFC 7797 / RFC 7515 §4.1.11 —
|
|
15
|
+
* semantics-changing members the signer does not implement).
|
|
9
16
|
*/
|
|
10
17
|
|
|
11
18
|
var helpers = require("../helpers");
|
|
@@ -140,6 +147,96 @@ async function testExpired() {
|
|
|
140
147
|
threw && /expired/.test(threw.code || ""));
|
|
141
148
|
}
|
|
142
149
|
|
|
150
|
+
// b.auth.jws.sign — the classical-JWS signer (inverse of verifyExternal).
|
|
151
|
+
// A token it mints must round-trip back through verifyExternal across the
|
|
152
|
+
// alg families, the alg must be derived from the key, and `none`/HMAC/
|
|
153
|
+
// alg-key-mismatch are refused.
|
|
154
|
+
async function testJwsSignSurface() {
|
|
155
|
+
check("auth.jws.sign exposed", typeof b.auth.jws.sign === "function");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function testJwsSignRoundTrip() {
|
|
159
|
+
var nowSec = Math.floor(Date.now() / 1000); // allow:raw-byte-literal — seconds-per-ms
|
|
160
|
+
// (key generator, public JWK with kid, expected alg)
|
|
161
|
+
var ec = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
162
|
+
var ed = nodeCrypto.generateKeyPairSync("ed25519");
|
|
163
|
+
var rsa = _rsaPair();
|
|
164
|
+
var cases = [
|
|
165
|
+
{ name: "ES256", priv: ec.privateKey, jwk: ec.publicKey.export({ format: "jwk" }), alg: "ES256" },
|
|
166
|
+
{ name: "EdDSA", priv: ed.privateKey, jwk: ed.publicKey.export({ format: "jwk" }), alg: "EdDSA" },
|
|
167
|
+
{ name: "RS256", priv: rsa.privateKey, jwk: rsa.publicKey, alg: "RS256" },
|
|
168
|
+
];
|
|
169
|
+
for (var i = 0; i < cases.length; i++) {
|
|
170
|
+
var c = cases[i];
|
|
171
|
+
var jwk = Object.assign({}, c.jwk, { kid: "k1", use: "sig", alg: c.alg });
|
|
172
|
+
var token = b.auth.jws.sign(
|
|
173
|
+
{ sub: "u1", iss: "client", aud: "https://as.example.com", exp: nowSec + 300, iat: nowSec }, // allow:raw-byte-literal — 5min
|
|
174
|
+
{ privateKey: c.priv, kid: "k1", typ: "JWT" });
|
|
175
|
+
var hdr = JSON.parse(Buffer.from(token.split(".")[0], "base64url").toString("utf8"));
|
|
176
|
+
check("jws.sign[" + c.name + "]: alg inferred from key", hdr.alg === c.alg);
|
|
177
|
+
check("jws.sign[" + c.name + "]: typ + kid set", hdr.typ === "JWT" && hdr.kid === "k1");
|
|
178
|
+
var rv = await b.auth.jwt.verifyExternal(token, {
|
|
179
|
+
algorithms: [c.alg], jwks: [jwk], audience: "https://as.example.com", issuer: "client" });
|
|
180
|
+
check("jws.sign[" + c.name + "]: round-trips through verifyExternal", rv.claims.sub === "u1");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function testJwsSignRefusals() {
|
|
185
|
+
var ec = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
186
|
+
var e1 = null;
|
|
187
|
+
try { b.auth.jws.sign({ sub: "u" }, { privateKey: ec.privateKey, alg: "none" }); } catch (e) { e1 = e; }
|
|
188
|
+
check("jws.sign: alg 'none' refused", e1 && /sign-alg-refused/.test(e1.code || ""));
|
|
189
|
+
var e2 = null;
|
|
190
|
+
try { b.auth.jws.sign({ sub: "u" }, { privateKey: ec.privateKey, alg: "HS256" }); } catch (e) { e2 = e; }
|
|
191
|
+
check("jws.sign: HMAC alg refused", e2 && /sign-alg-refused/.test(e2.code || ""));
|
|
192
|
+
var e3 = null;
|
|
193
|
+
// a P-256 key cannot produce RS256.
|
|
194
|
+
try { b.auth.jws.sign({ sub: "u" }, { privateKey: ec.privateKey, alg: "RS256" }); } catch (e) { e3 = e; }
|
|
195
|
+
check("jws.sign: alg incompatible with key refused", e3 && /sign-alg-key-mismatch/.test(e3.code || ""));
|
|
196
|
+
var e4 = null;
|
|
197
|
+
try { b.auth.jws.sign("not-an-object", { privateKey: ec.privateKey }); } catch (e) { e4 = e; }
|
|
198
|
+
check("jws.sign: non-object claims refused", e4 && /sign-bad-claims/.test(e4.code || ""));
|
|
199
|
+
var e5 = null;
|
|
200
|
+
try { b.auth.jws.sign({ sub: "u" }, { privateKey: ec.privateKey, bogus: 1 }); } catch (e) { e5 = e; }
|
|
201
|
+
check("jws.sign: unknown opt refused (config-time)", e5 !== null);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// A caller-supplied header.alg can never override the signer-derived alg —
|
|
205
|
+
// the canonical alg-substitution shape is closed.
|
|
206
|
+
async function testJwsSignHeaderCannotOverrideAlg() {
|
|
207
|
+
var ec = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
208
|
+
var token = b.auth.jws.sign({ sub: "u" }, {
|
|
209
|
+
privateKey: ec.privateKey, header: { alg: "HS256", foo: "bar" } });
|
|
210
|
+
var hdr = JSON.parse(Buffer.from(token.split(".")[0], "base64url").toString("utf8"));
|
|
211
|
+
check("jws.sign: header.alg override ignored (signer sets ES256)", hdr.alg === "ES256");
|
|
212
|
+
check("jws.sign: extra header members pass through", hdr.foo === "bar");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// `b64` (RFC 7797 unencoded payload) changes the signing input and `crit`
|
|
216
|
+
// (RFC 7515 §4.1.11) promises extension semantics the signer does not
|
|
217
|
+
// implement — passing either through would mint a JWS whose header claims
|
|
218
|
+
// semantics its signature was not computed under. Both refused.
|
|
219
|
+
async function testJwsSignB64CritRefused() {
|
|
220
|
+
var ec = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
221
|
+
var e1 = null;
|
|
222
|
+
try { b.auth.jws.sign({ sub: "u" }, { privateKey: ec.privateKey, header: { b64: false } }); }
|
|
223
|
+
catch (e) { e1 = e; }
|
|
224
|
+
check("jws.sign: header.b64 refused (RFC 7797 not implemented)",
|
|
225
|
+
e1 && /sign-unsupported-header/.test(e1.code || ""));
|
|
226
|
+
var e2 = null;
|
|
227
|
+
try { b.auth.jws.sign({ sub: "u" }, { privateKey: ec.privateKey, header: { crit: ["exp"] } }); }
|
|
228
|
+
catch (e) { e2 = e; }
|
|
229
|
+
check("jws.sign: header.crit refused (no critical extensions implemented)",
|
|
230
|
+
e2 && /sign-unsupported-header/.test(e2.code || ""));
|
|
231
|
+
var e3 = null;
|
|
232
|
+
try {
|
|
233
|
+
b.auth.jws.sign({ sub: "u" }, {
|
|
234
|
+
privateKey: ec.privateKey, header: { b64: true, crit: ["b64"] } });
|
|
235
|
+
} catch (e) { e3 = e; }
|
|
236
|
+
check("jws.sign: b64:true + crit:['b64'] refused too (no silent pass on the 'harmless' spelling)",
|
|
237
|
+
e3 && /sign-unsupported-header/.test(e3.code || ""));
|
|
238
|
+
}
|
|
239
|
+
|
|
143
240
|
async function run() {
|
|
144
241
|
testSurface();
|
|
145
242
|
await testAlgorithmsRequired();
|
|
@@ -149,6 +246,11 @@ async function run() {
|
|
|
149
246
|
await testRoundTripRs256();
|
|
150
247
|
await testAudMismatch();
|
|
151
248
|
await testExpired();
|
|
249
|
+
await testJwsSignSurface();
|
|
250
|
+
await testJwsSignRoundTrip();
|
|
251
|
+
await testJwsSignRefusals();
|
|
252
|
+
await testJwsSignHeaderCannotOverrideAlg();
|
|
253
|
+
await testJwsSignB64CritRefused();
|
|
152
254
|
}
|
|
153
255
|
|
|
154
256
|
module.exports = { run: run };
|