@blamejs/blamejs-shop 0.4.32 → 0.4.37

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/lib/asset-manifest.json +1 -1
  4. package/lib/vendor/MANIFEST.json +72 -52
  5. package/lib/vendor/blamejs/.github/workflows/ci.yml +12 -12
  6. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +37 -5
  7. package/lib/vendor/blamejs/.github/workflows/release-container.yml +2 -2
  8. package/lib/vendor/blamejs/CHANGELOG.md +6 -0
  9. package/lib/vendor/blamejs/MIGRATING.md +12 -0
  10. package/lib/vendor/blamejs/README.md +5 -2
  11. package/lib/vendor/blamejs/SECURITY.md +4 -2
  12. package/lib/vendor/blamejs/api-snapshot.json +137 -2
  13. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +1 -0
  14. package/lib/vendor/blamejs/index.js +4 -0
  15. package/lib/vendor/blamejs/lib/archive-read.js +2 -1
  16. package/lib/vendor/blamejs/lib/archive-tar-read.js +2 -1
  17. package/lib/vendor/blamejs/lib/atomic-file.js +5 -0
  18. package/lib/vendor/blamejs/lib/audit.js +2 -0
  19. package/lib/vendor/blamejs/lib/auth/elevation-grant.js +6 -2
  20. package/lib/vendor/blamejs/lib/auth/oauth.js +13 -0
  21. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +5 -2
  22. package/lib/vendor/blamejs/lib/cli.js +8 -1
  23. package/lib/vendor/blamejs/lib/compliance.js +4 -0
  24. package/lib/vendor/blamejs/lib/config-drift.js +2 -1
  25. package/lib/vendor/blamejs/lib/credential-hash.js +9 -0
  26. package/lib/vendor/blamejs/lib/db.js +15 -2
  27. package/lib/vendor/blamejs/lib/dsa.js +482 -0
  28. package/lib/vendor/blamejs/lib/framework-error.js +14 -0
  29. package/lib/vendor/blamejs/lib/http-client.js +5 -2
  30. package/lib/vendor/blamejs/lib/local-db-thin.js +3 -2
  31. package/lib/vendor/blamejs/lib/log-stream-local.js +1 -1
  32. package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +9 -2
  33. package/lib/vendor/blamejs/lib/log-stream-otlp.js +16 -7
  34. package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +36 -11
  35. package/lib/vendor/blamejs/lib/mtls-ca.js +2 -2
  36. package/lib/vendor/blamejs/lib/observability.js +3 -2
  37. package/lib/vendor/blamejs/lib/pipl-cn.js +377 -0
  38. package/lib/vendor/blamejs/lib/restore-rollback.js +5 -5
  39. package/lib/vendor/blamejs/lib/retention.js +16 -2
  40. package/lib/vendor/blamejs/lib/scheduler.js +12 -0
  41. package/lib/vendor/blamejs/lib/self-update.js +1 -1
  42. package/lib/vendor/blamejs/lib/session.js +64 -0
  43. package/lib/vendor/blamejs/lib/ssrf-guard.js +25 -7
  44. package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -3
  45. package/lib/vendor/blamejs/lib/watcher.js +8 -0
  46. package/lib/vendor/blamejs/package.json +2 -2
  47. package/lib/vendor/blamejs/release-notes/v0.15.7.json +43 -0
  48. package/lib/vendor/blamejs/release-notes/v0.15.8.json +48 -0
  49. package/lib/vendor/blamejs/release-notes/v0.15.9.json +58 -0
  50. package/lib/vendor/blamejs/scripts/gen-migrating.js +16 -0
  51. package/lib/vendor/blamejs/scripts/generate-ssdf-attestation.js +338 -0
  52. package/lib/vendor/blamejs/test/00-primitives.js +51 -0
  53. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-rename-retry.test.js +70 -0
  54. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +250 -3
  55. package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +18 -0
  56. package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +32 -0
  57. package/lib/vendor/blamejs/test/layer-0-primitives/dsa.test.js +169 -0
  58. package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +40 -1
  59. package/lib/vendor/blamejs/test/layer-0-primitives/pipl-cn.test.js +172 -0
  60. package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +59 -0
  61. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +64 -11
  62. package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-watchdog-stale-settle.test.js +71 -0
  63. package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +57 -0
  64. package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +7 -3
  65. package/package.json +2 -2
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ // Emit a NIST SP 800-218 (SSDF) / OMB M-22-18 producer self-attestation
3
+ // as a machine-readable JSON artifact, attached to each GitHub release.
4
+ //
5
+ // Run via:
6
+ // node scripts/generate-ssdf-attestation.js \
7
+ // --version 0.15.7 --commit <sha> --date 2026-06-13T00:00:00Z \
8
+ // > ssdf-attestation.json
9
+ //
10
+ // # or with --out:
11
+ // node scripts/generate-ssdf-attestation.js --out ssdf-attestation.json
12
+ //
13
+ // Wired into .github/workflows/npm-publish.yml alongside the SBOM +
14
+ // cosign + SLSA steps. Downstream consumers who require SSDF supplier-
15
+ // compliance evidence (OMB M-22-18 / M-23-16 self-attestation) download
16
+ // this from the release page.
17
+ //
18
+ // WHAT THIS IS — AND IS NOT.
19
+ // This is a PRODUCER SELF-ATTESTATION, the machine-readable companion
20
+ // to the CISA / OMB "Secure Software Development Attestation Form". It
21
+ // is the producer's own assertion that the SSDF practices below are in
22
+ // force, mapped to the framework's REAL implementing controls. It is
23
+ // NOT a third-party audit, NOT a FedRAMP authorization, and NOT a CMVP
24
+ // validation. Its trust derives entirely from the release boundary that
25
+ // carries it: the SSH-signed tag, the SLSA L3 npm provenance, the
26
+ // Sigstore-keyless SBOM signatures, and the ML-DSA-65 release-signing
27
+ // sidecar — the same four trust roots documented in SECURITY.md sign
28
+ // this file by signing the release that contains it. A consumer who
29
+ // verifies those roots is verifying that THIS attestation came from the
30
+ // producer of record; the claims inside are the producer's assertions,
31
+ // verifiable against the cited controls in the source tree at the
32
+ // release commit.
33
+ //
34
+ // Each statement carries its NIST SSDF practice IDs (PO/PS/PW/RV.*) and
35
+ // names the specific implementing control so the assertion is auditable,
36
+ // not aspirational. Output is deterministic: the timestamp comes from
37
+ // --date / SOURCE_DATE_EPOCH (never an unseeded clock), and the same
38
+ // inputs produce byte-identical output.
39
+
40
+ var fs = require("node:fs");
41
+ var path = require("node:path");
42
+
43
+ var ROOT = path.resolve(__dirname, "..");
44
+ var PKG_PATH = path.join(ROOT, "package.json");
45
+
46
+ // ---------------------------------------------------------------------
47
+ // Argument + environment resolution. Config-time inputs THROW on bad
48
+ // shape (operator catches a typo at invocation), per the three-tier
49
+ // validation discipline — this is an entry-point script, not a hot path.
50
+ // ---------------------------------------------------------------------
51
+
52
+ function _parseArgs(argv) {
53
+ var out = {};
54
+ for (var i = 0; i < argv.length; i++) {
55
+ var a = argv[i];
56
+ if (a === "--out") { out.out = argv[++i]; continue; }
57
+ if (a === "--version") { out.version = argv[++i]; continue; }
58
+ if (a === "--commit") { out.commit = argv[++i]; continue; }
59
+ if (a === "--date") { out.date = argv[++i]; continue; }
60
+ if (a === "--repository") { out.repository = argv[++i]; continue; }
61
+ if (a === "-h" || a === "--help") { out.help = true; continue; }
62
+ throw new Error("generate-ssdf-attestation: unrecognized argument: " + a);
63
+ }
64
+ return out;
65
+ }
66
+
67
+ // Deterministic timestamp resolution: --date (RFC 3339) wins; else
68
+ // SOURCE_DATE_EPOCH (seconds since the Unix epoch, the reproducible-build
69
+ // convention) is parsed; else fail closed. We NEVER call an unseeded
70
+ // clock — a reproducible artifact must produce identical bytes from
71
+ // identical inputs.
72
+ function _resolveTimestamp(args, env) {
73
+ if (typeof args.date === "string" && args.date.length > 0) {
74
+ var t = new Date(args.date);
75
+ if (isNaN(t.getTime())) {
76
+ throw new TypeError("generate-ssdf-attestation: --date is not a valid date: " + args.date);
77
+ }
78
+ return t.toISOString();
79
+ }
80
+ var epoch = env.SOURCE_DATE_EPOCH;
81
+ if (typeof epoch === "string" && epoch.length > 0) {
82
+ if (!/^[0-9]+$/.test(epoch)) {
83
+ throw new TypeError("generate-ssdf-attestation: SOURCE_DATE_EPOCH must be integer seconds, got: " + epoch);
84
+ }
85
+ var secs = parseInt(epoch, 10);
86
+ if (!isFinite(secs) || secs < 0) {
87
+ throw new TypeError("generate-ssdf-attestation: SOURCE_DATE_EPOCH out of range: " + epoch);
88
+ }
89
+ return new Date(secs * 1000).toISOString();
90
+ }
91
+ throw new Error(
92
+ "generate-ssdf-attestation: no deterministic timestamp source. " +
93
+ "Pass --date <RFC3339> or set SOURCE_DATE_EPOCH (no unseeded clock is used)."
94
+ );
95
+ }
96
+
97
+ // Software version: --version wins; else package.json. Cross-check when
98
+ // both are present so a tag/package drift fails the cut here too.
99
+ function _resolveVersion(args, pkg) {
100
+ if (typeof args.version === "string" && args.version.length > 0) {
101
+ if (pkg.version && args.version !== pkg.version) {
102
+ throw new Error(
103
+ "generate-ssdf-attestation: --version (" + args.version +
104
+ ") does not match package.json version (" + pkg.version + ")"
105
+ );
106
+ }
107
+ return args.version;
108
+ }
109
+ if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
110
+ throw new Error("generate-ssdf-attestation: no version (pass --version or set package.json version)");
111
+ }
112
+
113
+ // Source-control commit: --commit wins; else GITHUB_SHA; else null
114
+ // (a locally-generated attestation that omits the commit is honest about
115
+ // not knowing it rather than fabricating one).
116
+ function _resolveCommit(args, env) {
117
+ if (typeof args.commit === "string" && args.commit.length > 0) return args.commit;
118
+ var sha = env.GITHUB_SHA;
119
+ if (typeof sha === "string" && sha.length > 0) return sha;
120
+ return null;
121
+ }
122
+
123
+ // Normalize the package.json repository field to a bare https URL.
124
+ function _resolveRepository(args, pkg) {
125
+ if (typeof args.repository === "string" && args.repository.length > 0) return args.repository;
126
+ var r = pkg.repository;
127
+ var url = (r && typeof r === "object" && typeof r.url === "string") ? r.url
128
+ : (typeof r === "string" ? r : "");
129
+ url = url.replace(/^git\+/, "").replace(/\.git$/, "");
130
+ if (url.length === 0) return null;
131
+ return url;
132
+ }
133
+
134
+ // ---------------------------------------------------------------------
135
+ // The attestation document.
136
+ //
137
+ // Structure follows the OMB M-22-18 / CISA "Secure Software Development
138
+ // Attestation Form": producer identity, software identity, then the four
139
+ // attestation-statement groups the Form covers. Each statement is mapped
140
+ // to its NIST SP 800-218 v1.1 practice ID(s) and the framework control
141
+ // that implements it, so the assertion is checkable against the source
142
+ // tree at `commit`.
143
+ //
144
+ // SSDF practice families:
145
+ // PO Prepare the Organization
146
+ // PS Protect the Software
147
+ // PW Produce Well-Secured Software
148
+ // RV Respond to Vulnerabilities
149
+ // ---------------------------------------------------------------------
150
+
151
+ // The four M-22-18 Form attestation groups (the questions a producer
152
+ // answers "yes" to on the Form), each backed by SSDF-practice-mapped
153
+ // statements naming the framework's real implementing control.
154
+ function _attestationStatements() {
155
+ return [
156
+ {
157
+ "id": "secure-build-environment",
158
+ "form_section": "1. Secure software development environment",
159
+ "summary": "Software is developed and built in secure environments with separated, least-privilege, ephemeral CI.",
160
+ "statements": [
161
+ {
162
+ "ssdf": ["PO.5.1", "PO.5.2"],
163
+ "claim": "Builds run only in GitHub-hosted ephemeral runners; every release job declares the minimum permissions it needs and elevates per-job (workflow-level contents:read; id-token:write only where OIDC signing requires it).",
164
+ "control": ".github/workflows/npm-publish.yml job-level permissions blocks; no self-hosted runners.",
165
+ },
166
+ {
167
+ "ssdf": ["PO.3.1", "PO.3.2", "PS.1.1"],
168
+ "claim": "The build environment's integrity is established by SLSA Build L3 provenance: a non-falsifiable attestation binds the published artifact to the exact workflow run, commit, and tag that produced it.",
169
+ "control": "slsa-framework/slsa-github-generator generator_generic_slsa3.yml@v2.1.0 emits blamejs-<version>.intoto.jsonl; npm publish --provenance attaches the SLSA v1 provenance to the registry tarball.",
170
+ },
171
+ {
172
+ "ssdf": ["PO.5.1"],
173
+ "claim": "Third-party GitHub Actions are SHA-pinned (the one tag-pinned exception, the SLSA reusable workflow, is required by its builder-fetch and is documented + detector-allowlisted); a currency gate fails the release if any pin falls behind upstream.",
174
+ "control": ".github/workflows/*.yml SHA pins; scripts/check-actions-currency.js runs on every PR and in the release flow.",
175
+ },
176
+ ],
177
+ },
178
+ {
179
+ "id": "provenance-and-component-trust",
180
+ "form_section": "2. Provenance and trust of software components",
181
+ "summary": "The provenance of code and components is established and maintained; a complete SBOM accompanies every release.",
182
+ "statements": [
183
+ {
184
+ "ssdf": ["PW.4.1", "PW.4.4"],
185
+ "claim": "Zero npm runtime dependencies. Every third-party library is vendored under lib/vendor/ and pinned by SHA-256 in MANIFEST.json; the release refuses to publish if any runtime dependency component appears in the SBOM.",
186
+ "control": "lib/vendor/MANIFEST.json (per-artifact SHA-256 + version + license + source); npm-publish.yml runtime-deps gate; b.configDrift.verifyVendorIntegrity re-checks each artifact's SHA-256 at boot.",
187
+ },
188
+ {
189
+ "ssdf": ["PS.3.1", "PS.3.2"],
190
+ "claim": "Each release ships a complete CycloneDX 1.6 SBOM (npm-tree view + vendored-bundle view with per-file SHA-256 and purl) so consumers can inventory exactly what ships inside the tarball.",
191
+ "control": "sbom.cdx.json (npm tree) + sbom.vendored.cdx.json (scripts/build-vendored-sbom.js); both attached to the GitHub release.",
192
+ },
193
+ {
194
+ "ssdf": ["PS.2.1"],
195
+ "claim": "Release integrity is verifiable through four independent trust roots, each detecting tampering with the others: SLSA L3 npm provenance, Sigstore-keyless SBOM signatures, SSH-signed annotated tags, and an ML-DSA-65 (FIPS 204) release-signing sidecar over the tarball.",
196
+ "control": "cosign sign-blob (sbom.*.sigstore); SSH-signed tags enforced server-side by the release-tags ruleset; <tarball>.mldsa.sig via the framework's vendored ML-DSA-65 primitive. Verification recipes in SECURITY.md.",
197
+ },
198
+ ],
199
+ },
200
+ {
201
+ "id": "trusted-source-and-vuln-checking",
202
+ "form_section": "3. Trusted source-code supply chains and automated vulnerability checking",
203
+ "summary": "Good-faith effort to maintain trusted source-code supply chains and to perform automated vulnerability scanning on every release.",
204
+ "statements": [
205
+ {
206
+ "ssdf": ["RV.1.1", "RV.1.2", "PW.7.2"],
207
+ "claim": "Every release is scanned for known vulnerabilities before publish: OSV-Scanner runs against both SBOMs and the vendored tree, and the release fails on any finding. A vendored-dependency currency gate refuses a stale, potentially-vulnerable pin.",
208
+ "control": "OSV-Scanner step in npm-publish.yml (--sbom both + -r lib/vendor/); scripts/check-vendor-currency.js.",
209
+ },
210
+ {
211
+ "ssdf": ["PW.8.2", "PW.7.1"],
212
+ "claim": "Adversarial-input parsers are continuously fuzzed (coverage-guided libFuzzer via jazzer.js) and the pattern-catalog gate refuses any new parser primitive that lands without a matching fuzz harness.",
213
+ "control": "fuzz/*.fuzz.js harnesses; ClusterFuzzLite per-PR + daily batch; coverage gate in test/layer-0-primitives/codebase-patterns.test.js.",
214
+ },
215
+ {
216
+ "ssdf": ["PS.1.1", "PO.5.2"],
217
+ "claim": "Source-side supply-chain integrity is enforced at the repository boundary: protected default branch (no force-push, no non-linear merge, required status checks, required signed commits) and protected release tags (no deletion, no re-pointing) so a published tag cannot be silently rewritten.",
218
+ "control": "main-protection + release-tags GitHub rulesets; the sha-to-tag-verify workflow refuses a tag whose commit is not on main's first-parent PR-merged history.",
219
+ },
220
+ ],
221
+ },
222
+ {
223
+ "id": "vulnerability-disclosure-and-response",
224
+ "form_section": "4. Vulnerability disclosure and response",
225
+ "summary": "A vulnerability disclosure program and a process to respond to and remediate reported vulnerabilities are maintained.",
226
+ "statements": [
227
+ {
228
+ "ssdf": ["RV.1.3", "RV.2.1"],
229
+ "claim": "A coordinated vulnerability-disclosure process is published: a private reporting channel, an encryption option for sensitive reports, and committed first-response / triage / fix-release windows by severity.",
230
+ "control": "SECURITY.md (security@blamejs.com, maintainer PGP key, response-time table); GitHub private security advisories.",
231
+ },
232
+ {
233
+ "ssdf": ["RV.2.2", "RV.3.3"],
234
+ "claim": "Fixes are delivered through a stable, signed release path with a public LTS / deprecation policy; remediations are described in operator-facing release notes and the CHANGELOG drawn from a single structured source.",
235
+ "control": "scripts/release.js orchestrated flow; CHANGELOG.md + release-notes/<version>.json single source; LTS-CALENDAR.md.",
236
+ },
237
+ {
238
+ "ssdf": ["RV.3.1", "RV.3.4"],
239
+ "claim": "Root-cause analysis is institutional: a confirmed defect class is swept framework-wide and encoded as a recurrence detector so the same class cannot silently reappear in a later release.",
240
+ "control": "codebase-patterns class-level detectors (test/layer-0-primitives/codebase-patterns.test.js); behavioral regression tests ship with each fix.",
241
+ },
242
+ ],
243
+ },
244
+ ];
245
+ }
246
+
247
+ function buildAttestation(opts) {
248
+ var pkg = opts.pkg;
249
+ return {
250
+ "$schema_note": "NIST SP 800-218 (SSDF v1.1) / OMB M-22-18 producer self-attestation, machine-readable form.",
251
+ "attestation_type": "producer-self-attestation",
252
+ "attestation_format": "blamejs/ssdf-attestation",
253
+ "attestation_format_version": "1.0",
254
+ "framework": {
255
+ "name": "NIST SP 800-218",
256
+ "title": "Secure Software Development Framework (SSDF) Version 1.1",
257
+ "reference_form": "OMB M-22-18 / CISA Secure Software Development Attestation Form",
258
+ },
259
+ "generated": opts.timestamp,
260
+ "producer": {
261
+ // The producer of record. This is a self-attestation: the producer
262
+ // asserts the statements below, signed implicitly by the release
263
+ // boundary (SSH-signed tag + SLSA provenance + PQC sidecar).
264
+ "name": "blamejs",
265
+ "url": "https://blamejs.com/",
266
+ "security_contact": "security@blamejs.com",
267
+ "vulnerability_disclosure": "https://github.com/blamejs/blamejs/security",
268
+ },
269
+ "software": {
270
+ "name": pkg.name || "@blamejs/core",
271
+ "version": opts.version,
272
+ "repository": opts.repository,
273
+ "commit": opts.commit,
274
+ "license": pkg.license || null,
275
+ },
276
+ "attestation_statement":
277
+ "blamejs attests, as the producer of " + (pkg.name || "@blamejs/core") +
278
+ " version " + opts.version + ", that the secure software development " +
279
+ "practices enumerated below are followed for this release, mapped to " +
280
+ "NIST SP 800-218 (SSDF v1.1) practices. This is a self-attestation; " +
281
+ "its authenticity is bound to the signed release artifacts described " +
282
+ "in SECURITY.md (SSH-signed tag, SLSA L3 provenance, Sigstore SBOM " +
283
+ "signatures, ML-DSA-65 release-signing sidecar).",
284
+ "sections": _attestationStatements(),
285
+ "verification": {
286
+ "note": "This attestation is not independently signed; it is covered by the four release trust roots that sign the release containing it.",
287
+ "trust_roots": [
288
+ "SLSA L3 npm provenance (npm publish --provenance + blamejs-<version>.intoto.jsonl)",
289
+ "Sigstore-keyless SBOM signatures (sbom.cdx.json.sigstore, sbom.vendored.cdx.json.sigstore)",
290
+ "SSH-signed annotated git tag (release-tags ruleset, enforced server-side)",
291
+ "ML-DSA-65 release-signing sidecar (<tarball>.mldsa.sig, FIPS 204)",
292
+ ],
293
+ "recipes": "SECURITY.md -> 'Verifying release authenticity'",
294
+ },
295
+ };
296
+ }
297
+
298
+ function main() {
299
+ var args = _parseArgs(process.argv.slice(2));
300
+
301
+ if (args.help) {
302
+ process.stderr.write(
303
+ "Usage: node scripts/generate-ssdf-attestation.js " +
304
+ "[--version <v>] [--commit <sha>] [--date <RFC3339>] " +
305
+ "[--repository <url>] [--out <path>]\n" +
306
+ "Timestamp source (required, deterministic): --date or SOURCE_DATE_EPOCH.\n"
307
+ );
308
+ return;
309
+ }
310
+
311
+ var pkg;
312
+ try {
313
+ pkg = JSON.parse(fs.readFileSync(PKG_PATH, "utf8"));
314
+ } catch (e) {
315
+ process.stderr.write("[generate-ssdf-attestation] failed to read package.json: " + e.message + "\n");
316
+ process.exit(1);
317
+ return;
318
+ }
319
+
320
+ var doc = buildAttestation({
321
+ pkg: pkg,
322
+ version: _resolveVersion(args, pkg),
323
+ commit: _resolveCommit(args, process.env), // allow:raw-process-env — env-driven release script
324
+ repository: _resolveRepository(args, pkg),
325
+ timestamp: _resolveTimestamp(args, process.env), // allow:raw-process-env — env-driven release script
326
+ });
327
+
328
+ var json = JSON.stringify(doc, null, 2) + "\n";
329
+
330
+ if (typeof args.out === "string" && args.out.length > 0) {
331
+ fs.writeFileSync(args.out, json);
332
+ process.stderr.write("[generate-ssdf-attestation] wrote " + args.out + "\n");
333
+ } else {
334
+ process.stdout.write(json);
335
+ }
336
+ }
337
+
338
+ main();
@@ -7557,6 +7557,57 @@ async function testOAuthVerifyIdTokenRoundTrip() {
7557
7557
  var agedOk = await oa.verifyIdToken(agedLogout, { skipExpCheck: true, skipNonceCheck: true, maxAgeSec: 1200 });
7558
7558
  check("#137 verifyIdToken: the configured maxAgeSec widens the iat freshness window",
7559
7559
  agedOk && agedOk.claims && agedOk.claims.sub === "user-1");
7560
+
7561
+ // #134 — OIDC Core §3.1.3.7: an ID token minted for a DIFFERENT authorized
7562
+ // party that merely lists this RP in a multi-audience array must be
7563
+ // rejected. verifyIdToken checked only that aud contains clientId; without
7564
+ // the azp check that is a confused-deputy hole.
7565
+ var multiAudWrongAzp = _signRs256({
7566
+ iss: issuerUrl, sub: "user-1", aud: [clientId, "other-client"],
7567
+ azp: "other-client", exp: nowSec + 3600, iat: nowSec,
7568
+ }, { kid: "test-kid-1" }, kp.privateKey);
7569
+ threw = null;
7570
+ try { await oa.verifyIdToken(multiAudWrongAzp, { skipNonceCheck: true }); } catch (e) { threw = e; }
7571
+ check("#134 verifyIdToken: multi-aud token with azp for a different client is rejected",
7572
+ threw && threw.code === "auth-oauth/azp-mismatch");
7573
+
7574
+ // Multi-aud token with NO azp at all — §3.1.3.7 requires azp present.
7575
+ var multiAudNoAzp = _signRs256({
7576
+ iss: issuerUrl, sub: "user-1", aud: [clientId, "other-client"],
7577
+ exp: nowSec + 3600, iat: nowSec,
7578
+ }, { kid: "test-kid-1" }, kp.privateKey);
7579
+ threw = null;
7580
+ try { await oa.verifyIdToken(multiAudNoAzp, { skipNonceCheck: true }); } catch (e) { threw = e; }
7581
+ check("#134 verifyIdToken: multi-aud token with no azp is rejected",
7582
+ threw && threw.code === "auth-oauth/azp-required");
7583
+
7584
+ // A correct multi-aud token (azp === our clientId) still verifies.
7585
+ var multiAudOk = _signRs256({
7586
+ iss: issuerUrl, sub: "user-1", aud: [clientId, "other-client"],
7587
+ azp: clientId, exp: nowSec + 3600, iat: nowSec,
7588
+ }, { kid: "test-kid-1" }, kp.privateKey);
7589
+ var okMulti = await oa.verifyIdToken(multiAudOk, { skipNonceCheck: true });
7590
+ check("#134 verifyIdToken: multi-aud token with azp = clientId verifies",
7591
+ okMulti && okMulti.claims && okMulti.claims.sub === "user-1");
7592
+
7593
+ // Single-aud with a mismatched azp present is also rejected (azp, when
7594
+ // present, must equal clientId regardless of aud cardinality).
7595
+ var singleAudWrongAzp = _signRs256({
7596
+ iss: issuerUrl, sub: "user-1", aud: clientId, azp: "other-client",
7597
+ exp: nowSec + 3600, iat: nowSec,
7598
+ }, { kid: "test-kid-1" }, kp.privateKey);
7599
+ threw = null;
7600
+ try { await oa.verifyIdToken(singleAudWrongAzp, { skipNonceCheck: true }); } catch (e) { threw = e; }
7601
+ check("#134 verifyIdToken: single-aud token with a foreign azp is rejected",
7602
+ threw && threw.code === "auth-oauth/azp-mismatch");
7603
+
7604
+ // The common single-aud, no-azp token is unaffected.
7605
+ var plainSingle = _signRs256({
7606
+ iss: issuerUrl, sub: "user-1", aud: clientId, exp: nowSec + 3600, iat: nowSec,
7607
+ }, { kid: "test-kid-1" }, kp.privateKey);
7608
+ var okPlain = await oa.verifyIdToken(plainSingle, { skipNonceCheck: true });
7609
+ check("#134 verifyIdToken: single-aud token with no azp still verifies",
7610
+ okPlain && okPlain.claims && okPlain.claims.sub === "user-1");
7560
7611
  } finally { server.close(); }
