@blamejs/core 0.15.12 → 0.15.14
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/index.js +2 -0
- package/lib/a2a-tasks.js +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +13 -14
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +54 -41
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/validate-opts.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
var numericBounds = require("./numeric-bounds");
|
|
31
|
+
var pick = require("./pick");
|
|
31
32
|
|
|
32
33
|
function _format(primitive, unknownKey, allowedKeys) {
|
|
33
34
|
return primitive + ": unknown option '" + unknownKey + "'. " +
|
|
@@ -78,13 +79,18 @@ function auditShape(audit, callerLabel, errorClass, code) {
|
|
|
78
79
|
// _throw — shared error-emission for the optional* validators. Routes
|
|
79
80
|
// through the caller's framework-error class when supplied, falls back
|
|
80
81
|
// to plain Error so this helper itself stays decoupled from any one
|
|
81
|
-
// error hierarchy.
|
|
82
|
-
|
|
82
|
+
// error hierarchy. `permanent` (optional) is forwarded as the framework
|
|
83
|
+
// error's third constructor argument so callers whose config-time failure
|
|
84
|
+
// is non-retryable (a misconfigured dependency never becomes valid on
|
|
85
|
+
// retry) keep that flag when they route through these validators instead
|
|
86
|
+
// of hand-throwing; omitted → undefined → the class default (false), so
|
|
87
|
+
// existing callers are unaffected.
|
|
88
|
+
function _throw(errorClass, code, msg, defaultCode, permanent) {
|
|
83
89
|
if (errorClass && errorClass.factory) {
|
|
84
|
-
throw errorClass.factory(code || "BAD_OPT", msg);
|
|
90
|
+
throw errorClass.factory(code || "BAD_OPT", msg, permanent);
|
|
85
91
|
}
|
|
86
92
|
if (typeof errorClass === "function") {
|
|
87
|
-
throw new errorClass(code || defaultCode, msg);
|
|
93
|
+
throw new errorClass(code || defaultCode, msg, permanent);
|
|
88
94
|
}
|
|
89
95
|
throw new Error(msg);
|
|
90
96
|
}
|
|
@@ -132,6 +138,15 @@ function optionalFiniteNonNegative(value, label, errorClass, code) {
|
|
|
132
138
|
return value;
|
|
133
139
|
}
|
|
134
140
|
|
|
141
|
+
function optionalDate(value, label, errorClass, code) {
|
|
142
|
+
if (value === undefined || value === null) return value;
|
|
143
|
+
if (!(value instanceof Date) || !isFinite(value.getTime())) {
|
|
144
|
+
_throw(errorClass, code, (label || "opt") + " must be a valid Date",
|
|
145
|
+
"validate-opts/bad-date");
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
|
|
135
150
|
function optionalPositiveFinite(value, label, errorClass, code) {
|
|
136
151
|
if (value === undefined || value === null) return value;
|
|
137
152
|
if (typeof value !== "number" || !isFinite(value) || value <= 0) {
|
|
@@ -218,18 +233,25 @@ function requireObject(opts, callerLabel, errorClass, code) {
|
|
|
218
233
|
// (b.agent.*.reseal stores, b.dsr / b.outbox create() backends, etc.)
|
|
219
234
|
// into one definition. Throws on null / non-object / any missing-or-
|
|
220
235
|
// non-function method; returns obj on success.
|
|
221
|
-
|
|
236
|
+
//
|
|
237
|
+
// `permanent` (optional) forwards to the framework error's permanent flag
|
|
238
|
+
// — pass true for a config-time dependency check whose failure is not
|
|
239
|
+
// retryable (a backend/store/vault missing its contract never becomes
|
|
240
|
+
// valid on retry). Omitted → the error class default (false), matching
|
|
241
|
+
// the historical bare `new Err(code, msg)` shape, so existing callers are
|
|
242
|
+
// unchanged.
|
|
243
|
+
function requireMethods(obj, methods, callerLabel, errorClass, code, permanent) {
|
|
222
244
|
var label = callerLabel || "dependency";
|
|
223
245
|
if (!obj || typeof obj !== "object") {
|
|
224
246
|
_throw(errorClass, code, label + " must be an object exposing { " +
|
|
225
247
|
methods.join(", ") + " }, got " + (obj === null ? "null" : typeof obj),
|
|
226
|
-
"validate-opts/bad-methods-object");
|
|
248
|
+
"validate-opts/bad-methods-object", permanent);
|
|
227
249
|
}
|
|
228
250
|
for (var i = 0; i < methods.length; i += 1) {
|
|
229
251
|
if (typeof obj[methods[i]] !== "function") {
|
|
230
252
|
_throw(errorClass, code, label + " must expose a " + methods[i] +
|
|
231
253
|
"() method (requires { " + methods.join(", ") + " })",
|
|
232
|
-
"validate-opts/missing-method");
|
|
254
|
+
"validate-opts/missing-method", permanent);
|
|
233
255
|
}
|
|
234
256
|
}
|
|
235
257
|
return obj;
|
|
@@ -331,6 +353,128 @@ function optionalPlainObject(value, label, errorClass, code, description) {
|
|
|
331
353
|
return value;
|
|
332
354
|
}
|
|
333
355
|
|
|
356
|
+
// _SHAPE_RULES — rule-token → validator dispatch table for `shape`. Each
|
|
357
|
+
// validator has the uniform (value, label, errorClass, code) signature, so the
|
|
358
|
+
// table drives them all from a declarative schema.
|
|
359
|
+
var _SHAPE_RULES = {
|
|
360
|
+
"required-string": requireNonEmptyString,
|
|
361
|
+
"optional-string": optionalNonEmptyString,
|
|
362
|
+
"optional-string-array": optionalNonEmptyStringArray,
|
|
363
|
+
"optional-boolean": optionalBoolean,
|
|
364
|
+
"optional-positive-int": optionalPositiveInt,
|
|
365
|
+
"optional-positive-finite": optionalPositiveFinite,
|
|
366
|
+
"optional-non-negative": optionalFiniteNonNegative,
|
|
367
|
+
"optional-date": optionalDate,
|
|
368
|
+
"optional-function": optionalFunction,
|
|
369
|
+
"optional-plain-object": optionalPlainObject,
|
|
370
|
+
"optional-port": optionalPort,
|
|
371
|
+
// numeric-bounds finite-int family (rejects Infinity/NaN, unlike the
|
|
372
|
+
// optional-positive-int sugar) — so byte/count caps declare in the shape
|
|
373
|
+
// rather than a separate numericBounds call outside it.
|
|
374
|
+
"optional-positive-finite-int": numericBounds.requirePositiveFiniteIntIfPresent,
|
|
375
|
+
"optional-non-negative-finite-int": numericBounds.requireNonNegativeFiniteIntIfPresent,
|
|
376
|
+
"required-positive-finite-int": numericBounds.requirePositiveFiniteInt,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// shape — declarative opts validator. Collapses the `requireObject(opts) +
|
|
380
|
+
// requireNonEmptyString(opts.a) + optionalPositiveFinite(opts.b) + ...` preamble
|
|
381
|
+
// that every create()/build() factory re-rolls into one schema-driven call.
|
|
382
|
+
// The schema is expressive enough that a factory never has to contort its
|
|
383
|
+
// validation to fit a fixed vocabulary — each opt's rule is ANY of:
|
|
384
|
+
//
|
|
385
|
+
// - a rule TOKEN (see _SHAPE_RULES) — sugar for the common per-field checks,
|
|
386
|
+
// using the call-wide `code`;
|
|
387
|
+
// - a per-field DESCRIPTOR `{ rule: "<token>", code?, label? }` — keeps a
|
|
388
|
+
// DISTINCT per-field code (BAD_THIRD_PARTY vs BAD_CONSUMER_REF) or a custom
|
|
389
|
+
// label, so behavior a per-field test asserts is preserved;
|
|
390
|
+
// - an injected-DEPENDENCY `{ methods: [...], optional?, code?, label? }` —
|
|
391
|
+
// validated via requireMethods;
|
|
392
|
+
// - a NESTED `{ shape: {...}, optional?, code?, label? }` — recurses into a
|
|
393
|
+
// sub-object field (e.g. opts.authServer.{issuer,jwksUri});
|
|
394
|
+
// - an arbitrary VALIDATOR FUNCTION `(value, label, errorClass, code, opts)
|
|
395
|
+
// => void` that throws on invalid — the universal hatch for a bespoke check.
|
|
396
|
+
// It receives the whole `opts` as the 5th arg, so CROSS-FIELD logic ("field
|
|
397
|
+
// B required when opts.a === X", a custom message, a numeric-bounds call)
|
|
398
|
+
// lives IN the shape rather than as a hand-rolled check outside it.
|
|
399
|
+
//
|
|
400
|
+
// Validates `opts` is an object, then dispatches each declared field; returns
|
|
401
|
+
// opts. An unknown rule token throws at call time — a schema typo is the
|
|
402
|
+
// author's bug, surfaced loudly rather than silently skipping a field.
|
|
403
|
+
//
|
|
404
|
+
// The schema is ALWAYS the authoritative, exhaustive opts contract — there is
|
|
405
|
+
// no opt-in: any key present on `opts` that the schema does not declare (nor
|
|
406
|
+
// list in `options.allow`) is rejected. This is mandatory by design, so the
|
|
407
|
+
// "future code adds an opt but forgets to validate it" gap cannot reopen — an
|
|
408
|
+
// undeclared opt can't be silently consumed, because it is refused here;
|
|
409
|
+
// adding it forces declaring its rule in the schema, where it is validated.
|
|
410
|
+
// (A field declared in the schema is always validated; exhaustiveness adds the
|
|
411
|
+
// converse — nothing reaches the body unvalidated.) `options.allow` is the only
|
|
412
|
+
// escape: an explicit list of keys a factory forwards to a sub-component rather
|
|
413
|
+
// than validating locally — there is no "skip the contract" mode.
|
|
414
|
+
function shape(opts, schema, callerLabel, errorClass, code, options) {
|
|
415
|
+
requireObject(opts, callerLabel, errorClass, code);
|
|
416
|
+
var fields = Object.keys(schema);
|
|
417
|
+
for (var i = 0; i < fields.length; i += 1) {
|
|
418
|
+
var field = fields[i];
|
|
419
|
+
var rule = schema[field];
|
|
420
|
+
var fieldCode = code;
|
|
421
|
+
var label = (callerLabel || "opts") + ": " + field;
|
|
422
|
+
var value = opts[field];
|
|
423
|
+
// Arbitrary validator function — the universal hatch. Receives the whole
|
|
424
|
+
// `opts` as a 5th arg so a rule can do CROSS-FIELD validation (e.g. "field B
|
|
425
|
+
// is required when opts.a === 'material'") without dropping back to inline
|
|
426
|
+
// checks outside the shape.
|
|
427
|
+
if (typeof rule === "function") { rule(value, label, errorClass, fieldCode, opts); continue; }
|
|
428
|
+
if (rule && typeof rule === "object") {
|
|
429
|
+
if (Array.isArray(rule.methods)) {
|
|
430
|
+
if (rule.optional && (value === undefined || value === null)) continue;
|
|
431
|
+
requireMethods(value, rule.methods, rule.label || label, errorClass, rule.code || code, rule.permanent);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
// Nested sub-object: validate it is an object, then recurse.
|
|
435
|
+
if (rule.shape && typeof rule.shape === "object") {
|
|
436
|
+
if (rule.optional && (value === undefined || value === null)) continue;
|
|
437
|
+
requireObject(value, rule.label || label, errorClass, rule.code || code);
|
|
438
|
+
shape(value, rule.shape, rule.label || label, errorClass, rule.code || code);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
// Per-field descriptor: { rule, code?, label? }.
|
|
442
|
+
if (typeof rule.rule === "string") {
|
|
443
|
+
if (typeof rule.code === "string") fieldCode = rule.code;
|
|
444
|
+
if (typeof rule.label === "string") label = rule.label;
|
|
445
|
+
rule = rule.rule;
|
|
446
|
+
} else {
|
|
447
|
+
_throw(errorClass, code, (callerLabel || "opts") +
|
|
448
|
+
": unsupported shape rule object for field " + field,
|
|
449
|
+
"validate-opts/bad-shape-rule");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (rule === "required-object") { requireObject(value, label, errorClass, fieldCode); continue; }
|
|
453
|
+
var fn = _SHAPE_RULES[rule];
|
|
454
|
+
if (typeof fn !== "function") {
|
|
455
|
+
_throw(errorClass, code, (callerLabel || "opts") +
|
|
456
|
+
": unknown shape rule " + JSON.stringify(rule) + " for field " + field,
|
|
457
|
+
"validate-opts/bad-shape-rule");
|
|
458
|
+
}
|
|
459
|
+
fn(value, label, errorClass, fieldCode);
|
|
460
|
+
}
|
|
461
|
+
// Exhaustive contract enforcement — always on (the schema is complete).
|
|
462
|
+
var declared = Object.create(null);
|
|
463
|
+
for (var d = 0; d < fields.length; d += 1) declared[fields[d]] = true;
|
|
464
|
+
var allowList = (options && options.allow) || [];
|
|
465
|
+
for (var a = 0; a < allowList.length; a += 1) declared[allowList[a]] = true;
|
|
466
|
+
var present = Object.keys(opts);
|
|
467
|
+
for (var p = 0; p < present.length; p += 1) {
|
|
468
|
+
if (!declared[present[p]]) {
|
|
469
|
+
_throw(errorClass, code, (callerLabel || "opts") +
|
|
470
|
+
": unknown opt " + JSON.stringify(present[p]) +
|
|
471
|
+
" (not in the validated shape; add it to the schema or pass options.allow)",
|
|
472
|
+
"validate-opts/unknown-opt");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return opts;
|
|
476
|
+
}
|
|
477
|
+
|
|
334
478
|
// makeAuditEmitter — closure factory parallel to safeAsync.makeDropCallback.
|
|
335
479
|
// Replaces the per-file `function _emit(action, info) { if (!audit) return;
|
|
336
480
|
// try { audit.safeEmit(Object.assign({ action: action }, info || {})); }
|
|
@@ -412,7 +556,7 @@ function assignOwnEnumerable(target, source, reservedKeys) {
|
|
|
412
556
|
var entries = [];
|
|
413
557
|
for (var i = 0; i < keys.length; i += 1) {
|
|
414
558
|
var k = keys[i];
|
|
415
|
-
if (k
|
|
559
|
+
if (pick.isPoisonedKey(k)) continue;
|
|
416
560
|
if (reserved[k]) continue;
|
|
417
561
|
entries.push([k, source[k]]);
|
|
418
562
|
}
|
|
@@ -445,6 +589,7 @@ module.exports.auditShape = auditShape;
|
|
|
445
589
|
module.exports.optionalBoolean = optionalBoolean;
|
|
446
590
|
module.exports.optionalPositiveInt = optionalPositiveInt;
|
|
447
591
|
module.exports.optionalFiniteNonNegative = optionalFiniteNonNegative;
|
|
592
|
+
module.exports.optionalDate = optionalDate;
|
|
448
593
|
module.exports.optionalPositiveFinite = optionalPositiveFinite;
|
|
449
594
|
module.exports.optionalPort = optionalPort;
|
|
450
595
|
module.exports.optionalFunction = optionalFunction;
|
|
@@ -456,6 +601,7 @@ module.exports.requireNonEmptyString = requireNonEmptyString;
|
|
|
456
601
|
module.exports.observabilityShape = observabilityShape;
|
|
457
602
|
module.exports.requireObject = requireObject;
|
|
458
603
|
module.exports.requireMethods = requireMethods;
|
|
604
|
+
module.exports.shape = shape;
|
|
459
605
|
module.exports.applyDefaults = applyDefaults;
|
|
460
606
|
module.exports.makeAuditEmitter = makeAuditEmitter;
|
|
461
607
|
module.exports.makeNamespacedEmitters = makeNamespacedEmitters;
|
package/lib/vault/index.js
CHANGED
|
@@ -681,6 +681,11 @@ module.exports = {
|
|
|
681
681
|
init: init,
|
|
682
682
|
seal: seal,
|
|
683
683
|
unseal: unseal,
|
|
684
|
+
// The default sealed-storage Store: a { seal, unseal } pair backed by the
|
|
685
|
+
// in-process vault key. b.cert.create (and other sealed-disk consumers)
|
|
686
|
+
// resolve `opts.vault || getDefaultStore()`; documented in their @opts.
|
|
687
|
+
getDefaultStore: function () { return { seal: seal, unseal: unseal }; },
|
|
688
|
+
Store: { seal: seal, unseal: unseal },
|
|
684
689
|
getDerivedHashSalt: getDerivedHashSalt,
|
|
685
690
|
getDerivedHashMacKey: getDerivedHashMacKey,
|
|
686
691
|
_zeroizeAndReplace: _zeroizeAndReplace,
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
var nodeFs = require("node:fs");
|
|
39
39
|
var nodePath = require("node:path");
|
|
40
40
|
var atomicFile = require("../atomic-file");
|
|
41
|
+
var C = require("../constants");
|
|
41
42
|
var frameworkFiles = require("../framework-files");
|
|
42
43
|
var vaultWrap = require("./wrap");
|
|
43
44
|
var { defineClass } = require("../framework-error");
|
|
@@ -75,17 +76,6 @@ function _requirePassphrase(opts, fieldName) {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
// fsync-by-path semantic: open then sync then close. atomicFile.fsync
|
|
79
|
-
// expects an already-open fd; this wrapper opens the file we just
|
|
80
|
-
// wrote, flushes its contents, and closes — the right shape when we
|
|
81
|
-
// don't have the original write fd around.
|
|
82
|
-
function _fsyncPath(p) {
|
|
83
|
-
try {
|
|
84
|
-
var fd = nodeFs.openSync(p, "r+");
|
|
85
|
-
try { atomicFile.fsync(fd); } finally { nodeFs.closeSync(fd); }
|
|
86
|
-
} catch (_e) { /* best-effort across filesystems */ }
|
|
87
|
-
}
|
|
88
|
-
|
|
89
79
|
// ---- Pre-flight checks (no side effects) ----
|
|
90
80
|
|
|
91
81
|
function preflightSealable(opts) {
|
|
@@ -142,16 +132,18 @@ async function seal(opts) {
|
|
|
142
132
|
var p = _paths(opts.dataDir);
|
|
143
133
|
var keepPlaintext = !!opts.keepPlaintext;
|
|
144
134
|
|
|
145
|
-
var plainBytes =
|
|
135
|
+
var plainBytes = atomicFile.fdSafeReadSync(p.plaintext, { maxBytes: C.BYTES.kib(64) });
|
|
146
136
|
var sealedBytes = await vaultWrap.wrap(plainBytes, opts.passphrase);
|
|
147
137
|
|
|
148
|
-
// Step 1: write sealed.tmp +
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
// Step 1: write sealed.tmp (exclusive + no-follow create, fsynced) — a
|
|
139
|
+
// bare writeFileSync to this PREDICTABLE temp name would follow a symlink
|
|
140
|
+
// pre-planted at sealed.tmp (CWE-59); writeExclSync clears any stale entry
|
|
141
|
+
// then creates with O_EXCL | O_NOFOLLOW and fsyncs the bytes.
|
|
142
|
+
atomicFile.writeExclSync(p.sealedTmp, sealedBytes, { fileMode: 0o600 });
|
|
151
143
|
atomicFile.fsyncDir(opts.dataDir);
|
|
152
144
|
|
|
153
145
|
// Step 2: round-trip verify the .tmp before committing the rename
|
|
154
|
-
var verifyBytes =
|
|
146
|
+
var verifyBytes = atomicFile.fdSafeReadSync(p.sealedTmp, { maxBytes: C.BYTES.kib(64) });
|
|
155
147
|
var unwrapped;
|
|
156
148
|
try {
|
|
157
149
|
unwrapped = await vaultWrap.unwrap(verifyBytes, opts.passphrase);
|
|
@@ -195,7 +187,7 @@ async function unseal(opts) {
|
|
|
195
187
|
}
|
|
196
188
|
var p = _paths(opts.dataDir);
|
|
197
189
|
|
|
198
|
-
var sealedBytes =
|
|
190
|
+
var sealedBytes = atomicFile.fdSafeReadSync(p.sealed, { maxBytes: C.BYTES.kib(64) });
|
|
199
191
|
var plainBytes;
|
|
200
192
|
try {
|
|
201
193
|
plainBytes = await vaultWrap.unwrap(sealedBytes, opts.passphrase);
|
|
@@ -205,13 +197,15 @@ async function unseal(opts) {
|
|
|
205
197
|
" — " + SEALED_NAME + " is UNCHANGED");
|
|
206
198
|
}
|
|
207
199
|
|
|
208
|
-
// Step 1: write plaintext.tmp +
|
|
209
|
-
|
|
210
|
-
|
|
200
|
+
// Step 1: write plaintext.tmp (exclusive + no-follow create, fsynced) — a
|
|
201
|
+
// bare writeFileSync to this PREDICTABLE temp name would follow a symlink
|
|
202
|
+
// pre-planted at plaintext.tmp (CWE-59); writeExclSync clears any stale
|
|
203
|
+
// entry then creates with O_EXCL | O_NOFOLLOW and fsyncs the bytes.
|
|
204
|
+
atomicFile.writeExclSync(p.plaintextTmp, plainBytes, { fileMode: 0o600 });
|
|
211
205
|
atomicFile.fsyncDir(opts.dataDir);
|
|
212
206
|
|
|
213
207
|
// Step 2: round-trip sanity — re-read tmp and verify
|
|
214
|
-
var verifyBytes =
|
|
208
|
+
var verifyBytes = atomicFile.fdSafeReadSync(p.plaintextTmp, { maxBytes: C.BYTES.kib(64) });
|
|
215
209
|
if (Buffer.compare(verifyBytes, plainBytes) !== 0) {
|
|
216
210
|
try { nodeFs.unlinkSync(p.plaintextTmp); } catch (_e) { /* cleanup */ }
|
|
217
211
|
throw new VaultPassphraseError("vault-passphrase/verify-mismatch",
|
|
@@ -246,7 +240,7 @@ async function rotate(opts) {
|
|
|
246
240
|
}
|
|
247
241
|
var p = _paths(opts.dataDir);
|
|
248
242
|
|
|
249
|
-
var sealedBytes =
|
|
243
|
+
var sealedBytes = atomicFile.fdSafeReadSync(p.sealed, { maxBytes: C.BYTES.kib(64) });
|
|
250
244
|
var plainBytes;
|
|
251
245
|
try {
|
|
252
246
|
plainBytes = await vaultWrap.unwrap(sealedBytes, opts.oldPassphrase);
|
|
@@ -257,14 +251,16 @@ async function rotate(opts) {
|
|
|
257
251
|
}
|
|
258
252
|
var newSealedBytes = await vaultWrap.wrap(plainBytes, opts.newPassphrase);
|
|
259
253
|
|
|
260
|
-
// Step 1: write new sealed.tmp +
|
|
261
|
-
|
|
262
|
-
|
|
254
|
+
// Step 1: write new sealed.tmp (exclusive + no-follow create, fsynced) — a
|
|
255
|
+
// bare writeFileSync to this PREDICTABLE temp name would follow a symlink
|
|
256
|
+
// pre-planted at sealed.tmp (CWE-59); writeExclSync clears any stale entry
|
|
257
|
+
// then creates with O_EXCL | O_NOFOLLOW and fsyncs the bytes.
|
|
258
|
+
atomicFile.writeExclSync(p.sealedTmp, newSealedBytes, { fileMode: 0o600 });
|
|
263
259
|
atomicFile.fsyncDir(opts.dataDir);
|
|
264
260
|
|
|
265
261
|
// Step 2: round-trip verify with NEW passphrase, AND assert unwrap
|
|
266
262
|
// with the OLD passphrase fails — otherwise the rotation didn't take.
|
|
267
|
-
var verifyBytes =
|
|
263
|
+
var verifyBytes = atomicFile.fdSafeReadSync(p.sealedTmp, { maxBytes: C.BYTES.kib(64) });
|
|
268
264
|
var verifyPlain;
|
|
269
265
|
try { verifyPlain = await vaultWrap.unwrap(verifyBytes, opts.newPassphrase); }
|
|
270
266
|
catch (e) {
|
|
@@ -23,10 +23,10 @@
|
|
|
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 nodeFs = require("node:fs");
|
|
27
26
|
var readline = require("node:readline");
|
|
28
27
|
var safeEnv = require("../parsers/safe-env");
|
|
29
28
|
var safeBuffer = require("../safe-buffer");
|
|
29
|
+
var atomicFile = require("../atomic-file");
|
|
30
30
|
|
|
31
31
|
var MAX_PASSPHRASE_BYTES = 4096;
|
|
32
32
|
|
|
@@ -83,9 +83,14 @@ async function fromFile(filePath, opts) {
|
|
|
83
83
|
}
|
|
84
84
|
var raw;
|
|
85
85
|
try {
|
|
86
|
-
|
|
86
|
+
// Cap the passphrase-file read at MAX_PASSPHRASE_BYTES at READ time (was only
|
|
87
|
+
// checked AFTER the whole file was in memory), so a swapped multi-GiB target
|
|
88
|
+
// can't OOM boot before validatePassphraseBuffer. NO refuseSymlink: the
|
|
89
|
+
// BLAMEJS_VAULT_PASSPHRASE_FILE is frequently a k8s projected-secret mount,
|
|
90
|
+
// which is a symlink chain — following it is correct.
|
|
91
|
+
raw = atomicFile.fdSafeReadSync(filePath, { maxBytes: MAX_PASSPHRASE_BYTES });
|
|
87
92
|
} catch (e) {
|
|
88
|
-
throw new Error("failed to read " + envVars.file + " (" + filePath + "): " + e.code);
|
|
93
|
+
throw new Error("failed to read " + envVars.file + " (" + filePath + "): " + (e.code || e.message));
|
|
89
94
|
}
|
|
90
95
|
var buf = trimTrailingNewlines(raw);
|
|
91
96
|
validatePassphraseBuffer(buf, "file source (" + filePath + ")");
|
package/lib/vault/rotate.js
CHANGED
|
@@ -493,16 +493,11 @@ function _emit(cb, ev) {
|
|
|
493
493
|
// staging dir is already 0o700 owner-only, so this is defense in depth
|
|
494
494
|
// against a same-user pre-plant / symlink swap (CWE-377 / CWE-379 / CWE-59).
|
|
495
495
|
function _writeStagedFileExclusive(p, data) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
nodeFs.writeFileSync(fd, data);
|
|
502
|
-
nodeFs.fsyncSync(fd);
|
|
503
|
-
} finally {
|
|
504
|
-
nodeFs.closeSync(fd);
|
|
505
|
-
}
|
|
496
|
+
// The clear-stale + O_EXCL|O_NOFOLLOW create + write + fsync sequence now
|
|
497
|
+
// lives in the atomic-file primitive (also used by the vault passphrase
|
|
498
|
+
// seal/unseal staging writes) so every staged exclusive write shares one
|
|
499
|
+
// implementation.
|
|
500
|
+
atomicFile.writeExclSync(p, data, { fileMode: 0o600 });
|
|
506
501
|
}
|
|
507
502
|
|
|
508
503
|
function _reSealValue(sealedValue, oldKeys, newKeys) {
|
|
@@ -759,7 +754,7 @@ async function rotate(opts) {
|
|
|
759
754
|
// Stage via the exclusive-create + fsync helper rather than a plain copy,
|
|
760
755
|
// so the verbatim file is durable at write time (no later by-path fsync)
|
|
761
756
|
// and a pre-planted file/symlink at the staging path hard-fails.
|
|
762
|
-
_writeStagedFileExclusive(dest,
|
|
757
|
+
_writeStagedFileExclusive(dest, atomicFile.fdSafeReadSync(src, { maxBytes: C.BYTES.mib(64) }));
|
|
763
758
|
}
|
|
764
759
|
for (var vd = 0; vd < paths.verbatimDirs.length; vd++) {
|
|
765
760
|
var dent = paths.verbatimDirs[vd];
|
|
@@ -797,7 +792,7 @@ async function rotate(opts) {
|
|
|
797
792
|
var dbKeySealedPath = nodePath.join(dataDir, paths.dbKeySealed);
|
|
798
793
|
var dbKey = null;
|
|
799
794
|
if (nodeFs.existsSync(dbKeySealedPath)) {
|
|
800
|
-
var sealedKey =
|
|
795
|
+
var sealedKey = atomicFile.fdSafeReadSync(dbKeySealedPath, { maxBytes: C.BYTES.kib(64), encoding: "utf8" }).trim();
|
|
801
796
|
if (vaultAad.isAadSealed(sealedKey)) {
|
|
802
797
|
// AAD-bound db.key.enc (db.js since v0.14.7): unseal under the OLD
|
|
803
798
|
// root with the deployment-context AAD, then re-emit under the NEW
|
|
@@ -832,7 +827,7 @@ async function rotate(opts) {
|
|
|
832
827
|
}
|
|
833
828
|
continue;
|
|
834
829
|
}
|
|
835
|
-
var current =
|
|
830
|
+
var current = atomicFile.fdSafeReadSync(asSrc, { maxBytes: C.BYTES.mib(1), encoding: "utf8" }).trim();
|
|
836
831
|
if (current.indexOf(C.VAULT_PREFIX) !== 0) {
|
|
837
832
|
throw new VaultRotateError("vault-rotate/bad-sealed",
|
|
838
833
|
"rotate: sealed file does not start with the vault prefix: " + ase.relativePath);
|
|
@@ -854,11 +849,11 @@ async function rotate(opts) {
|
|
|
854
849
|
// Stage via the exclusive-create + fsync helper (not a plain copy) so the
|
|
855
850
|
// salt is durable at write time and no later by-path fsync is needed.
|
|
856
851
|
_writeStagedFileExclusive(nodePath.join(stagingDir, "vault.derived-hash-salt"),
|
|
857
|
-
|
|
852
|
+
atomicFile.fdSafeReadSync(saltSrc, { maxBytes: C.BYTES.kib(4) }));
|
|
858
853
|
}
|
|
859
854
|
var macSrc = nodePath.join(dataDir, "vault.derived-hash-mac.sealed");
|
|
860
855
|
if (nodeFs.existsSync(macSrc)) {
|
|
861
|
-
var macCurrent =
|
|
856
|
+
var macCurrent = atomicFile.fdSafeReadSync(macSrc, { maxBytes: C.BYTES.kib(64), encoding: "utf8" }).trim();
|
|
862
857
|
if (macCurrent.indexOf(C.VAULT_PREFIX) === 0) {
|
|
863
858
|
_writeStagedFileExclusive(nodePath.join(stagingDir, "vault.derived-hash-mac.sealed"),
|
|
864
859
|
_reSealValue(macCurrent, oldKeys, newKeys));
|
|
@@ -873,7 +868,7 @@ async function rotate(opts) {
|
|
|
873
868
|
var verifyResult = null;
|
|
874
869
|
|
|
875
870
|
if (nodeFs.existsSync(encDbPath) && dbKey) {
|
|
876
|
-
var packed =
|
|
871
|
+
var packed = atomicFile.fdSafeReadSync(encDbPath, { maxBytes: C.BYTES.gib(2) });
|
|
877
872
|
// db.enc is XChaCha20-Poly1305-sealed AAD-bound to its dataDir
|
|
878
873
|
// (db.js _dbEncAad). Read with the dataDir AAD; retry without AAD for
|
|
879
874
|
// pre-AAD envelopes (mirrors db.js:765-768). The in-place swap keeps
|
|
@@ -945,7 +940,7 @@ async function rotate(opts) {
|
|
|
945
940
|
// _writeStagedFileExclusive — exclusive + no-follow create, owner-only
|
|
946
941
|
// 0o600 — so a same-user pre-plant or symlink swap is a hard failure
|
|
947
942
|
// rather than a followed write, and the bytes never inherit a wider mode.
|
|
948
|
-
var rotatedBytes =
|
|
943
|
+
var rotatedBytes = atomicFile.fdSafeReadSync(tmpDbPath, { maxBytes: C.BYTES.gib(2) });
|
|
949
944
|
// Re-encrypt under the SAME dataDir AAD so db.init's AAD-first open
|
|
950
945
|
// succeeds after the staged dir is swapped over dataDir in place.
|
|
951
946
|
_writeStagedFileExclusive(nodePath.join(stagingDir, paths.encryptedDb),
|
|
@@ -956,7 +951,7 @@ async function rotate(opts) {
|
|
|
956
951
|
_emit(progress, { phase: "verify" });
|
|
957
952
|
var verifyTmp = nodePath.join(stagingDir, "_blamejs_verify.tmp.db");
|
|
958
953
|
_writeStagedFileExclusive(verifyTmp,
|
|
959
|
-
bCrypto.decryptPacked(
|
|
954
|
+
bCrypto.decryptPacked(atomicFile.fdSafeReadSync(nodePath.join(stagingDir, paths.encryptedDb), { maxBytes: C.BYTES.gib(2) }), dbKey, dbEncAad));
|
|
960
955
|
var vdb = new DatabaseSync(verifyTmp);
|
|
961
956
|
try {
|
|
962
957
|
verifyResult = verify({ keys: newKeys, db: vdb, oldKeys: oldKeys });
|
|
@@ -185,7 +185,6 @@ function sealPemFile(opts) {
|
|
|
185
185
|
// optionalPositiveFinite above already threw on a bad-shaped opts.pollInterval;
|
|
186
186
|
// here only undefined / null / valid-positive-finite remain.
|
|
187
187
|
var pollInterval = opts.pollInterval || DEFAULT_POLL_MS;
|
|
188
|
-
var auditOn = opts.audit !== false;
|
|
189
188
|
var onResealed = typeof opts.onResealed === "function" ? opts.onResealed : null;
|
|
190
189
|
var onError = typeof opts.onError === "function" ? opts.onError : null;
|
|
191
190
|
validateOpts.optionalPositiveFinite(opts.maxSourceBytes,
|
|
@@ -200,16 +199,7 @@ function sealPemFile(opts) {
|
|
|
200
199
|
var resealing = false;
|
|
201
200
|
var pendingMtime = null;
|
|
202
201
|
|
|
203
|
-
|
|
204
|
-
if (!auditOn) return;
|
|
205
|
-
try {
|
|
206
|
-
audit().safeEmit({
|
|
207
|
-
action: "vault.seal_pem_file." + action,
|
|
208
|
-
outcome: outcome,
|
|
209
|
-
metadata: metadata || {},
|
|
210
|
-
});
|
|
211
|
-
} catch (_e) { /* drop-silent */ }
|
|
212
|
-
}
|
|
202
|
+
var _emitAudit = audit().namespaced("vault.seal_pem_file", opts.audit);
|
|
213
203
|
|
|
214
204
|
function _writeSealed(plaintextBytes) {
|
|
215
205
|
// atomicFile.writeSync already does the .tmp + fsync + rename +
|
|
@@ -242,7 +232,10 @@ function sealPemFile(opts) {
|
|
|
242
232
|
}
|
|
243
233
|
}
|
|
244
234
|
var sealed = vault().seal(plaintextBytes);
|
|
245
|
-
|
|
235
|
+
// Atomic, symlink-refusing marker write (matches the destination write
|
|
236
|
+
// just below) — a bare writeFileSync would follow a symlink planted at
|
|
237
|
+
// markerPath (CWE-59).
|
|
238
|
+
atomicFile.writeSync(markerPath, String(Date.now()), { fileMode: 0o600 }); // POSIX file mode
|
|
246
239
|
try {
|
|
247
240
|
atomicFile.writeSync(destination, sealed, { fileMode: 0o600 }); // POSIX file mode
|
|
248
241
|
} catch (e) {
|
|
@@ -274,43 +267,35 @@ function sealPemFile(opts) {
|
|
|
274
267
|
// source isn't a symlink we don't expect, then read via fd so
|
|
275
268
|
// a swap-after-stat doesn't change which bytes we read.
|
|
276
269
|
try {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
if (read !== fstat.size) {
|
|
308
|
-
throw new SealPemFileError("seal-pem-file/short-read",
|
|
309
|
-
"short read: " + read + " of " + fstat.size + " bytes");
|
|
310
|
-
}
|
|
311
|
-
} finally {
|
|
312
|
-
try { nodeFs.closeSync(fd); } catch (_e) { /* close best-effort */ }
|
|
313
|
-
}
|
|
270
|
+
// TOCTOU-safe read via atomic-file with the strongest guards:
|
|
271
|
+
// symlink refusal + inode-equality + a byte cap. The bespoke
|
|
272
|
+
// SealPemFileError codes/messages are preserved via errorFor; this
|
|
273
|
+
// call sits inside the outer try/catch that wraps any failure into
|
|
274
|
+
// seal-pem-file/source-read-failed + audit + the onError callback.
|
|
275
|
+
plaintext = atomicFile.fdSafeReadSync(source, {
|
|
276
|
+
refuseSymlink: true,
|
|
277
|
+
inodeCheck: true,
|
|
278
|
+
maxBytes: maxSourceBytes,
|
|
279
|
+
errorFor: function (kind, detail) {
|
|
280
|
+
if (kind === "symlink") {
|
|
281
|
+
return new SealPemFileError("seal-pem-file/symlink-refused",
|
|
282
|
+
"source is a symlink (refused; follow + re-stat opens TOCTOU)");
|
|
283
|
+
}
|
|
284
|
+
if (kind === "too-large") {
|
|
285
|
+
return new SealPemFileError("seal-pem-file/source-too-large",
|
|
286
|
+
"source size " + detail.size + " exceeds maxSourceBytes " + maxSourceBytes);
|
|
287
|
+
}
|
|
288
|
+
if (kind === "toctou") {
|
|
289
|
+
return new SealPemFileError("seal-pem-file/toctou-detected",
|
|
290
|
+
"source mutated between lstat and open (TOCTOU defense)");
|
|
291
|
+
}
|
|
292
|
+
if (kind === "short-read") {
|
|
293
|
+
return new SealPemFileError("seal-pem-file/short-read",
|
|
294
|
+
"short read: " + detail.read + " of " + detail.size + " bytes");
|
|
295
|
+
}
|
|
296
|
+
return undefined;
|
|
297
|
+
},
|
|
298
|
+
});
|
|
314
299
|
}
|
|
315
300
|
catch (e) {
|
|
316
301
|
var err = new SealPemFileError("seal-pem-file/source-read-failed",
|
package/lib/vault-aad.js
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
var lazyRequire = require("./lazy-require");
|
|
50
50
|
var validateOpts = require("./validate-opts");
|
|
51
|
+
var pick = require("./pick");
|
|
51
52
|
var C = require("./constants");
|
|
52
53
|
var { defineClass } = require("./framework-error");
|
|
53
54
|
var VaultAadError = defineClass("VaultAadError", { alwaysPermanent: true });
|
|
@@ -80,7 +81,7 @@ function _canonicalize(parts) {
|
|
|
80
81
|
chunks.push(Buffer.from([AAD_VERSION]));
|
|
81
82
|
for (var i = 0; i < keys.length; i += 1) {
|
|
82
83
|
var key = keys[i];
|
|
83
|
-
if (key
|
|
84
|
+
if (pick.isPoisonedKey(key)) {
|
|
84
85
|
throw new VaultAadError("vault-aad/bad-aad",
|
|
85
86
|
"AAD field name " + JSON.stringify(key) + " is forbidden (poisoned key)");
|
|
86
87
|
}
|
|
@@ -130,7 +131,7 @@ function buildContextAad(parts) {
|
|
|
130
131
|
var out = {};
|
|
131
132
|
for (var k in parts) {
|
|
132
133
|
if (!Object.prototype.hasOwnProperty.call(parts, k)) continue;
|
|
133
|
-
if (k
|
|
134
|
+
if (pick.isPoisonedKey(k)) continue;
|
|
134
135
|
if (parts[k] == null) continue;
|
|
135
136
|
out[k] = parts[k];
|
|
136
137
|
}
|
package/lib/vc.js
CHANGED
|
@@ -226,7 +226,7 @@ function _verifyJose(token, opts, expectedTyp) {
|
|
|
226
226
|
throw new VcError("vc/crit-unsupported",
|
|
227
227
|
"vc.verify: JWS 'crit' header lists extensions this verifier does not support (RFC 7515 §4.1.11)");
|
|
228
228
|
}
|
|
229
|
-
if (header.alg === "none" || !JOSE_ALGS
|
|
229
|
+
if (header.alg === "none" || !Object.prototype.hasOwnProperty.call(JOSE_ALGS, header.alg)) {
|
|
230
230
|
throw new VcError("vc/bad-alg", "vc.verify: unsupported or unsecured JWS alg '" + header.alg + "'");
|
|
231
231
|
}
|
|
232
232
|
if (opts.algorithms.indexOf(header.alg) === -1) {
|
|
@@ -316,13 +316,8 @@ async function verify(secured, opts) {
|
|
|
316
316
|
if (!opts.publicKey && typeof opts.keyResolver !== "function") {
|
|
317
317
|
throw new VcError("vc/no-key", "vc.verify: pass publicKey or keyResolver");
|
|
318
318
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (!(opts.at instanceof Date) || !isFinite(opts.at.getTime())) {
|
|
322
|
-
throw new VcError("vc/bad-at", "vc.verify: opts.at must be a valid Date");
|
|
323
|
-
}
|
|
324
|
-
at = opts.at;
|
|
325
|
-
}
|
|
319
|
+
validateOpts.optionalDate(opts.at, "vc.verify: opts.at", VcError, "vc/bad-at");
|
|
320
|
+
var at = (opts.at !== undefined && opts.at !== null) ? opts.at : new Date();
|
|
326
321
|
|
|
327
322
|
var result = await _verifySecured(secured, opts, JOSE_TYP, COSE_TYP);
|
|
328
323
|
|