@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
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
var validateOpts = require("./validate-opts");
|
|
24
24
|
var bCrypto = require("./crypto");
|
|
25
|
+
var safeJson = require("./safe-json");
|
|
25
26
|
var { defineClass } = require("./framework-error");
|
|
26
27
|
|
|
27
28
|
var ImportmapError = defineClass("ImportmapError", { alwaysPermanent: true });
|
|
@@ -46,7 +47,9 @@ var ImportmapError = defineClass("ImportmapError", { alwaysPermanent: true });
|
|
|
46
47
|
* "@org/lib": { url: "/static/lib.js", body: fileBytes },
|
|
47
48
|
* },
|
|
48
49
|
* });
|
|
49
|
-
*
|
|
50
|
+
* // Embed with the <script>-safe helper — NEVER raw JSON.stringify, which
|
|
51
|
+
* // does not escape "</script>" in a module url and breaks out of the tag.
|
|
52
|
+
* res.end(b.importmapIntegrity.scriptTag(im));
|
|
50
53
|
*/
|
|
51
54
|
function build(opts) {
|
|
52
55
|
opts = validateOpts.requireObject(opts, "importmapIntegrity.build",
|
|
@@ -84,7 +87,44 @@ function build(opts) {
|
|
|
84
87
|
return { imports: imports, integrity: integrity };
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @primitive b.importmapIntegrity.scriptTag
|
|
92
|
+
* @signature b.importmapIntegrity.scriptTag(importmap, opts?)
|
|
93
|
+
* @since 0.15.14
|
|
94
|
+
* @status stable
|
|
95
|
+
* @related b.importmapIntegrity.build, b.safeJson.stringifyForScript
|
|
96
|
+
*
|
|
97
|
+
* Render an import-map object (from `build`) as a ready-to-embed
|
|
98
|
+
* `<script type="importmap">…</script>` tag using the `<script>`-safe
|
|
99
|
+
* JSON serializer. Raw `JSON.stringify` does not escape `</script>` in a
|
|
100
|
+
* module url, so concatenating it into the page lets a `</script>` in a
|
|
101
|
+
* url close the element and inject markup — this escapes `< > &` (and the
|
|
102
|
+
* U+2028 / U+2029 separators) so no url can break out.
|
|
103
|
+
*
|
|
104
|
+
* @opts
|
|
105
|
+
* nonce: string, // CSP nonce added as nonce="…" on the <script>
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* res.end(b.importmapIntegrity.scriptTag(im, { nonce: req.cspNonce }));
|
|
109
|
+
*/
|
|
110
|
+
function scriptTag(importmap, opts) {
|
|
111
|
+
opts = opts || {};
|
|
112
|
+
var nonceAttr = "";
|
|
113
|
+
if (typeof opts.nonce === "string" && opts.nonce.length > 0) {
|
|
114
|
+
// The nonce is operator-supplied (a CSP per-request token); reject any
|
|
115
|
+
// value that could itself break the attribute rather than escape it.
|
|
116
|
+
if (/[^A-Za-z0-9+/=_-]/.test(opts.nonce)) {
|
|
117
|
+
throw new ImportmapError("importmap/bad-nonce",
|
|
118
|
+
"scriptTag: opts.nonce must be a base64/token string");
|
|
119
|
+
}
|
|
120
|
+
nonceAttr = ' nonce="' + opts.nonce + '"';
|
|
121
|
+
}
|
|
122
|
+
return '<script type="importmap"' + nonceAttr + '>' +
|
|
123
|
+
safeJson.stringifyForScript(importmap) + "</script>";
|
|
124
|
+
}
|
|
125
|
+
|
|
87
126
|
module.exports = {
|
|
88
127
|
build: build,
|
|
128
|
+
scriptTag: scriptTag,
|
|
89
129
|
ImportmapError: ImportmapError,
|
|
90
130
|
};
|
package/lib/inbox.js
CHANGED
|
@@ -43,9 +43,11 @@
|
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
45
|
var C = require("./constants");
|
|
46
|
+
var codepointClass = require("./codepoint-class");
|
|
46
47
|
var lazyRequire = require("./lazy-require");
|
|
47
48
|
var safeJson = require("./safe-json");
|
|
48
49
|
var safeSql = require("./safe-sql");
|
|
50
|
+
var safeBuffer = require("./safe-buffer");
|
|
49
51
|
var sql = require("./sql");
|
|
50
52
|
var validateOpts = require("./validate-opts");
|
|
51
53
|
var { defineClass } = require("./framework-error");
|
|
@@ -204,13 +206,11 @@ function create(opts) {
|
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
function _rejectControlChars(value, label, field) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
" (codepoint " + code + ")");
|
|
213
|
-
}
|
|
209
|
+
var _off = codepointClass.firstControlCharOffset(value); // allow tab (RFC 5322 folding WS)
|
|
210
|
+
if (_off !== -1) {
|
|
211
|
+
throw new InboxError("inbox/bad-receive",
|
|
212
|
+
label + ": " + field + " contains control character at index " + _off +
|
|
213
|
+
" (codepoint " + value.charCodeAt(_off) + ")");
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
@@ -223,7 +223,7 @@ function create(opts) {
|
|
|
223
223
|
var metaJson = null;
|
|
224
224
|
if (receiveOpts.metadata != null) {
|
|
225
225
|
var serialized = safeJson.stringify(receiveOpts.metadata);
|
|
226
|
-
if (serialized
|
|
226
|
+
if (safeBuffer.byteLengthOf(serialized) > maxPayloadBytes) {
|
|
227
227
|
throw new InboxError("inbox/bad-receive",
|
|
228
228
|
"recordReceive: metadata serialized exceeds maxPayloadBytes (" +
|
|
229
229
|
maxPayloadBytes + " bytes)");
|
package/lib/incident-report.js
CHANGED
|
@@ -93,12 +93,15 @@ var REGIME_DEADLINES = Object.freeze({
|
|
|
93
93
|
intermediate: C.TIME.hours(72),
|
|
94
94
|
final: C.TIME.days(30),
|
|
95
95
|
}),
|
|
96
|
-
// DORA (EU Digital Operational Resilience Act)
|
|
97
|
-
// within 4h of
|
|
98
|
-
// within
|
|
96
|
+
// DORA (EU Digital Operational Resilience Act) Art. 19 + RTS (EU)
|
|
97
|
+
// 2024/1772 Art. 5: initial within 4h of classifying the incident as
|
|
98
|
+
// major (outer bound 24h from awareness), intermediate within 72h of the
|
|
99
|
+
// initial notification, final within 1 month of the intermediate report.
|
|
100
|
+
// (intermediate was 24h here, contradicting lib/dora.js's 72h — the RTS
|
|
101
|
+
// value is 72h from the initial notification; aligned.)
|
|
99
102
|
dora: Object.freeze({
|
|
100
103
|
initial: C.TIME.hours(4),
|
|
101
|
-
intermediate: C.TIME.hours(
|
|
104
|
+
intermediate: C.TIME.hours(72),
|
|
102
105
|
final: C.TIME.days(30),
|
|
103
106
|
}),
|
|
104
107
|
// CRA (EU Cyber Resilience Act) Article 14: early warning within
|
|
@@ -138,7 +141,6 @@ function create(opts) {
|
|
|
138
141
|
"audit", "persist", "onStage", "deadlines", "now",
|
|
139
142
|
], "incident.report");
|
|
140
143
|
|
|
141
|
-
var auditOn = opts.audit !== false;
|
|
142
144
|
var persist = typeof opts.persist === "function" ? opts.persist : null;
|
|
143
145
|
var onStage = typeof opts.onStage === "function" ? opts.onStage : null;
|
|
144
146
|
var deadlinesOverride = opts.deadlines || null;
|
|
@@ -150,20 +152,8 @@ function create(opts) {
|
|
|
150
152
|
var incidents = new Map();
|
|
151
153
|
var seq = 0;
|
|
152
154
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
audit().safeEmit({
|
|
157
|
-
action: "incident.report." + action,
|
|
158
|
-
outcome: outcome,
|
|
159
|
-
metadata: metadata || {},
|
|
160
|
-
});
|
|
161
|
-
} catch (_e) { /* drop-silent */ }
|
|
162
|
-
}
|
|
163
|
-
function _emitMetric(verb, n, labels) {
|
|
164
|
-
try { observability().safeEvent("incident.report." + verb, n || 1, labels || {}); }
|
|
165
|
-
catch (_e) { /* drop-silent */ }
|
|
166
|
-
}
|
|
155
|
+
var _emitAudit = audit().namespaced("incident.report", opts.audit);
|
|
156
|
+
var _emitMetric = observability().namespaced("incident.report");
|
|
167
157
|
|
|
168
158
|
function _genIncidentId(regime, detectedAt) {
|
|
169
159
|
seq += 1;
|
|
@@ -219,7 +209,7 @@ function create(opts) {
|
|
|
219
209
|
}
|
|
220
210
|
|
|
221
211
|
async function _recordStage(incidentId, stage, payload) {
|
|
222
|
-
if (!VALID_STAGES
|
|
212
|
+
if (!Object.prototype.hasOwnProperty.call(VALID_STAGES, stage)) {
|
|
223
213
|
throw new IncidentReportError("incident-report/bad-stage",
|
|
224
214
|
"incident.report._recordStage: stage must be one of " + Object.keys(VALID_STAGES).join(", "));
|
|
225
215
|
}
|
|
@@ -339,12 +329,7 @@ function createDeadlineClock(opts) {
|
|
|
339
329
|
var tracked = new Map(); // incidentId -> { detectedAt, dueBy, regime, acked, fired }
|
|
340
330
|
var timer = null;
|
|
341
331
|
|
|
342
|
-
|
|
343
|
-
if (!auditOn) return;
|
|
344
|
-
try {
|
|
345
|
-
audit().safeEmit({ action: "incident.report.clock." + action, outcome: outcome, metadata: metadata || {} });
|
|
346
|
-
} catch (_e) { /* drop-silent — by design */ }
|
|
347
|
-
}
|
|
332
|
+
var _emit = audit().namespaced("incident.report.clock", auditOn);
|
|
348
333
|
function _notify(payload) {
|
|
349
334
|
if (!notify) return;
|
|
350
335
|
try {
|
|
@@ -376,7 +361,7 @@ function createDeadlineClock(opts) {
|
|
|
376
361
|
function untrack(id) { return tracked.delete(id); }
|
|
377
362
|
|
|
378
363
|
function acknowledgeSubmission(id, stage, info) {
|
|
379
|
-
if (!VALID_STAGES
|
|
364
|
+
if (!Object.prototype.hasOwnProperty.call(VALID_STAGES, stage)) {
|
|
380
365
|
throw new IncidentReportError("incident-report/bad-stage",
|
|
381
366
|
"createDeadlineClock.acknowledgeSubmission: stage must be one of " + Object.keys(VALID_STAGES).join(", "));
|
|
382
367
|
}
|
package/lib/ip-utils.js
CHANGED
|
@@ -82,9 +82,9 @@ function expandIpv6Groups(ip) {
|
|
|
82
82
|
// Loose IPv4 textual-form check — for primitives that need to
|
|
83
83
|
// classify a string as "looks like dotted-quad IPv4" but don't need
|
|
84
84
|
// the strict per-octet bound check (callers that DO need octet
|
|
85
|
-
// bounds use
|
|
86
|
-
// `lib/mail
|
|
87
|
-
//
|
|
85
|
+
// bounds use `isIPv4` below). Shared so `lib/mail.js`,
|
|
86
|
+
// `lib/mail-helo.js`, and `lib/redis-client.js` don't drift on the
|
|
87
|
+
// same shape.
|
|
88
88
|
//
|
|
89
89
|
// isIPv4Shape("1.2.3.4") → true
|
|
90
90
|
// isIPv4Shape("999.0.0.0") → true (shape only; octets unbounded)
|
|
@@ -95,8 +95,51 @@ function isIPv4Shape(s) {
|
|
|
95
95
|
return typeof s === "string" && IPV4_SHAPE_RE.test(s);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Strict RFC 791 dotted-quad IPv4: four 0-255 octets separated by `.`.
|
|
99
|
+
// The single source for textual IPv4 validation across the framework
|
|
100
|
+
// — `b.mail.greylist` / `b.mail.rbl` CIDR fingerprinting, `b.mail.helo`
|
|
101
|
+
// RFC 5321 §4.1.3 address-literals, `b.guardDomain` IPv4-as-domain
|
|
102
|
+
// detection, and `b.safeSchema` `.ip()`. Hand-rolling the per-octet
|
|
103
|
+
// alternation had drifted into several spellings; this is canonical.
|
|
104
|
+
//
|
|
105
|
+
// isIPv4("203.0.113.42") → true
|
|
106
|
+
// isIPv4("256.0.0.1") → false (octet > 255)
|
|
107
|
+
// isIPv4("1.2.3") → false (three octets)
|
|
108
|
+
var IPV4_RE = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; // allow:regex-no-length-cap — anchored + per-octet repeat-cap
|
|
109
|
+
function isIPv4(s) {
|
|
110
|
+
return typeof s === "string" && IPV4_RE.test(s);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// RFC 5321 §4.1.3 IPv4 address-literal `[1.2.3.4]`. Capture group 1 is
|
|
114
|
+
// the inner dotted-quad; `b.mail.helo` matches a claimed literal
|
|
115
|
+
// against the connection IP. Same per-octet bound as `IPV4_RE`.
|
|
116
|
+
var IPV4_ADDR_LITERAL_RE = /^\[((?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})\]$/; // allow:regex-no-length-cap — anchored + per-octet repeat-cap
|
|
117
|
+
|
|
118
|
+
// Loose IPv6 textual pre-filter: hex digits + colons only, bounded to
|
|
119
|
+
// the RFC 4291 §2.2 max no-IPv4-tail textual length (8 groups × 4 hex
|
|
120
|
+
// + 7 colons = 39). A cheap fail-fast before the full `expandIpv6Hex`
|
|
121
|
+
// parse — NOT a complete validator; callers follow with
|
|
122
|
+
// `expandIpv6Hex`. A hex-colon string longer than 39 chars is never a
|
|
123
|
+
// valid IPv6 textual form, so the bound only rejects garbage.
|
|
124
|
+
//
|
|
125
|
+
// looksLikeIPv6Hex("2001:db8::1") → true
|
|
126
|
+
// looksLikeIPv6Hex("::1") → true
|
|
127
|
+
// looksLikeIPv6Hex("::ffff:1.2.3.4")→ false (dotted tail — use expandIpv6Hex)
|
|
128
|
+
// looksLikeIPv6Hex("nothex") → false
|
|
129
|
+
var IPV6_TEXT_MAX_LEN = 39; // RFC 4291 §2.2 max no-IPv4-tail textual length
|
|
130
|
+
var IPV6_HEX_RE = /^[0-9a-fA-F:]+$/; // allow:regex-no-length-cap — length-bounded by IPV6_TEXT_MAX_LEN in looksLikeIPv6Hex
|
|
131
|
+
function looksLikeIPv6Hex(s) {
|
|
132
|
+
return typeof s === "string" && s.length <= IPV6_TEXT_MAX_LEN && IPV6_HEX_RE.test(s);
|
|
133
|
+
}
|
|
134
|
+
|
|
98
135
|
module.exports = {
|
|
99
|
-
expandIpv6Hex:
|
|
100
|
-
expandIpv6Groups:
|
|
101
|
-
isIPv4Shape:
|
|
136
|
+
expandIpv6Hex: expandIpv6Hex,
|
|
137
|
+
expandIpv6Groups: expandIpv6Groups,
|
|
138
|
+
isIPv4Shape: isIPv4Shape,
|
|
139
|
+
isIPv4: isIPv4,
|
|
140
|
+
IPV4_RE: IPV4_RE,
|
|
141
|
+
IPV4_ADDR_LITERAL_RE: IPV4_ADDR_LITERAL_RE,
|
|
142
|
+
looksLikeIPv6Hex: looksLikeIPv6Hex,
|
|
143
|
+
IPV6_HEX_RE: IPV6_HEX_RE,
|
|
144
|
+
IPV6_TEXT_MAX_LEN: IPV6_TEXT_MAX_LEN,
|
|
102
145
|
};
|
package/lib/jobs.js
CHANGED
|
@@ -77,6 +77,7 @@ var { boot } = require("./log");
|
|
|
77
77
|
var queue = require("./queue");
|
|
78
78
|
var validateOpts = require("./validate-opts");
|
|
79
79
|
var { JobsError } = require("./framework-error");
|
|
80
|
+
var boundedMap = require("./bounded-map");
|
|
80
81
|
|
|
81
82
|
var log = boot("jobs");
|
|
82
83
|
|
|
@@ -102,10 +103,10 @@ function create(opts) {
|
|
|
102
103
|
if (typeof handler !== "function") {
|
|
103
104
|
throw _err("INVALID_HANDLER", "jobs.define: handler must be a function", true);
|
|
104
105
|
}
|
|
105
|
-
|
|
106
|
+
boundedMap.requireAbsent(registry, name, function () {
|
|
106
107
|
throw _err("DUPLICATE_NAME",
|
|
107
108
|
"jobs.define: '" + name + "' is already defined", true);
|
|
108
|
-
}
|
|
109
|
+
});
|
|
109
110
|
if (started) {
|
|
110
111
|
// Defining after start would mean the new handler doesn't run
|
|
111
112
|
// until the next start cycle. Reject loudly so operators don't
|
package/lib/json-patch.js
CHANGED
|
@@ -157,7 +157,7 @@ function apply(doc, operations) {
|
|
|
157
157
|
var work = structuredClone(doc);
|
|
158
158
|
for (var i = 0; i < operations.length; i += 1) {
|
|
159
159
|
var op = operations[i];
|
|
160
|
-
if (!op || typeof op !== "object" || typeof op.op !== "string" || !OPS
|
|
160
|
+
if (!op || typeof op !== "object" || typeof op.op !== "string" || !Object.prototype.hasOwnProperty.call(OPS, op.op)) {
|
|
161
161
|
throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: operations[" + i + "] has an invalid 'op'");
|
|
162
162
|
}
|
|
163
163
|
if (typeof op.path !== "string") throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: operations[" + i + "] is missing 'path'");
|
package/lib/json-path.js
CHANGED
|
@@ -35,6 +35,7 @@ var { defineClass } = require("./framework-error");
|
|
|
35
35
|
var JsonPathError = defineClass("JsonPathError", { alwaysPermanent: true });
|
|
36
36
|
|
|
37
37
|
var MAX_DESCEND_NODES = 1000000; // DoS ceiling on nodes visited by a descendant walk
|
|
38
|
+
var MAX_TOTAL_NODES = 1000000; // DoS ceiling on the running nodelist across ALL segments (chained wildcard/slice/filter cross-product)
|
|
38
39
|
|
|
39
40
|
// ---------------------------------------------------------------------------
|
|
40
41
|
// Parser — recursive descent over the RFC 9535 ABNF.
|
|
@@ -46,7 +47,20 @@ function _isDigit(c) { return c >= "0" && c <= "9"; }
|
|
|
46
47
|
function _isNameFirst(c) { var cc = c.charCodeAt(0); return (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c === "_" || cc >= 0x80; }
|
|
47
48
|
function _isNameChar(c) { return _isNameFirst(c) || _isDigit(c); }
|
|
48
49
|
|
|
49
|
-
function _Parser(s) { this.s = s; this.i = 0; }
|
|
50
|
+
function _Parser(s) { this.s = s; this.i = 0; this._depth = 0; }
|
|
51
|
+
|
|
52
|
+
// Nesting-depth cap for the filter-expression recursion (parens + `!`). Without
|
|
53
|
+
// it, a deeply-nested filter like `?(((((...)))))` recurses until V8 throws a
|
|
54
|
+
// raw RangeError ("Maximum call stack size exceeded") that escapes JsonPathError
|
|
55
|
+
// handling — a DoS / unhandled-crash lever on attacker-supplied JSONPath.
|
|
56
|
+
var MAX_FILTER_DEPTH = 200;
|
|
57
|
+
_Parser.prototype._descend = function () {
|
|
58
|
+
if (++this._depth > MAX_FILTER_DEPTH) {
|
|
59
|
+
throw new JsonPathError("json-path/filter-too-deep",
|
|
60
|
+
"jsonPath: filter expression nesting exceeds " + MAX_FILTER_DEPTH);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
_Parser.prototype._ascend = function () { this._depth -= 1; };
|
|
50
64
|
_Parser.prototype.err = function (msg) { throw new JsonPathError("json-path/invalid", "jsonPath: " + msg + " at index " + this.i); };
|
|
51
65
|
_Parser.prototype.peek = function () { return this.i < this.s.length ? this.s.charAt(this.i) : ""; };
|
|
52
66
|
_Parser.prototype.eat = function (c) { if (this.peek() !== c) this.err("expected '" + c + "'"); this.i += 1; };
|
|
@@ -226,9 +240,9 @@ _Parser.prototype.parseLogicalAnd = function () {
|
|
|
226
240
|
};
|
|
227
241
|
_Parser.prototype.parseBasic = function () {
|
|
228
242
|
this.skipBlank();
|
|
229
|
-
if (this.peek() === "!") { this.i += 1; this.skipBlank(); var inner = this.parseBasic(); this._requireTestable(inner); return { type: "not", e: inner, vtype: "logical" }; }
|
|
243
|
+
if (this.peek() === "!") { this.i += 1; this.skipBlank(); this._descend(); var inner = this.parseBasic(); this._ascend(); this._requireTestable(inner); return { type: "not", e: inner, vtype: "logical" }; }
|
|
230
244
|
if (this.peek() === "(") {
|
|
231
|
-
this.i += 1; this.skipBlank(); var e = this.parseLogicalOr(); this.skipBlank(); this.eat(")");
|
|
245
|
+
this.i += 1; this.skipBlank(); this._descend(); var e = this.parseLogicalOr(); this._ascend(); this.skipBlank(); this.eat(")");
|
|
232
246
|
return e;
|
|
233
247
|
}
|
|
234
248
|
// comparison or test
|
|
@@ -436,6 +450,13 @@ function _evalSegments(segments, root) {
|
|
|
436
450
|
base = acc;
|
|
437
451
|
}
|
|
438
452
|
base.forEach(function (nd) { seg.selectors.forEach(function (sel) { _applySelector(sel, nd, root, next); }); });
|
|
453
|
+
// Cap the running nodelist across the WHOLE query, not just per descendant
|
|
454
|
+
// walk: chained wildcard/slice/filter selectors multiply the nodelist each
|
|
455
|
+
// segment ([*][*][*]…), an OOM lever the per-`_descend` budget never sees.
|
|
456
|
+
if (next.length > MAX_TOTAL_NODES) {
|
|
457
|
+
throw new JsonPathError("json-path/too-large",
|
|
458
|
+
"jsonPath: nodelist exceeded " + MAX_TOTAL_NODES + " nodes (chained selector cross-product)");
|
|
459
|
+
}
|
|
439
460
|
nodes = next;
|
|
440
461
|
}
|
|
441
462
|
return nodes;
|
package/lib/jtd.js
CHANGED
|
@@ -58,7 +58,7 @@ function _checkSchema(schema, root, isRoot) {
|
|
|
58
58
|
if (!_isPlainObject(schema)) throw new JtdError("jtd/bad-schema", "jtd: schema must be an object");
|
|
59
59
|
if (!isRoot && Object.prototype.hasOwnProperty.call(schema, "definitions")) throw new JtdError("jtd/bad-schema", "jtd: 'definitions' is allowed only at the root");
|
|
60
60
|
Object.keys(schema).forEach(function (k) {
|
|
61
|
-
if (FORM_KEYWORDS.indexOf(k) === -1 && !SHARED_KEYWORDS
|
|
61
|
+
if (FORM_KEYWORDS.indexOf(k) === -1 && !Object.prototype.hasOwnProperty.call(SHARED_KEYWORDS, k) && k !== "additionalProperties" && k !== "mapping") throw new JtdError("jtd/bad-schema", "jtd: unknown keyword '" + k + "'");
|
|
62
62
|
});
|
|
63
63
|
if (Object.prototype.hasOwnProperty.call(schema, "nullable") && typeof schema.nullable !== "boolean") throw new JtdError("jtd/bad-schema", "jtd: 'nullable' must be a boolean");
|
|
64
64
|
if (Object.prototype.hasOwnProperty.call(schema, "metadata") && !_isPlainObject(schema.metadata)) throw new JtdError("jtd/bad-schema", "jtd: 'metadata' must be an object");
|
|
@@ -75,7 +75,7 @@ function _checkSchema(schema, root, isRoot) {
|
|
|
75
75
|
if ("ref" in schema) {
|
|
76
76
|
if (typeof schema.ref !== "string" || !root.definitions || !Object.prototype.hasOwnProperty.call(root.definitions, schema.ref)) throw new JtdError("jtd/bad-schema", "jtd: 'ref' must name a key in the root definitions");
|
|
77
77
|
}
|
|
78
|
-
if ("type" in schema && !TYPES
|
|
78
|
+
if ("type" in schema && !Object.prototype.hasOwnProperty.call(TYPES, schema.type)) throw new JtdError("jtd/bad-schema", "jtd: unknown type '" + schema.type + "'");
|
|
79
79
|
if ("enum" in schema) {
|
|
80
80
|
if (!Array.isArray(schema.enum) || schema.enum.length === 0) throw new JtdError("jtd/bad-schema", "jtd: 'enum' must be a non-empty array");
|
|
81
81
|
var seen = Object.create(null);
|
package/lib/keychain.js
CHANGED
|
@@ -242,24 +242,12 @@ async function _writeFile(fallbackFile, doc, passphrase) {
|
|
|
242
242
|
// Drain a stream into a Buffer, honoring an upper byte cap so a
|
|
243
243
|
// runaway tool can't OOM the framework.
|
|
244
244
|
function _drain(stream, capBytes) {
|
|
245
|
-
return
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
sizeMessage: "native tool output exceeded " + capBytes + " bytes",
|
|
252
|
-
});
|
|
253
|
-
stream.on("data", function (chunk) {
|
|
254
|
-
try {
|
|
255
|
-
collector.push(chunk);
|
|
256
|
-
} catch (e) {
|
|
257
|
-
try { stream.destroy(); } catch (_e) { /* destroy best-effort */ }
|
|
258
|
-
reject(e);
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
stream.on("end", function () { resolve(collector.result()); });
|
|
262
|
-
stream.on("error", function (e) { reject(e); });
|
|
245
|
+
if (!stream) return Promise.resolve(Buffer.alloc(0));
|
|
246
|
+
return safeBuffer.collectStream(stream, {
|
|
247
|
+
maxBytes: capBytes,
|
|
248
|
+
errorClass: KeychainError,
|
|
249
|
+
sizeCode: "keychain/native-output-too-large",
|
|
250
|
+
sizeMessage: "native tool output exceeded " + capBytes + " bytes",
|
|
263
251
|
});
|
|
264
252
|
}
|
|
265
253
|
|
package/lib/legal-hold.js
CHANGED
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
* on policy denials (already-held / not-held / invalid-citation).
|
|
61
61
|
*/
|
|
62
62
|
var bCrypto = require("./crypto");
|
|
63
|
-
var lazyRequire = require("./lazy-require");
|
|
64
63
|
var safeJson = require("./safe-json");
|
|
65
64
|
var validateOpts = require("./validate-opts");
|
|
66
65
|
var sql = require("./sql");
|
|
@@ -77,7 +76,7 @@ var { defineClass } = require("./framework-error");
|
|
|
77
76
|
var HOLD_TABLE = "_blamejs_legal_hold"; // allow:hand-rolled-sql — canonical local table-name; passed to b.sql with quoteName
|
|
78
77
|
var AUDIT_TABLE = "audit_log";
|
|
79
78
|
|
|
80
|
-
var
|
|
79
|
+
var auditEmit = require("./audit-emit");
|
|
81
80
|
|
|
82
81
|
var LegalHoldError = defineClass("LegalHoldError", { alwaysPermanent: true });
|
|
83
82
|
var _err = LegalHoldError.factory;
|
|
@@ -151,19 +150,11 @@ function create(opts) {
|
|
|
151
150
|
validateOpts.optionalObjectWithMethod(opts.signWith, "sign",
|
|
152
151
|
"create: opts.signWith", LegalHoldError, "BAD_OPT", "b.auditSign-shaped object");
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
action: action,
|
|
160
|
-
outcome: outcome,
|
|
161
|
-
resource: { kind: "legal-hold", id: info && info.subjectId },
|
|
162
|
-
reason: (info && info.reason) || null,
|
|
163
|
-
metadata: info || {},
|
|
164
|
-
});
|
|
165
|
-
} catch (_e) { /* best-effort */ }
|
|
166
|
-
}
|
|
153
|
+
var _emit = auditEmit.gatedReasonEmitter({
|
|
154
|
+
audit: auditOn,
|
|
155
|
+
sink: auditInstance,
|
|
156
|
+
extra: function (info) { return { resource: { kind: "legal-hold", id: info && info.subjectId } }; },
|
|
157
|
+
});
|
|
167
158
|
|
|
168
159
|
function _ensureSchema() {
|
|
169
160
|
// Idempotent migration. The framework SQLite path installs the
|
|
@@ -219,19 +210,34 @@ function create(opts) {
|
|
|
219
210
|
_ensureSchema();
|
|
220
211
|
var hash = _hashSubject(sid);
|
|
221
212
|
var placeSelBuilt = sql.select(HOLD_TABLE, SQL_OPTS)
|
|
222
|
-
.columns(["placedAt"])
|
|
213
|
+
.columns(["placedAt", "retainUntil"])
|
|
223
214
|
.where("subjectIdHash", hash)
|
|
224
215
|
.toSql();
|
|
225
216
|
var placeSelStmt = db.prepare(placeSelBuilt.sql);
|
|
226
217
|
var existing = placeSelStmt.get.apply(placeSelStmt, placeSelBuilt.params);
|
|
218
|
+
var nowMs = Date.now();
|
|
219
|
+
var renewedFromLapsed = false;
|
|
227
220
|
if (existing) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
221
|
+
// A LAPSED hold (retainUntil sunset already passed → isHeld() is already
|
|
222
|
+
// false) must not block a fresh placement: otherwise a subject whose hold
|
|
223
|
+
// expired cannot be re-held when new litigation arises, leaving the data
|
|
224
|
+
// both unprotected (isHeld false) and erasable by retention. Replace the
|
|
225
|
+
// lapsed row with the new hold. An ACTIVE hold still rejects as before.
|
|
226
|
+
var existingLapsed = existing.retainUntil && existing.retainUntil < nowMs;
|
|
227
|
+
if (!existingLapsed) {
|
|
228
|
+
_emit("legalhold.place_rejected",
|
|
229
|
+
{ subjectId: sid, reason: "already-held",
|
|
230
|
+
existingSince: existing.placedAt },
|
|
231
|
+
"denied");
|
|
232
|
+
return { error: "already-held", placedAt: existing.placedAt };
|
|
233
|
+
}
|
|
234
|
+
renewedFromLapsed = true;
|
|
235
|
+
var lapseDelBuilt = sql.delete(HOLD_TABLE, SQL_OPTS)
|
|
236
|
+
.where("subjectIdHash", hash)
|
|
237
|
+
.toSql();
|
|
238
|
+
var lapseDelStmt = db.prepare(lapseDelBuilt.sql);
|
|
239
|
+
lapseDelStmt.run.apply(lapseDelStmt, lapseDelBuilt.params);
|
|
233
240
|
}
|
|
234
|
-
var nowMs = Date.now();
|
|
235
241
|
var placeInsBuilt = sql.insert(HOLD_TABLE, SQL_OPTS)
|
|
236
242
|
.values(cryptoField.sealRow(HOLD_TABLE, {
|
|
237
243
|
subjectIdHash: hash,
|
|
@@ -251,9 +257,10 @@ function create(opts) {
|
|
|
251
257
|
citation: args.citation || null,
|
|
252
258
|
retainUntil: args.retainUntil || null,
|
|
253
259
|
placedBy: args.placedBy || null,
|
|
260
|
+
renewedFromLapsed: renewedFromLapsed,
|
|
254
261
|
knownCitation: args.citation && KNOWN_CITATIONS.indexOf(args.citation) !== -1 },
|
|
255
262
|
"success");
|
|
256
|
-
return { placed: true, placedAt: nowMs };
|
|
263
|
+
return { placed: true, placedAt: nowMs, renewedFromLapsed: renewedFromLapsed };
|
|
257
264
|
}
|
|
258
265
|
|
|
259
266
|
function release(subjectId, args) {
|