@blamejs/core 0.9.12 → 0.9.15

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 (119) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/lib/a2a.js +11 -11
  3. package/lib/acme.js +5 -5
  4. package/lib/ai-input.js +2 -2
  5. package/lib/api-key.js +4 -4
  6. package/lib/api-snapshot.js +10 -7
  7. package/lib/app-shutdown.js +2 -2
  8. package/lib/app.js +5 -5
  9. package/lib/archive.js +8 -8
  10. package/lib/argon2-builtin.js +2 -2
  11. package/lib/atomic-file.js +53 -53
  12. package/lib/audit-sign.js +8 -8
  13. package/lib/audit-tools.js +22 -22
  14. package/lib/audit.js +29 -17
  15. package/lib/auth/dpop.js +3 -3
  16. package/lib/auth/sd-jwt-vc.js +2 -2
  17. package/lib/backup/bundle.js +17 -17
  18. package/lib/backup/index.js +36 -36
  19. package/lib/budr.js +3 -3
  20. package/lib/bundler.js +20 -20
  21. package/lib/circuit-breaker.js +24 -9
  22. package/lib/cli.js +25 -26
  23. package/lib/cluster.js +2 -2
  24. package/lib/compliance-sanctions.js +2 -2
  25. package/lib/config-drift.js +15 -15
  26. package/lib/content-credentials.js +4 -4
  27. package/lib/credential-hash.js +3 -3
  28. package/lib/crypto.js +145 -0
  29. package/lib/daemon.js +19 -19
  30. package/lib/db-file-lifecycle.js +24 -24
  31. package/lib/db-schema.js +2 -2
  32. package/lib/db.js +35 -35
  33. package/lib/dev.js +10 -10
  34. package/lib/dr-runbook.js +5 -5
  35. package/lib/dsr.js +22 -15
  36. package/lib/dual-control.js +2 -2
  37. package/lib/external-db-migrate.js +2 -2
  38. package/lib/external-db.js +2 -2
  39. package/lib/fdx.js +2 -2
  40. package/lib/file-upload.js +30 -30
  41. package/lib/flag-providers.js +4 -4
  42. package/lib/gate-contract.js +5 -5
  43. package/lib/graphql-federation.js +4 -7
  44. package/lib/honeytoken.js +6 -6
  45. package/lib/http-client-cookie-jar.js +6 -6
  46. package/lib/http-client.js +18 -18
  47. package/lib/i18n.js +5 -5
  48. package/lib/inbox.js +21 -15
  49. package/lib/keychain.js +9 -9
  50. package/lib/legal-hold.js +2 -2
  51. package/lib/local-db-thin.js +9 -9
  52. package/lib/log-stream-local.js +17 -17
  53. package/lib/log-stream-syslog.js +2 -2
  54. package/lib/log-stream.js +3 -3
  55. package/lib/mail-bounce.js +2 -2
  56. package/lib/mail-mdn.js +2 -2
  57. package/lib/mail-srs.js +2 -2
  58. package/lib/mail.js +4 -4
  59. package/lib/mcp.js +2 -2
  60. package/lib/metrics.js +249 -2
  61. package/lib/middleware/api-encrypt.js +16 -16
  62. package/lib/middleware/body-parser.js +16 -16
  63. package/lib/middleware/compression.js +3 -3
  64. package/lib/middleware/csp-nonce.js +4 -4
  65. package/lib/middleware/health.js +7 -7
  66. package/lib/middleware/idempotency-key.js +250 -0
  67. package/lib/migrations.js +3 -3
  68. package/lib/mtls-ca.js +26 -26
  69. package/lib/mtls-engine-default.js +5 -5
  70. package/lib/network-dns.js +2 -2
  71. package/lib/network-nts.js +2 -2
  72. package/lib/network-proxy.js +3 -3
  73. package/lib/network-smtp-policy.js +2 -2
  74. package/lib/network-tls.js +17 -17
  75. package/lib/network.js +13 -13
  76. package/lib/notify.js +3 -3
  77. package/lib/object-store/gcs-bucket-ops.js +2 -2
  78. package/lib/object-store/gcs.js +5 -5
  79. package/lib/object-store/index.js +6 -6
  80. package/lib/object-store/local.js +19 -19
  81. package/lib/object-store/sigv4.js +3 -3
  82. package/lib/observability-tracer.js +4 -4
  83. package/lib/otel-export.js +3 -3
  84. package/lib/pagination.js +5 -5
  85. package/lib/parsers/safe-xml.js +3 -3
  86. package/lib/pqc-agent.js +116 -26
  87. package/lib/pqc-gate.js +5 -5
  88. package/lib/pubsub-redis.js +2 -2
  89. package/lib/queue-local.js +3 -3
  90. package/lib/queue.js +2 -2
  91. package/lib/redis-client.js +4 -4
  92. package/lib/restore-bundle.js +18 -18
  93. package/lib/restore-rollback.js +34 -34
  94. package/lib/restore.js +16 -16
  95. package/lib/retry.js +50 -0
  96. package/lib/router.js +13 -13
  97. package/lib/sandbox.js +8 -8
  98. package/lib/sec-cyber.js +3 -3
  99. package/lib/security-assert.js +2 -2
  100. package/lib/seeders.js +4 -4
  101. package/lib/self-update-standalone-verifier.js +280 -0
  102. package/lib/self-update.js +32 -26
  103. package/lib/session-device-binding.js +2 -2
  104. package/lib/static.js +22 -22
  105. package/lib/template.js +19 -19
  106. package/lib/testing.js +7 -7
  107. package/lib/tls-exporter.js +5 -5
  108. package/lib/tracing.js +3 -3
  109. package/lib/vault/index.js +11 -11
  110. package/lib/vault/passphrase-ops.js +37 -37
  111. package/lib/vault/passphrase-source.js +2 -2
  112. package/lib/vault/rotate.js +70 -66
  113. package/lib/vault/seal-pem-file.js +26 -26
  114. package/lib/watcher.js +23 -23
  115. package/lib/webhook.js +10 -10
  116. package/lib/worker-pool.js +6 -6
  117. package/lib/ws-client.js +4 -4
  118. package/package.json +1 -1
  119. package/sbom.cdx.json +6 -6
