@blamejs/core 0.9.14 → 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.
- package/CHANGELOG.md +1 -0
- package/lib/a2a.js +11 -11
- package/lib/acme.js +5 -5
- package/lib/ai-input.js +2 -2
- package/lib/api-key.js +4 -4
- package/lib/api-snapshot.js +6 -6
- package/lib/app-shutdown.js +2 -2
- package/lib/app.js +5 -5
- package/lib/archive.js +8 -8
- package/lib/argon2-builtin.js +2 -2
- package/lib/atomic-file.js +53 -53
- package/lib/audit-sign.js +8 -8
- package/lib/audit-tools.js +22 -22
- package/lib/auth/dpop.js +3 -3
- package/lib/auth/sd-jwt-vc.js +2 -2
- package/lib/backup/bundle.js +17 -17
- package/lib/backup/index.js +36 -36
- package/lib/budr.js +3 -3
- package/lib/bundler.js +20 -20
- package/lib/circuit-breaker.js +4 -4
- package/lib/cli.js +25 -26
- package/lib/cluster.js +2 -2
- package/lib/compliance-sanctions.js +2 -2
- package/lib/config-drift.js +15 -15
- package/lib/content-credentials.js +4 -4
- package/lib/credential-hash.js +3 -3
- package/lib/daemon.js +19 -19
- package/lib/db-file-lifecycle.js +24 -24
- package/lib/db-schema.js +2 -2
- package/lib/db.js +35 -35
- package/lib/dev.js +10 -10
- package/lib/dr-runbook.js +5 -5
- package/lib/dual-control.js +2 -2
- package/lib/external-db-migrate.js +2 -2
- package/lib/external-db.js +2 -2
- package/lib/fdx.js +2 -2
- package/lib/file-upload.js +30 -30
- package/lib/flag-providers.js +4 -4
- package/lib/gate-contract.js +5 -5
- package/lib/graphql-federation.js +4 -7
- package/lib/honeytoken.js +6 -6
- package/lib/http-client-cookie-jar.js +6 -6
- package/lib/http-client.js +18 -18
- package/lib/i18n.js +5 -5
- package/lib/keychain.js +9 -9
- package/lib/legal-hold.js +2 -2
- package/lib/local-db-thin.js +9 -9
- package/lib/log-stream-local.js +17 -17
- package/lib/log-stream-syslog.js +2 -2
- package/lib/log-stream.js +3 -3
- package/lib/mail-bounce.js +2 -2
- package/lib/mail-mdn.js +2 -2
- package/lib/mail-srs.js +2 -2
- package/lib/mail.js +4 -4
- package/lib/mcp.js +2 -2
- package/lib/metrics.js +2 -2
- package/lib/middleware/api-encrypt.js +16 -16
- package/lib/middleware/body-parser.js +16 -16
- package/lib/middleware/compression.js +3 -3
- package/lib/middleware/csp-nonce.js +4 -4
- package/lib/middleware/health.js +7 -7
- package/lib/middleware/idempotency-key.js +163 -63
- package/lib/migrations.js +3 -3
- package/lib/mtls-ca.js +26 -26
- package/lib/mtls-engine-default.js +5 -5
- package/lib/network-dns.js +2 -2
- package/lib/network-nts.js +2 -2
- package/lib/network-proxy.js +3 -3
- package/lib/network-smtp-policy.js +2 -2
- package/lib/network-tls.js +17 -17
- package/lib/network.js +13 -13
- package/lib/notify.js +3 -3
- package/lib/object-store/gcs-bucket-ops.js +2 -2
- package/lib/object-store/gcs.js +5 -5
- package/lib/object-store/index.js +6 -6
- package/lib/object-store/local.js +19 -19
- package/lib/object-store/sigv4.js +3 -3
- package/lib/observability-tracer.js +4 -4
- package/lib/otel-export.js +3 -3
- package/lib/pagination.js +5 -5
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/pqc-gate.js +5 -5
- package/lib/pubsub-redis.js +2 -2
- package/lib/queue-local.js +3 -3
- package/lib/queue.js +2 -2
- package/lib/redis-client.js +4 -4
- package/lib/restore-bundle.js +18 -18
- package/lib/restore-rollback.js +34 -34
- package/lib/restore.js +16 -16
- package/lib/router.js +13 -13
- package/lib/sandbox.js +8 -8
- package/lib/sec-cyber.js +3 -3
- package/lib/security-assert.js +2 -2
- package/lib/seeders.js +4 -4
- package/lib/self-update.js +18 -18
- package/lib/session-device-binding.js +2 -2
- package/lib/static.js +22 -22
- package/lib/template.js +19 -19
- package/lib/testing.js +7 -7
- package/lib/tls-exporter.js +5 -5
- package/lib/tracing.js +3 -3
- package/lib/vault/index.js +11 -11
- package/lib/vault/passphrase-ops.js +37 -37
- package/lib/vault/passphrase-source.js +2 -2
- package/lib/vault/rotate.js +64 -64
- package/lib/vault/seal-pem-file.js +26 -26
- package/lib/watcher.js +23 -23
- package/lib/webhook.js +10 -10
- package/lib/worker-pool.js +6 -6
- package/lib/ws-client.js +4 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/template.js
CHANGED
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
* is the second line: even if a template loaded, it can't execute
|
|
90
90
|
* arbitrary JS — only the limited expression grammar above.
|
|
91
91
|
*/
|
|
92
|
-
var
|
|
93
|
-
var
|
|
92
|
+
var nodeFs = require("fs");
|
|
93
|
+
var nodePath = require("path");
|
|
94
94
|
var lazyRequire = require("./lazy-require");
|
|
95
95
|
var validateOpts = require("./validate-opts");
|
|
96
96
|
|
|
@@ -149,13 +149,13 @@ function _resolveViewPath(viewsDir, viewName) {
|
|
|
149
149
|
if (viewName.indexOf("..") !== -1 || viewName.indexOf("\0") !== -1) {
|
|
150
150
|
throw new Error("template: view name contains forbidden character: " + JSON.stringify(viewName));
|
|
151
151
|
}
|
|
152
|
-
var resolved =
|
|
153
|
-
var resolvedDir =
|
|
152
|
+
var resolved = nodePath.resolve(viewsDir, viewName + ".html");
|
|
153
|
+
var resolvedDir = nodePath.resolve(viewsDir);
|
|
154
154
|
if (resolved !== resolvedDir &&
|
|
155
|
-
!resolved.startsWith(resolvedDir +
|
|
155
|
+
!resolved.startsWith(resolvedDir + nodePath.sep)) {
|
|
156
156
|
throw new Error("template: view path escapes viewsDir: " + viewName);
|
|
157
157
|
}
|
|
158
|
-
if (!
|
|
158
|
+
if (!nodeFs.existsSync(resolved)) {
|
|
159
159
|
throw new Error("template: view not found: " + viewName);
|
|
160
160
|
}
|
|
161
161
|
return resolved;
|
|
@@ -164,11 +164,11 @@ function _resolveViewPath(viewsDir, viewName) {
|
|
|
164
164
|
function _resolvePartialPath(viewsDir, partialName) {
|
|
165
165
|
if (typeof partialName !== "string" || partialName.length === 0) return null;
|
|
166
166
|
if (partialName.indexOf("..") !== -1 || partialName.indexOf("\0") !== -1) return null;
|
|
167
|
-
var resolved =
|
|
168
|
-
var partialsDir =
|
|
167
|
+
var resolved = nodePath.resolve(viewsDir, "partials", partialName + ".html");
|
|
168
|
+
var partialsDir = nodePath.resolve(viewsDir, "partials");
|
|
169
169
|
if (resolved !== partialsDir &&
|
|
170
|
-
!resolved.startsWith(partialsDir +
|
|
171
|
-
return
|
|
170
|
+
!resolved.startsWith(partialsDir + nodePath.sep)) return null;
|
|
171
|
+
return nodeFs.existsSync(resolved) ? resolved : null;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// ============================================================
|
|
@@ -227,7 +227,7 @@ function _resolveExtends(viewsDir, source) {
|
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
var parentPath = _resolveViewPath(viewsDir, parentName);
|
|
230
|
-
current =
|
|
230
|
+
current = nodeFs.readFileSync(parentPath, "utf8");
|
|
231
231
|
depth++;
|
|
232
232
|
}
|
|
233
233
|
return _substituteBlocks(current, allOverrides);
|
|
@@ -245,7 +245,7 @@ function _inlinePartials(viewsDir, source, depth) {
|
|
|
245
245
|
return source.replace(/\{\{>\s*([A-Za-z_][A-Za-z0-9_-]*)\s*\}\}/g, function (_, name) {
|
|
246
246
|
var p = _resolvePartialPath(viewsDir, name);
|
|
247
247
|
if (!p) return ""; // missing partial → silent empty so a stale `{{> name}}` reference doesn't crash the render
|
|
248
|
-
return _inlinePartials(viewsDir,
|
|
248
|
+
return _inlinePartials(viewsDir, nodeFs.readFileSync(p, "utf8"), depth + 1);
|
|
249
249
|
});
|
|
250
250
|
}
|
|
251
251
|
|
|
@@ -771,10 +771,10 @@ function create(opts) {
|
|
|
771
771
|
if (!opts.viewsDir) {
|
|
772
772
|
throw new Error("template.create({ viewsDir }) is required");
|
|
773
773
|
}
|
|
774
|
-
if (!
|
|
774
|
+
if (!nodeFs.existsSync(opts.viewsDir)) {
|
|
775
775
|
throw new Error("template: viewsDir does not exist: " + opts.viewsDir);
|
|
776
776
|
}
|
|
777
|
-
var viewsDir =
|
|
777
|
+
var viewsDir = nodePath.resolve(opts.viewsDir);
|
|
778
778
|
var cacheOn = opts.cache !== false;
|
|
779
779
|
var customEscape = typeof opts.escapeHtml === "function" ? opts.escapeHtml : escapeHtml;
|
|
780
780
|
var astCache = {};
|
|
@@ -823,7 +823,7 @@ function create(opts) {
|
|
|
823
823
|
function compile(viewName) {
|
|
824
824
|
if (cacheOn && astCache[viewName]) return astCache[viewName];
|
|
825
825
|
var viewPath = _resolveViewPath(viewsDir, viewName);
|
|
826
|
-
var source =
|
|
826
|
+
var source = nodeFs.readFileSync(viewPath, "utf8");
|
|
827
827
|
source = _resolveExtends(viewsDir, source);
|
|
828
828
|
source = _inlinePartials(viewsDir, source, 0);
|
|
829
829
|
var tokens = _tokenize(source);
|
|
@@ -847,12 +847,12 @@ function create(opts) {
|
|
|
847
847
|
function precompileAll() {
|
|
848
848
|
var compiled = [];
|
|
849
849
|
function walk(dir, prefix) {
|
|
850
|
-
var entries =
|
|
850
|
+
var entries = nodeFs.readdirSync(dir, { withFileTypes: true });
|
|
851
851
|
for (var i = 0; i < entries.length; i++) {
|
|
852
852
|
var e = entries[i];
|
|
853
853
|
var rel = prefix ? prefix + "/" + e.name : e.name;
|
|
854
854
|
if (e.isDirectory()) {
|
|
855
|
-
walk(
|
|
855
|
+
walk(nodePath.join(dir, e.name), rel);
|
|
856
856
|
} else if (e.isFile() && /\.html$/.test(e.name)) {
|
|
857
857
|
var viewName = rel.replace(/\.html$/, "");
|
|
858
858
|
try {
|
|
@@ -890,8 +890,8 @@ function create(opts) {
|
|
|
890
890
|
var _default = null;
|
|
891
891
|
function _ensureDefault() {
|
|
892
892
|
if (!_default) {
|
|
893
|
-
var defaultDir =
|
|
894
|
-
if (!
|
|
893
|
+
var defaultDir = nodePath.resolve(process.cwd(), "views");
|
|
894
|
+
if (!nodeFs.existsSync(defaultDir)) {
|
|
895
895
|
throw new Error("template.render() default uses <cwd>/views which doesn't exist; " +
|
|
896
896
|
"call template.create({ viewsDir }) for a custom location");
|
|
897
897
|
}
|
package/lib/testing.js
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* Operator-facing test helpers.
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
var
|
|
45
|
+
var nodeFs = require("node:fs");
|
|
46
46
|
// testing.js IS the test injector — bypasses b.httpClient by design so
|
|
47
47
|
// tests can assert on raw request shape. This is the one production
|
|
48
48
|
// module where direct http.request is the contract.
|
|
@@ -792,9 +792,9 @@ async function waitFor(predicate, opts) {
|
|
|
792
792
|
* @example
|
|
793
793
|
* var dir = b.testing.tempDir("my-fixture");
|
|
794
794
|
* try {
|
|
795
|
-
* var
|
|
796
|
-
* var
|
|
797
|
-
*
|
|
795
|
+
* var fs = require("node:fs");
|
|
796
|
+
* var path = require("node:path");
|
|
797
|
+
* fs.writeFileSync(path.join(dir.path, "fixture.json"), '{"ok":1}');
|
|
798
798
|
* dir.path.indexOf("my-fixture-") !== -1; // → true
|
|
799
799
|
* } finally {
|
|
800
800
|
* dir.cleanup();
|
|
@@ -813,9 +813,9 @@ function tempDir(prefix) {
|
|
|
813
813
|
"tempDir: prefix must not contain '..', '/', '\\', or null bytes; got " + JSON.stringify(prefix));
|
|
814
814
|
}
|
|
815
815
|
// Path containment check mirroring static.js _resolveSafe — verify
|
|
816
|
-
// the resolved tempdir is actually inside os.tmpdir() before
|
|
816
|
+
// the resolved tempdir is actually inside os.tmpdir() before nodeFs.mkdir.
|
|
817
817
|
var root = nodePath.resolve(os.tmpdir());
|
|
818
|
-
var dirPath =
|
|
818
|
+
var dirPath = nodeFs.mkdtempSync(nodePath.join(root, prefix + "-"));
|
|
819
819
|
var resolved = nodePath.resolve(dirPath);
|
|
820
820
|
if (resolved !== root && !resolved.startsWith(root + nodePath.sep)) {
|
|
821
821
|
throw _err("BAD_STATE",
|
|
@@ -827,7 +827,7 @@ function tempDir(prefix) {
|
|
|
827
827
|
cleanup: function () {
|
|
828
828
|
if (cleanedUp) return;
|
|
829
829
|
cleanedUp = true;
|
|
830
|
-
try {
|
|
830
|
+
try { nodeFs.rmSync(resolved, { recursive: true, force: true }); }
|
|
831
831
|
catch (_e) { /* best-effort on Windows locked files */ }
|
|
832
832
|
},
|
|
833
833
|
};
|
package/lib/tls-exporter.js
CHANGED
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
* RFC 5705 / RFC 9266 TLS Exporter for binding application-layer keys and tokens to the live TLS session.
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
var
|
|
34
|
+
var bCrypto = require("./crypto");
|
|
35
35
|
var C = require("./constants");
|
|
36
36
|
var lazyRequire = require("./lazy-require");
|
|
37
|
-
var
|
|
37
|
+
var numericBounds = require("./numeric-bounds");
|
|
38
38
|
var { TlsExporterError } = require("./framework-error");
|
|
39
39
|
|
|
40
40
|
var _err = TlsExporterError.factory;
|
|
@@ -119,7 +119,7 @@ function fromSocket(socketOrReq, opts) {
|
|
|
119
119
|
// length is operator-tunable; validate-when-present via numeric-bounds
|
|
120
120
|
// so a non-finite / negative / NaN input surfaces with the same error
|
|
121
121
|
// shape every other framework primitive uses for numeric opts.
|
|
122
|
-
|
|
122
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.length,
|
|
123
123
|
"tlsExporter.fromSocket: length", TlsExporterError, "BAD_LENGTH");
|
|
124
124
|
var length = opts.length !== undefined ? opts.length : EXPORTER_LENGTH;
|
|
125
125
|
if (length < C.BYTES.bytes(16) || length > C.BYTES.bytes(255)) {
|
|
@@ -193,7 +193,7 @@ function bindToken(socketOrReq, token) {
|
|
|
193
193
|
// does NOT produce the same hash if used in another framework
|
|
194
194
|
// primitive (e.g., the audit-chain row hash).
|
|
195
195
|
var labelBuf = Buffer.from("blamejs/tls-exporter/bind/v1", "utf8");
|
|
196
|
-
return
|
|
196
|
+
return bCrypto.sha3Hash(Buffer.concat([labelBuf, exporter, tokenBuf]));
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/**
|
|
@@ -226,7 +226,7 @@ function verifyTokenBinding(socketOrReq, token, claimedBinding) {
|
|
|
226
226
|
if (typeof claimedBinding !== "string" || claimedBinding.length === 0) {
|
|
227
227
|
return false;
|
|
228
228
|
}
|
|
229
|
-
return
|
|
229
|
+
return bCrypto.timingSafeEqual(actual, claimedBinding);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
module.exports = {
|
package/lib/tracing.js
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
var C = require("./constants");
|
|
42
|
-
var
|
|
42
|
+
var bCrypto = require("./crypto");
|
|
43
43
|
var validateOpts = require("./validate-opts");
|
|
44
44
|
var { defineClass } = require("./framework-error");
|
|
45
45
|
var { resolveRoute, captureResponseStatus } = require("./request-helpers");
|
|
@@ -101,10 +101,10 @@ function _formatTraceparent(traceId, spanId, flags) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
function _newTraceId() {
|
|
104
|
-
return
|
|
104
|
+
return bCrypto.generateToken(W3C_TRACE_ID_BYTES);
|
|
105
105
|
}
|
|
106
106
|
function _newSpanId() {
|
|
107
|
-
return
|
|
107
|
+
return bCrypto.generateToken(W3C_SPAN_ID_BYTES);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// ---- Pass-through span (used when OTel isn't installed) ----
|
package/lib/vault/index.js
CHANGED
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
* @card
|
|
63
63
|
* Sealed keystore that anchors every other framework subsystem holding secrets at rest: db field encryption, encrypted session storage, audit-log signing keys, OAuth refresh tokens, anything that flows through `b.vault.seal` / `b.vault.unseal`.
|
|
64
64
|
*/
|
|
65
|
-
var
|
|
66
|
-
var
|
|
65
|
+
var nodeFs = require("fs");
|
|
66
|
+
var nodePath = require("path");
|
|
67
67
|
var atomicFile = require("../atomic-file");
|
|
68
68
|
var C = require("../constants");
|
|
69
69
|
var { generateEncryptionKeyPair, encrypt, decrypt } = require("../crypto");
|
|
@@ -99,9 +99,9 @@ var log = boot("vault");
|
|
|
99
99
|
function resolvePaths(dataDir) {
|
|
100
100
|
return {
|
|
101
101
|
dataDir: dataDir,
|
|
102
|
-
plaintext:
|
|
103
|
-
sealed:
|
|
104
|
-
derivedHashSalt:
|
|
102
|
+
plaintext: nodePath.join(dataDir, "vault.key"),
|
|
103
|
+
sealed: nodePath.join(dataDir, "vault.key.sealed"),
|
|
104
|
+
derivedHashSalt: nodePath.join(dataDir, "vault.derived-hash-salt"),
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -118,7 +118,7 @@ function _readOrCreateDerivedHashSalt() {
|
|
|
118
118
|
throw new VaultError("vault/not-initialized",
|
|
119
119
|
"vault.derivedHashSalt() requires init()");
|
|
120
120
|
}
|
|
121
|
-
if (
|
|
121
|
+
if (nodeFs.existsSync(paths.derivedHashSalt)) {
|
|
122
122
|
var raw = atomicFile.readSync(paths.derivedHashSalt);
|
|
123
123
|
if (raw.length !== 32) { // allow:raw-byte-literal — 32-byte (256-bit) salt
|
|
124
124
|
throw new VaultError("vault/derived-hash-salt-corrupted",
|
|
@@ -242,16 +242,16 @@ async function init(opts) {
|
|
|
242
242
|
currentMode = mode;
|
|
243
243
|
paths = resolvePaths(opts.dataDir);
|
|
244
244
|
|
|
245
|
-
if (!
|
|
246
|
-
|
|
245
|
+
if (!nodeFs.existsSync(paths.dataDir)) {
|
|
246
|
+
nodeFs.mkdirSync(paths.dataDir, { recursive: true });
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
// Sweep tmp files left behind by a previously-crashed write
|
|
250
250
|
atomicFile.cleanOrphans(paths.sealed);
|
|
251
251
|
atomicFile.cleanOrphans(paths.plaintext);
|
|
252
252
|
|
|
253
|
-
var hasPlaintext =
|
|
254
|
-
var hasSealed =
|
|
253
|
+
var hasPlaintext = nodeFs.existsSync(paths.plaintext);
|
|
254
|
+
var hasSealed = nodeFs.existsSync(paths.sealed);
|
|
255
255
|
|
|
256
256
|
// Refuse to guess when both files coexist
|
|
257
257
|
if (hasPlaintext && hasSealed) {
|
|
@@ -287,7 +287,7 @@ async function init(opts) {
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
function initPlaintext() {
|
|
290
|
-
if (
|
|
290
|
+
if (nodeFs.existsSync(paths.plaintext)) {
|
|
291
291
|
var loaded;
|
|
292
292
|
try {
|
|
293
293
|
loaded = safeJson.parse(atomicFile.readSync(paths.plaintext), {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
* with the original file untouched.
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
var
|
|
39
|
-
var
|
|
38
|
+
var nodeFs = require("fs");
|
|
39
|
+
var nodePath = require("path");
|
|
40
40
|
var atomicFile = require("../atomic-file");
|
|
41
41
|
var vaultWrap = require("./wrap");
|
|
42
42
|
var { defineClass } = require("../framework-error");
|
|
@@ -48,10 +48,10 @@ var SEALED_NAME = "vault.key.sealed";
|
|
|
48
48
|
|
|
49
49
|
function _paths(dataDir) {
|
|
50
50
|
return {
|
|
51
|
-
plaintext:
|
|
52
|
-
plaintextTmp:
|
|
53
|
-
sealed:
|
|
54
|
-
sealedTmp:
|
|
51
|
+
plaintext: nodePath.join(dataDir, PLAINTEXT_NAME),
|
|
52
|
+
plaintextTmp: nodePath.join(dataDir, PLAINTEXT_NAME + ".tmp"),
|
|
53
|
+
sealed: nodePath.join(dataDir, SEALED_NAME),
|
|
54
|
+
sealedTmp: nodePath.join(dataDir, SEALED_NAME + ".tmp"),
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -60,7 +60,7 @@ function _requireDataDir(opts) {
|
|
|
60
60
|
throw new VaultPassphraseError("vault-passphrase/no-datadir",
|
|
61
61
|
"opts.dataDir is required (path to the framework data directory)");
|
|
62
62
|
}
|
|
63
|
-
if (!
|
|
63
|
+
if (!nodeFs.existsSync(opts.dataDir)) {
|
|
64
64
|
throw new VaultPassphraseError("vault-passphrase/no-datadir",
|
|
65
65
|
"opts.dataDir does not exist: " + opts.dataDir);
|
|
66
66
|
}
|
|
@@ -80,8 +80,8 @@ function _requirePassphrase(opts, fieldName) {
|
|
|
80
80
|
// don't have the original write fd around.
|
|
81
81
|
function _fsyncPath(p) {
|
|
82
82
|
try {
|
|
83
|
-
var fd =
|
|
84
|
-
try { atomicFile.fsync(fd); } finally {
|
|
83
|
+
var fd = nodeFs.openSync(p, "r+");
|
|
84
|
+
try { atomicFile.fsync(fd); } finally { nodeFs.closeSync(fd); }
|
|
85
85
|
} catch (_e) { /* best-effort across filesystems */ }
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -90,13 +90,13 @@ function _fsyncPath(p) {
|
|
|
90
90
|
function preflightSealable(opts) {
|
|
91
91
|
_requireDataDir(opts);
|
|
92
92
|
var p = _paths(opts.dataDir);
|
|
93
|
-
if (!
|
|
93
|
+
if (!nodeFs.existsSync(p.plaintext)) {
|
|
94
94
|
return { ok: false, reason: "plaintext " + PLAINTEXT_NAME + " does not exist — nothing to seal" };
|
|
95
95
|
}
|
|
96
|
-
if (
|
|
96
|
+
if (nodeFs.existsSync(p.sealed)) {
|
|
97
97
|
return { ok: false, reason: SEALED_NAME + " already exists; refusing to overwrite" };
|
|
98
98
|
}
|
|
99
|
-
if (
|
|
99
|
+
if (nodeFs.existsSync(p.sealedTmp)) {
|
|
100
100
|
return { ok: false, reason: "stale " + SEALED_NAME + ".tmp from a previous crash; remove it manually after verifying the directory state" };
|
|
101
101
|
}
|
|
102
102
|
return { ok: true };
|
|
@@ -105,13 +105,13 @@ function preflightSealable(opts) {
|
|
|
105
105
|
function preflightUnsealable(opts) {
|
|
106
106
|
_requireDataDir(opts);
|
|
107
107
|
var p = _paths(opts.dataDir);
|
|
108
|
-
if (!
|
|
108
|
+
if (!nodeFs.existsSync(p.sealed)) {
|
|
109
109
|
return { ok: false, reason: SEALED_NAME + " does not exist — nothing to unseal" };
|
|
110
110
|
}
|
|
111
|
-
if (
|
|
111
|
+
if (nodeFs.existsSync(p.plaintext)) {
|
|
112
112
|
return { ok: false, reason: "plaintext " + PLAINTEXT_NAME + " already exists; refusing to overwrite" };
|
|
113
113
|
}
|
|
114
|
-
if (
|
|
114
|
+
if (nodeFs.existsSync(p.plaintextTmp)) {
|
|
115
115
|
return { ok: false, reason: "stale " + PLAINTEXT_NAME + ".tmp from a previous crash; remove it manually after verifying the directory state" };
|
|
116
116
|
}
|
|
117
117
|
return { ok: true };
|
|
@@ -120,10 +120,10 @@ function preflightUnsealable(opts) {
|
|
|
120
120
|
function preflightRotatable(opts) {
|
|
121
121
|
_requireDataDir(opts);
|
|
122
122
|
var p = _paths(opts.dataDir);
|
|
123
|
-
if (!
|
|
123
|
+
if (!nodeFs.existsSync(p.sealed)) {
|
|
124
124
|
return { ok: false, reason: SEALED_NAME + " does not exist — rotate has nothing to operate on" };
|
|
125
125
|
}
|
|
126
|
-
if (
|
|
126
|
+
if (nodeFs.existsSync(p.sealedTmp)) {
|
|
127
127
|
return { ok: false, reason: "stale " + SEALED_NAME + ".tmp from a previous crash; remove it manually after verifying the directory state" };
|
|
128
128
|
}
|
|
129
129
|
return { ok: true };
|
|
@@ -141,39 +141,39 @@ async function seal(opts) {
|
|
|
141
141
|
var p = _paths(opts.dataDir);
|
|
142
142
|
var keepPlaintext = !!opts.keepPlaintext;
|
|
143
143
|
|
|
144
|
-
var plainBytes =
|
|
144
|
+
var plainBytes = nodeFs.readFileSync(p.plaintext);
|
|
145
145
|
var sealedBytes = await vaultWrap.wrap(plainBytes, opts.passphrase);
|
|
146
146
|
|
|
147
147
|
// Step 1: write sealed.tmp + fsync
|
|
148
|
-
|
|
148
|
+
nodeFs.writeFileSync(p.sealedTmp, sealedBytes, { mode: 0o600 });
|
|
149
149
|
_fsyncPath(p.sealedTmp);
|
|
150
150
|
atomicFile.fsyncDir(opts.dataDir);
|
|
151
151
|
|
|
152
152
|
// Step 2: round-trip verify the .tmp before committing the rename
|
|
153
|
-
var verifyBytes =
|
|
153
|
+
var verifyBytes = nodeFs.readFileSync(p.sealedTmp);
|
|
154
154
|
var unwrapped;
|
|
155
155
|
try {
|
|
156
156
|
unwrapped = await vaultWrap.unwrap(verifyBytes, opts.passphrase);
|
|
157
157
|
} catch (e) {
|
|
158
|
-
try {
|
|
158
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
159
159
|
throw new VaultPassphraseError("vault-passphrase/verify-failed",
|
|
160
160
|
"round-trip verification of sealed file failed: " + ((e && e.message) || String(e)) +
|
|
161
161
|
" — original " + PLAINTEXT_NAME + " is UNCHANGED");
|
|
162
162
|
}
|
|
163
163
|
if (Buffer.compare(unwrapped, plainBytes) !== 0) {
|
|
164
|
-
try {
|
|
164
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
165
165
|
throw new VaultPassphraseError("vault-passphrase/verify-mismatch",
|
|
166
166
|
"round-trip produced different bytes than the original — original " + PLAINTEXT_NAME +
|
|
167
167
|
" is UNCHANGED. Filesystem may be faulty.");
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
// Step 3: atomic rename sealed.tmp → sealed
|
|
171
|
-
|
|
171
|
+
nodeFs.renameSync(p.sealedTmp, p.sealed);
|
|
172
172
|
atomicFile.fsyncDir(opts.dataDir);
|
|
173
173
|
|
|
174
174
|
// Step 4: delete plaintext (unless keepPlaintext)
|
|
175
175
|
if (!keepPlaintext) {
|
|
176
|
-
|
|
176
|
+
nodeFs.unlinkSync(p.plaintext);
|
|
177
177
|
atomicFile.fsyncDir(opts.dataDir);
|
|
178
178
|
}
|
|
179
179
|
|
|
@@ -194,7 +194,7 @@ async function unseal(opts) {
|
|
|
194
194
|
}
|
|
195
195
|
var p = _paths(opts.dataDir);
|
|
196
196
|
|
|
197
|
-
var sealedBytes =
|
|
197
|
+
var sealedBytes = nodeFs.readFileSync(p.sealed);
|
|
198
198
|
var plainBytes;
|
|
199
199
|
try {
|
|
200
200
|
plainBytes = await vaultWrap.unwrap(sealedBytes, opts.passphrase);
|
|
@@ -205,25 +205,25 @@ async function unseal(opts) {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
// Step 1: write plaintext.tmp + fsync
|
|
208
|
-
|
|
208
|
+
nodeFs.writeFileSync(p.plaintextTmp, plainBytes, { mode: 0o600 });
|
|
209
209
|
_fsyncPath(p.plaintextTmp);
|
|
210
210
|
atomicFile.fsyncDir(opts.dataDir);
|
|
211
211
|
|
|
212
212
|
// Step 2: round-trip sanity — re-read tmp and verify
|
|
213
|
-
var verifyBytes =
|
|
213
|
+
var verifyBytes = nodeFs.readFileSync(p.plaintextTmp);
|
|
214
214
|
if (Buffer.compare(verifyBytes, plainBytes) !== 0) {
|
|
215
|
-
try {
|
|
215
|
+
try { nodeFs.unlinkSync(p.plaintextTmp); } catch (_e) { /* cleanup */ }
|
|
216
216
|
throw new VaultPassphraseError("vault-passphrase/verify-mismatch",
|
|
217
217
|
"plaintext.tmp re-read differs from in-memory bytes — filesystem may be faulty. " +
|
|
218
218
|
SEALED_NAME + " is UNCHANGED");
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// Step 3: atomic rename plaintext.tmp → plaintext
|
|
222
|
-
|
|
222
|
+
nodeFs.renameSync(p.plaintextTmp, p.plaintext);
|
|
223
223
|
atomicFile.fsyncDir(opts.dataDir);
|
|
224
224
|
|
|
225
225
|
// Step 4: delete sealed file
|
|
226
|
-
|
|
226
|
+
nodeFs.unlinkSync(p.sealed);
|
|
227
227
|
atomicFile.fsyncDir(opts.dataDir);
|
|
228
228
|
|
|
229
229
|
return { plaintextPath: p.plaintext };
|
|
@@ -245,7 +245,7 @@ async function rotate(opts) {
|
|
|
245
245
|
}
|
|
246
246
|
var p = _paths(opts.dataDir);
|
|
247
247
|
|
|
248
|
-
var sealedBytes =
|
|
248
|
+
var sealedBytes = nodeFs.readFileSync(p.sealed);
|
|
249
249
|
var plainBytes;
|
|
250
250
|
try {
|
|
251
251
|
plainBytes = await vaultWrap.unwrap(sealedBytes, opts.oldPassphrase);
|
|
@@ -257,23 +257,23 @@ async function rotate(opts) {
|
|
|
257
257
|
var newSealedBytes = await vaultWrap.wrap(plainBytes, opts.newPassphrase);
|
|
258
258
|
|
|
259
259
|
// Step 1: write new sealed.tmp + fsync
|
|
260
|
-
|
|
260
|
+
nodeFs.writeFileSync(p.sealedTmp, newSealedBytes, { mode: 0o600 });
|
|
261
261
|
_fsyncPath(p.sealedTmp);
|
|
262
262
|
atomicFile.fsyncDir(opts.dataDir);
|
|
263
263
|
|
|
264
264
|
// Step 2: round-trip verify with NEW passphrase, AND assert unwrap
|
|
265
265
|
// with the OLD passphrase fails — otherwise the rotation didn't take.
|
|
266
|
-
var verifyBytes =
|
|
266
|
+
var verifyBytes = nodeFs.readFileSync(p.sealedTmp);
|
|
267
267
|
var verifyPlain;
|
|
268
268
|
try { verifyPlain = await vaultWrap.unwrap(verifyBytes, opts.newPassphrase); }
|
|
269
269
|
catch (e) {
|
|
270
|
-
try {
|
|
270
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
271
271
|
throw new VaultPassphraseError("vault-passphrase/verify-failed",
|
|
272
272
|
"round-trip with new passphrase failed: " + ((e && e.message) || String(e)) +
|
|
273
273
|
" — " + SEALED_NAME + " is UNCHANGED");
|
|
274
274
|
}
|
|
275
275
|
if (Buffer.compare(verifyPlain, plainBytes) !== 0) {
|
|
276
|
-
try {
|
|
276
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
277
277
|
throw new VaultPassphraseError("vault-passphrase/verify-mismatch",
|
|
278
278
|
"rotated sealed file decrypts under new passphrase but to different bytes — " +
|
|
279
279
|
SEALED_NAME + " is UNCHANGED. Filesystem may be faulty.");
|
|
@@ -283,7 +283,7 @@ async function rotate(opts) {
|
|
|
283
283
|
// input unchanged — refuse to commit.
|
|
284
284
|
try {
|
|
285
285
|
await vaultWrap.unwrap(verifyBytes, opts.oldPassphrase);
|
|
286
|
-
try {
|
|
286
|
+
try { nodeFs.unlinkSync(p.sealedTmp); } catch (_e) { /* cleanup */ }
|
|
287
287
|
throw new VaultPassphraseError("vault-passphrase/rotate-noop",
|
|
288
288
|
"old passphrase still unwraps the new sealed bytes — rotation did not take effect");
|
|
289
289
|
} catch (e) {
|
|
@@ -292,7 +292,7 @@ async function rotate(opts) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// Step 3: atomic rename — swap in the new sealed file
|
|
295
|
-
|
|
295
|
+
nodeFs.renameSync(p.sealedTmp, p.sealed);
|
|
296
296
|
atomicFile.fsyncDir(opts.dataDir);
|
|
297
297
|
|
|
298
298
|
return { sealedPath: p.sealed };
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* exposure to later env-dump surfaces. This doesn't zero the memory
|
|
24
24
|
* (JavaScript can't) but does remove the env-object reference.
|
|
25
25
|
*/
|
|
26
|
-
var
|
|
26
|
+
var nodeFs = require("fs");
|
|
27
27
|
var readline = require("readline");
|
|
28
28
|
var safeEnv = require("../parsers/safe-env");
|
|
29
29
|
var safeBuffer = require("../safe-buffer");
|
|
@@ -83,7 +83,7 @@ async function fromFile(filePath, opts) {
|
|
|
83
83
|
}
|
|
84
84
|
var raw;
|
|
85
85
|
try {
|
|
86
|
-
raw =
|
|
86
|
+
raw = nodeFs.readFileSync(filePath);
|
|
87
87
|
} catch (e) {
|
|
88
88
|
throw new Error("failed to read " + envVars.file + " (" + filePath + "): " + e.code);
|
|
89
89
|
}
|