@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/db-query.js
CHANGED
|
@@ -51,6 +51,7 @@ var sql = require("./sql");
|
|
|
51
51
|
var audit = require("./audit");
|
|
52
52
|
var lazyRequire = require("./lazy-require");
|
|
53
53
|
var { DbQueryError } = require("./framework-error");
|
|
54
|
+
var numericBounds = require("./numeric-bounds");
|
|
54
55
|
|
|
55
56
|
// Circular load — db.js requires db-query at module scope, so the
|
|
56
57
|
// residency gate reaches back for getDataResidency() lazily.
|
|
@@ -682,11 +683,8 @@ class Query {
|
|
|
682
683
|
var dbModule = require("./db"); // allow:inline-require — circular-load defense (db imports db-query)
|
|
683
684
|
perCallLimit = dbModule.getStreamLimit();
|
|
684
685
|
if (opts && opts.streamLimit !== undefined) {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
throw new Error("Query.stream: opts.streamLimit must be a positive finite integer; got " +
|
|
688
|
-
JSON.stringify(opts.streamLimit));
|
|
689
|
-
}
|
|
686
|
+
numericBounds.requirePositiveFiniteIntIfPresent(opts.streamLimit,
|
|
687
|
+
"Query.stream: opts.streamLimit", DbQueryError, "db-query/bad-stream-limit");
|
|
690
688
|
perCallLimit = opts.streamLimit;
|
|
691
689
|
}
|
|
692
690
|
var stmt = this._db.prepare(built.sql);
|
|
@@ -1299,41 +1297,9 @@ class WhereBuilder {
|
|
|
1299
1297
|
// Single linear pass, no backtracking regex; shares the scan shape with
|
|
1300
1298
|
// b.safeSql.countPlaceholders.
|
|
1301
1299
|
function _assertRawNoStringLiteral(rawSql, where) {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
var ch = rawSql.charAt(i);
|
|
1306
|
-
var next = i + 1 < len ? rawSql.charAt(i + 1) : "";
|
|
1307
|
-
if (ch === '"') {
|
|
1308
|
-
i += 1;
|
|
1309
|
-
while (i < len) {
|
|
1310
|
-
if (rawSql.charAt(i) === '"') {
|
|
1311
|
-
if (rawSql.charAt(i + 1) === '"') { i += 2; continue; }
|
|
1312
|
-
i += 1; break;
|
|
1313
|
-
}
|
|
1314
|
-
i += 1;
|
|
1315
|
-
}
|
|
1316
|
-
continue;
|
|
1317
|
-
}
|
|
1318
|
-
if (ch === "-" && next === "-") {
|
|
1319
|
-
while (i < len && rawSql.charAt(i) !== "\n") i += 1;
|
|
1320
|
-
continue;
|
|
1321
|
-
}
|
|
1322
|
-
if (ch === "/" && next === "*") {
|
|
1323
|
-
i += 2;
|
|
1324
|
-
while (i < len && !(rawSql.charAt(i) === "*" && rawSql.charAt(i + 1) === "/")) i += 1;
|
|
1325
|
-
i += 2;
|
|
1326
|
-
continue;
|
|
1327
|
-
}
|
|
1328
|
-
if (ch === "'") {
|
|
1329
|
-
throw new safeSql.SafeSqlError(
|
|
1330
|
-
where + ": raw SQL must not contain a string literal ('...') — bind every " +
|
|
1331
|
-
"value with a ? placeholder, or pass { allowLiterals: true } when the literal " +
|
|
1332
|
-
"is static and operator-controlled.",
|
|
1333
|
-
"sql/raw-literal");
|
|
1334
|
-
}
|
|
1335
|
-
i += 1;
|
|
1336
|
-
}
|
|
1300
|
+
// Routes through the shared safeSql scanner; the default error reproduces
|
|
1301
|
+
// this module's exact SafeSqlError("sql/raw-literal") message.
|
|
1302
|
+
safeSql.assertNoRawStringLiteral(rawSql, where);
|
|
1337
1303
|
}
|
|
1338
1304
|
|
|
1339
1305
|
// Apply one recorded WhereBuilder part onto a b.sql Predicate. `or`
|
|
@@ -1412,12 +1378,62 @@ function _unquoteIdent(s) {
|
|
|
1412
1378
|
return s;
|
|
1413
1379
|
}
|
|
1414
1380
|
|
|
1381
|
+
// Strip LEADING SQL comments + whitespace so the ^-anchored write-detection
|
|
1382
|
+
// regexes see the real statement head. Without this, a residency write smuggled
|
|
1383
|
+
// behind a leading "/* x */" or "-- x\n" comment is NOT recognized as a write →
|
|
1384
|
+
// the residency gate is skipped entirely (a cross-border-write BYPASS). The
|
|
1385
|
+
// executed SQL is unchanged; this normalized copy is only for the gate's parse.
|
|
1386
|
+
// Each replace is ^-anchored single-pass; the loop terminates when nothing more
|
|
1387
|
+
// is stripped (an unterminated /* leaves the head intact → write still detected
|
|
1388
|
+
// or fails closed downstream).
|
|
1389
|
+
function _stripLeadingSqlComments(sql) {
|
|
1390
|
+
var s = String(sql), prev;
|
|
1391
|
+
do {
|
|
1392
|
+
prev = s;
|
|
1393
|
+
s = s.replace(/^\s+/, ""); // allow:regex-no-length-cap — anchored, single leading run
|
|
1394
|
+
s = s.replace(/^--[^\n]*\r?\n?/, ""); // allow:regex-no-length-cap — anchored leading line comment
|
|
1395
|
+
s = s.replace(/^\/\*[\s\S]*?\*\//, ""); // allow:regex-no-length-cap — anchored leading block comment (lazy, single scan)
|
|
1396
|
+
} while (s !== prev);
|
|
1397
|
+
return s;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// Non-anchored write-target scan for the writable-CTE / EXPLAIN-prefixed case.
|
|
1401
|
+
// A SQLite `WITH c AS (...) INSERT INTO residents ...` / `WITH ... UPDATE residents
|
|
1402
|
+
// SET ...` is a real write, but its effective verb is hidden behind the prefix so
|
|
1403
|
+
// the ^-anchored _RAW_WRITE_KEYWORD_RE misses it. This matches every INSERT/REPLACE
|
|
1404
|
+
// INTO, MERGE INTO, and UPDATE ... SET target token anywhere in the statement and
|
|
1405
|
+
// returns the first that names a residency table — so such a write still ENGAGES
|
|
1406
|
+
// the residency gate (_assertRawWriteResidency then fails CLOSED: the ^-anchored
|
|
1407
|
+
// body parsers can't read a CTE body, so it throws row-residency-raw-unparseable
|
|
1408
|
+
// directing the operator to b.db.from().insertOne/.updateOne). DELETE moves no
|
|
1409
|
+
// residency value across a border, so it is not a residency write. Linear scan.
|
|
1410
|
+
var _CTE_WRITE_TARGET_RE = /(?:\b(?:INSERT|REPLACE)\s+(?:OR\s+[A-Za-z]+\s+)?INTO|\bMERGE\s+INTO)\s+(?:[\x22\x27\x60]?[A-Za-z_]\w*[\x22\x27\x60]?\s*\.\s*){0,3}[\x22\x27\x60]?([A-Za-z_]\w*)[\x22\x27\x60]?|\bUPDATE\s+(?:[\x22\x27\x60]?[A-Za-z_]\w*[\x22\x27\x60]?\s*\.\s*){0,3}[\x22\x27\x60]?([A-Za-z_]\w*)[\x22\x27\x60]?\s+SET\b/ig; // allow:regex-no-length-cap — alternation, no nested quantifiers; linear
|
|
1411
|
+
function _firstResidencyWriteTarget(s) {
|
|
1412
|
+
_CTE_WRITE_TARGET_RE.lastIndex = 0;
|
|
1413
|
+
var m;
|
|
1414
|
+
while ((m = _CTE_WRITE_TARGET_RE.exec(s)) !== null) { // allow:regex-no-length-cap
|
|
1415
|
+
var t = _unquoteIdent(m[1] || m[2]);
|
|
1416
|
+
if (t && (cryptoField.getPerRowResidency(t) || cryptoField.getColumnResidency(t))) return t;
|
|
1417
|
+
}
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1415
1421
|
function _rawWriteTable(sql) {
|
|
1416
|
-
//
|
|
1417
|
-
//
|
|
1418
|
-
if (typeof sql !== "string"
|
|
1419
|
-
var
|
|
1420
|
-
|
|
1422
|
+
// The ^-anchored regexes scan only the statement head (constant-time). Strip
|
|
1423
|
+
// leading comments first so a commented-out head can't hide the write.
|
|
1424
|
+
if (typeof sql !== "string") return null;
|
|
1425
|
+
var s = _stripLeadingSqlComments(sql);
|
|
1426
|
+
if (_RAW_WRITE_KEYWORD_RE.test(s)) { // allow:regex-no-length-cap
|
|
1427
|
+
var m = _RAW_TABLE_RE.exec(s); // allow:regex-no-length-cap
|
|
1428
|
+
return m ? _unquoteIdent(m[1] || m[2]) : null;
|
|
1429
|
+
}
|
|
1430
|
+
// Writable-CTE / EXPLAIN-prefixed write: the effective write verb is hidden
|
|
1431
|
+
// behind the prefix the ^-anchored test misses. If it writes a residency table,
|
|
1432
|
+
// return it so the gate engages and then fails closed on the unparseable body.
|
|
1433
|
+
if (/^\s*(?:WITH|EXPLAIN)\b/i.test(s)) { // allow:regex-no-length-cap
|
|
1434
|
+
return _firstResidencyWriteTarget(s);
|
|
1435
|
+
}
|
|
1436
|
+
return null;
|
|
1421
1437
|
}
|
|
1422
1438
|
|
|
1423
1439
|
// Cheap prepare-time pre-check so only writes to a residency table get wrapped.
|
|
@@ -1497,17 +1513,23 @@ function _assertRawWriteResidency(sql, boundParams) {
|
|
|
1497
1513
|
if (!cryptoField.getPerRowResidency(table) && !cryptoField.getColumnResidency(table)) return;
|
|
1498
1514
|
boundParams = _flattenRunParams(boundParams);
|
|
1499
1515
|
|
|
1516
|
+
// Parse the comment-stripped head: a leading "/* x */" / "-- x" comment must
|
|
1517
|
+
// not hide the INSERT/UPDATE body from the ^-anchored regexes below (that would
|
|
1518
|
+
// let a residency-restricted write through the gate). The executed SQL is
|
|
1519
|
+
// unchanged; this normalized copy is only for residency parsing.
|
|
1520
|
+
var norm = _stripLeadingSqlComments(sql);
|
|
1521
|
+
|
|
1500
1522
|
// The INSERT/UPDATE body regexes below scan with [\s\S]+; bound the input
|
|
1501
1523
|
// first and fail CLOSED on an over-long statement - a residency write the
|
|
1502
1524
|
// framework cannot safely parse must be refused, never let past the gate.
|
|
1503
|
-
if (
|
|
1525
|
+
if (norm.length > 100000) {
|
|
1504
1526
|
throw new DbQueryError("db-query/row-residency-raw-unparseable",
|
|
1505
1527
|
"raw write to residency table '" + table + "' exceeds the parse limit (" +
|
|
1506
|
-
|
|
1528
|
+
norm.length + " chars) - use b.db.from(\"" + table + "\") so residency is validated", true);
|
|
1507
1529
|
}
|
|
1508
1530
|
|
|
1509
|
-
var mi = _RAW_INSERT_RE.exec(
|
|
1510
|
-
var mu = mi ? null : _RAW_UPDATE_RE.exec(
|
|
1531
|
+
var mi = _RAW_INSERT_RE.exec(norm); // allow:regex-no-length-cap — input length-capped above
|
|
1532
|
+
var mu = mi ? null : _RAW_UPDATE_RE.exec(norm); // allow:regex-no-length-cap — input length-capped above
|
|
1511
1533
|
if (!mi && !mu) {
|
|
1512
1534
|
throw new DbQueryError("db-query/row-residency-raw-unparseable",
|
|
1513
1535
|
"raw write to residency table '" + table + "' cannot be parsed to validate its " +
|
|
@@ -1543,4 +1565,8 @@ module.exports = {
|
|
|
1543
1565
|
Query: Query,
|
|
1544
1566
|
_isRawWriteToResidencyTable: _isRawWriteToResidencyTable,
|
|
1545
1567
|
_assertRawWriteResidency: _assertRawWriteResidency,
|
|
1568
|
+
// Shared leading-comment stripper so db.js's storage-low write-gate sees the
|
|
1569
|
+
// real statement head (a `/* x */ INSERT` / WITH-prefixed write must not slip
|
|
1570
|
+
// the ENOSPC gate the way it slipped the residency gate).
|
|
1571
|
+
_stripLeadingSqlComments: _stripLeadingSqlComments,
|
|
1546
1572
|
};
|
package/lib/db.js
CHANGED
|
@@ -57,7 +57,7 @@ var { generateToken, generateBytes, encryptPacked, decryptPacked, sha3Hash } = r
|
|
|
57
57
|
var cryptoField = require("./crypto-field");
|
|
58
58
|
var dbDeclareRowPolicy = require("./db-declare-row-policy");
|
|
59
59
|
var dbDeclareView = require("./db-declare-view");
|
|
60
|
-
var { Query, _isRawWriteToResidencyTable, _assertRawWriteResidency } = require("./db-query");
|
|
60
|
+
var { Query, _isRawWriteToResidencyTable, _assertRawWriteResidency, _stripLeadingSqlComments } = require("./db-query");
|
|
61
61
|
var dbSchema = require("./db-schema");
|
|
62
62
|
var { defineClass } = require("./framework-error");
|
|
63
63
|
var frameworkFiles = require("./framework-files");
|
|
@@ -161,6 +161,14 @@ var statfsProbe = null; // free-space reader (fs.statfsSync; injectable f
|
|
|
161
161
|
var _exitHandlerRegistered = false;
|
|
162
162
|
var dataDir = null;
|
|
163
163
|
var initialized = false;
|
|
164
|
+
// Monotonic identity of the live `database` handle. Bumped on every (re)open,
|
|
165
|
+
// close, and reset so a deferred operation that captured the generation while
|
|
166
|
+
// one database was live can detect — before it writes — that the handle has
|
|
167
|
+
// since changed (closed / replaced). Anchors the fix for a fire-and-forget
|
|
168
|
+
// audit.checkpoint() launched by close() whose deferred insert would otherwise
|
|
169
|
+
// land in whatever database is live when its continuation resumes.
|
|
170
|
+
var _dbGenerationCounter = 0;
|
|
171
|
+
function dbGeneration() { return _dbGenerationCounter; }
|
|
164
172
|
var dataResidency = null; // operator's declared region config (validated by storage backends)
|
|
165
173
|
var subjectTables = []; // [{ name, subjectField, personalDataCategories }] — for subject.export/erase
|
|
166
174
|
var tableMetadata = {}; // table name → metadata snapshot (PK/FK/sealed/derived) for getTableMetadata
|
|
@@ -193,6 +201,7 @@ var RESERVED_TABLE_NAMES = new Set([
|
|
|
193
201
|
"_blamejs_subject_restrictions", // allow:hand-rolled-sql — canonical reserved local table-name declaration
|
|
194
202
|
"_blamejs_subject_erasures", // allow:hand-rolled-sql — canonical reserved local table-name declaration
|
|
195
203
|
"_blamejs_sessions", // allow:hand-rolled-sql — canonical reserved local table-name declaration
|
|
204
|
+
"_blamejs_session_valid_from", // allow:hand-rolled-sql — canonical reserved local table-name declaration
|
|
196
205
|
"_blamejs_jobs", // allow:hand-rolled-sql — canonical reserved local table-name declaration
|
|
197
206
|
"_blamejs_migrations", // allow:hand-rolled-sql — canonical reserved local table-name declaration
|
|
198
207
|
"_blamejs_counters", // allow:hand-rolled-sql — canonical reserved local table-name declaration
|
|
@@ -480,6 +489,23 @@ var FRAMEWORK_SCHEMA = [
|
|
|
480
489
|
sealedFields: ["userId", "data"],
|
|
481
490
|
derivedHashes: { userIdHash: { from: "userId" } },
|
|
482
491
|
},
|
|
492
|
+
{
|
|
493
|
+
// _blamejs_session_valid_from — per-subject valid-from boundary for
|
|
494
|
+
// stateless-token revocation (b.session.bump / check / validFrom). Same
|
|
495
|
+
// dual-storage pattern as _blamejs_sessions: mirrors the cluster-mode DDL
|
|
496
|
+
// in framework-schema.js so cluster-storage routes to either backend.
|
|
497
|
+
// subjectHash is the PRIMARY KEY — the plaintext subject id never lands
|
|
498
|
+
// here. No sealed columns: the hash is non-reversible and the epoch is not
|
|
499
|
+
// personal data.
|
|
500
|
+
name: "_blamejs_session_valid_from", // allow:hand-rolled-sql — canonical local-schema table-name declaration
|
|
501
|
+
columns: {
|
|
502
|
+
subjectHash: "TEXT PRIMARY KEY",
|
|
503
|
+
validFromEpoch: "INTEGER NOT NULL",
|
|
504
|
+
updatedAt: "INTEGER NOT NULL",
|
|
505
|
+
},
|
|
506
|
+
indexes: [],
|
|
507
|
+
sealedFields: [],
|
|
508
|
+
},
|
|
483
509
|
{
|
|
484
510
|
// _blamejs_api_keys — operator-facing API-key registry. Sealed
|
|
485
511
|
// columns: ownerId / scopes / metadata. The secret never lands
|
|
@@ -722,7 +748,7 @@ function loadOrCreateDbKey(dataDirPath, keyPathOverride) {
|
|
|
722
748
|
var keyPath = keyPathOverride || nodePath.join(dataDirPath, frameworkFiles.fileName("dbKeyEnc"));
|
|
723
749
|
var aad = _dbKeyAad(dataDirPath, keyPath);
|
|
724
750
|
if (nodeFs.existsSync(keyPath)) {
|
|
725
|
-
var sealed = atomicFile.readSync(keyPath, { encoding: "utf8" }).trim();
|
|
751
|
+
var sealed = atomicFile.readSync(keyPath, { encoding: "utf8", maxBytes: C.BYTES.kib(64) }).trim();
|
|
726
752
|
var b64;
|
|
727
753
|
// isAadSealed is checked FIRST and is load-bearing: AAD_PREFIX
|
|
728
754
|
// ("vault.aad:") is NOT a prefix of VAULT_PREFIX ("vault:"), so a
|
|
@@ -785,7 +811,10 @@ function decryptToTmp() {
|
|
|
785
811
|
try { nodeFs.unlinkSync(dbPath + "-shm"); } catch (_e) { /* may not exist */ }
|
|
786
812
|
}
|
|
787
813
|
}
|
|
788
|
-
|
|
814
|
+
// Cap + fd-bound db.enc read at db.init. NO refuseSymlink: the data dir may be
|
|
815
|
+
// a symlink-mounted volume (k8s PVC). db.enc is AEAD-verified downstream, so
|
|
816
|
+
// the cap is the OOM-before-cap defense (was an uncapped read).
|
|
817
|
+
var packed = atomicFile.fdSafeReadSync(encPath, { maxBytes: C.BYTES.gib(2) });
|
|
789
818
|
if (packed.length < 26) return; // too short to be a valid envelope
|
|
790
819
|
// AAD binds the envelope to this deployment's data dir so two
|
|
791
820
|
// installs sharing the same operator passphrase can't swap each
|
|
@@ -864,11 +893,27 @@ function _probeStorageHeadroom() {
|
|
|
864
893
|
// DELETE, PRAGMA, and DDL pass through ungated. Called once in init() after
|
|
865
894
|
// schema setup so init's own writes are never gated (writesRefused is false
|
|
866
895
|
// until the first probe anyway).
|
|
896
|
+
// A growth write for the storage-low gate: INSERT/UPDATE/REPLACE at the head
|
|
897
|
+
// AFTER stripping leading comments, OR a writable-CTE / EXPLAIN-prefixed write
|
|
898
|
+
// (`WITH c AS (...) INSERT INTO t ...`) whose effective verb places rows. The
|
|
899
|
+
// leading-keyword-only test missed both a `/* x */ INSERT` and a WITH-prefixed
|
|
900
|
+
// growth write, letting them proceed into a near-full tmpfs (the ENOSPC the gate
|
|
901
|
+
// exists to prevent). Matches write SYNTAX (INTO / UPDATE..SET) so a WITH..SELECT
|
|
902
|
+
// carrying the word "insert" in a value is not refused.
|
|
903
|
+
function _isGrowthWrite(sql) {
|
|
904
|
+
if (typeof sql !== "string") return false;
|
|
905
|
+
var s = _stripLeadingSqlComments(sql);
|
|
906
|
+
if (/^\s*(?:INSERT|UPDATE|REPLACE)\b/i.test(s)) return true;
|
|
907
|
+
if (/^\s*(?:WITH|EXPLAIN)\b/i.test(s) &&
|
|
908
|
+
/\b(?:(?:INSERT|REPLACE|MERGE)\s+(?:OR\s+[A-Za-z]+\s+)?INTO|UPDATE\s+[\w".`]+\s+SET)\b/i.test(s)) return true;
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
|
|
867
912
|
function _installWriteGate() {
|
|
868
913
|
var rawPrepare = database.prepare.bind(database);
|
|
869
914
|
database.prepare = function (sql) {
|
|
870
915
|
var stmt = rawPrepare(sql);
|
|
871
|
-
if (
|
|
916
|
+
if (_isGrowthWrite(sql)) {
|
|
872
917
|
var rawRun = stmt.run.bind(stmt);
|
|
873
918
|
stmt.run = function () {
|
|
874
919
|
if (writesRefused) {
|
|
@@ -889,7 +934,7 @@ function encryptToDisk() {
|
|
|
889
934
|
// Force WAL checkpoint so the .db file holds all committed transactions.
|
|
890
935
|
try { runSql(database, "PRAGMA wal_checkpoint(TRUNCATE)"); } catch (_e) { /* best effort */ }
|
|
891
936
|
if (!nodeFs.existsSync(dbPath)) return;
|
|
892
|
-
atomicFile.writeSync(encPath, encryptPacked(
|
|
937
|
+
atomicFile.writeSync(encPath, encryptPacked(atomicFile.fdSafeReadSync(dbPath, { maxBytes: C.BYTES.gib(2) }), encKey, _dbEncAad(dataDir)));
|
|
893
938
|
}
|
|
894
939
|
|
|
895
940
|
/**
|
|
@@ -926,7 +971,7 @@ function snapshot() {
|
|
|
926
971
|
throw _dbErr("db/snapshot-no-source",
|
|
927
972
|
"snapshot: plaintext DB at " + dbPath + " is missing — did init complete?");
|
|
928
973
|
}
|
|
929
|
-
var plain =
|
|
974
|
+
var plain = atomicFile.fdSafeReadSync(dbPath, { maxBytes: C.BYTES.gib(2) });
|
|
930
975
|
if (!encPath || !encKey) {
|
|
931
976
|
// atRest: 'plain' — return the raw bytes. Operators wanting an
|
|
932
977
|
// encrypted snapshot under plain mode wrap with their own
|
|
@@ -1043,12 +1088,8 @@ async function init(opts) {
|
|
|
1043
1088
|
// on bad shape so a typo surfaces at boot rather than as an
|
|
1044
1089
|
// unbounded stream at first export.
|
|
1045
1090
|
if (opts.streamLimit !== undefined) {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
throw new DbError("db/bad-init",
|
|
1049
|
-
"db.init: streamLimit must be a positive finite integer; got " +
|
|
1050
|
-
JSON.stringify(opts.streamLimit));
|
|
1051
|
-
}
|
|
1091
|
+
require("./numeric-bounds").requirePositiveFiniteIntIfPresent(opts.streamLimit,
|
|
1092
|
+
"db.init: streamLimit", DbError, "db/bad-init");
|
|
1052
1093
|
streamLimit = opts.streamLimit;
|
|
1053
1094
|
}
|
|
1054
1095
|
// Column-membership gate mode — throw at config-time on a typo so it
|
|
@@ -1171,6 +1212,7 @@ async function init(opts) {
|
|
|
1171
1212
|
sqlLength: C.BYTES.mib(1),
|
|
1172
1213
|
},
|
|
1173
1214
|
});
|
|
1215
|
+
_dbGenerationCounter++; // a new live handle — see dbGeneration()
|
|
1174
1216
|
|
|
1175
1217
|
// Performance pragmas
|
|
1176
1218
|
runSql(database, "PRAGMA journal_mode=WAL");
|
|
@@ -1813,12 +1855,8 @@ function stream(sql) {
|
|
|
1813
1855
|
// typo surfaces instead of an unbounded stream.
|
|
1814
1856
|
var perCallLimit = streamLimit;
|
|
1815
1857
|
if (opts && opts.streamLimit !== undefined) {
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
throw new DbError("db/bad-stream-limit",
|
|
1819
|
-
"db.stream: opts.streamLimit must be a positive finite integer; got " +
|
|
1820
|
-
JSON.stringify(opts.streamLimit));
|
|
1821
|
-
}
|
|
1858
|
+
require("./numeric-bounds").requirePositiveFiniteIntIfPresent(opts.streamLimit,
|
|
1859
|
+
"db.stream: opts.streamLimit", DbError, "db/bad-stream-limit");
|
|
1822
1860
|
perCallLimit = opts.streamLimit;
|
|
1823
1861
|
}
|
|
1824
1862
|
|
|
@@ -2355,6 +2393,7 @@ function close() {
|
|
|
2355
2393
|
// boot (integrity-probed, falling back to db.enc if it is itself corrupt).
|
|
2356
2394
|
if (atRest === "encrypted" && encryptOk) removePlaintextFiles();
|
|
2357
2395
|
database = null;
|
|
2396
|
+
_dbGenerationCounter++; // handle gone — invalidate any deferred op that captured the prior generation
|
|
2358
2397
|
initialized = false;
|
|
2359
2398
|
}
|
|
2360
2399
|
|
|
@@ -2591,14 +2630,12 @@ function declareRequireDualControl(args) {
|
|
|
2591
2630
|
}
|
|
2592
2631
|
var m = args.m === undefined ? 2 : args.m;
|
|
2593
2632
|
var n = args.n === undefined ? Math.max(2, m) : args.n;
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
"declareRequireDualControl: n must be an integer >= m");
|
|
2601
|
-
}
|
|
2633
|
+
// m >= 2, n >= m, both integers — via numericBounds (matches db.js's existing
|
|
2634
|
+
// inline numeric-bounds usage; throws DbError("db/dual-control-bad-quorum")).
|
|
2635
|
+
require("./numeric-bounds").requirePositiveFiniteInt(m,
|
|
2636
|
+
"declareRequireDualControl: m", DbError, "db/dual-control-bad-quorum", { min: 2 });
|
|
2637
|
+
require("./numeric-bounds").requirePositiveFiniteInt(n,
|
|
2638
|
+
"declareRequireDualControl: n", DbError, "db/dual-control-bad-quorum", { min: m });
|
|
2602
2639
|
if (args.posture !== undefined && args.posture !== null &&
|
|
2603
2640
|
(typeof args.posture !== "string" || args.posture.length === 0)) {
|
|
2604
2641
|
throw new DbError("db/dual-control-bad-posture",
|
|
@@ -2765,7 +2802,7 @@ function _checkRollback(dataDirPath) {
|
|
|
2765
2802
|
}
|
|
2766
2803
|
var tip;
|
|
2767
2804
|
try {
|
|
2768
|
-
tip = safeJson.parse(atomicFile.readSync(tipPath), { schema: AUDIT_TIP_SCHEMA });
|
|
2805
|
+
tip = safeJson.parse(atomicFile.readSync(tipPath, { maxBytes: C.BYTES.kib(64) }), { schema: AUDIT_TIP_SCHEMA });
|
|
2769
2806
|
} catch (e) {
|
|
2770
2807
|
throw _dbErr("db/audit-tip-unreadable",
|
|
2771
2808
|
"FATAL: audit.tip unreadable or schema-invalid at " + tipPath + " — " + e.message +
|
|
@@ -2879,6 +2916,7 @@ function _resetForTest() {
|
|
|
2879
2916
|
try { if (database) database.close(); }
|
|
2880
2917
|
catch (e) { log.debug("test-reset close failed", { error: e.message }); }
|
|
2881
2918
|
database = null;
|
|
2919
|
+
_dbGenerationCounter++; // handle gone — see dbGeneration()
|
|
2882
2920
|
dbPath = null;
|
|
2883
2921
|
encPath = null;
|
|
2884
2922
|
encKey = null;
|
|
@@ -3308,6 +3346,7 @@ function getActivePosture() { return _activePosture; }
|
|
|
3308
3346
|
|
|
3309
3347
|
module.exports = {
|
|
3310
3348
|
init: init,
|
|
3349
|
+
_dbGeneration: dbGeneration,
|
|
3311
3350
|
applyPosture: applyPosture,
|
|
3312
3351
|
getActivePosture: getActivePosture,
|
|
3313
3352
|
vacuumAfterErase: vacuumAfterErase,
|
package/lib/dbsc.js
CHANGED
|
@@ -198,12 +198,11 @@ function verifyBindingAssertion(assertion, opts) {
|
|
|
198
198
|
// import boundary (alg-confusion defense — JWT_KEY_CONFUSION-class
|
|
199
199
|
// attacks pass an HS256 jwk for an ES256-claimed token).
|
|
200
200
|
jwtExternal._assertAlgKtyMatch(headerJson.alg, headerJson.jwk);
|
|
201
|
-
var pubKey
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
201
|
+
var pubKey = bCrypto.importPublicJwk(headerJson.jwk, {
|
|
202
|
+
errorClass: DbscError,
|
|
203
|
+
code: "dbsc/bad-jwk",
|
|
204
|
+
messagePrefix: "verifyBindingAssertion: jwk could not be imported: ",
|
|
205
|
+
});
|
|
207
206
|
var signingInput = parts[0] + "." + parts[1];
|
|
208
207
|
var sigBytes = Buffer.from(parts[2], "base64url");
|
|
209
208
|
var ok;
|
|
@@ -207,20 +207,24 @@ function _memoryStore() {
|
|
|
207
207
|
*/
|
|
208
208
|
function create(opts) {
|
|
209
209
|
opts = opts || {};
|
|
210
|
-
validateOpts(opts,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
210
|
+
validateOpts.shape(opts, {
|
|
211
|
+
audit: function (value) {
|
|
212
|
+
validateOpts.auditShape(value, "ddlChangeControl",
|
|
213
|
+
DdlChangeControlError, "ddl-change-control/bad-audit");
|
|
214
|
+
},
|
|
215
|
+
signWith: { rule: "optional-function", code: "ddl-change-control/bad-signer" },
|
|
216
|
+
verifyWith: { rule: "optional-function", code: "ddl-change-control/bad-verifier" },
|
|
217
|
+
now: { rule: "optional-function", code: "ddl-change-control/bad-now" },
|
|
218
|
+
posture: { rule: "optional-string", code: "ddl-change-control/bad-posture" },
|
|
219
|
+
// approvers / windowSpec / store keep their own bespoke validation in
|
|
220
|
+
// the factory body (distinct error codes, floor semantics, the window
|
|
221
|
+
// grammar parser, the silent in-memory fallback) — declared here so the
|
|
222
|
+
// exhaustive contract accepts them while the body remains authoritative.
|
|
223
|
+
approvers: function () {},
|
|
224
|
+
windowSpec: function () {},
|
|
225
|
+
store: function () {},
|
|
226
|
+
selfApproval: { rule: "optional-boolean", code: "ddl-change-control/bad-self-approval" },
|
|
227
|
+
}, "ddlChangeControl", DdlChangeControlError, "ddl-change-control/bad-posture");
|
|
224
228
|
|
|
225
229
|
var approvers = 2;
|
|
226
230
|
if (opts.approvers !== undefined) {
|
package/lib/deprecate.js
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
var safeEnv = require("./parsers/safe-env");
|
|
50
50
|
var validateOpts = require("./validate-opts");
|
|
51
|
+
var boundedMap = require("./bounded-map");
|
|
51
52
|
var { FrameworkError } = require("./framework-error");
|
|
52
53
|
|
|
53
54
|
class DeprecateError extends FrameworkError {
|
|
@@ -131,9 +132,8 @@ function warn(name, opts) {
|
|
|
131
132
|
_validateOpts(opts, "warn");
|
|
132
133
|
|
|
133
134
|
var key = name + ":" + opts.since;
|
|
134
|
-
var entry =
|
|
135
|
-
|
|
136
|
-
entry = {
|
|
135
|
+
var entry = boundedMap.getOrInsert(_seen, key, function () {
|
|
136
|
+
return {
|
|
137
137
|
name: name,
|
|
138
138
|
since: opts.since,
|
|
139
139
|
removeIn: opts.removeIn,
|
|
@@ -142,8 +142,7 @@ function warn(name, opts) {
|
|
|
142
142
|
callCount: 0,
|
|
143
143
|
firstSeen: new Date().toISOString(),
|
|
144
144
|
};
|
|
145
|
-
|
|
146
|
-
}
|
|
145
|
+
});
|
|
147
146
|
entry.callCount++;
|
|
148
147
|
|
|
149
148
|
var mode = _modeFromEnv();
|
package/lib/did.js
CHANGED
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
48
|
var nodeCrypto = require("node:crypto");
|
|
49
|
+
var bCrypto = require("./crypto");
|
|
49
50
|
var safeJson = require("./safe-json");
|
|
50
51
|
var validateOpts = require("./validate-opts");
|
|
51
52
|
var { defineClass } = require("./framework-error");
|
|
@@ -380,8 +381,11 @@ function _jwkToKey(jwk) {
|
|
|
380
381
|
throw new DidError("did/unsupported-key",
|
|
381
382
|
"did: verificationMethod publicKeyJwk has unsupported kty/crv (" + jwk.kty + "/" + jwk.crv + ")");
|
|
382
383
|
}
|
|
383
|
-
|
|
384
|
-
|
|
384
|
+
return bCrypto.importPublicJwk(jwk, {
|
|
385
|
+
errorClass: DidError,
|
|
386
|
+
code: "did/bad-key",
|
|
387
|
+
messagePrefix: "did: verificationMethod publicKeyJwk is invalid: ",
|
|
388
|
+
});
|
|
385
389
|
}
|
|
386
390
|
|
|
387
391
|
// Extract verification methods from a DID document → KeyObjects.
|
package/lib/dora.js
CHANGED
|
@@ -62,12 +62,19 @@ var SIGNIFICANT_INCIDENT_THRESHOLDS = Object.freeze({
|
|
|
62
62
|
durationCriticalProcessMs: C.TIME.hours(2), // 2h
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
// DORA major-incident reporting timeline (Art. 19 + RTS (EU) 2024/1772 Art. 5):
|
|
66
|
+
// - Initial notification: as early as possible and WITHIN 4 HOURS of
|
|
67
|
+
// classifying the incident as major (the outer bound is 24h from first
|
|
68
|
+
// awareness). classify() returns mustReportInitialByMs as a duration FROM
|
|
69
|
+
// classification, so the operative window encoded here is 4h — the
|
|
70
|
+
// 24h-from-awareness outer bound is enforced separately via
|
|
71
|
+
// INITIAL_REPORT_OUTER_DEADLINE_MS below.
|
|
72
|
+
// - Intermediate update: within 72h of the INITIAL notification's submission.
|
|
73
|
+
// - Final report: within 1 month of the intermediate report's submission.
|
|
74
|
+
var INITIAL_REPORT_DEADLINE_MS = C.TIME.hours(4);
|
|
75
|
+
var INITIAL_REPORT_OUTER_DEADLINE_MS = C.TIME.hours(24);
|
|
76
|
+
var INTERMEDIATE_REPORT_DEADLINE_MS = C.TIME.hours(72);
|
|
77
|
+
var FINAL_REPORT_DEADLINE_MS = C.TIME.days(30);
|
|
71
78
|
|
|
72
79
|
// Adjacent-regulation incident-reporting deadlines — operators wiring
|
|
73
80
|
// NIS2 / CRA / HIPAA breach notification reach for these constants
|
|
@@ -124,14 +131,26 @@ function _classifyImpl(input) {
|
|
|
124
131
|
reasons.push("severity-high");
|
|
125
132
|
}
|
|
126
133
|
|
|
127
|
-
// 2. Affected clients
|
|
134
|
+
// 2. Affected clients — absolute count OR share of the client base. The
|
|
135
|
+
// percentile thresholds (RTS 2024/1772 Art. 1(1)(a): >10% major, ESA-
|
|
136
|
+
// guideline 1% significant) were declared but never evaluated, so a
|
|
137
|
+
// large-share-but-small-absolute incident (e.g. 16% of a 50k client base)
|
|
138
|
+
// under-classified. Operators pass clientBase (total clients) to enable
|
|
139
|
+
// the percentage criterion; absolute-only behavior is unchanged when it
|
|
140
|
+
// is omitted.
|
|
128
141
|
if (typeof input.affectedClients === "number" && input.affectedClients > 0) {
|
|
129
|
-
|
|
142
|
+
var clientBase = (typeof input.clientBase === "number" && input.clientBase > 0) ? input.clientBase : null;
|
|
143
|
+
var clientPct = clientBase ? (input.affectedClients / clientBase) : 0;
|
|
144
|
+
var majorByClients = input.affectedClients >= MAJOR_INCIDENT_THRESHOLDS.affectedClientsAbsolute ||
|
|
145
|
+
(clientBase !== null && clientPct >= MAJOR_INCIDENT_THRESHOLDS.affectedClientsPercentile);
|
|
146
|
+
var significantByClients = input.affectedClients >= SIGNIFICANT_INCIDENT_THRESHOLDS.affectedClientsAbsolute ||
|
|
147
|
+
(clientBase !== null && clientPct >= SIGNIFICANT_INCIDENT_THRESHOLDS.affectedClientsPercentile);
|
|
148
|
+
if (majorByClients) {
|
|
130
149
|
hitsMajor += 1;
|
|
131
|
-
reasons.push("clients-major
|
|
132
|
-
} else if (
|
|
150
|
+
reasons.push("clients-major");
|
|
151
|
+
} else if (significantByClients) {
|
|
133
152
|
hitsSignificant += 1;
|
|
134
|
-
reasons.push("clients-significant
|
|
153
|
+
reasons.push("clients-significant");
|
|
135
154
|
}
|
|
136
155
|
}
|
|
137
156
|
|
|
@@ -188,7 +207,11 @@ function _classifyImpl(input) {
|
|
|
188
207
|
return {
|
|
189
208
|
classification: classification,
|
|
190
209
|
mustReport: mustReport,
|
|
210
|
+
// Operative initial-notification window: 4h from this classification.
|
|
191
211
|
mustReportInitialByMs: mustReport ? INITIAL_REPORT_DEADLINE_MS : null,
|
|
212
|
+
// Outer bound: no later than 24h from first awareness — surfaced so an
|
|
213
|
+
// operator scheduling from `detectedAt` can enforce min(class+4h, aware+24h).
|
|
214
|
+
mustReportInitialOuterByMs: mustReport ? INITIAL_REPORT_OUTER_DEADLINE_MS : null,
|
|
192
215
|
reasons: reasons,
|
|
193
216
|
};
|
|
194
217
|
}
|
|
@@ -343,10 +366,16 @@ function create(opts) {
|
|
|
343
366
|
// Article 19 deadline — operator-side scheduler uses this.
|
|
344
367
|
nextStageDueAt: null,
|
|
345
368
|
};
|
|
369
|
+
// Anchor each downstream deadline on the SUBMISSION time of the report
|
|
370
|
+
// being filed (record.reportedAt), not on detectedAt: the intermediate is
|
|
371
|
+
// due 72h after the initial is *submitted*, and the final one month after
|
|
372
|
+
// the intermediate is *submitted* (DORA RTS 2024/1772 reporting timeline).
|
|
373
|
+
// Anchoring on detectedAt understates the deadline whenever a report is
|
|
374
|
+
// filed later than detection.
|
|
346
375
|
if (input.stage === "initial") {
|
|
347
|
-
record.nextStageDueAt =
|
|
376
|
+
record.nextStageDueAt = record.reportedAt + INTERMEDIATE_REPORT_DEADLINE_MS;
|
|
348
377
|
} else if (input.stage === "intermediate") {
|
|
349
|
-
record.nextStageDueAt =
|
|
378
|
+
record.nextStageDueAt = record.reportedAt + FINAL_REPORT_DEADLINE_MS;
|
|
350
379
|
}
|
|
351
380
|
_emit("dora.incident.reported", {
|
|
352
381
|
metadata: {
|
|
@@ -396,6 +425,7 @@ module.exports = {
|
|
|
396
425
|
MAJOR_INCIDENT_THRESHOLDS: MAJOR_INCIDENT_THRESHOLDS,
|
|
397
426
|
SIGNIFICANT_INCIDENT_THRESHOLDS: SIGNIFICANT_INCIDENT_THRESHOLDS,
|
|
398
427
|
INITIAL_REPORT_DEADLINE_MS: INITIAL_REPORT_DEADLINE_MS,
|
|
428
|
+
INITIAL_REPORT_OUTER_DEADLINE_MS: INITIAL_REPORT_OUTER_DEADLINE_MS,
|
|
399
429
|
INTERMEDIATE_REPORT_DEADLINE_MS: INTERMEDIATE_REPORT_DEADLINE_MS,
|
|
400
430
|
FINAL_REPORT_DEADLINE_MS: FINAL_REPORT_DEADLINE_MS,
|
|
401
431
|
DEADLINES_NIS2: DEADLINES_NIS2,
|
package/lib/dr-runbook.js
CHANGED
|
@@ -275,7 +275,7 @@ async function emit(opts) {
|
|
|
275
275
|
"drRunbook.emit: outDir", DrRunbookError, "dr-runbook/no-outdir");
|
|
276
276
|
validateOpts.requireNonEmptyString(opts.posture,
|
|
277
277
|
"drRunbook.emit: posture", DrRunbookError, "dr-runbook/no-posture");
|
|
278
|
-
if (!POSTURE_BLOCKS
|
|
278
|
+
if (!Object.prototype.hasOwnProperty.call(POSTURE_BLOCKS, opts.posture)) {
|
|
279
279
|
throw new DrRunbookError("dr-runbook/unknown-posture",
|
|
280
280
|
"drRunbook.emit: posture '" + opts.posture + "' not in supported list (" +
|
|
281
281
|
Object.keys(POSTURE_BLOCKS).join(", ") + ")");
|
package/lib/dsa.js
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
var validateOpts = require("./validate-opts");
|
|
38
38
|
var lazyRequire = require("./lazy-require");
|
|
39
|
+
var numericBounds = require("./numeric-bounds");
|
|
39
40
|
var C = require("./constants");
|
|
40
41
|
var { DsaError } = require("./framework-error");
|
|
41
42
|
|
|
@@ -414,7 +415,7 @@ function transparencyReport(opts) {
|
|
|
414
415
|
METRIC_FIELDS.forEach(function (field) {
|
|
415
416
|
var v = supplied[field];
|
|
416
417
|
if (v === undefined || v === null) { metrics[field] = 0; return; }
|
|
417
|
-
if (
|
|
418
|
+
if (!numericBounds.isNonNegativeFiniteInt(v)) {
|
|
418
419
|
throw new DsaError("dsa/bad-metric-value",
|
|
419
420
|
"b.dsa.transparencyReport: metrics." + field +
|
|
420
421
|
" must be a non-negative integer, got " +
|