package/lib/sec-cyber.js CHANGED
@@ -69,7 +69,7 @@
69
69
  var audit = require("./audit");
70
70
  var C = require("./constants");
71
71
  var validateOpts = require("./validate-opts");
72
- var nb = require("./numeric-bounds");
72
+ var numericBounds = require("./numeric-bounds");
73
73
  var { defineClass } = require("./framework-error");
74
74
  var SecCyberError = defineClass("SecCyberError", { alwaysPermanent: true });
75
75
 
@@ -104,9 +104,9 @@ function eightKArtifact(opts) {
104
104
  "secCyber.eightKArtifact: registrant.name", SecCyberError, "BAD_REGISTRANT_NAME");
105
105
  validateOpts.requireNonEmptyString(opts.registrant.cik,
106
106
  "secCyber.eightKArtifact: registrant.cik", SecCyberError, "BAD_CIK");
107
- nb.requirePositiveFiniteIntIfPresent(opts.detectedAt,
107
+ numericBounds.requirePositiveFiniteIntIfPresent(opts.detectedAt,
108
108
  "secCyber.eightKArtifact: detectedAt", SecCyberError, "BAD_DETECTED_AT");
109
- nb.requirePositiveFiniteIntIfPresent(opts.materialityDeterminedAt,
109
+ numericBounds.requirePositiveFiniteIntIfPresent(opts.materialityDeterminedAt,
110
110
  "secCyber.eightKArtifact: materialityDeterminedAt", SecCyberError, "BAD_MAT_AT");
111
111
 
112
112
  if (FINDINGS.indexOf(opts.materialityFinding) === -1) {
@@ -67,7 +67,7 @@
67
67
  * non-function extra entry, etc.) so the operator catches typos at
68
68
  * boot, not at the moment they were trying to gate the boot.
69
69
  */
70
- var fs = require("fs");
70
+ var nodeFs = require("fs");
71
71
  var nodeTls = require("node:tls");
72
72
  var lazyRequire = require("./lazy-require");
73
73
  var safeEnv = require("./parsers/safe-env");
@@ -286,7 +286,7 @@ async function assertProduction(opts) {
286
286
  if (typeof opts.dataDir === "string" && opts.dataDir.length > 0 && process.platform !== "win32") {
287
287
  var maxMode = typeof opts.maxDataDirMode === "number" ? opts.maxDataDirMode : 0o750;
288
288
  try {
289
- var stat = fs.statSync(opts.dataDir);
289
+ var stat = nodeFs.statSync(opts.dataDir);
290
290
  var mode = stat.mode & 0o777;
291
291
  if (mode > maxMode) {
292
292
  failures.push({ ok: false, code: "security/datadir-permissions",
package/lib/seeders.js CHANGED
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * module.exports = {
17
17
  * description: "Create default admin user for local dev",
18
- * // Optional — when omitted, the env is inferred from the path.
18
+ * // Optional — when omitted, the env is inferred from the nodePath.
19
19
  * // When present, this seed only applies under one of these envs.
20
20
  * envs: ["dev", "test"],
21
21
  * // Default false — applied once and recorded in registry.
@@ -54,7 +54,7 @@
54
54
  * applied state)
55
55
  */
56
56
 
57
- var path = require("path");
57
+ var nodePath = require("path");
58
58
  var atomicFile = require("./atomic-file");
59
59
  var C = require("./constants");
60
60
  var dbSchema = require("./db-schema");
@@ -161,7 +161,7 @@ function _resolveDb(opts) {
161
161
  // ---- Directory walking + seed loading ----
162
162
 
163
163
  function _envDir(rootDir, env) {
164
- return path.join(rootDir, env);
164
+ return nodePath.join(rootDir, env);
165
165
  }
166
166
 
167
167
  function _listSeedFiles(rootDir, env) {
@@ -171,7 +171,7 @@ function _listSeedFiles(rootDir, env) {
171
171
  }
172
172
 
173
173
  function _loadSeed(rootDir, env, file) {
174
- var fullPath = path.join(_envDir(rootDir, env), file);
174
+ var fullPath = nodePath.join(_envDir(rootDir, env), file);
175
175
  // Drop require cache for this path so a test rewriting a fixture
176
176
  // between calls picks it up. Production restarts the process anyway.
177
177
  try { delete require.cache[require.resolve(fullPath)]; } catch (_e) { /* not yet cached */ }
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.selfUpdate.standaloneVerifier
4
+ * @nav Production
5
+ * @title Self-Update Standalone Verifier
6
+ * @order 640
7
+ *
8
+ * @intro
9
+ * Zero-dep companion to `b.selfUpdate.verify` for install-pipeline
10
+ * contexts that run BEFORE the framework itself is installed —
11
+ * Dockerfile build stages, `install.sh`, `update.sh`, SEA-bundle
12
+ * verification at deploy time. The full `b.selfUpdate.verify`
13
+ * chain reaches into `b.crypto`, `b.httpClient`, `b.audit`, vendor
14
+ * imports, etc.; none of those exist yet when an operator's
15
+ * install script runs `node verify-release.js` against the
16
+ * downloaded artifact.
17
+ *
18
+ * This module is intentionally hermetic — `node:crypto` + `node:fs`
19
+ * only, no framework imports, no third-party modules. Operators
20
+ * physically copy the file into their install pipeline alongside a
21
+ * public-key module they own. Both go into version control on the
22
+ * operator's side; neither updates without their explicit action.
23
+ *
24
+ * Surface (single function):
25
+ *
26
+ * verify(assetPath, signaturePath, pubkeyPem, opts?) → {
27
+ * ok: boolean,
28
+ * sha3_512: string, // hex digest of asset bytes (SBOM correlation)
29
+ * sha256: string, // hex digest of asset bytes (defense-in-depth)
30
+ * alg: string, // detected algorithm: "ecdsa-p384" | "ed25519" | "ml-dsa-87"
31
+ * }
32
+ *
33
+ * The function refuses to load the asset into memory in one go;
34
+ * it streams the bytes through both hashers + the signature
35
+ * verifier so multi-GB SEA bundles don't OOM the install runner.
36
+ *
37
+ * Throws on:
38
+ * - missing asset / signature / pubkey file
39
+ * - unrecognized pubkey PEM shape
40
+ * - signature length mismatch with the algorithm
41
+ * - cryptographic verify failure
42
+ *
43
+ * Per the operator's request that surfaced this primitive
44
+ * (hermitstash-sync 2026-05-13): the install pipeline needs P-384
45
+ * ECDSA + SHA3-512 as the baseline cross-check. ML-DSA-87 is also
46
+ * supported when the operator's pubkey carries the corresponding
47
+ * OID (Node 22+ via the FIPS 204 OIDs in node:crypto).
48
+ *
49
+ * ## How operators consume this
50
+ *
51
+ * ```sh
52
+ * # one-time copy at framework-install time:
53
+ * cp "$(node -p "require('@blamejs/core').selfUpdate.standaloneVerifier.path")" \
54
+ * install/standalone-verifier.js
55
+ * ```
56
+ *
57
+ * ```js
58
+ * // install/verify-release.js (operator-owned, in their repo):
59
+ * var verifier = require("./standalone-verifier");
60
+ * var pubkey = require("./release-pubkey"); // operator-owned PEM
61
+ *
62
+ * var result = verifier.verify(
63
+ * "/tmp/blamejs-sea-bundle",
64
+ * "/tmp/blamejs-sea-bundle.sig",
65
+ * pubkey,
66
+ * );
67
+ * if (!result.ok) {
68
+ * process.stderr.write("release verification FAILED\n");
69
+ * process.exit(1);
70
+ * }
71
+ * process.stdout.write("verified " + result.alg + " sha3-512=" + result.sha3_512 + "\n");
72
+ * ```
73
+ *
74
+ * The module is also reachable as `b.selfUpdate.standaloneVerifier.verify`
75
+ * from inside a fully-installed framework process — useful for tests
76
+ * that exercise the same code path the operator's install pipeline
77
+ * does, without forking a subprocess.
78
+ *
79
+ * @card
80
+ * Zero-dep verifier for use BEFORE the framework is installed.
81
+ * Install-pipeline scripts copy this file alongside an operator-owned
82
+ * pubkey to verify signed release artifacts during Dockerfile build
83
+ * or systemd `install.sh`. node:crypto + node:fs only.
84
+ */
85
+
86
+ var nodeCrypto = require("crypto");
87
+ var nodeFs = require("fs");
88
+
89
+ // _streamHashAndVerify — read the asset in 64 KiB chunks, feed each
90
+ // chunk into sha256, sha3-512, AND the signature verifier in parallel.
91
+ // Single pass over the file; no in-memory copy. node:crypto's
92
+ // `createVerify` consumes streaming input via `.update()` for ECDSA +
93
+ // EdDSA; ML-DSA's `crypto.verify` requires the full payload, so we
94
+ // also accumulate to a buffer ONLY when the alg requires it.
95
+ function _detectAlg(pubkeyPem) {
96
+ // Inspect the PEM header / SPKI for a recognizable curve / OID. The
97
+ // pubkey PEM carries the algorithm identifier in the SPKI ASN.1; we
98
+ // load it via createPublicKey() and read `asymmetricKeyType` +
99
+ // `asymmetricKeyDetails.namedCurve`.
100
+ var key;
101
+ try {
102
+ key = nodeCrypto.createPublicKey(pubkeyPem);
103
+ } catch (e) {
104
+ throw new Error("standalone-verifier: pubkey PEM did not parse: " +
105
+ (e && e.message ? e.message : String(e)));
106
+ }
107
+ var t = key.asymmetricKeyType;
108
+ if (t === "ec") {
109
+ var curve = key.asymmetricKeyDetails && key.asymmetricKeyDetails.namedCurve;
110
+ if (curve === "P-384" || curve === "secp384r1") return { alg: "ecdsa-p384", key: key };
111
+ throw new Error("standalone-verifier: unsupported EC curve '" + curve + "' (need P-384)");
112
+ }
113
+ if (t === "ed25519") return { alg: "ed25519", key: key };
114
+ if (t === "ml-dsa-87" || t === "ml-dsa") return { alg: "ml-dsa-87", key: key };
115
+ throw new Error("standalone-verifier: unrecognized pubkey type '" + t + "' " +
116
+ "(need ecdsa-p384, ed25519, or ml-dsa-87)");
117
+ }
118
+
119
+ /**
120
+ * @primitive b.selfUpdate.standaloneVerifier.verify
121
+ * @signature b.selfUpdate.standaloneVerifier.verify(assetPath, signaturePath, pubkeyPem)
122
+ * @since 0.9.13
123
+ * @status stable
124
+ * @related b.selfUpdate.verify
125
+ *
126
+ * Verify a signed release asset using only `node:crypto` + `node:fs`
127
+ * (no framework imports). For install-pipeline contexts where the
128
+ * framework itself is not yet installed.
129
+ *
130
+ * Streams the asset in 64 KiB chunks through SHA-256 + SHA-3-512 + the
131
+ * signature verifier in parallel — single allocation peak (one buffer
132
+ * sized to fstat(asset).size for Ed25519 / ML-DSA-87, ECDSA P-384 needs
133
+ * no buffer because createVerify is incremental).
134
+ *
135
+ * Returns `{ ok, sha3_512, sha256, alg }` on success; throws on
136
+ * unrecognized pubkey shape, missing files, or signature mismatch.
137
+ * `alg` is one of `"ecdsa-p384"`, `"ed25519"`, `"ml-dsa-87"` (auto-
138
+ * detected from the pubkey PEM).
139
+ *
140
+ * @example
141
+ * var verifier = require("./standalone-verifier");
142
+ * var pubkey = require("./release-pubkey");
143
+ * var result = verifier.verify(
144
+ * "/tmp/blamejs-sea-bundle",
145
+ * "/tmp/blamejs-sea-bundle.sig",
146
+ * pubkey,
147
+ * );
148
+ * if (!result.ok) process.exit(1);
149
+ * process.stdout.write("verified " + result.alg + " sha3-512=" + result.sha3_512 + "\n");
150
+ */
151
+ function verify(assetPath, signaturePath, pubkeyPem) {
152
+ if (typeof assetPath !== "string" || assetPath.length === 0) {
153
+ throw new Error("standalone-verifier.verify: assetPath must be a non-empty string");
154
+ }
155
+ if (typeof signaturePath !== "string" || signaturePath.length === 0) {
156
+ throw new Error("standalone-verifier.verify: signaturePath must be a non-empty string");
157
+ }
158
+ if (typeof pubkeyPem !== "string" || pubkeyPem.indexOf("-----BEGIN ") !== 0) {
159
+ throw new Error("standalone-verifier.verify: pubkeyPem must be a PEM-encoded public key string");
160
+ }
161
+
162
+ // Open both files BEFORE parsing the pubkey so we own stable fds
163
+ // against TOCTOU races (CodeQL js/file-system-race) — checking
164
+ // existsSync before readFileSync leaves a swap window. Asset opens
165
+ // first so a missing-asset path surfaces before a missing-sig path.
166
+ var assetFd;
167
+ try {
168
+ assetFd = nodeFs.openSync(assetPath, "r");
169
+ } catch (e) {
170
+ throw new Error("standalone-verifier.verify: asset not found at " + assetPath +
171
+ " — " + (e && e.message ? e.message : String(e)));
172
+ }
173
+ var sigFd;
174
+ try {
175
+ sigFd = nodeFs.openSync(signaturePath, "r");
176
+ } catch (e) {
177
+ nodeFs.closeSync(assetFd);
178
+ throw new Error("standalone-verifier.verify: signature not found at " + signaturePath +
179
+ " — " + (e && e.message ? e.message : String(e)));
180
+ }
181
+ var signature;
182
+ try {
183
+ var sigStat = nodeFs.fstatSync(sigFd);
184
+ signature = Buffer.allocUnsafe(sigStat.size);
185
+ if (sigStat.size > 0) nodeFs.readSync(sigFd, signature, 0, sigStat.size, 0);
186
+ } finally {
187
+ nodeFs.closeSync(sigFd);
188
+ }
189
+ if (signature.length === 0) {
190
+ nodeFs.closeSync(assetFd);
191
+ throw new Error("standalone-verifier.verify: signature file is empty");
192
+ }
193
+
194
+ var detected;
195
+ try {
196
+ detected = _detectAlg(pubkeyPem);
197
+ } catch (e) {
198
+ nodeFs.closeSync(assetFd);
199
+ throw e;
200
+ }
201
+ var alg = detected.alg;
202
+ var key = detected.key;
203
+
204
+ // Stream the asset through both hashers. For ECDSA we stream through
205
+ // createVerify (incremental). For Ed25519 / ML-DSA we pre-allocate
206
+ // ONE buffer of stat.size and stream-fill it at increasing offsets —
207
+ // single allocation peak, not the 2× peak that Buffer.concat([...chunks])
208
+ // produces. 64 KiB chunks match the framework's hash-while-streaming
209
+ // convention elsewhere.
210
+ var sha256 = nodeCrypto.createHash("sha256");
211
+ var sha3 = nodeCrypto.createHash("sha3-512");
212
+ var verifier = (alg === "ecdsa-p384") ? nodeCrypto.createVerify("sha3-512") : null;
213
+ var fullBuf = null;
214
+ var fullOff = 0;
215
+ if (verifier === null) {
216
+ var assetStat = nodeFs.fstatSync(assetFd);
217
+ fullBuf = Buffer.allocUnsafe(assetStat.size);
218
+ }
219
+
220
+ try {
221
+ var chunk = Buffer.allocUnsafe(64 * 1024); // allow:raw-byte-literal — module is zero-dep by contract; cannot import C.BYTES
222
+ while (true) {
223
+ var n = nodeFs.readSync(assetFd, chunk, 0, chunk.length, null);
224
+ if (n === 0) break;
225
+ var slice = chunk.subarray(0, n);
226
+ sha256.update(slice);
227
+ sha3.update(slice);
228
+ if (verifier) verifier.update(slice);
229
+ if (fullBuf) {
230
+ slice.copy(fullBuf, fullOff);
231
+ fullOff += n;
232
+ }
233
+ }
234
+ } finally {
235
+ nodeFs.closeSync(assetFd);
236
+ }
237
+
238
+ var sha256Hex = sha256.digest("hex");
239
+ var sha3Hex = sha3.digest("hex");
240
+
241
+ var ok = false;
242
+ if (alg === "ecdsa-p384") {
243
+ // P-384 IEEE-P1363 sigs are exactly 96 bytes (48-byte r || 48-byte s).
244
+ // P-384 DER sigs are variable (~100-104 bytes — ASN.1 SEQUENCE
245
+ // wrapping two INTEGERs). Detect by length so we only call
246
+ // verifier.verify ONCE — calling it a second time after a failed
247
+ // verify returns stale state and silently passes tampered assets.
248
+ // 96 = P-384 IEEE-P1363 signature length; protocol constant, not a byte-size.
249
+ var dsaEncoding = signature.length === 96 ? "ieee-p1363" : "der"; // allow:raw-byte-literal — IEEE-P1363 P-384 signature length
250
+ ok = verifier.verify({ key: key, dsaEncoding: dsaEncoding }, signature);
251
+ } else if (alg === "ed25519") {
252
+ // fullBuf may be shorter than allocated (sparse files / size-races);
253
+ // slice to fullOff so verify sees only the bytes we actually read.
254
+ ok = nodeCrypto.verify(null, fullBuf.subarray(0, fullOff), key, signature);
255
+ } else if (alg === "ml-dsa-87") {
256
+ ok = nodeCrypto.verify(null, fullBuf.subarray(0, fullOff), key, signature);
257
+ }
258
+
259
+ if (!ok) {
260
+ throw new Error("standalone-verifier.verify: " + alg + " signature INVALID for " +
261
+ assetPath + " (sha3-512=" + sha3Hex.slice(0, 16) + "...). " + // allow:raw-byte-literal — 16-char hex prefix for forensic display, not bytes
262
+ "Either the asset was tampered with after signing, the signature " +
263
+ "doesn't match this asset, or the pubkey doesn't match the signing key.");
264
+ }
265
+
266
+ return {
267
+ ok: true,
268
+ sha3_512: sha3Hex,
269
+ sha256: sha256Hex,
270
+ alg: alg,
271
+ };
272
+ }
273
+
274
+ module.exports = {
275
+ verify: verify,
276
+ // Absolute path to this module file. Operators copy it via:
277
+ // cp "$(node -p "require('@blamejs/core').selfUpdate.standaloneVerifier.path")" \
278
+ // install/standalone-verifier.js
279
+ path: __filename,
280
+ };
@@ -47,18 +47,19 @@
47
47
  * Framework / vendored-deps integrity check plus version pinning — refuses to install a new build when the asset's detached signature does not verify against the operator-supplied public key, or when the vendored SHA the new build would ship does not match the manifest the opera...
48
48
  */
49
49
 
50
- var fs = require("fs");
51
- var path = require("path");
50
+ var nodeFs = require("fs");
51
+ var nodePath = require("path");
52
52
  var nodeCrypto = require("crypto");
53
- var nb = require("./numeric-bounds");
53
+ var numericBounds = require("./numeric-bounds");
54
54
  var atomicFile = require("./atomic-file");
55
55
  var validateOpts = require("./validate-opts");
56
- var bjCrypto = require("./crypto");
56
+ var bCrypto = require("./crypto");
57
57
  var httpClient = require("./http-client");
58
58
  var safeJson = require("./safe-json");
59
59
  var { URL: NodeUrl } = require("url");
60
60
  var lazyRequire = require("./lazy-require");
61
61
  var C = require("./constants");
62
+ var standaloneVerifier = require("./self-update-standalone-verifier");
62
63
  var { boot } = require("./log");
63
64
  var { defineClass } = require("./framework-error");
64
65
 
@@ -152,9 +153,9 @@ function _validatePollOpts(opts) {
152
153
  throw new SelfUpdateError("selfupdate/bad-sig-pattern",
153
154
  "selfUpdate.poll: opts.signaturePattern must be a RegExp or string when present");
154
155
  }
155
- nb.requirePositiveFiniteIntIfPresent(opts.maxBytes,
156
+ numericBounds.requirePositiveFiniteIntIfPresent(opts.maxBytes,
156
157
  "selfUpdate.poll: opts.maxBytes", SelfUpdateError, "selfupdate/bad-max-bytes");
157
- nb.requirePositiveFiniteIntIfPresent(opts.timeoutMs,
158
+ numericBounds.requirePositiveFiniteIntIfPresent(opts.timeoutMs,
158
159
  "selfUpdate.poll: opts.timeoutMs", SelfUpdateError, "selfupdate/bad-timeout");
159
160
  }
160
161
 
@@ -177,7 +178,7 @@ function _matchAsset(name, pattern, fallback) {
177
178
  * Fetch a releases feed and report whether a newer tag is available.
178
179
  * Tags are compared semver-style with a leading `v` stripped. When
179
180
  * `opts.etag` is supplied an `If-None-Match` header makes a 304 a fast
180
- * "no update" path. The match against asset and signature URLs uses
181
+ * "no update" nodePath. The match against asset and signature URLs uses
181
182
  * `opts.assetPattern` and `opts.signaturePattern` (RegExp or substring)
182
183
  * with conservative fallbacks. Throws SelfUpdateError on a non-2xx
183
184
  * upstream, malformed JSON, or unexpected shape.
@@ -366,7 +367,7 @@ function _validateVerifyOpts(opts) {
366
367
  throw new SelfUpdateError("selfupdate/bad-hash-algo",
367
368
  "selfUpdate.verify: opts.hashAlgo must be one of " + ALLOWED_HASH_ALGS.join(", "));
368
369
  }
369
- nb.requirePositiveFiniteIntIfPresent(opts.maxBytes,
370
+ numericBounds.requirePositiveFiniteIntIfPresent(opts.maxBytes,
370
371
  "selfUpdate.verify: opts.maxBytes", SelfUpdateError, "selfupdate/bad-max-bytes");
371
372
  }
372
373
 
@@ -425,7 +426,7 @@ async function verify(opts) {
425
426
  }
426
427
 
427
428
  var ok = false;
428
- try { ok = bjCrypto.verify(assetBytes, sigBytes, opts.pubkeyPem); }
429
+ try { ok = bCrypto.verify(assetBytes, sigBytes, opts.pubkeyPem); }
429
430
  catch (e) {
430
431
  _safeAuditEmit("selfupdate.verify.failed", "denied", {
431
432
  assetPath: opts.assetPath, signaturePath: opts.signaturePath,
@@ -514,19 +515,19 @@ async function swap(opts) {
514
515
  var to = opts.to;
515
516
  var backupTo = opts.backupTo;
516
517
 
517
- if (!fs.existsSync(from)) {
518
+ if (!nodeFs.existsSync(from)) {
518
519
  throw new SelfUpdateError("selfupdate/missing-from",
519
520
  "selfUpdate.swap: from path does not exist: " + from);
520
521
  }
521
522
 
522
- var toDir = path.dirname(to);
523
- var backupDir = path.dirname(backupTo);
523
+ var toDir = nodePath.dirname(to);
524
+ var backupDir = nodePath.dirname(backupTo);
524
525
  atomicFile.ensureDir(toDir);
525
526
  atomicFile.ensureDir(backupDir);
526
527
 
527
528
  // Step 2 — backup if `to` exists. Use atomicFile.copy so the backup
528
529
  // hits disk via temp+fsync+rename.
529
- var hadOriginal = fs.existsSync(to);
530
+ var hadOriginal = nodeFs.existsSync(to);
530
531
  if (hadOriginal) {
531
532
  try {
532
533
  await atomicFile.copy(to, backupTo, { fileMode: 0o600 });
@@ -540,14 +541,14 @@ async function swap(opts) {
540
541
  // Step 3 — install. Rename is atomic on same FS; on cross-device we
541
542
  // fall back to copy + unlink.
542
543
  try {
543
- fs.renameSync(from, to);
544
+ nodeFs.renameSync(from, to);
544
545
  } catch (e) {
545
546
  if (e && e.code === "EXDEV") {
546
547
  // Cross-device — copy + unlink. Use atomicFile.copy for the safety
547
548
  // net (temp+fsync+rename on dest FS); then remove the source.
548
549
  try {
549
550
  await atomicFile.copy(from, to, { fileMode: 0o600 });
550
- try { fs.unlinkSync(from); } catch (_u) { /* tmp source leak — operator-cleanable */ }
551
+ try { nodeFs.unlinkSync(from); } catch (_u) { /* tmp source leak — operator-cleanable */ }
551
552
  } catch (ce) {
552
553
  // Roll back from backup if we have one.
553
554
  if (hadOriginal) {
@@ -612,12 +613,12 @@ async function rollback(opts) {
612
613
  var to = opts.to;
613
614
  var backupTo = opts.backupTo;
614
615
 
615
- if (!fs.existsSync(backupTo)) {
616
+ if (!nodeFs.existsSync(backupTo)) {
616
617
  throw new SelfUpdateError("selfupdate/missing-backup",
617
618
  "selfUpdate.rollback: backupTo path does not exist: " + backupTo);
618
619
  }
619
620
 
620
- atomicFile.ensureDir(path.dirname(to));
621
+ atomicFile.ensureDir(nodePath.dirname(to));
621
622
  try {
622
623
  await atomicFile.copy(backupTo, to, { fileMode: 0o600 });
623
624
  } catch (e) {
@@ -625,7 +626,7 @@ async function rollback(opts) {
625
626
  "selfUpdate.rollback: copy " + backupTo + " -> " + to + " failed: " +
626
627
  ((e && e.message) || String(e)));
627
628
  }
628
- atomicFile.fsyncDir(path.dirname(to));
629
+ atomicFile.fsyncDir(nodePath.dirname(to));
629
630
 
630
631
  _safeAuditEmit("selfupdate.rollback.completed", "success", {
631
632
  to: to, backupTo: backupTo,
@@ -635,13 +636,18 @@ async function rollback(opts) {
635
636
  }
636
637
 
637
638
  module.exports = {
638
- poll: poll,
639
- verify: verify,
640
- swap: swap,
641
- rollback: rollback,
642
- SelfUpdateError: SelfUpdateError,
643
- ALLOWED_HASH_ALGS: ALLOWED_HASH_ALGS,
644
- DEFAULT_HASH_ALG: DEFAULT_HASH_ALG,
639
+ poll: poll,
640
+ verify: verify,
641
+ swap: swap,
642
+ rollback: rollback,
643
+ // Standalone verifier — zero-dep companion for install-pipeline
644
+ // contexts that run BEFORE the framework is installed (Dockerfile
645
+ // build stages, install.sh, update.sh). See the module's intro for
646
+ // the copy-this-file workflow.
647
+ standaloneVerifier: standaloneVerifier,
648
+ SelfUpdateError: SelfUpdateError,
649
+ ALLOWED_HASH_ALGS: ALLOWED_HASH_ALGS,
650
+ DEFAULT_HASH_ALG: DEFAULT_HASH_ALG,
645
651
  // Internal — exposed for the layer-0 test suite only.
646
- _compareTags: _compareTags,
652
+ _compareTags: _compareTags,
647
653
  };
@@ -69,7 +69,7 @@
69
69
  */
70
70
 
71
71
  var C = require("./constants");
72
- var blamejsCrypto = require("./crypto");
72
+ var bCrypto = require("./crypto");
73
73
  var nodeCrypto = require("crypto");
74
74
  var lazyRequire = require("./lazy-require");
75
75
  var requestHelpers = require("./request-helpers");
@@ -386,7 +386,7 @@ function create(opts) {
386
386
  return { ok: false, reason: "missing-bind" };
387
387
  }
388
388
  if (!Buffer.isBuffer(stored) || stored.length !== fpResult.fingerprint.length ||
389
- !blamejsCrypto.timingSafeEqual(stored, fpResult.fingerprint)) {
389
+ !bCrypto.timingSafeEqual(stored, fpResult.fingerprint)) {
390
390
  _emitObs("session.device.drift", {});
391
391
  _emitAudit("session.device.drift", _hashTokenForAudit(token), "denied",
392
392
  { components: fpResult.components, stage: "verify" }, req);