7561
7612
  }
7562
7613
 
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ // b.atomicFile.renameWithRetry — the bounded retry on a Windows-transient
3
+ // destination lock (EPERM/EACCES/EBUSY from AV / search indexer / Dropbox /
4
+ // OneDrive briefly holding the target). #146: httpClient.downloadStream and
5
+ // every other final temp->dest rename route through this instead of a bare
6
+ // nodeFs.renameSync, so a transient lock is retried, not surfaced as a hard
7
+ // failure. A non-transient error (ENOENT, etc.) still throws immediately.
8
+
9
+ var nodeFs = require("node:fs");
10
+ var os = require("node:os");
11
+ var path = require("node:path");
12
+ var helpers = require("../helpers");
13
+ var check = helpers.check;
14
+ var atomicFile = require("../../lib/atomic-file");
15
+
16
+ function run() {
17
+ check("renameWithRetry is exported", typeof atomicFile.renameWithRetry === "function");
18
+
19
+ var dir = nodeFs.mkdtempSync(path.join(os.tmpdir(), "renameretry-"));
20
+ var realRename = nodeFs.renameSync;
21
+
22
+ // ---- transient EPERM is retried, then the rename succeeds ----
23
+ var src1 = path.join(dir, "a.tmp");
24
+ var dst1 = path.join(dir, "a.dst");
25
+ nodeFs.writeFileSync(src1, "payload");
26
+ var calls = 0;
27
+ nodeFs.renameSync = function (from, to) {
28
+ calls += 1;
29
+ if (calls < 3) { var e = new Error("transient lock"); e.code = "EPERM"; throw e; }
30
+ return realRename(from, to);
31
+ };
32
+ try { atomicFile.renameWithRetry(src1, dst1); }
33
+ finally { nodeFs.renameSync = realRename; }
34
+ check("renameWithRetry retries past a transient EPERM (3 attempts)", calls === 3);
35
+ check("renameWithRetry: the rename ultimately succeeds",
36
+ nodeFs.existsSync(dst1) && !nodeFs.existsSync(src1));
37
+
38
+ // ---- a non-transient error throws immediately, with NO retry ----
39
+ var attempts = 0;
40
+ nodeFs.renameSync = function () {
41
+ attempts += 1;
42
+ var e = new Error("no such file"); e.code = "ENOENT"; throw e;
43
+ };
44
+ var threw = null;
45
+ try { atomicFile.renameWithRetry(path.join(dir, "missing"), path.join(dir, "x")); }
46
+ catch (e) { threw = e; }
47
+ finally { nodeFs.renameSync = realRename; }
48
+ check("renameWithRetry rethrows a non-transient error", threw !== null && threw.code === "ENOENT");
49
+ check("renameWithRetry does NOT retry a non-transient error", attempts === 1);
50
+
51
+ // ---- a persistently-transient lock eventually gives up (does not hang) ----
52
+ var stuck = 0;
53
+ nodeFs.renameSync = function () {
54
+ stuck += 1;
55
+ var e = new Error("still locked"); e.code = "EBUSY"; throw e;
56
+ };
57
+ var stuckThrew = null;
58
+ try { atomicFile.renameWithRetry(path.join(dir, "s"), path.join(dir, "d")); }
59
+ catch (e) { stuckThrew = e; }
60
+ finally { nodeFs.renameSync = realRename; }
61
+ check("renameWithRetry gives up after the bounded attempts (no infinite loop)",
62
+ stuckThrew !== null && stuckThrew.code === "EBUSY" && stuck === 5);
63
+ }
64
+
65
+ module.exports = { run: run };
66
+
67
+ if (require.main === module) {
68
+ try { run(); console.log("[atomic-file-rename-retry] OK"); }
69
+ catch (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
70
+ }