@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/safe-ical.js
CHANGED
|
@@ -80,8 +80,11 @@
|
|
|
80
80
|
*/
|
|
81
81
|
|
|
82
82
|
var C = require("./constants");
|
|
83
|
+
var codepointClass = require("./codepoint-class");
|
|
84
|
+
var structuredFields = require("./structured-fields");
|
|
83
85
|
var { defineClass } = require("./framework-error");
|
|
84
86
|
var gateContract = require("./gate-contract");
|
|
87
|
+
var pick = require("./pick");
|
|
85
88
|
|
|
86
89
|
var SafeIcalError = defineClass("SafeIcalError", { alwaysPermanent: true });
|
|
87
90
|
|
|
@@ -369,13 +372,11 @@ function _parseContentLine(line) {
|
|
|
369
372
|
|
|
370
373
|
// Refuse NUL, C0 control bytes (other than TAB), and DEL in the
|
|
371
374
|
// value. Header-injection / log-poisoning defense.
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
" in property value (header-injection defense)");
|
|
378
|
-
}
|
|
375
|
+
var ctrlAt = codepointClass.firstControlCharOffset(value); // NUL / C0 (except TAB) / DEL refusal
|
|
376
|
+
if (ctrlAt !== -1) {
|
|
377
|
+
throw new SafeIcalError("safe-ical/control-char-in-value",
|
|
378
|
+
"safeIcal.parse: control char 0x" + value.charCodeAt(ctrlAt).toString(16) +
|
|
379
|
+
" in property value (header-injection defense)");
|
|
379
380
|
}
|
|
380
381
|
|
|
381
382
|
// Split params off the property name.
|
|
@@ -391,7 +392,7 @@ function _parseContentLine(line) {
|
|
|
391
392
|
}
|
|
392
393
|
var pname = seg.slice(0, eq).toUpperCase();
|
|
393
394
|
var pvalue = seg.slice(eq + 1);
|
|
394
|
-
if (pname
|
|
395
|
+
if (pick.isPoisonedKey(pname)) continue;
|
|
395
396
|
if (params[pname]) {
|
|
396
397
|
params[pname].push(_stripDoubleQuotes(pvalue));
|
|
397
398
|
} else {
|
|
@@ -412,26 +413,11 @@ function _findUnquotedColon(line) {
|
|
|
412
413
|
}
|
|
413
414
|
|
|
414
415
|
function _splitUnquoted(s, sep) {
|
|
415
|
-
|
|
416
|
-
var inQ = false;
|
|
417
|
-
var start = 0;
|
|
418
|
-
for (var i = 0; i < s.length; i++) {
|
|
419
|
-
var c = s.charAt(i);
|
|
420
|
-
if (c === '"') { inQ = !inQ; continue; }
|
|
421
|
-
if (c === sep && !inQ) {
|
|
422
|
-
out.push(s.slice(start, i));
|
|
423
|
-
start = i + 1;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
out.push(s.slice(start));
|
|
427
|
-
return out;
|
|
416
|
+
return structuredFields.splitUnquoted(s, sep);
|
|
428
417
|
}
|
|
429
418
|
|
|
430
419
|
function _stripDoubleQuotes(s) {
|
|
431
|
-
|
|
432
|
-
return s.slice(1, -1);
|
|
433
|
-
}
|
|
434
|
-
return s;
|
|
420
|
+
return structuredFields.stripDoubleQuotes(s);
|
|
435
421
|
}
|
|
436
422
|
|
|
437
423
|
// ---- Component parser (RFC 5545 §3.6) ----
|
|
@@ -458,7 +444,7 @@ function _parseComponent(lines, startIdx, ctx, depth) {
|
|
|
458
444
|
"safeIcal.parse: expected BEGIN, got '" + begin.name + "'");
|
|
459
445
|
}
|
|
460
446
|
var compName = begin.value.toUpperCase();
|
|
461
|
-
if (!KNOWN_COMPONENTS
|
|
447
|
+
if (!Object.prototype.hasOwnProperty.call(KNOWN_COMPONENTS, compName) && !ctx.extraComps[compName] &&
|
|
462
448
|
compName.indexOf("X-") !== 0) {
|
|
463
449
|
throw new SafeIcalError("safe-ical/unknown-component",
|
|
464
450
|
"safeIcal.parse: unknown component '" + compName +
|
|
@@ -489,7 +475,7 @@ function _parseComponent(lines, startIdx, ctx, depth) {
|
|
|
489
475
|
}
|
|
490
476
|
// Validate property name.
|
|
491
477
|
var pn = ln.name;
|
|
492
|
-
if (!KNOWN_PROPERTIES
|
|
478
|
+
if (!Object.prototype.hasOwnProperty.call(KNOWN_PROPERTIES, pn) && !ctx.extraProps[pn] && pn.indexOf("X-") !== 0) {
|
|
493
479
|
throw new SafeIcalError("safe-ical/unknown-property",
|
|
494
480
|
"safeIcal.parse: unknown property '" + pn +
|
|
495
481
|
"' (extend via opts.extraProperties or use X- prefix)");
|
|
@@ -504,7 +490,7 @@ function _parseComponent(lines, startIdx, ctx, depth) {
|
|
|
504
490
|
"safeIcal.parse: property count in " + compName +
|
|
505
491
|
" exceeds maxPropertiesPerComponent=" + ctx.caps.maxPropertiesPerComponent);
|
|
506
492
|
}
|
|
507
|
-
if (pn
|
|
493
|
+
if (pick.isPoisonedKey(pn)) {
|
|
508
494
|
i += 1;
|
|
509
495
|
continue;
|
|
510
496
|
}
|
package/lib/safe-icap.js
CHANGED
|
@@ -426,7 +426,7 @@ function _parseEncapsulated(value) {
|
|
|
426
426
|
}
|
|
427
427
|
var part = token.slice(0, eq);
|
|
428
428
|
var offStr = token.slice(eq + 1);
|
|
429
|
-
if (!ENCAPSULATED_PARTS
|
|
429
|
+
if (!Object.prototype.hasOwnProperty.call(ENCAPSULATED_PARTS, part)) {
|
|
430
430
|
throw new SafeIcapError("safe-icap/bad-encapsulated",
|
|
431
431
|
"safeIcap.parse: Encapsulated part '" + part + "' is not one of " +
|
|
432
432
|
Object.keys(ENCAPSULATED_PARTS).join(", "));
|
package/lib/safe-json.js
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
// ---- Error class ----
|
|
38
38
|
|
|
39
39
|
var C = require("./constants");
|
|
40
|
+
var pick = require("./pick");
|
|
40
41
|
var safeBuffer = require("./safe-buffer");
|
|
41
42
|
var safeUrl = require("./safe-url");
|
|
42
43
|
var time = require("./time");
|
|
@@ -93,8 +94,6 @@ var DEFAULT_MAX_DEPTH = 100;
|
|
|
93
94
|
var DEFAULT_MAX_KEYS = 10_000;
|
|
94
95
|
var ABSOLUTE_MAX_KEYS = 1_000_000;
|
|
95
96
|
|
|
96
|
-
var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
97
|
-
|
|
98
97
|
// ---- parse ----
|
|
99
98
|
|
|
100
99
|
/**
|
|
@@ -242,7 +241,7 @@ function parseOrDefault(input, fallback, opts) {
|
|
|
242
241
|
}
|
|
243
242
|
|
|
244
243
|
function _stripProtoKeys(key, value) {
|
|
245
|
-
if (
|
|
244
|
+
if (pick.isPoisonedKey(key)) return undefined;
|
|
246
245
|
return value;
|
|
247
246
|
}
|
|
248
247
|
|
|
@@ -256,7 +255,7 @@ function _walkAndCheck(value, depth, maxDepth, allowProto, maxKeys) {
|
|
|
256
255
|
return;
|
|
257
256
|
}
|
|
258
257
|
if (!allowProto) {
|
|
259
|
-
POISONED_KEYS.forEach(function (k) {
|
|
258
|
+
pick.POISONED_KEYS.forEach(function (k) {
|
|
260
259
|
if (Object.prototype.hasOwnProperty.call(value, k)) delete value[k];
|
|
261
260
|
});
|
|
262
261
|
}
|
|
@@ -338,7 +337,7 @@ function stringify(value, opts) {
|
|
|
338
337
|
}
|
|
339
338
|
|
|
340
339
|
function replacer(key, val) {
|
|
341
|
-
if (!allowProto &&
|
|
340
|
+
if (!allowProto && pick.isPoisonedKey(key)) return undefined;
|
|
342
341
|
return val;
|
|
343
342
|
}
|
|
344
343
|
|
|
@@ -355,6 +354,49 @@ function stringify(value, opts) {
|
|
|
355
354
|
}
|
|
356
355
|
}
|
|
357
356
|
|
|
357
|
+
/**
|
|
358
|
+
* @primitive b.safeJson.stringifyForScript
|
|
359
|
+
* @signature b.safeJson.stringifyForScript(value, opts?)
|
|
360
|
+
* @since 0.15.14
|
|
361
|
+
* @status stable
|
|
362
|
+
* @related b.safeJson.stringify
|
|
363
|
+
*
|
|
364
|
+
* Like `b.safeJson.stringify` but safe to embed verbatim inside an
|
|
365
|
+
* inline `<script>` element. Raw `JSON.stringify` does not escape `<`,
|
|
366
|
+
* `>`, or `&`, so a string value containing `</script>` (or `<!--`)
|
|
367
|
+
* closes the surrounding script element and injects markup; the
|
|
368
|
+
* Unicode line/paragraph separators U+2028 / U+2029 are also illegal
|
|
369
|
+
* unescaped in a script context on older parsers. This escapes all of
|
|
370
|
+
* them to their equivalent `\uXXXX` JSON escapes — the parsed value is
|
|
371
|
+
* byte-identical, but no substring can break out of a `<script>` block.
|
|
372
|
+
*
|
|
373
|
+
* @opts
|
|
374
|
+
* indent: number | string, // forwarded to b.safeJson.stringify
|
|
375
|
+
* allowProto:boolean, // forwarded
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* var json = b.safeJson.stringifyForScript({ url: "/a</script>x" });
|
|
379
|
+
* res.end('<script type="importmap">' + json + '</script>');
|
|
380
|
+
* // → the "</script>" inside the value is emitted as "</script>"
|
|
381
|
+
*/
|
|
382
|
+
function stringifyForScript(value, opts) {
|
|
383
|
+
var json = stringify(value, opts);
|
|
384
|
+
// BS is a single backslash built at runtime so this source carries no
|
|
385
|
+
// escape literals (they round-trip badly through tooling). Escape < > &
|
|
386
|
+
// so the JSON cannot close / comment-open the surrounding inline
|
|
387
|
+
// <script>; escape U+2028 / U+2029 (illegal unescaped in a script on
|
|
388
|
+
// older parsers). The parsed value is unchanged.
|
|
389
|
+
var BS = String.fromCharCode(92);
|
|
390
|
+
json = json.replace(/[<>&]/g, function (c) {
|
|
391
|
+
if (c === "<") return BS + "u003c";
|
|
392
|
+
if (c === ">") return BS + "u003e";
|
|
393
|
+
return BS + "u0026";
|
|
394
|
+
});
|
|
395
|
+
json = json.split(String.fromCharCode(0x2028)).join(BS + "u2028");
|
|
396
|
+
json = json.split(String.fromCharCode(0x2029)).join(BS + "u2029");
|
|
397
|
+
return json;
|
|
398
|
+
}
|
|
399
|
+
|
|
358
400
|
// Walk the value, substituting any references that would create a cycle
|
|
359
401
|
// with `replacement`. Uses an active-stack Set so SHARED non-circular
|
|
360
402
|
// subtrees are preserved (only true cycles are replaced).
|
|
@@ -373,7 +415,7 @@ function _cleanCycles(value, replacement, allowProto) {
|
|
|
373
415
|
out = {};
|
|
374
416
|
for (var k in v) {
|
|
375
417
|
if (!Object.prototype.hasOwnProperty.call(v, k)) continue;
|
|
376
|
-
if (!allowProto &&
|
|
418
|
+
if (!allowProto && pick.isPoisonedKey(k)) continue;
|
|
377
419
|
out[k] = walk(v[k]);
|
|
378
420
|
}
|
|
379
421
|
}
|
|
@@ -432,7 +474,7 @@ function canonical(value) {
|
|
|
432
474
|
if (typeof v === "string") return JSON.stringify(v);
|
|
433
475
|
if (Array.isArray(v)) return "[" + v.map(ser).join(",") + "]";
|
|
434
476
|
if (typeof v === "object") {
|
|
435
|
-
var keys = Object.keys(v).filter(function (k) { return !
|
|
477
|
+
var keys = Object.keys(v).filter(function (k) { return !pick.isPoisonedKey(k); }).sort();
|
|
436
478
|
var pairs = keys.map(function (k) { return JSON.stringify(k) + ":" + ser(v[k]); });
|
|
437
479
|
return "{" + pairs.join(",") + "}";
|
|
438
480
|
}
|
|
@@ -931,6 +973,7 @@ module.exports = {
|
|
|
931
973
|
parse: parse,
|
|
932
974
|
parseOrDefault: parseOrDefault,
|
|
933
975
|
stringify: stringify,
|
|
976
|
+
stringifyForScript: stringifyForScript,
|
|
934
977
|
canonical: canonical,
|
|
935
978
|
validate: validate,
|
|
936
979
|
registerFormat: registerFormat,
|
|
@@ -942,5 +985,5 @@ module.exports = {
|
|
|
942
985
|
ABSOLUTE_MAX_BYTES: ABSOLUTE_MAX_BYTES,
|
|
943
986
|
ABSOLUTE_MAX_DEPTH: ABSOLUTE_MAX_DEPTH,
|
|
944
987
|
ABSOLUTE_MAX_KEYS: ABSOLUTE_MAX_KEYS,
|
|
945
|
-
POISONED_KEYS:
|
|
988
|
+
POISONED_KEYS: pick.POISONED_KEYS.slice(),
|
|
946
989
|
};
|
package/lib/safe-jsonpath.js
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
var codepointClass = require("./codepoint-class");
|
|
40
40
|
var C = require("./constants");
|
|
41
|
+
var safeBuffer = require("./safe-buffer");
|
|
41
42
|
var { defineClass, FrameworkError } = require("./framework-error");
|
|
42
43
|
|
|
43
44
|
// SafeJsonPathError — alwaysPermanent because every code path is a
|
|
@@ -86,7 +87,7 @@ function _hasControlOrNul(value) {
|
|
|
86
87
|
// legitimate use in a JSON pointer / key / path expression.
|
|
87
88
|
for (var i = 0; i < value.length; i++) {
|
|
88
89
|
var c = value.charCodeAt(i);
|
|
89
|
-
if (
|
|
90
|
+
if (codepointClass.isForbiddenControlChar(c)) return true; // ASCII control-byte range
|
|
90
91
|
}
|
|
91
92
|
if (codepointClass.BIDI_RE.test(value)) return true; // allow:regex-no-length-cap — callers cap length via MAX_KEY_BYTES / MAX_EXPRESSION_BYTES
|
|
92
93
|
if (codepointClass.ZERO_WIDTH_RE.test(value)) return true; // allow:regex-no-length-cap — callers cap length via MAX_KEY_BYTES / MAX_EXPRESSION_BYTES
|
|
@@ -108,9 +109,9 @@ function validateKey(key, opts) {
|
|
|
108
109
|
"validateKey: key must be non-empty");
|
|
109
110
|
}
|
|
110
111
|
var maxBytes = opts.maxBytes || MAX_KEY_BYTES;
|
|
111
|
-
if (key
|
|
112
|
+
if (safeBuffer.byteLengthOf(key) > maxBytes) {
|
|
112
113
|
throw _err("safe-jsonpath/key-too-long",
|
|
113
|
-
"validateKey: key exceeds " + maxBytes + " bytes (got " + key
|
|
114
|
+
"validateKey: key exceeds " + maxBytes + " bytes (got " + safeBuffer.byteLengthOf(key) + ")");
|
|
114
115
|
}
|
|
115
116
|
if (_hasControlOrNul(key)) {
|
|
116
117
|
throw _err("safe-jsonpath/key-control-char",
|
|
@@ -167,9 +168,9 @@ function validateExpression(expr, opts) {
|
|
|
167
168
|
"validateExpression: expr must be non-empty");
|
|
168
169
|
}
|
|
169
170
|
var maxBytes = opts.maxBytes || MAX_EXPRESSION_BYTES;
|
|
170
|
-
if (expr
|
|
171
|
+
if (safeBuffer.byteLengthOf(expr) > maxBytes) {
|
|
171
172
|
throw _err("safe-jsonpath/expression-too-long",
|
|
172
|
-
"validateExpression: expr exceeds " + maxBytes + " bytes (got " + expr
|
|
173
|
+
"validateExpression: expr exceeds " + maxBytes + " bytes (got " + safeBuffer.byteLengthOf(expr) + ")");
|
|
173
174
|
}
|
|
174
175
|
if (_hasControlOrNul(expr)) {
|
|
175
176
|
throw _err("safe-jsonpath/expression-control-char",
|
package/lib/safe-mime.js
CHANGED
|
@@ -45,7 +45,12 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
var C = require("./constants");
|
|
48
|
+
var safeBuffer = require("./safe-buffer");
|
|
49
|
+
var numericBounds = require("./numeric-bounds");
|
|
50
|
+
var codepointClass = require("./codepoint-class");
|
|
51
|
+
var structuredFields = require("./structured-fields");
|
|
48
52
|
var { defineClass } = require("./framework-error");
|
|
53
|
+
var pick = require("./pick");
|
|
49
54
|
|
|
50
55
|
var SafeMimeError = defineClass("SafeMimeError", { alwaysPermanent: true });
|
|
51
56
|
|
|
@@ -147,7 +152,7 @@ function parse(bytes, opts) {
|
|
|
147
152
|
var encodings = _normalizeStringSet(opts.transferEncodingAllowlist || DEFAULT_TRANSFER_ENCODINGS);
|
|
148
153
|
|
|
149
154
|
var buf = _toBuffer(bytes);
|
|
150
|
-
if (buf
|
|
155
|
+
if (safeBuffer.byteLengthOf(buf) > maxMessageBytes) {
|
|
151
156
|
throw new SafeMimeError("safe-mime/oversize-message",
|
|
152
157
|
"safeMime.parse: message size " + buf.length + " exceeds maxMessageBytes " + maxMessageBytes);
|
|
153
158
|
}
|
|
@@ -398,7 +403,7 @@ function _parsePart(buf, ctx, depth) {
|
|
|
398
403
|
};
|
|
399
404
|
}
|
|
400
405
|
|
|
401
|
-
if (bodyBytes
|
|
406
|
+
if (safeBuffer.byteLengthOf(bodyBytes) > ctx.maxBodyBytes) {
|
|
402
407
|
throw new SafeMimeError("safe-mime/oversize-body",
|
|
403
408
|
"safeMime.parse: body " + bodyBytes.length + " bytes exceeds maxBodyBytes=" + ctx.maxBodyBytes);
|
|
404
409
|
}
|
|
@@ -413,7 +418,7 @@ function _parsePart(buf, ctx, depth) {
|
|
|
413
418
|
"safeMime.parse: charset '" + charset + "' not in allowlist; refused");
|
|
414
419
|
}
|
|
415
420
|
var decodedBody = _decodeBody(bodyBytes, encoding);
|
|
416
|
-
if (decodedBody
|
|
421
|
+
if (safeBuffer.byteLengthOf(decodedBody) > ctx.maxBodyBytes) {
|
|
417
422
|
throw new SafeMimeError("safe-mime/oversize-body",
|
|
418
423
|
"safeMime.parse: decoded body " + decodedBody.length +
|
|
419
424
|
" bytes exceeds maxBodyBytes=" + ctx.maxBodyBytes);
|
|
@@ -458,13 +463,13 @@ function _parseHeaders(buf, ctx) {
|
|
|
458
463
|
var headerMap = Object.create(null);
|
|
459
464
|
for (var i = 0; i < lines.length; i += 1) {
|
|
460
465
|
var line = lines[i];
|
|
461
|
-
var
|
|
462
|
-
if (
|
|
466
|
+
var khv = structuredFields.parseKeyValuePiece(line, ":");
|
|
467
|
+
if (khv.value === null) {
|
|
463
468
|
throw new SafeMimeError("safe-mime/malformed-headers",
|
|
464
469
|
"safeMime.parse: header line missing colon: " + _previewBytes(line));
|
|
465
470
|
}
|
|
466
|
-
var name =
|
|
467
|
-
var value =
|
|
471
|
+
var name = khv.key;
|
|
472
|
+
var value = khv.value.trim();
|
|
468
473
|
// Refuse NUL, CR, LF, and other C0 control chars in header values.
|
|
469
474
|
// Tab (0x09) is allowed (header folding). C1 control range
|
|
470
475
|
// (0x80-0x9F) NOT refused — legitimate non-ASCII via EAI/RFC 2047
|
|
@@ -473,17 +478,15 @@ function _parseHeaders(buf, ctx) {
|
|
|
473
478
|
// string prefix) rather than the UTF-16 code-unit index, so the
|
|
474
479
|
// operator audit log lines up with the wire-level byte stream
|
|
475
480
|
// they're inspecting.
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
hcc.toString(16) + " at byte offset " + byteOffset); // toString radix 16 hex, not bytes
|
|
483
|
-
}
|
|
481
|
+
var hci = codepointClass.firstControlCharOffset(value); // C0 control char (except TAB) + DEL refusal
|
|
482
|
+
if (hci !== -1) {
|
|
483
|
+
var byteOffset = Buffer.byteLength(value.slice(0, hci), "utf8");
|
|
484
|
+
throw new SafeMimeError("safe-mime/control-char-in-header",
|
|
485
|
+
"safeMime.parse: header '" + name + "' contains control char 0x" +
|
|
486
|
+
value.charCodeAt(hci).toString(16) + " at byte offset " + byteOffset); // toString radix 16 hex, not bytes
|
|
484
487
|
}
|
|
485
488
|
value = _decodeRfc2047Words(value);
|
|
486
|
-
if (name
|
|
489
|
+
if (pick.isPoisonedKey(name)) continue;
|
|
487
490
|
if (!headerMap[name]) headerMap[name] = [];
|
|
488
491
|
headerMap[name].push(value);
|
|
489
492
|
}
|
|
@@ -525,19 +528,14 @@ function _parseContentType(value) {
|
|
|
525
528
|
var parts = String(value).split(";");
|
|
526
529
|
var type = parts[0].toLowerCase().trim();
|
|
527
530
|
var params = Object.create(null);
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (p.length === 0) continue;
|
|
531
|
-
var eq = p.indexOf("=");
|
|
532
|
-
if (eq < 0) continue;
|
|
533
|
-
var k = p.slice(0, eq).toLowerCase().trim();
|
|
534
|
-
var v = p.slice(eq + 1).trim();
|
|
531
|
+
var kvps = structuredFields.parseKeyValuePieces(parts, 1);
|
|
532
|
+
structuredFields.forEachKeyValue(kvps, function (k, v) {
|
|
535
533
|
if (v.length >= 2 && v.charAt(0) === '"' && v.charAt(v.length - 1) === '"') {
|
|
536
534
|
v = v.slice(1, -1).replace(/\\(.)/g, "$1");
|
|
537
535
|
}
|
|
538
|
-
if (k
|
|
536
|
+
if (pick.isPoisonedKey(k)) return;
|
|
539
537
|
params[k] = v;
|
|
540
|
-
}
|
|
538
|
+
});
|
|
541
539
|
return { type: type, params: params };
|
|
542
540
|
}
|
|
543
541
|
|
|
@@ -779,10 +777,8 @@ function _toBuffer(input) {
|
|
|
779
777
|
|
|
780
778
|
function _intOpt(opts, key, fallback) {
|
|
781
779
|
if (opts[key] === undefined || opts[key] === null) return fallback;
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
"safeMime.parse: opts." + key + " must be a positive finite integer (got " + opts[key] + ")");
|
|
785
|
-
}
|
|
780
|
+
numericBounds.requirePositiveFiniteInt(opts[key],
|
|
781
|
+
"safeMime.parse: opts." + key, SafeMimeError, "safe-mime/bad-opt");
|
|
786
782
|
return opts[key];
|
|
787
783
|
}
|
|
788
784
|
|
package/lib/safe-redirect.js
CHANGED
|
@@ -40,15 +40,12 @@
|
|
|
40
40
|
|
|
41
41
|
var safeUrl = require("./safe-url");
|
|
42
42
|
var validateOpts = require("./validate-opts");
|
|
43
|
+
var codepointClass = require("./codepoint-class");
|
|
43
44
|
|
|
44
45
|
var DEFAULT_FALLBACK = "/";
|
|
45
46
|
|
|
46
47
|
function _hasControlChar(s) {
|
|
47
|
-
|
|
48
|
-
var c = s.charCodeAt(i);
|
|
49
|
-
if (c < 0x20 || c === 0x7f) return true; // ASCII control range thresholds
|
|
50
|
-
}
|
|
51
|
-
return false;
|
|
48
|
+
return codepointClass.firstControlCharOffset(s, { forbidTab: true }) !== -1;
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
function resolve(rawTarget, opts) {
|
package/lib/safe-schema.js
CHANGED
|
@@ -98,6 +98,8 @@
|
|
|
98
98
|
|
|
99
99
|
var C = require("./constants");
|
|
100
100
|
var safeJson = require("./safe-json");
|
|
101
|
+
var pick = require("./pick");
|
|
102
|
+
var ipUtils = require("./ip-utils");
|
|
101
103
|
var { defineClass } = require("./framework-error");
|
|
102
104
|
|
|
103
105
|
// Maximum URL length per RFC 7230 §3.1.1 guidance — also reused as the
|
|
@@ -154,7 +156,6 @@ var SafeSchemaError = defineClass("SafeSchemaError", { alwaysPermanent: true });
|
|
|
154
156
|
// Object.prototype by submitting a JSON body with __proto__ /
|
|
155
157
|
// constructor / prototype keys, even if the operator schema is
|
|
156
158
|
// .passthrough().
|
|
157
|
-
var POISONED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
158
159
|
|
|
159
160
|
// Pragmatic regexes — RFC-correct is impractical without exploding the
|
|
160
161
|
// regex (especially email). Operators wanting deeper validation chain
|
|
@@ -169,7 +170,7 @@ var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0
|
|
|
169
170
|
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
170
171
|
// ISO-8601 datetime with timezone (Z or ±HH:MM); fractional seconds optional.
|
|
171
172
|
var DATETIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
172
|
-
var IPV4_RE =
|
|
173
|
+
var IPV4_RE = ipUtils.IPV4_RE; // canonical strict dotted-quad — single source in lib/ip-utils.js
|
|
173
174
|
// CUID v1 / v2: 25-char base36, starts with 'c'. Common in TypeScript ecosystems.
|
|
174
175
|
var CUID_RE = /^c[a-z0-9]{24}$/;
|
|
175
176
|
// ULID: Crockford-base32, 26 chars, time-sortable.
|
|
@@ -1012,7 +1013,7 @@ function object(shape) {
|
|
|
1012
1013
|
// such a key) gets a refusal at construction time.
|
|
1013
1014
|
var allOwnKeys = Object.getOwnPropertyNames(shape);
|
|
1014
1015
|
for (var ai = 0; ai < allOwnKeys.length; ai++) {
|
|
1015
|
-
if (
|
|
1016
|
+
if (pick.isPoisonedKey(allOwnKeys[ai])) {
|
|
1016
1017
|
throw new SafeSchemaError("safe-schema/poisoned-shape-key",
|
|
1017
1018
|
"object shape: key '" + allOwnKeys[ai] + "' is forbidden (prototype-pollution defense)");
|
|
1018
1019
|
}
|
|
@@ -1054,7 +1055,7 @@ function _objectWithMode(shape, keys, mode) {
|
|
|
1054
1055
|
// Prototype-pollution defense — refuse __proto__/constructor/
|
|
1055
1056
|
// prototype regardless of mode. .passthrough() never propagates
|
|
1056
1057
|
// these because they are always rejected as input.
|
|
1057
|
-
if (
|
|
1058
|
+
if (pick.isPoisonedKey(ik)) {
|
|
1058
1059
|
issues.push({
|
|
1059
1060
|
path: path.concat([ik]),
|
|
1060
1061
|
code: "object/poisoned-key",
|
|
@@ -1449,7 +1450,7 @@ function record(a, b) {
|
|
|
1449
1450
|
for (var i = 0; i < keys.length; i++) {
|
|
1450
1451
|
var k = keys[i];
|
|
1451
1452
|
// Prototype-pollution defense — same shape as object() schema.
|
|
1452
|
-
if (
|
|
1453
|
+
if (pick.isPoisonedKey(k)) {
|
|
1453
1454
|
issues.push({
|
|
1454
1455
|
path: path.concat([k]),
|
|
1455
1456
|
code: "record/poisoned-key",
|
|
@@ -1533,7 +1534,7 @@ function discriminatedUnion(discriminator, options) {
|
|
|
1533
1534
|
throw new SafeSchemaError("safe-schema/bad-discriminator",
|
|
1534
1535
|
"discriminatedUnion: discriminator must be a non-empty string key name");
|
|
1535
1536
|
}
|
|
1536
|
-
if (
|
|
1537
|
+
if (pick.isPoisonedKey(discriminator)) {
|
|
1537
1538
|
throw new SafeSchemaError("safe-schema/poisoned-discriminator",
|
|
1538
1539
|
"discriminatedUnion: discriminator key '" + discriminator + "' is forbidden");
|
|
1539
1540
|
}
|
package/lib/safe-sieve.js
CHANGED
|
@@ -133,7 +133,7 @@ function _resolveProfile(opts) {
|
|
|
133
133
|
if (!opts) return "strict";
|
|
134
134
|
if (typeof opts.profile === "string") return opts.profile;
|
|
135
135
|
if (typeof opts.compliancePosture === "string") {
|
|
136
|
-
return COMPLIANCE_POSTURES[opts.compliancePosture] || "strict";
|
|
136
|
+
return (Object.prototype.hasOwnProperty.call(COMPLIANCE_POSTURES, opts.compliancePosture) && COMPLIANCE_POSTURES[opts.compliancePosture]) || "strict";
|
|
137
137
|
}
|
|
138
138
|
return "strict";
|
|
139
139
|
}
|
package/lib/safe-sql.js
CHANGED
|
@@ -429,6 +429,69 @@ function countPlaceholders(sql) {
|
|
|
429
429
|
return count;
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
/**
|
|
433
|
+
* @primitive b.safeSql.toPositional
|
|
434
|
+
* @signature b.safeSql.toPositional(sql, dialect)
|
|
435
|
+
* @since 0.15.13
|
|
436
|
+
* @status stable
|
|
437
|
+
* @related b.safeSql.countPlaceholders, b.sql
|
|
438
|
+
*
|
|
439
|
+
* Rewrite bound `?` placeholders to Postgres `$N` positional form,
|
|
440
|
+
* skipping any `?` inside a string literal (`'...'` / `"..."` /
|
|
441
|
+
* `` `...` ``, doubled-quote escape aware) or a line / block comment. For
|
|
442
|
+
* any non-Postgres dialect the SQL is returned unchanged (`?` is already
|
|
443
|
+
* the wire form). This is the same quote- and comment-aware scan as
|
|
444
|
+
* `countPlaceholders`, extended to emit the rewritten string and to skip
|
|
445
|
+
* MySQL backtick-quoted identifiers; the query builder and the cluster
|
|
446
|
+
* store both compose it so the rewrite lives in one place.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* var b = require("blamejs");
|
|
450
|
+
* b.safeSql.toPositional("a = ? AND b = ?", "postgres");
|
|
451
|
+
* // → "a = $1 AND b = $2"
|
|
452
|
+
*
|
|
453
|
+
* b.safeSql.toPositional("note = 'is ? literal' AND id = ?", "postgres");
|
|
454
|
+
* // → "note = 'is ? literal' AND id = $1"
|
|
455
|
+
*/
|
|
456
|
+
function toPositional(sql, dialect) {
|
|
457
|
+
if (dialect !== "postgres") return sql;
|
|
458
|
+
var out = "";
|
|
459
|
+
var n = 0;
|
|
460
|
+
var i = 0;
|
|
461
|
+
var len = sql.length;
|
|
462
|
+
while (i < len) {
|
|
463
|
+
var c = sql.charAt(i);
|
|
464
|
+
var nx = i + 1 < len ? sql.charAt(i + 1) : "";
|
|
465
|
+
if (c === "'" || c === '"' || c === "`") {
|
|
466
|
+
out += c;
|
|
467
|
+
i += 1;
|
|
468
|
+
while (i < len) {
|
|
469
|
+
var q = sql.charAt(i);
|
|
470
|
+
if (q === c) {
|
|
471
|
+
if (sql.charAt(i + 1) === c) { out += c + c; i += 2; continue; }
|
|
472
|
+
out += c; i += 1; break;
|
|
473
|
+
}
|
|
474
|
+
out += q; i += 1;
|
|
475
|
+
}
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (c === "-" && nx === "-") {
|
|
479
|
+
while (i < len && sql.charAt(i) !== "\n") { out += sql.charAt(i); i += 1; }
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (c === "/" && nx === "*") {
|
|
483
|
+
out += "/*"; i += 2;
|
|
484
|
+
while (i < len && !(sql.charAt(i) === "*" && sql.charAt(i + 1) === "/")) { out += sql.charAt(i); i += 1; }
|
|
485
|
+
if (i < len) { out += "*/"; i += 2; }
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (c === "?") { n += 1; out += "$" + n; i += 1; continue; }
|
|
489
|
+
out += c;
|
|
490
|
+
i += 1;
|
|
491
|
+
}
|
|
492
|
+
return out;
|
|
493
|
+
}
|
|
494
|
+
|
|
432
495
|
/**
|
|
433
496
|
* @primitive b.safeSql.DEFAULT_IDENTIFIER_RE
|
|
434
497
|
* @signature b.safeSql.DEFAULT_IDENTIFIER_RE
|
|
@@ -557,7 +620,69 @@ function assertSingleStatement(sql, opts) {
|
|
|
557
620
|
return sql;
|
|
558
621
|
}
|
|
559
622
|
|
|
623
|
+
/**
|
|
624
|
+
* @primitive b.safeSql.assertNoRawStringLiteral
|
|
625
|
+
* @signature b.safeSql.assertNoRawStringLiteral(sql, where, makeError?)
|
|
626
|
+
* @since 0.15.13
|
|
627
|
+
* @status stable
|
|
628
|
+
* @related b.safeSql.assertSingleStatement, b.safeSql.countPlaceholders, b.sql
|
|
629
|
+
*
|
|
630
|
+
* The one quote/comment-aware scan that refuses a `'...'` STRING LITERAL in
|
|
631
|
+
* raw SQL — the injection backstop for the b.sql builder's raw fragments and
|
|
632
|
+
* the external-db raw-query path, which must bind every value with a `?`
|
|
633
|
+
* placeholder rather than splice a literal. Walks the SQL skipping `"..."`
|
|
634
|
+
* quoted identifiers (doubled-quote escapes handled), `-- line` comments, and
|
|
635
|
+
* slash-star block comments; on the first top-level `'` it throws the caller's
|
|
636
|
+
* error (`makeError(where)` returns the Error to throw). Both b.sql and the
|
|
637
|
+
* external-db raw gate route through this single scan so a fix to the scanner
|
|
638
|
+
* cannot drift between them.
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* b.safeSql.assertNoRawStringLiteral("WHERE id = ?", "where"); // ok (no literal)
|
|
642
|
+
* try { b.safeSql.assertNoRawStringLiteral("WHERE name = 'x'", "where"); }
|
|
643
|
+
* catch (e) { e.code; } // → "sql/raw-literal"
|
|
644
|
+
*/
|
|
645
|
+
function assertNoRawStringLiteral(sql, where, makeError) {
|
|
646
|
+
var mkErr = typeof makeError === "function" ? makeError : function (w) {
|
|
647
|
+
return new SafeSqlError(w + ": raw SQL must not contain a string literal ('...') — bind every " +
|
|
648
|
+
"value with a ? placeholder, or pass { allowLiterals: true } when the literal " +
|
|
649
|
+
"is static and operator-controlled.", "sql/raw-literal");
|
|
650
|
+
};
|
|
651
|
+
var i = 0;
|
|
652
|
+
var len = sql.length;
|
|
653
|
+
while (i < len) {
|
|
654
|
+
var ch = sql.charAt(i);
|
|
655
|
+
var next = i + 1 < len ? sql.charAt(i + 1) : "";
|
|
656
|
+
if (ch === '"') {
|
|
657
|
+
i += 1;
|
|
658
|
+
while (i < len) {
|
|
659
|
+
if (sql.charAt(i) === '"') {
|
|
660
|
+
if (sql.charAt(i + 1) === '"') { i += 2; continue; }
|
|
661
|
+
i += 1; break;
|
|
662
|
+
}
|
|
663
|
+
i += 1;
|
|
664
|
+
}
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (ch === "-" && next === "-") {
|
|
668
|
+
while (i < len && sql.charAt(i) !== "\n") i += 1;
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (ch === "/" && next === "*") {
|
|
672
|
+
i += 2;
|
|
673
|
+
while (i < len && !(sql.charAt(i) === "*" && sql.charAt(i + 1) === "/")) i += 1;
|
|
674
|
+
i += 2;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (ch === "'") {
|
|
678
|
+
throw mkErr(where);
|
|
679
|
+
}
|
|
680
|
+
i += 1;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
560
684
|
module.exports = {
|
|
685
|
+
assertNoRawStringLiteral: assertNoRawStringLiteral,
|
|
561
686
|
validateIdentifier: validateIdentifier,
|
|
562
687
|
assertSingleStatement: assertSingleStatement,
|
|
563
688
|
quoteIdentifier: quoteIdentifier,
|
|
@@ -565,6 +690,7 @@ module.exports = {
|
|
|
565
690
|
quoteList: quoteList,
|
|
566
691
|
assertOneOf: assertOneOf,
|
|
567
692
|
countPlaceholders: countPlaceholders,
|
|
693
|
+
toPositional: toPositional,
|
|
568
694
|
SafeSqlError: SafeSqlError,
|
|
569
695
|
// Exposed so consumers can compose their own validators
|
|
570
696
|
DEFAULT_IDENTIFIER_RE: DEFAULT_IDENTIFIER_RE,
|