@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/codepoint-class.js
CHANGED
|
@@ -172,7 +172,7 @@ function detectMixedScripts(label, allowedScripts) {
|
|
|
172
172
|
//
|
|
173
173
|
// issues.push.apply(issues,
|
|
174
174
|
// codepointClass.detectCharThreats(text, opts, "html"));
|
|
175
|
-
function detectCharThreats(text, opts, codePrefix) {
|
|
175
|
+
function detectCharThreats(text, opts, codePrefix, zeroWidthSeverity) {
|
|
176
176
|
var issues = [];
|
|
177
177
|
if (typeof text !== "string") return issues;
|
|
178
178
|
if (opts && opts.bidiPolicy !== "allow") {
|
|
@@ -208,6 +208,28 @@ function detectCharThreats(text, opts, codePrefix) {
|
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
|
+
// Zero-width / invisible-formatting chars — the fourth Trojan-source-class
|
|
212
|
+
// character threat, detected here alongside its siblings so no guard
|
|
213
|
+
// hand-rolls it. OPT-IN AND severity via zeroWidthSeverity: a caller that
|
|
214
|
+
// wants zero-width detection passes the context-appropriate severity
|
|
215
|
+
// ("high" where an invisible char spoofs an identifier / filename / line of
|
|
216
|
+
// text; "warn" where it is cosmetic), and omitting it skips the scan. Gated
|
|
217
|
+
// further on a defined non-`allow` zeroWidthPolicy — flagged under `strip`
|
|
218
|
+
// too (like bidi / null / control) so a zero-width-only input under `strip`
|
|
219
|
+
// reaches the sanitizer and is removed rather than served unchanged.
|
|
220
|
+
if (zeroWidthSeverity && opts && opts.zeroWidthPolicy &&
|
|
221
|
+
opts.zeroWidthPolicy !== "allow") {
|
|
222
|
+
var zwMatch = text.match(ZERO_WIDTH_RE);
|
|
223
|
+
if (zwMatch) {
|
|
224
|
+
issues.push({
|
|
225
|
+
kind: "zero-width", severity: zeroWidthSeverity,
|
|
226
|
+
ruleId: codePrefix + ".zero-width",
|
|
227
|
+
location: zwMatch.index,
|
|
228
|
+
snippet: "zero-width / invisible-formatting char U+" +
|
|
229
|
+
zwMatch[0].charCodeAt(0).toString(HEX_RADIX) + " at byte " + zwMatch.index,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
211
233
|
return issues;
|
|
212
234
|
}
|
|
213
235
|
|
|
@@ -292,7 +314,68 @@ function isUnreserved(cc) {
|
|
|
292
314
|
cc === 0x7e; // ~
|
|
293
315
|
}
|
|
294
316
|
|
|
317
|
+
// isForbiddenControlChar — the header-injection / RFC 5322 control-byte
|
|
318
|
+
// predicate every "refuse control bytes in a header / line / value" loop
|
|
319
|
+
// shares across the mail guards (dsn / imap / list-id / list-unsubscribe /
|
|
320
|
+
// mail-compose / mail-sieve / managesieve) and the safe-* text parsers
|
|
321
|
+
// (ical / mime / vcard / toml). TRUE for DEL (0x7f) and any C0 control
|
|
322
|
+
// (< 0x20) other than TAB (0x09); LF (0x0a) and CR (0x0d) are refused by
|
|
323
|
+
// default but can be permitted per call (a line reader that has already
|
|
324
|
+
// split on CRLF, or a grammar that allows folding). Distinct from the
|
|
325
|
+
// C0_CTRL_RANGES regex above, which is the stripping/scanning table that
|
|
326
|
+
// always exempts LF/CR and never matches DEL.
|
|
327
|
+
//
|
|
328
|
+
// opts.forbidTab — TAB (0x09) is normally permitted (it is folding
|
|
329
|
+
// whitespace in RFC 5322 header values). The stricter identifier / key /
|
|
330
|
+
// name / single-line-value contexts (idempotency keys, step-up challenge
|
|
331
|
+
// values, mailbox / folder / message-id names, assembly ids, DNSBL-free
|
|
332
|
+
// single-line values) forbid EVERY C0 control plus DEL — there is no
|
|
333
|
+
// folding to honour — so they pass `{ forbidTab: true }`, making the
|
|
334
|
+
// predicate exactly `code < 0x20 || code === 0x7f`.
|
|
335
|
+
function isForbiddenControlChar(code, opts) {
|
|
336
|
+
if (code === 0x7f) return true; // DEL
|
|
337
|
+
if (code >= 0x20) return false;
|
|
338
|
+
if (code === 0x09 && (!opts || !opts.forbidTab)) return false; // TAB — permitted unless forbidTab
|
|
339
|
+
if (opts) {
|
|
340
|
+
if (opts.allowLf && code === 0x0a) return false;
|
|
341
|
+
if (opts.allowCr && code === 0x0d) return false;
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// firstControlCharOffset — index of the first isForbiddenControlChar in `s`
|
|
347
|
+
// (under the same opts), or -1 when none. Callers wrap it as a boolean
|
|
348
|
+
// (`offset !== -1`), throw with the offending code (`s.charCodeAt(offset)`),
|
|
349
|
+
// or derive a byte offset from the index — replacing the open-coded
|
|
350
|
+
// `for (i) { c = charCodeAt(i); if (c === 0 || c === 0x7f || (c < 0x20 &&
|
|
351
|
+
// c !== 0x09)) … }` scan each parser previously rolled by hand.
|
|
352
|
+
function firstControlCharOffset(s, opts) {
|
|
353
|
+
for (var i = 0; i < s.length; i += 1) {
|
|
354
|
+
if (isForbiddenControlChar(s.charCodeAt(i), opts)) return i;
|
|
355
|
+
}
|
|
356
|
+
return -1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Decode HTML numeric character references (hex &#x..; and decimal &#..;) just
|
|
360
|
+
// enough to expose a scheme hidden behind entity-encoding. The trailing
|
|
361
|
+
// semicolon is OPTIONAL — a browser decodes `javascript:` (no semicolon)
|
|
362
|
+
// the same as `javascript:`, so a semicolon-required decoder lets the
|
|
363
|
+
// no-semicolon form bypass a scheme allowlist. Shared so guard-html / guard-svg
|
|
364
|
+
// / guard-markdown cannot drift on this (the bug class that shipped one buggy
|
|
365
|
+
// + one correct copy).
|
|
366
|
+
var NUMERIC_ENTITY_RE_G = /&#(?:x([0-9a-f]+)|(\d+));?/gi;
|
|
367
|
+
function decodeNumericEntities(s) {
|
|
368
|
+
return String(s == null ? "" : s).replace(NUMERIC_ENTITY_RE_G, function (m, hex, dec) {
|
|
369
|
+
var cp = hex !== undefined ? parseInt(hex, 16) : parseInt(dec, 10);
|
|
370
|
+
if (!isFinite(cp) || cp < 0 || cp > 0x10FFFF) return m;
|
|
371
|
+
try { return String.fromCodePoint(cp); } catch (_e) { return m; }
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
295
375
|
module.exports = {
|
|
376
|
+
isForbiddenControlChar: isForbiddenControlChar,
|
|
377
|
+
firstControlCharOffset: firstControlCharOffset,
|
|
378
|
+
decodeNumericEntities: decodeNumericEntities,
|
|
296
379
|
isAsciiAlnum: isAsciiAlnum,
|
|
297
380
|
isUnreserved: isUnreserved,
|
|
298
381
|
hex4: hex4,
|
|
@@ -131,12 +131,7 @@ function emit(event) {
|
|
|
131
131
|
// namespace uses underscores, so the kind is rewritten before emit.
|
|
132
132
|
try {
|
|
133
133
|
var kindCanonical = String(event.kind || "log").replace(/-/g, "_");
|
|
134
|
-
audit().
|
|
135
|
-
action: "compliance.aiact." + kindCanonical,
|
|
136
|
-
outcome: event.outcome || "success",
|
|
137
|
-
actor: event.actor || null,
|
|
138
|
-
metadata: event,
|
|
139
|
-
});
|
|
134
|
+
audit().namespaced("compliance.aiact")(kindCanonical, event.outcome || "success", event, { actor: event.actor || null });
|
|
140
135
|
} catch (_e) { /* drop-silent */ }
|
|
141
136
|
return event;
|
|
142
137
|
}
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
var validateOpts = require("./validate-opts");
|
|
42
|
+
var markupEscape = require("./markup-escape").markupEscape;
|
|
42
43
|
var { ComplianceError } = require("./framework-error");
|
|
43
44
|
|
|
44
45
|
var BANNER_KINDS = Object.freeze([
|
|
@@ -118,10 +119,7 @@ function htmlBanner(opts) {
|
|
|
118
119
|
|
|
119
120
|
function _escapeHtml(s) {
|
|
120
121
|
if (typeof s !== "string") return "";
|
|
121
|
-
return s
|
|
122
|
-
.replace(/</g, "<")
|
|
123
|
-
.replace(/>/g, ">")
|
|
124
|
-
.replace(/"/g, """);
|
|
122
|
+
return markupEscape(s);
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
// Watermark builder for Art. 50(2). Operators integrate with C2PA /
|
package/lib/compliance-eaa.js
CHANGED
|
@@ -66,22 +66,12 @@ function create(opts) {
|
|
|
66
66
|
var standards = opts.standards.slice();
|
|
67
67
|
var contact = opts.contact || null;
|
|
68
68
|
var supervisoryAuthority = opts.supervisoryAuthority || null;
|
|
69
|
-
var auditOn = opts.audit !== false;
|
|
70
69
|
var now = typeof opts.now === "function" ? opts.now : function () { return Date.now(); };
|
|
71
70
|
|
|
72
71
|
var criteria = new Map();
|
|
73
72
|
var nonConformances = [];
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
if (!auditOn) return;
|
|
77
|
-
try {
|
|
78
|
-
audit().safeEmit({
|
|
79
|
-
action: "compliance.eaa." + action,
|
|
80
|
-
outcome: outcome,
|
|
81
|
-
metadata: metadata || {},
|
|
82
|
-
});
|
|
83
|
-
} catch (_e) { /* drop-silent */ }
|
|
84
|
-
}
|
|
74
|
+
var _emitAudit = audit().namespaced("compliance.eaa", opts.audit);
|
|
85
75
|
|
|
86
76
|
function declareCriterion(id, decl) {
|
|
87
77
|
if (typeof id !== "string" || id.length === 0) {
|
|
@@ -92,7 +82,7 @@ function create(opts) {
|
|
|
92
82
|
throw new ComplianceEaaError("compliance-eaa/bad-decl",
|
|
93
83
|
"compliance.eaa.declareCriterion: decl must be an object with { conformance, note? }");
|
|
94
84
|
}
|
|
95
|
-
if (!VALID_CONFORMANCE
|
|
85
|
+
if (!Object.prototype.hasOwnProperty.call(VALID_CONFORMANCE, decl.conformance)) {
|
|
96
86
|
throw new ComplianceEaaError("compliance-eaa/bad-conformance",
|
|
97
87
|
"compliance.eaa.declareCriterion: conformance must be one of " + Object.keys(VALID_CONFORMANCE).join(", "));
|
|
98
88
|
}
|
|
@@ -65,20 +65,25 @@ function create(opts) {
|
|
|
65
65
|
"fetchOnStart", "audit",
|
|
66
66
|
], "compliance.sanctions.fetcher.create");
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
68
|
+
validateOpts.shape(opts, {
|
|
69
|
+
screener: function (value) {
|
|
70
|
+
if (!value || typeof value.reload !== "function") {
|
|
71
|
+
throw new SanctionsFetcherError("sanctions-fetcher/bad-screener",
|
|
72
|
+
"fetcher.create: screener must be a sanctions.create() instance");
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
fetch: function (value) {
|
|
76
|
+
if (typeof value !== "function") {
|
|
77
|
+
throw new SanctionsFetcherError("sanctions-fetcher/bad-fetch",
|
|
78
|
+
"fetcher.create: fetch must be an async function returning entry[]");
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
intervalMs: "optional-positive-finite",
|
|
82
|
+
onRefreshed: "optional-function",
|
|
83
|
+
onError: "optional-function",
|
|
84
|
+
fetchOnStart: "optional-boolean",
|
|
85
|
+
audit: "optional-boolean",
|
|
86
|
+
}, "fetcher.create", SanctionsFetcherError, "sanctions-fetcher/bad-opts");
|
|
82
87
|
|
|
83
88
|
var intervalMs = opts.intervalMs || C.TIME.hours(24);
|
|
84
89
|
var fetchOnStart = opts.fetchOnStart !== false;
|
|
@@ -93,21 +98,9 @@ function create(opts) {
|
|
|
93
98
|
var refreshCount = 0;
|
|
94
99
|
var failureCount = 0;
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
if (!auditOn) return;
|
|
98
|
-
try {
|
|
99
|
-
audit().safeEmit({
|
|
100
|
-
action: action,
|
|
101
|
-
outcome: outcome,
|
|
102
|
-
metadata: metadata || {},
|
|
103
|
-
});
|
|
104
|
-
} catch (_e) { /* drop-silent */ }
|
|
105
|
-
}
|
|
101
|
+
var _emitAudit = audit().namespaced(null, { audit: auditOn });
|
|
106
102
|
|
|
107
|
-
|
|
108
|
-
try { observability().safeEvent("compliance.sanctions.fetcher." + verb, n || 1, {}); }
|
|
109
|
-
catch (_e) { /* drop-silent */ }
|
|
110
|
-
}
|
|
103
|
+
var _emitMetric = observability().namespaced("compliance.sanctions.fetcher");
|
|
111
104
|
|
|
112
105
|
async function _tick() {
|
|
113
106
|
if (stopping) return;
|
|
@@ -284,21 +284,9 @@ function create(opts) {
|
|
|
284
284
|
// strings.
|
|
285
285
|
var index = opts.entries.map(_normalizeEntry);
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
if (!auditOn) return;
|
|
289
|
-
try {
|
|
290
|
-
audit().safeEmit({
|
|
291
|
-
action: action,
|
|
292
|
-
outcome: outcome,
|
|
293
|
-
metadata: metadata || {},
|
|
294
|
-
});
|
|
295
|
-
} catch (_e) { /* drop-silent — audit sink */ }
|
|
296
|
-
}
|
|
287
|
+
var _emitAudit = audit().namespaced(null, { audit: auditOn });
|
|
297
288
|
|
|
298
|
-
|
|
299
|
-
try { observability().safeEvent("compliance.sanctions." + verb, n || 1, labels || {}); }
|
|
300
|
-
catch (_e) { /* drop-silent */ }
|
|
301
|
-
}
|
|
289
|
+
var _emitMetric = observability().namespaced("compliance.sanctions");
|
|
302
290
|
|
|
303
291
|
function _exactMatch(qNorm, candidate) {
|
|
304
292
|
for (var i = 0; i < candidate._allNamesNormalized.length; i++) {
|
|
@@ -380,13 +368,14 @@ function create(opts) {
|
|
|
380
368
|
|
|
381
369
|
for (var c = 0; c < index.length; c++) {
|
|
382
370
|
var candidate = index[c];
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
371
|
+
// A sanctions screen MUST over-match on the legally-operative NAME. The
|
|
372
|
+
// operator-asserted input.type is an unverified counterparty
|
|
373
|
+
// self-classification, NOT data-driven non-match confidence — using it to
|
|
374
|
+
// EXCLUDE a name hit inverts the screen to under-match (a false negative
|
|
375
|
+
// that processes a payment which should have been blocked). So never skip
|
|
376
|
+
// a candidate on type; the name match runs against every record and the
|
|
377
|
+
// type mismatch is surfaced as a non-dispositive `typeMatch` signal on
|
|
378
|
+
// each hit for operator triage.
|
|
390
379
|
|
|
391
380
|
var bestForCandidate = { score: 0, name: "" };
|
|
392
381
|
for (var qi = 0; qi < queryNames.length; qi++) {
|
|
@@ -416,6 +405,7 @@ function create(opts) {
|
|
|
416
405
|
listed: candidate.listedAt,
|
|
417
406
|
programs: candidate.programs,
|
|
418
407
|
type: candidate.type,
|
|
408
|
+
typeMatch: input.type ? candidate.type === input.type : null,
|
|
419
409
|
country: candidate.country,
|
|
420
410
|
});
|
|
421
411
|
}
|
package/lib/compliance.js
CHANGED
|
@@ -47,6 +47,7 @@ var aiAct = require("./compliance-ai-act");
|
|
|
47
47
|
var { ComplianceError } = require("./framework-error");
|
|
48
48
|
|
|
49
49
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
50
|
+
var auditEmit = require("./audit-emit");
|
|
50
51
|
var retentionMod = lazyRequire(function () { return require("./retention"); });
|
|
51
52
|
var db = lazyRequire(function () { return require("./db"); });
|
|
52
53
|
var cryptoField = lazyRequire(function () { return require("./crypto-field"); });
|
|
@@ -316,15 +317,7 @@ var ARTIFACT_STANDARDS = Object.freeze([
|
|
|
316
317
|
|
|
317
318
|
var STATE = { posture: null, setAt: null, fipsMode: false };
|
|
318
319
|
|
|
319
|
-
|
|
320
|
-
try {
|
|
321
|
-
audit().safeEmit({
|
|
322
|
-
action: action,
|
|
323
|
-
outcome: outcome || "success",
|
|
324
|
-
metadata: metadata,
|
|
325
|
-
});
|
|
326
|
-
} catch (_e) { /* audit best-effort */ }
|
|
327
|
-
}
|
|
320
|
+
var _emitAudit = auditEmit.emit;
|
|
328
321
|
|
|
329
322
|
/**
|
|
330
323
|
* @primitive b.compliance.set
|
|
@@ -960,7 +953,7 @@ var REGIME_MAP = Object.freeze({
|
|
|
960
953
|
* b.compliance.describe("not-a-real-posture"); // → null
|
|
961
954
|
*/
|
|
962
955
|
function describe(posture) {
|
|
963
|
-
return REGIME_MAP[posture]
|
|
956
|
+
return Object.prototype.hasOwnProperty.call(REGIME_MAP, posture) ? REGIME_MAP[posture] : null;
|
|
964
957
|
}
|
|
965
958
|
|
|
966
959
|
// POSTURE_DEFAULTS — per-posture configuration knobs that primitives
|
package/lib/config-drift.js
CHANGED
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
* @card
|
|
36
36
|
* Monitor + alert when runtime config diverges from a declared baseline.
|
|
37
37
|
*/
|
|
38
|
-
var nodeFs = require("node:fs");
|
|
39
38
|
var nodePath = require("node:path");
|
|
40
39
|
var auditSign = require("./audit-sign");
|
|
41
40
|
var canonicalJson = require("./canonical-json");
|
|
@@ -45,8 +44,10 @@ var safeJson = require("./safe-json");
|
|
|
45
44
|
var validateOpts = require("./validate-opts");
|
|
46
45
|
var { defineClass } = require("./framework-error");
|
|
47
46
|
var atomicFile = require("./atomic-file");
|
|
47
|
+
var C = require("./constants");
|
|
48
48
|
|
|
49
49
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
50
|
+
var auditEmit = require("./audit-emit");
|
|
50
51
|
|
|
51
52
|
var ConfigDriftError = defineClass("ConfigDriftError", { alwaysPermanent: true });
|
|
52
53
|
var _err = ConfigDriftError.factory;
|
|
@@ -159,23 +160,16 @@ function create(opts) {
|
|
|
159
160
|
var sidecarPath = nodePath.join(dataDir,
|
|
160
161
|
baselineName === "default" ? SIDECAR_NAME : ("config-baseline-" + baselineName + ".sig"));
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
if (!auditOn) return;
|
|
164
|
-
var sink = auditInstance || audit();
|
|
165
|
-
try {
|
|
166
|
-
sink.safeEmit({
|
|
167
|
-
action: action,
|
|
168
|
-
outcome: outcome,
|
|
169
|
-
metadata: info || {},
|
|
170
|
-
reason: info && info.reason ? info.reason : null,
|
|
171
|
-
});
|
|
172
|
-
} catch (_e) { /* audit best-effort */ }
|
|
173
|
-
}
|
|
163
|
+
var _emit = auditEmit.gatedReasonEmitter({ audit: auditOn, sink: auditInstance });
|
|
174
164
|
|
|
175
165
|
function _readSidecar() {
|
|
176
|
-
|
|
166
|
+
// Capped fd-bound read (no existsSync check-then-read window): the signed
|
|
167
|
+
// config-baseline sidecar is parsed + verified, so a tampered multi-GB file
|
|
168
|
+
// would OOM the reader before signature verify. refuseSymlink stays OFF: the
|
|
169
|
+
// data dir may be a symlink-mounted volume (k8s PVC). Any read failure
|
|
170
|
+
// (missing / too-large) → null, the existing "no baseline yet" behavior.
|
|
177
171
|
var raw;
|
|
178
|
-
try { raw =
|
|
172
|
+
try { raw = atomicFile.fdSafeReadSync(sidecarPath, { maxBytes: C.BYTES.mib(1), encoding: "utf8" }); }
|
|
179
173
|
catch (_e) { return null; }
|
|
180
174
|
var parsed;
|
|
181
175
|
try { parsed = safeJson.parse(raw); }
|
|
@@ -200,9 +194,12 @@ function create(opts) {
|
|
|
200
194
|
publicKeyPem: auditSign.getPublicKey(),
|
|
201
195
|
snapshot: snapshot,
|
|
202
196
|
};
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
197
|
+
// Atomic, symlink-refusing write. The previous hand-rolled form staged
|
|
198
|
+
// into a PREDICTABLE temp name (`sidecarPath + ".tmp"`) via a plain
|
|
199
|
+
// writeFileSync, so an attacker could pre-plant a symlink at that exact
|
|
200
|
+
// path and have the signed sidecar written through it (CWE-59 / CWE-377).
|
|
201
|
+
// writeSync uses a CSPRNG temp name opened O_EXCL | O_NOFOLLOW.
|
|
202
|
+
atomicFile.writeSync(sidecarPath, JSON.stringify(payload, null, 2), { fileMode: 0o600 });
|
|
206
203
|
}
|
|
207
204
|
|
|
208
205
|
function _verifySidecar(parsed) {
|
|
@@ -377,7 +374,10 @@ function verifyVendorIntegrity(opts) {
|
|
|
377
374
|
var libVendorDir = opts.libVendorDir || nodePath.join(__dirname, "vendor");
|
|
378
375
|
var manifestPath = opts.manifestPath || nodePath.join(libVendorDir, "MANIFEST.json");
|
|
379
376
|
var raw;
|
|
380
|
-
|
|
377
|
+
// Capped fd-bound read of the vendor MANIFEST.json (operator-bundled, install-
|
|
378
|
+
// time). refuseSymlink OFF — the vendored tree ships read-only and an install
|
|
379
|
+
// may symlink lib/. The cap precedes the alloc.
|
|
380
|
+
try { raw = atomicFile.fdSafeReadSync(manifestPath, { maxBytes: C.BYTES.mib(4), encoding: "utf8" }); }
|
|
381
381
|
catch (_e) {
|
|
382
382
|
throw _err("VENDOR_MANIFEST_MISSING",
|
|
383
383
|
"vendor MANIFEST.json missing at " + manifestPath, true);
|
|
@@ -407,7 +407,11 @@ function verifyVendorIntegrity(opts) {
|
|
|
407
407
|
var abs = nodePath.isAbsolute(rel) ? rel : nodePath.join(libVendorDir, relInVendor);
|
|
408
408
|
var actual;
|
|
409
409
|
try {
|
|
410
|
-
|
|
410
|
+
// Capped fd-bound read (raw bytes — hashing, no encoding). Sanity ceiling
|
|
411
|
+
// so a corrupted/huge vendored file is a read-failed mismatch (caught
|
|
412
|
+
// below) rather than an OOM in the boot integrity loop. NO refuseSymlink
|
|
413
|
+
// (vendored tree ships read-only; installs may symlink lib/).
|
|
414
|
+
var bytes = atomicFile.fdSafeReadSync(abs, { maxBytes: C.BYTES.mib(64) });
|
|
411
415
|
actual = "sha256:" + require("node:crypto")
|
|
412
416
|
.createHash("sha256").update(bytes).digest("hex");
|
|
413
417
|
} catch (_e) {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
var nodeCrypto = require("node:crypto");
|
|
32
32
|
var C = require("./constants");
|
|
33
33
|
var bCrypto = require("./crypto");
|
|
34
|
+
var x509Chain = require("./x509-chain");
|
|
34
35
|
var canonicalJson = require("./canonical-json");
|
|
35
36
|
var safeJson = require("./safe-json");
|
|
36
37
|
var validateOpts = require("./validate-opts");
|
|
@@ -1103,7 +1104,7 @@ function attachIdentityAssertion(opts) {
|
|
|
1103
1104
|
validateOpts.requireObject(opts, "contentCredentials.attachIdentityAssertion", ContentCredentialsError);
|
|
1104
1105
|
validateOpts(opts, ["binding", "subject", "referencedAssertions", "privateKeyPem", "audit"],
|
|
1105
1106
|
"contentCredentials.attachIdentityAssertion");
|
|
1106
|
-
if (typeof opts.binding !== "string" || !IDENTITY_BINDINGS
|
|
1107
|
+
if (typeof opts.binding !== "string" || !Object.prototype.hasOwnProperty.call(IDENTITY_BINDINGS, opts.binding)) {
|
|
1107
1108
|
throw ContentCredentialsError.factory("content-credentials/bad-identity-binding",
|
|
1108
1109
|
"attachIdentityAssertion: binding must be one of " + Object.keys(IDENTITY_BINDINGS).join(" / "));
|
|
1109
1110
|
}
|
|
@@ -1217,7 +1218,7 @@ function verifyIdentityAssertion(assertion, publicKeyPem, opts) {
|
|
|
1217
1218
|
return _fail("public-key-required");
|
|
1218
1219
|
}
|
|
1219
1220
|
var sp = assertion.signer_payload;
|
|
1220
|
-
if (!sp || typeof sp !== "object" || typeof sp.binding !== "string" || !IDENTITY_BINDINGS
|
|
1221
|
+
if (!sp || typeof sp !== "object" || typeof sp.binding !== "string" || !Object.prototype.hasOwnProperty.call(IDENTITY_BINDINGS, sp.binding) ||
|
|
1221
1222
|
!Array.isArray(sp.referenced_assertions)) {
|
|
1222
1223
|
return _fail("signer-payload-shape");
|
|
1223
1224
|
}
|
|
@@ -1334,9 +1335,10 @@ function _verifyIdentityX509Chain(certChainPem, trustAnchorsPem) {
|
|
|
1334
1335
|
// only direct-root / self-signed leaves.
|
|
1335
1336
|
for (var li = 0; li < certs.length - 1; li += 1) {
|
|
1336
1337
|
var child = certs[li], parent = certs[li + 1];
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1338
|
+
// issuerValidlyIssued enforces basicConstraints cA:TRUE on the parent in
|
|
1339
|
+
// addition to the issuance + signature linkage — a non-CA cert cannot be
|
|
1340
|
+
// an intermediate issuer (basicConstraints bypass, CVE-2002-0862 class).
|
|
1341
|
+
var linked = x509Chain.issuerValidlyIssued(parent, child);
|
|
1340
1342
|
if (!linked) { return { ok: false, reason: "broken-chain" }; }
|
|
1341
1343
|
}
|
|
1342
1344
|
// The top of the presented chain must chain to (or BE) a trust anchor.
|
|
@@ -1347,8 +1349,9 @@ function _verifyIdentityX509Chain(certChainPem, trustAnchorsPem) {
|
|
|
1347
1349
|
if (top.fingerprint256 === anchor.fingerprint256) {
|
|
1348
1350
|
chained = true; // top of the chain IS the anchor (root-in-chain or self-signed leaf == anchor)
|
|
1349
1351
|
} else {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
+
// issuerValidlyIssued enforces basicConstraints cA:TRUE on the anchor
|
|
1353
|
+
// in addition to issuance + signature (basicConstraints bypass class).
|
|
1354
|
+
chained = x509Chain.issuerValidlyIssued(anchor, top);
|
|
1352
1355
|
}
|
|
1353
1356
|
if (chained) {
|
|
1354
1357
|
if (now < anchor.validFromDate.getTime() || now > anchor.validToDate.getTime()) {
|
|
@@ -1427,7 +1430,7 @@ function cacImplicitLabel(opts) {
|
|
|
1427
1430
|
throw new ContentCredentialsError("cac-implicit-label/bad-content-id",
|
|
1428
1431
|
"cacImplicitLabel: contentId must match [A-Za-z0-9._:/-]");
|
|
1429
1432
|
}
|
|
1430
|
-
if (typeof opts.contentKind !== "string" || !CAC_KIND_ENUM
|
|
1433
|
+
if (typeof opts.contentKind !== "string" || !Object.prototype.hasOwnProperty.call(CAC_KIND_ENUM, opts.contentKind)) {
|
|
1431
1434
|
throw new ContentCredentialsError("cac-implicit-label/bad-content-kind",
|
|
1432
1435
|
"cacImplicitLabel: contentKind must be one of " +
|
|
1433
1436
|
Object.keys(CAC_KIND_ENUM).join("/"));
|
package/lib/content-digest.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
var nodeCrypto = require("node:crypto");
|
|
31
31
|
var bCrypto = require("./crypto");
|
|
32
|
+
var safeBuffer = require("./safe-buffer");
|
|
32
33
|
var structuredFields = require("./structured-fields");
|
|
33
34
|
var validateOpts = require("./validate-opts");
|
|
34
35
|
var { defineClass } = require("./framework-error");
|
|
@@ -59,12 +60,12 @@ function _strictBase64(s, what) {
|
|
|
59
60
|
return buf;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
63
|
+
var _bodyBytes = safeBuffer.makeByteCoercer({
|
|
64
|
+
errorClass: ContentDigestError,
|
|
65
|
+
typeCode: "content-digest/bad-body",
|
|
66
|
+
messagePrefix: "contentDigest: ",
|
|
67
|
+
messageSuffix: " must be a Buffer / Uint8Array / string",
|
|
68
|
+
});
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
71
|
* @primitive b.contentDigest.create
|
|
@@ -98,9 +99,12 @@ function create(body, opts) {
|
|
|
98
99
|
if (!Array.isArray(algos) || algos.length === 0) throw new ContentDigestError("content-digest/bad-arg", "contentDigest.create: opts.algorithms must be a non-empty array");
|
|
99
100
|
var members = algos.map(function (a) {
|
|
100
101
|
var name = String(a).toLowerCase();
|
|
101
|
-
|
|
102
|
+
// hasOwnProperty: the algorithm name is operator/caller input; a bracket
|
|
103
|
+
// lookup lets "constructor"/"toString" inherit a truthy value off the
|
|
104
|
+
// prototype and pass the support check (proto shadowing).
|
|
105
|
+
var nodeAlg = Object.prototype.hasOwnProperty.call(ACTIVE, name) ? ACTIVE[name] : undefined;
|
|
102
106
|
if (!nodeAlg) {
|
|
103
|
-
if (DEPRECATED
|
|
107
|
+
if (Object.prototype.hasOwnProperty.call(DEPRECATED, name)) throw new ContentDigestError("content-digest/insecure-algorithm", "contentDigest.create: '" + name + "' is a deprecated/insecure digest algorithm (RFC 9530 §6); use sha-256 or sha-512");
|
|
104
108
|
throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.create: unsupported digest algorithm '" + name + "'");
|
|
105
109
|
}
|
|
106
110
|
var digest = nodeCrypto.createHash(nodeAlg).update(bytes).digest("base64");
|
|
@@ -150,10 +154,15 @@ function verify(fieldValue, body, opts) {
|
|
|
150
154
|
if (m === "") continue;
|
|
151
155
|
var eq = m.indexOf("=");
|
|
152
156
|
if (eq < 1) throw new ContentDigestError("content-digest/bad-field", "contentDigest.verify: malformed dictionary member");
|
|
153
|
-
var
|
|
154
|
-
var
|
|
157
|
+
var kvp = structuredFields.parseKeyValuePiece(m);
|
|
158
|
+
var name = kvp.key;
|
|
159
|
+
var raw = kvp.value.trim();
|
|
160
|
+
// hasOwnProperty: `name` is the algorithm token from the untrusted inbound
|
|
161
|
+
// Content-Digest header; a bracket lookup lets "constructor"/"__proto__"
|
|
162
|
+
// inherit a truthy value off the prototype and reach createHash (proto
|
|
163
|
+
// shadowing).
|
|
164
|
+
if (!Object.prototype.hasOwnProperty.call(ACTIVE, name)) continue; // ignore legacy / unknown entries
|
|
155
165
|
var nodeAlg = ACTIVE[name];
|
|
156
|
-
if (!nodeAlg) continue; // ignore legacy / unknown entries
|
|
157
166
|
if (raw.length < 2 || raw.charAt(0) !== ":" || raw.charAt(raw.length - 1) !== ":") {
|
|
158
167
|
throw new ContentDigestError("content-digest/bad-field", "contentDigest.verify: '" + name + "' value is not an RFC 8941 byte sequence (:base64:)");
|
|
159
168
|
}
|
|
@@ -170,7 +179,7 @@ function verify(fieldValue, body, opts) {
|
|
|
170
179
|
if (!Array.isArray(opts.required)) throw new ContentDigestError("content-digest/bad-arg", "contentDigest.verify: opts.required must be an array");
|
|
171
180
|
for (var r = 0; r < opts.required.length; r++) {
|
|
172
181
|
var req = String(opts.required[r]).toLowerCase();
|
|
173
|
-
if (!ACTIVE
|
|
182
|
+
if (!Object.prototype.hasOwnProperty.call(ACTIVE, req)) throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.verify: required algorithm '" + req + "' is not a modern digest");
|
|
174
183
|
if (!seen[req]) throw new ContentDigestError("content-digest/missing-algorithm", "contentDigest.verify: required digest '" + req + "' is not present");
|
|
175
184
|
}
|
|
176
185
|
}
|
package/lib/cookies.js
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
*/
|
|
49
49
|
|
|
50
50
|
var C = require("./constants");
|
|
51
|
+
var structuredFields = require("./structured-fields");
|
|
51
52
|
var validateOpts = require("./validate-opts");
|
|
52
53
|
var { FrameworkError } = require("./framework-error");
|
|
53
54
|
|
|
@@ -95,7 +96,11 @@ function _validateValue(value) {
|
|
|
95
96
|
// Length cap before the regex test (defense in depth — the regex
|
|
96
97
|
// here is a simple character class, but the same discipline that
|
|
97
98
|
// bounds longer regexes elsewhere applies).
|
|
98
|
-
|
|
99
|
+
// Byte length, not value.length (UTF-16 code units): the cap bounds the
|
|
100
|
+
// on-the-wire Set-Cookie size, and a multibyte value is 2-4x larger in bytes
|
|
101
|
+
// than chars, so a char-count check under-enforces and a 4096-char value can
|
|
102
|
+
// emit a ~12 KiB+ header.
|
|
103
|
+
if (Buffer.byteLength(value, "utf8") > MAX_VALUE_LENGTH || FORBIDDEN_VALUE_RE.test(value)) {
|
|
99
104
|
throw new CookieError("cookies/invalid-value",
|
|
100
105
|
"cookie value is too long or contains forbidden control character (CRLF/NUL/;/,)");
|
|
101
106
|
}
|
|
@@ -107,7 +112,11 @@ function _validateValue(value) {
|
|
|
107
112
|
// header — never trust unscrubbed values reach the wire.
|
|
108
113
|
function _scrubAttr(s) {
|
|
109
114
|
if (typeof s !== "string") return s;
|
|
110
|
-
|
|
115
|
+
// `;` joins Set-Cookie attributes, so an unscrubbed `;` in Path/SameSite is
|
|
116
|
+
// attribute injection (e.g. Path=/x; HttpOnly). Domain is regex-validated
|
|
117
|
+
// upstream; Path/SameSite only flow through here, so strip `;` alongside the
|
|
118
|
+
// CR/LF/NUL header-injection set.
|
|
119
|
+
return s.replace(/[\r\n\0;]/g, ""); // allow:duplicate-regex — CR/LF/NUL(+;) header-injection rejection appears in cookies / mail / security-headers; each is the boundary primitive for its domain
|
|
111
120
|
|
|
112
121
|
}
|
|
113
122
|
|
|
@@ -143,9 +152,7 @@ function parse(cookieHeader) {
|
|
|
143
152
|
if (!k) continue;
|
|
144
153
|
var v = pair.slice(eq + 1).trim();
|
|
145
154
|
// Strip surrounding double-quotes per RFC 6265 §5.2.
|
|
146
|
-
|
|
147
|
-
v = v.slice(1, -1);
|
|
148
|
-
}
|
|
155
|
+
v = structuredFields.stripDoubleQuotes(v);
|
|
149
156
|
try { v = decodeURIComponent(v); }
|
|
150
157
|
catch (_e) { /* malformed encoding — keep raw */ }
|
|
151
158
|
// Last write wins per RFC; matches every browser's behavior.
|
|
@@ -386,10 +393,8 @@ function create(opts) {
|
|
|
386
393
|
}
|
|
387
394
|
|
|
388
395
|
function _requireVault() {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
"sealed cookies require opts.vault (a value with .seal/.unseal)");
|
|
392
|
-
}
|
|
396
|
+
validateOpts.requireMethods(vault, ["seal", "unseal"],
|
|
397
|
+
"sealed cookies: opts.vault", CookieError, "cookies/no-vault");
|
|
393
398
|
}
|
|
394
399
|
|
|
395
400
|
// Vault.seal returns "vault:<base64>". We strip the constant prefix
|
|
@@ -515,10 +520,7 @@ function parseSafe(cookieHeader, opts) {
|
|
|
515
520
|
});
|
|
516
521
|
continue;
|
|
517
522
|
}
|
|
518
|
-
var v = pair.slice(eq + 1).trim();
|
|
519
|
-
if (v.length >= 2 && v.charAt(0) === '"' && v.charAt(v.length - 1) === '"') {
|
|
520
|
-
v = v.slice(1, -1);
|
|
521
|
-
}
|
|
523
|
+
var v = structuredFields.stripDoubleQuotes(pair.slice(eq + 1).trim());
|
|
522
524
|
try { v = decodeURIComponent(v); }
|
|
523
525
|
catch (_e) { /* malformed encoding — keep raw */ }
|
|
524
526
|
|