@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/chain-writer.js
CHANGED
|
@@ -45,12 +45,14 @@ var safeAsync = require("./safe-async");
|
|
|
45
45
|
var safeSql = require("./safe-sql");
|
|
46
46
|
var sql = require("./sql");
|
|
47
47
|
var C = require("./constants");
|
|
48
|
+
var boundedMap = require("./bounded-map");
|
|
48
49
|
var { FrameworkError } = require("./framework-error");
|
|
49
50
|
|
|
50
|
-
// Allowlist of chain table names.
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// table
|
|
51
|
+
// Allowlist of chain table names. The two framework chains ship registered; a
|
|
52
|
+
// consumer's own append-only hash-chained table is added at config time via
|
|
53
|
+
// registerTable() BEFORE create() accepts it. The allowlist is never bypassed
|
|
54
|
+
// — an unregistered table throws at create(), so a misconfig can't point a
|
|
55
|
+
// chain-writer at a non-chain table and corrupt the chain semantics.
|
|
54
56
|
var ALLOWED_CHAIN_TABLES = new Set(["audit_log", "consent_log"]);
|
|
55
57
|
|
|
56
58
|
var FRAMEWORK_SQL_TIMEOUT_MS = C.TIME.seconds(30);
|
|
@@ -74,24 +76,83 @@ class ChainWriterError extends FrameworkError {
|
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
/**
|
|
80
|
+
* @primitive b.chainWriter.registerTable
|
|
81
|
+
* @signature b.chainWriter.registerTable(table)
|
|
82
|
+
* @since 0.15.13
|
|
83
|
+
* @status stable
|
|
84
|
+
* @related b.chainWriter.create, b.safeSql.validateIdentifier
|
|
85
|
+
*
|
|
86
|
+
* Register a consumer-owned append-only table as chain-writable so
|
|
87
|
+
* `b.chainWriter.create({ table })` accepts it. Call once at boot (config
|
|
88
|
+
* time) for each app table carrying the chain columns (`monotonicCounter`,
|
|
89
|
+
* `recordedAt`, `nonce`, `prevHash`, `rowHash` — plus `fencingToken` in
|
|
90
|
+
* cluster mode). The framework chains (`audit_log`, `consent_log`) are
|
|
91
|
+
* pre-registered. Throws `ChainWriterError` (`chain-writer/invalid-config`) on
|
|
92
|
+
* a non-identifier name; the name is validated against the SQL identifier
|
|
93
|
+
* rules because it is interpolated into the chain SQL. Idempotent. Returns the
|
|
94
|
+
* registered name.
|
|
95
|
+
*
|
|
96
|
+
* Operator footgun to avoid on a MULTI-chain table (one configured with a
|
|
97
|
+
* `chainKey`): the per-key writer restarts `monotonicCounter` at 1 for each
|
|
98
|
+
* key, so a UNIQUE index on `monotonicCounter` ALONE (the shape the framework
|
|
99
|
+
* `audit_log` uses for its single chain) will reject the second key's first
|
|
100
|
+
* row. A keyed chain's uniqueness must be the composite
|
|
101
|
+
* `(chainKey, monotonicCounter)`, never `monotonicCounter` by itself.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* b.chainWriter.registerTable("device_event_log");
|
|
105
|
+
* var writer = b.chainWriter.create({
|
|
106
|
+
* table: "device_event_log",
|
|
107
|
+
* chainKey: "deviceId",
|
|
108
|
+
* columnsForInsert: ["_id", "deviceId", "monotonicCounter", "recordedAt",
|
|
109
|
+
* "kind", "payload",
|
|
110
|
+
* "prevHash", "rowHash", "nonce", "fencingToken"],
|
|
111
|
+
* hashableColumns: ["_id", "deviceId", "monotonicCounter", "recordedAt",
|
|
112
|
+
* "kind", "payload"],
|
|
113
|
+
* });
|
|
114
|
+
*/
|
|
115
|
+
function registerTable(table) {
|
|
116
|
+
if (typeof table !== "string" || table.length === 0) {
|
|
117
|
+
throw new ChainWriterError(
|
|
118
|
+
"registerTable requires a non-empty table name",
|
|
119
|
+
"chain-writer/invalid-config"
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
// Identifier-validate before admitting to the allowlist — the name is
|
|
123
|
+
// interpolated into the chain SQL, so the same shape rules create() relies
|
|
124
|
+
// on must hold here, at the config-time entry point.
|
|
125
|
+
safeSql.validateIdentifier(table);
|
|
126
|
+
ALLOWED_CHAIN_TABLES.add(table);
|
|
127
|
+
return table;
|
|
128
|
+
}
|
|
129
|
+
|
|
77
130
|
/**
|
|
78
131
|
* @primitive b.chainWriter.create
|
|
79
132
|
* @signature b.chainWriter.create(opts)
|
|
80
133
|
* @since 0.8.48
|
|
81
134
|
* @status stable
|
|
82
|
-
* @related b.audit, b.consent, b.auditChain
|
|
135
|
+
* @related b.audit, b.consent, b.auditChain, b.chainWriter.registerTable
|
|
83
136
|
*
|
|
84
137
|
* Build a chain-writer bound to a single hash-chained table. Returns
|
|
85
|
-
* `{ table, append, _resetForTest, _getMutexForTest }`.
|
|
86
|
-
* is the public surface — async, leader-gated,
|
|
87
|
-
* success it returns the logical row decorated with the
|
|
88
|
-
* `rowHash` and `prevHash`.
|
|
138
|
+
* `{ table, chainKey, append, _resetForTest, _getMutexForTest }`.
|
|
139
|
+
* `append(logical)` is the public surface — async, leader-gated,
|
|
140
|
+
* mutex-serialized; on success it returns the logical row decorated with the
|
|
141
|
+
* computed `rowHash` and `prevHash`.
|
|
142
|
+
*
|
|
143
|
+
* A `chainKey` makes one table hold many independent chains (one per account /
|
|
144
|
+
* device / tenant): tip-read, counter monotonicity, and the append Mutex all
|
|
145
|
+
* scope per key, so concurrent appends to DIFFERENT keys run in parallel while
|
|
146
|
+
* same-key appends serialize. Bind `chainKey` into `hashableColumns` so the
|
|
147
|
+
* partition is tamper-evident in the row hash, and key the table's uniqueness
|
|
148
|
+
* constraint on `(chainKey, monotonicCounter)`, never `monotonicCounter` alone.
|
|
89
149
|
*
|
|
90
150
|
* @opts
|
|
91
|
-
* table: string, //
|
|
151
|
+
* table: string, // a registered chain table (audit_log | consent_log | registerTable name)
|
|
152
|
+
* chainKey: string, // optional partition column — one independent chain per key value
|
|
92
153
|
* columnsForInsert: string[], // INSERT column order (every name is identifier-validated)
|
|
93
154
|
* hashableColumns: string[], // columns that participate in the rowHash canonicalization
|
|
94
|
-
* validateInput: Function, // optional; (logical)
|
|
155
|
+
* validateInput: Function, // optional; (logical) -> throws on invalid shape
|
|
95
156
|
*
|
|
96
157
|
* @example
|
|
97
158
|
* var writer = b.chainWriter.create({
|
|
@@ -136,27 +197,57 @@ function create(opts) {
|
|
|
136
197
|
var hashableColumns = opts.hashableColumns.slice();
|
|
137
198
|
var validateInput = opts.validateInput || null;
|
|
138
199
|
|
|
139
|
-
//
|
|
200
|
+
// chainKey: the partition column for many independent chains in one table
|
|
201
|
+
// (one chain per account / device / tenant). When set, the tip read,
|
|
202
|
+
// counter priming, and the append Mutex all scope to a single key value, so
|
|
203
|
+
// appends to DIFFERENT keys run in parallel while same-key appends
|
|
204
|
+
// serialize. Identifier-validated at config time (THROW tier) because it is
|
|
205
|
+
// interpolated as a column name; it must also appear in columnsForInsert so
|
|
206
|
+
// every row carries its partition key.
|
|
207
|
+
var chainKey = opts.chainKey || null;
|
|
208
|
+
if (chainKey !== null) {
|
|
209
|
+
safeSql.validateIdentifier(chainKey);
|
|
210
|
+
if (columnsForInsert.indexOf(chainKey) === -1) {
|
|
211
|
+
throw new ChainWriterError(
|
|
212
|
+
"chainKey '" + chainKey + "' must be listed in columnsForInsert so " +
|
|
213
|
+
"every appended row carries its partition key",
|
|
214
|
+
"chain-writer/invalid-config"
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Per-CHAIN-KEY Mutex serializes the read-prev-tip + compute-hash + insert
|
|
140
220
|
// sequence. Without serialization, two concurrent awaiting append() calls
|
|
141
|
-
// would hash against the same prev-tip and
|
|
142
|
-
//
|
|
143
|
-
|
|
221
|
+
// would hash against the same prev-tip and fork the chain. A single-chain
|
|
222
|
+
// writer (no chainKey) uses one shared lock under the sentinel key; a
|
|
223
|
+
// multi-chain writer keys the lock by the partition value so appends to
|
|
224
|
+
// DIFFERENT keys run concurrently while same-key appends serialize.
|
|
225
|
+
var _SINGLE_CHAIN_KEY = "__single_chain__"; // sentinel; not a valid driver value
|
|
226
|
+
var _mutexByKey = new Map();
|
|
227
|
+
function _mutexFor(keyValue) {
|
|
228
|
+
var k = chainKey !== null ? String(keyValue) : _SINGLE_CHAIN_KEY;
|
|
229
|
+
return boundedMap.getOrInsert(_mutexByKey, k, function () { return new safeAsync.Mutex(); });
|
|
230
|
+
}
|
|
144
231
|
|
|
145
|
-
// Lazy counter primer — first append
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
var
|
|
232
|
+
// Lazy counter primer — first append for a given key reads
|
|
233
|
+
// MAX(monotonicCounter) [WHERE chainKey = ?] and increments from there.
|
|
234
|
+
// Per-key so each chain's counter is independent; a per-key Once shares one
|
|
235
|
+
// in-flight init across concurrent first-callers for the same key.
|
|
236
|
+
var _nextCounterByKey = new Map();
|
|
237
|
+
var _counterInitByKey = new Map();
|
|
150
238
|
|
|
151
|
-
function _ensureCounterInit() {
|
|
152
|
-
|
|
153
|
-
|
|
239
|
+
function _ensureCounterInit(keyValue) {
|
|
240
|
+
var k = chainKey !== null ? String(keyValue) : _SINGLE_CHAIN_KEY;
|
|
241
|
+
var once = boundedMap.getOrInsert(_counterInitByKey, k, function () {
|
|
242
|
+
return new safeAsync.Once(async function () {
|
|
154
243
|
// BARE logical table name — clusterStorage rewrites the framework
|
|
155
|
-
// name to the configured-prefix form
|
|
156
|
-
// quotes the camelCase column +
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
244
|
+
// name to the configured-prefix form (consumer tables pass through
|
|
245
|
+
// unchanged) and placeholderizes; b.sql quotes the camelCase column +
|
|
246
|
+
// emits the MAX aggregate. A keyed writer scopes the MAX to the
|
|
247
|
+
// partition via a bound WHERE.
|
|
248
|
+
var maxQ = sql.select(table, _sqlOpts()).max("monotonicCounter", "m");
|
|
249
|
+
if (chainKey !== null) maxQ = maxQ.where(chainKey, keyValue);
|
|
250
|
+
var maxBuilt = maxQ.toSql();
|
|
160
251
|
var row = await safeAsync.withTimeout(
|
|
161
252
|
safeAsync.asyncRetry(function () {
|
|
162
253
|
return clusterStorage.executeOne(maxBuilt.sql, maxBuilt.params);
|
|
@@ -164,18 +255,21 @@ function create(opts) {
|
|
|
164
255
|
FRAMEWORK_SQL_TIMEOUT_MS,
|
|
165
256
|
{ name: table + ".readMaxCounter" }
|
|
166
257
|
);
|
|
167
|
-
|
|
258
|
+
_nextCounterByKey.set(k, (row && row.m ? Number(row.m) : 0) + 1);
|
|
168
259
|
});
|
|
169
|
-
}
|
|
170
|
-
return
|
|
260
|
+
});
|
|
261
|
+
return once.invoke();
|
|
171
262
|
}
|
|
172
263
|
|
|
173
|
-
async function _readChainTipRow() {
|
|
174
|
-
var
|
|
264
|
+
async function _readChainTipRow(keyValue) {
|
|
265
|
+
var tipQ = sql.select(table, _sqlOpts())
|
|
175
266
|
.columns(["rowHash"])
|
|
176
267
|
.orderBy("monotonicCounter", "desc")
|
|
177
|
-
.limit(1)
|
|
178
|
-
|
|
268
|
+
.limit(1);
|
|
269
|
+
// Scope the tip to the partition so per-key chains link correctly —
|
|
270
|
+
// bound value, never interpolated.
|
|
271
|
+
if (chainKey !== null) tipQ = tipQ.where(chainKey, keyValue);
|
|
272
|
+
var tipBuilt = tipQ.toSql();
|
|
179
273
|
return await safeAsync.withTimeout(
|
|
180
274
|
safeAsync.asyncRetry(function () {
|
|
181
275
|
return clusterStorage.executeOne(tipBuilt.sql, tipBuilt.params);
|
|
@@ -209,15 +303,33 @@ function create(opts) {
|
|
|
209
303
|
async function append(logical) {
|
|
210
304
|
if (validateInput) validateInput(logical);
|
|
211
305
|
cluster.requireLeader();
|
|
212
|
-
await _ensureCounterInit();
|
|
213
306
|
|
|
214
|
-
|
|
215
|
-
|
|
307
|
+
// Resolve the partition key from the logical row for a multi-chain writer.
|
|
308
|
+
// Fail closed: a keyed writer with a missing / empty key can't pick a
|
|
309
|
+
// chain to append to, so refuse rather than silently fold the row into the
|
|
310
|
+
// wrong chain.
|
|
311
|
+
var keyValue = _SINGLE_CHAIN_KEY;
|
|
312
|
+
if (chainKey !== null) {
|
|
313
|
+
keyValue = logical ? logical[chainKey] : undefined;
|
|
314
|
+
if (keyValue === undefined || keyValue === null || String(keyValue).length === 0) {
|
|
315
|
+
throw new ChainWriterError(
|
|
316
|
+
"append: a chainKey writer requires logical['" + chainKey + "'] to be a " +
|
|
317
|
+
"non-empty partition value",
|
|
318
|
+
"chain-writer/invalid-input"
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
await _ensureCounterInit(keyValue);
|
|
323
|
+
|
|
324
|
+
return await _mutexFor(keyValue).runExclusive(async function () {
|
|
325
|
+
return await _appendInsideMutex(logical, keyValue);
|
|
216
326
|
});
|
|
217
327
|
}
|
|
218
328
|
|
|
219
|
-
async function _appendInsideMutex(logical) {
|
|
220
|
-
var
|
|
329
|
+
async function _appendInsideMutex(logical, keyValue) {
|
|
330
|
+
var _ck = chainKey !== null ? String(keyValue) : _SINGLE_CHAIN_KEY;
|
|
331
|
+
var counter = _nextCounterByKey.get(_ck);
|
|
332
|
+
_nextCounterByKey.set(_ck, counter + 1);
|
|
221
333
|
var nowMs = Date.now();
|
|
222
334
|
var nonce = generateBytes(C.BYTES.bytes(16));
|
|
223
335
|
|
|
@@ -239,8 +351,8 @@ function create(opts) {
|
|
|
239
351
|
if (!(hashableColumns[hci] in sealed)) sealed[hashableColumns[hci]] = null;
|
|
240
352
|
}
|
|
241
353
|
|
|
242
|
-
// Compute rowHash over the sealed content fields
|
|
243
|
-
var tipRow = await _readChainTipRow();
|
|
354
|
+
// Compute rowHash over the sealed content fields, linking to THIS key's tip.
|
|
355
|
+
var tipRow = await _readChainTipRow(keyValue);
|
|
244
356
|
var prevHash = tipRow ? tipRow.rowHash : auditChain.ZERO_HASH;
|
|
245
357
|
var rowHash = auditChain.computeRowHash(prevHash, sealed, nonce);
|
|
246
358
|
|
|
@@ -259,22 +371,25 @@ function create(opts) {
|
|
|
259
371
|
}
|
|
260
372
|
|
|
261
373
|
function _resetForTest() {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
374
|
+
_mutexByKey = new Map();
|
|
375
|
+
_counterInitByKey = new Map();
|
|
376
|
+
_nextCounterByKey = new Map();
|
|
265
377
|
}
|
|
266
378
|
|
|
267
379
|
return {
|
|
268
380
|
table: table,
|
|
381
|
+
chainKey: chainKey,
|
|
269
382
|
append: append,
|
|
270
383
|
_resetForTest: _resetForTest,
|
|
271
|
-
// Expose for diagnostic introspection
|
|
272
|
-
|
|
384
|
+
// Expose for diagnostic introspection — the lock for a given key (or the
|
|
385
|
+
// single-chain lock when no chainKey is configured).
|
|
386
|
+
_getMutexForTest: function (keyValue) { return _mutexFor(keyValue); },
|
|
273
387
|
};
|
|
274
388
|
}
|
|
275
389
|
|
|
276
390
|
module.exports = {
|
|
277
391
|
create: create,
|
|
392
|
+
registerTable: registerTable,
|
|
278
393
|
ChainWriterError: ChainWriterError,
|
|
279
394
|
ALLOWED_CHAIN_TABLES: ALLOWED_CHAIN_TABLES,
|
|
280
395
|
FRAMEWORK_SQL_TIMEOUT_MS: FRAMEWORK_SQL_TIMEOUT_MS,
|
package/lib/cli.js
CHANGED
|
@@ -39,6 +39,7 @@ var os = require("node:os");
|
|
|
39
39
|
var nodePath = require("node:path");
|
|
40
40
|
var apiSnapshot = require("./api-snapshot");
|
|
41
41
|
var argParser = require("./arg-parser");
|
|
42
|
+
var atomicFile = require("./atomic-file");
|
|
42
43
|
var auditChain = require("./audit-chain");
|
|
43
44
|
var auditTools = require("./audit-tools");
|
|
44
45
|
var backup = require("./backup");
|
|
@@ -1110,12 +1111,16 @@ async function _runApiKey(args, ctx) {
|
|
|
1110
1111
|
return report.error("--scopes must contain at least one non-empty scope", 2);
|
|
1111
1112
|
}
|
|
1112
1113
|
var label = typeof args.flags.label === "string" ? args.flags.label : null;
|
|
1114
|
+
// --expires-ms is the absolute expiry as a unix-ms timestamp; the
|
|
1115
|
+
// registry's validated opt is `expiresAt` (apiKey.issue rejects any other
|
|
1116
|
+
// key). Passing `expiresMs` silently did nothing before — the lib only
|
|
1117
|
+
// ever read `expiresAt` — so the flag was a no-op; map it correctly.
|
|
1113
1118
|
var expiresMs = args.flags["expires-ms"];
|
|
1114
1119
|
var issued = await registry.issue({
|
|
1115
1120
|
ownerId: String(ownerId),
|
|
1116
1121
|
scopes: scopeList,
|
|
1117
1122
|
metadata: label ? { label: label } : null,
|
|
1118
|
-
|
|
1123
|
+
expiresAt: expiresMs && expiresMs !== true ? Number(expiresMs) : undefined,
|
|
1119
1124
|
});
|
|
1120
1125
|
report.write("id: " + issued.id);
|
|
1121
1126
|
report.write("key: " + issued.key);
|
|
@@ -1480,7 +1485,10 @@ async function _runMtls(args, ctx) {
|
|
|
1480
1485
|
validityDays: daysP,
|
|
1481
1486
|
});
|
|
1482
1487
|
if (outPath) {
|
|
1483
|
-
|
|
1488
|
+
// Atomic, symlink-refusing write — a bare writeFileSync follows a
|
|
1489
|
+
// symlink an attacker pre-planted at the operator-supplied --out
|
|
1490
|
+
// path (CWE-59) and could expose the client key bundle through it.
|
|
1491
|
+
atomicFile.writeSync(outPath, p12.p12, { fileMode: 0o600 });
|
|
1484
1492
|
report.write("p12 written: " + outPath);
|
|
1485
1493
|
} else {
|
|
1486
1494
|
// No --out: stream the bytes to stdout for piping. Operators
|
package/lib/client-hints.js
CHANGED
|
@@ -158,15 +158,13 @@ function _parseBrandMember(piece) {
|
|
|
158
158
|
var brand = _parseSfString(params[0].trim());
|
|
159
159
|
if (brand === null) return null;
|
|
160
160
|
var member = { brand: brand, version: null, params: {} };
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
member.params[k] = v;
|
|
169
|
-
if (k === "v" && v !== null) member.version = v;
|
|
161
|
+
var kvps = structuredFields.parseKeyValuePieces(params, 1);
|
|
162
|
+
for (var i = 0; i < kvps.length; i += 1) {
|
|
163
|
+
var kv = kvps[i];
|
|
164
|
+
if (kv.value === null) { member.params[kv.key] = true; continue; }
|
|
165
|
+
var v = _parseSfString(kv.value);
|
|
166
|
+
member.params[kv.key] = v;
|
|
167
|
+
if (kv.key === "v" && v !== null) member.version = v;
|
|
170
168
|
}
|
|
171
169
|
return member;
|
|
172
170
|
}
|
package/lib/cloud-events.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
var nodeCrypto = require("node:crypto");
|
|
38
|
+
var numericBounds = require("./numeric-bounds");
|
|
38
39
|
var validateOpts = require("./validate-opts");
|
|
39
40
|
var rfc3339 = require("./rfc3339");
|
|
40
41
|
var safeJson = require("./safe-json");
|
|
@@ -118,21 +119,41 @@ function _genId() {
|
|
|
118
119
|
* // → "1.0"
|
|
119
120
|
*/
|
|
120
121
|
function wrap(opts) {
|
|
121
|
-
validateOpts.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
122
|
+
validateOpts.shape(opts, {
|
|
123
|
+
source: { rule: "required-string", code: "cloud-events/bad-source" },
|
|
124
|
+
type: { rule: "required-string", code: "cloud-events/bad-type" },
|
|
125
|
+
id: { rule: "optional-string", code: "cloud-events/bad-id" },
|
|
126
|
+
subject: { rule: "optional-string", code: "cloud-events/bad-subject" },
|
|
127
|
+
time: { rule: "optional-string", code: "cloud-events/bad-time" },
|
|
128
|
+
datacontenttype: { rule: "optional-string", code: "cloud-events/bad-datacontenttype" },
|
|
129
|
+
dataschema: { rule: "optional-string", code: "cloud-events/bad-dataschema" },
|
|
130
|
+
// data is any JSON value or a Buffer — wrap routes Buffers to
|
|
131
|
+
// data_base64 and everything else to the data attribute, imposing no
|
|
132
|
+
// type constraint of its own, so accept any value here.
|
|
133
|
+
data: function () {},
|
|
134
|
+
// extensions — plain object whose keys conform to the §3.1 naming
|
|
135
|
+
// rules (lowercase ASCII alnum, 1-20 chars) and collide with no spec
|
|
136
|
+
// attribute. Validation lives IN the shape; the body only copies the
|
|
137
|
+
// validated keys onto the envelope.
|
|
138
|
+
extensions: function (v) {
|
|
139
|
+
validateOpts.optionalPlainObject(v,
|
|
140
|
+
"cloudEvents.wrap: extensions", CloudEventsError, "cloud-events/bad-extensions");
|
|
141
|
+
if (v === undefined || v === null) return;
|
|
142
|
+
var extKeys = Object.keys(v);
|
|
143
|
+
for (var i = 0; i < extKeys.length; i += 1) {
|
|
144
|
+
var k = extKeys[i];
|
|
145
|
+
// bound BEFORE regex test — k.length > 0 && k.length <= 20
|
|
146
|
+
if (typeof k !== "string" || k.length === 0 || k.length > 20 || !EXT_ATTR_NAME_RE.test(k)) {
|
|
147
|
+
throw new CloudEventsError("cloud-events/bad-extension-name",
|
|
148
|
+
"cloudEvents.wrap: extension '" + k + "' must match [a-z0-9]{1,20}");
|
|
149
|
+
}
|
|
150
|
+
if (REQUIRED_ATTRS.indexOf(k) !== -1 || KNOWN_OPTIONAL_ATTRS[k]) {
|
|
151
|
+
throw new CloudEventsError("cloud-events/extension-conflicts-with-spec",
|
|
152
|
+
"cloudEvents.wrap: extension '" + k + "' conflicts with a spec attribute");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
}, "cloudEvents.wrap", CloudEventsError);
|
|
136
157
|
|
|
137
158
|
var out = {
|
|
138
159
|
specversion: SPECVERSION,
|
|
@@ -158,24 +179,13 @@ function wrap(opts) {
|
|
|
158
179
|
out.datacontenttype = opts.datacontenttype;
|
|
159
180
|
}
|
|
160
181
|
|
|
161
|
-
// Extension attributes —
|
|
162
|
-
// §3.1 naming
|
|
182
|
+
// Extension attributes — validated in the shape above (plain object,
|
|
183
|
+
// §3.1 key naming, no spec collisions); copy the validated keys onto
|
|
184
|
+
// the envelope.
|
|
163
185
|
if (opts.extensions !== undefined && opts.extensions !== null) {
|
|
164
|
-
validateOpts.optionalPlainObject(opts.extensions,
|
|
165
|
-
"cloudEvents.wrap: extensions", CloudEventsError, "cloud-events/bad-extensions");
|
|
166
186
|
var extKeys = Object.keys(opts.extensions);
|
|
167
187
|
for (var i = 0; i < extKeys.length; i += 1) {
|
|
168
|
-
|
|
169
|
-
// bound BEFORE regex test — k.length > 0 && k.length <= 20
|
|
170
|
-
if (typeof k !== "string" || k.length === 0 || k.length > 20 || !EXT_ATTR_NAME_RE.test(k)) {
|
|
171
|
-
throw new CloudEventsError("cloud-events/bad-extension-name",
|
|
172
|
-
"cloudEvents.wrap: extension '" + k + "' must match [a-z0-9]{1,20}");
|
|
173
|
-
}
|
|
174
|
-
if (REQUIRED_ATTRS.indexOf(k) !== -1 || KNOWN_OPTIONAL_ATTRS[k]) {
|
|
175
|
-
throw new CloudEventsError("cloud-events/extension-conflicts-with-spec",
|
|
176
|
-
"cloudEvents.wrap: extension '" + k + "' conflicts with a spec attribute");
|
|
177
|
-
}
|
|
178
|
-
out[k] = opts.extensions[k];
|
|
188
|
+
out[extKeys[i]] = opts.extensions[extKeys[i]];
|
|
179
189
|
}
|
|
180
190
|
}
|
|
181
191
|
return out;
|
|
@@ -256,7 +266,7 @@ function parse(envelope) {
|
|
|
256
266
|
for (var j = 0; j < keys.length; j += 1) {
|
|
257
267
|
var key = keys[j];
|
|
258
268
|
if (REQUIRED_ATTRS.indexOf(key) !== -1) continue;
|
|
259
|
-
if (KNOWN_OPTIONAL_ATTRS
|
|
269
|
+
if (Object.prototype.hasOwnProperty.call(KNOWN_OPTIONAL_ATTRS, key)) continue;
|
|
260
270
|
extensions[key] = envelope[key];
|
|
261
271
|
}
|
|
262
272
|
|
|
@@ -300,7 +310,7 @@ function _extIssue(name, v) {
|
|
|
300
310
|
if (v === null) return null;
|
|
301
311
|
if (typeof v === "string" || typeof v === "boolean") return null;
|
|
302
312
|
if (typeof v === "number") {
|
|
303
|
-
if (!
|
|
313
|
+
if (!numericBounds.isFiniteInt(v)) return "extension '" + name + "' must be an integer (CloudEvents has no float type)";
|
|
304
314
|
if (v < INT_MIN || v > INT_MAX) return "extension '" + name + "' integer out of 32-bit range";
|
|
305
315
|
return null;
|
|
306
316
|
}
|
|
@@ -668,7 +678,7 @@ function decodeBinary(headers, body, opts) {
|
|
|
668
678
|
else if (Buffer.isBuffer(body)) raw = body;
|
|
669
679
|
else if (typeof body === "string") raw = Buffer.from(body, "utf8");
|
|
670
680
|
else throw new CloudEventsError("cloud-events/bad-input", "cloudEvents.http.decodeBinary: body must be a string or Buffer");
|
|
671
|
-
if (raw
|
|
681
|
+
if (safeBuffer.byteLengthOf(raw) > maxBytes) throw new CloudEventsError("cloud-events/too-large", "cloudEvents.http.decodeBinary: body exceeds maxBytes (" + maxBytes + ")");
|
|
672
682
|
if (raw.length > 0) {
|
|
673
683
|
if (_isJsonMedia(ct)) {
|
|
674
684
|
try { event.data = safeJson.parse(raw, { maxBytes: maxBytes }); }
|
package/lib/cluster-storage.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
var cluster = require("./cluster");
|
|
31
31
|
var frameworkSchema = require("./framework-schema");
|
|
32
32
|
var externalDb = require("./external-db");
|
|
33
|
+
var safeSql = require("./safe-sql");
|
|
33
34
|
var lazyRequire = require("./lazy-require");
|
|
34
35
|
var { FrameworkError } = require("./framework-error");
|
|
35
36
|
|
|
@@ -277,44 +278,7 @@ function resolveTables(sql) {
|
|
|
277
278
|
* // → "SELECT id FROM audit_log WHERE counter > $1 AND actor = $2"
|
|
278
279
|
*/
|
|
279
280
|
function placeholderize(sql, dialect) {
|
|
280
|
-
|
|
281
|
-
var out = "";
|
|
282
|
-
var n = 0;
|
|
283
|
-
var i = 0;
|
|
284
|
-
var len = sql.length;
|
|
285
|
-
while (i < len) {
|
|
286
|
-
var c = sql.charAt(i);
|
|
287
|
-
var nx = i + 1 < len ? sql.charAt(i + 1) : "";
|
|
288
|
-
// Quote contexts: ' string literal, " identifier, ` mysql identifier.
|
|
289
|
-
// A doubled quote escapes itself within the span.
|
|
290
|
-
if (c === "'" || c === '"' || c === "`") {
|
|
291
|
-
out += c;
|
|
292
|
-
i += 1;
|
|
293
|
-
while (i < len) {
|
|
294
|
-
var q = sql.charAt(i);
|
|
295
|
-
if (q === c) {
|
|
296
|
-
if (sql.charAt(i + 1) === c) { out += c + c; i += 2; continue; }
|
|
297
|
-
out += c; i += 1; break;
|
|
298
|
-
}
|
|
299
|
-
out += q; i += 1;
|
|
300
|
-
}
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
if (c === "-" && nx === "-") { // line comment
|
|
304
|
-
while (i < len && sql.charAt(i) !== "\n") { out += sql.charAt(i); i += 1; }
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
if (c === "/" && nx === "*") { // block comment
|
|
308
|
-
out += "/*"; i += 2;
|
|
309
|
-
while (i < len && !(sql.charAt(i) === "*" && sql.charAt(i + 1) === "/")) { out += sql.charAt(i); i += 1; }
|
|
310
|
-
if (i < len) { out += "*/"; i += 2; }
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
if (c === "?") { n += 1; out += "$" + n; i += 1; continue; }
|
|
314
|
-
out += c;
|
|
315
|
-
i += 1;
|
|
316
|
-
}
|
|
317
|
-
return out;
|
|
281
|
+
return safeSql.toPositional(sql, dialect);
|
|
318
282
|
}
|
|
319
283
|
|
|
320
284
|
// ---- execute() ----
|
|
@@ -363,9 +327,13 @@ var _activeTx = null;
|
|
|
363
327
|
// synchronous this runs atomically to completion with no interleaving.
|
|
364
328
|
function _localExec(sql, params) {
|
|
365
329
|
var stmt = _localDb().prepare(sql);
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
|
|
330
|
+
// Choose .all() vs .run() by whether the statement returns rows. A leading-
|
|
331
|
+
// keyword check (`^\s*SELECT`) mis-routes a WITH (CTE) read — "WITH c AS (...)
|
|
332
|
+
// SELECT ..." does not start with SELECT — to .run(), which on node:sqlite
|
|
333
|
+
// silently returns only a changes count and DROPS the rows. The shared
|
|
334
|
+
// CTE/EXPLAIN-aware classifier resolves the effective verb (and a top-level
|
|
335
|
+
// RETURNING) so every row-returning statement reaches the caller via .all().
|
|
336
|
+
if (externalDb._statementReturnsRows(sql)) {
|
|
369
337
|
var rows = stmt.all.apply(stmt, params || []);
|
|
370
338
|
return { rows: rows, rowCount: rows.length };
|
|
371
339
|
}
|
package/lib/cms-codec.js
CHANGED
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
*/
|
|
69
69
|
|
|
70
70
|
var nodeCrypto = require("node:crypto");
|
|
71
|
+
var safeBuffer = require("./safe-buffer");
|
|
71
72
|
var asn1 = require("./asn1-der");
|
|
72
73
|
var bCrypto = require("./crypto");
|
|
73
74
|
var pqcSoftware = require("./pqc-software");
|
|
@@ -322,7 +323,7 @@ function decode(buf, opts) {
|
|
|
322
323
|
throw new CmsCodecError("cms/bad-input", "decode: buf must be a Buffer");
|
|
323
324
|
}
|
|
324
325
|
var maxBytes = opts.maxBytes || DEFAULT_MAX_LEN;
|
|
325
|
-
if (buf
|
|
326
|
+
if (safeBuffer.byteLengthOf(buf) > maxBytes) {
|
|
326
327
|
throw new CmsCodecError("cms/oversize",
|
|
327
328
|
"decode: input " + buf.length + " bytes exceeds maxBytes=" + maxBytes);
|
|
328
329
|
}
|