@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/audit-chain.js
CHANGED
|
@@ -38,6 +38,8 @@ var C = require("./constants");
|
|
|
38
38
|
var clusterStorage = require("./cluster-storage");
|
|
39
39
|
var frameworkSchema = require("./framework-schema");
|
|
40
40
|
var sql = require("./sql");
|
|
41
|
+
var safeSql = require("./safe-sql");
|
|
42
|
+
var safeBuffer = require("./safe-buffer");
|
|
41
43
|
var { sha3Hash } = require("./crypto");
|
|
42
44
|
|
|
43
45
|
// b.sql opts for the chain read SQL these primitives compose. The reader
|
|
@@ -135,7 +137,7 @@ function computeRowHash(prevHash, rowFields, nonce) {
|
|
|
135
137
|
|
|
136
138
|
/**
|
|
137
139
|
* @primitive b.auditChain.getChainTip
|
|
138
|
-
* @signature b.auditChain.getChainTip(queryOneAsync, tableName)
|
|
140
|
+
* @signature b.auditChain.getChainTip(queryOneAsync, tableName, opts?)
|
|
139
141
|
* @since 0.4.0
|
|
140
142
|
* @related b.auditChain.verifyChain, b.auditChain.computeRowHash
|
|
141
143
|
*
|
|
@@ -146,6 +148,14 @@ function computeRowHash(prevHash, rowFields, nonce) {
|
|
|
146
148
|
* drivers can use any await-able query function of the shape
|
|
147
149
|
* `async (sql, params?) -> row | null`.
|
|
148
150
|
*
|
|
151
|
+
* Pass `{ chainKey, keyValue }` to scope the tip to one partition of a
|
|
152
|
+
* multi-chain table (one chain per account / device / tenant) — the tip read
|
|
153
|
+
* filters `WHERE <chainKey> = ?` with the value bound, never interpolated.
|
|
154
|
+
*
|
|
155
|
+
* @opts
|
|
156
|
+
* chainKey: string, // partition column for a multi-chain table
|
|
157
|
+
* keyValue: any, // the partition value to scope the tip to (bound)
|
|
158
|
+
*
|
|
149
159
|
* @example
|
|
150
160
|
* async function queryOne(sql) {
|
|
151
161
|
* var rows = await myDriver.query(sql);
|
|
@@ -154,16 +164,22 @@ function computeRowHash(prevHash, rowFields, nonce) {
|
|
|
154
164
|
* var tip = await b.auditChain.getChainTip(queryOne, "audit_log");
|
|
155
165
|
* // → { prevHash: "<128-char hex>", counter: 4217 }
|
|
156
166
|
*/
|
|
157
|
-
async function getChainTip(queryOneAsync, tableName) {
|
|
167
|
+
async function getChainTip(queryOneAsync, tableName, opts) {
|
|
168
|
+
opts = opts || {};
|
|
158
169
|
// Emit a BARE logical table name — the operator-supplied reader routes
|
|
159
170
|
// through clusterStorage, which rewrites bare framework names to the
|
|
160
171
|
// configured-prefix form and placeholderizes. b.sql quotes the camelCase
|
|
161
|
-
// columns + runs the output validator.
|
|
162
|
-
|
|
172
|
+
// columns + runs the output validator. A chainKey scopes the tip to one
|
|
173
|
+
// partition; the key value binds as a ? placeholder.
|
|
174
|
+
var q = sql.select(tableName, _sqlOpts())
|
|
163
175
|
.columns(["rowHash", "monotonicCounter"])
|
|
164
176
|
.orderBy("monotonicCounter", "desc")
|
|
165
|
-
.limit(1)
|
|
166
|
-
|
|
177
|
+
.limit(1);
|
|
178
|
+
if (opts.chainKey) {
|
|
179
|
+
safeSql.validateIdentifier(opts.chainKey);
|
|
180
|
+
q = q.where(opts.chainKey, opts.keyValue);
|
|
181
|
+
}
|
|
182
|
+
var built = q.toSql();
|
|
167
183
|
var row = await queryOneAsync(built.sql, built.params);
|
|
168
184
|
if (!row) return { prevHash: ZERO_HASH, counter: 0 };
|
|
169
185
|
// Normalize driver shape (Postgres returns BIGINT monotonicCounter as a
|
|
@@ -192,60 +208,32 @@ async function getChainTip(queryOneAsync, tableName) {
|
|
|
192
208
|
* after a successful archive and lets the chain math survive deletion
|
|
193
209
|
* of historical rows without the archive bundle as source of truth.
|
|
194
210
|
*
|
|
211
|
+
* Pass `{ chainKey }` to verify a MULTI-chain table partitioned by a key
|
|
212
|
+
* column (one chain per account / device / tenant): each key's sub-chain is
|
|
213
|
+
* walked independently from `ZERO_HASH`, and the first break in any key returns
|
|
214
|
+
* `{ ok:false, chainKey, breakAt, ... }`. Under `chainKey`, `maxRows` is
|
|
215
|
+
* per-sub-chain and `maxChains` bounds the partition fan-out, failing closed
|
|
216
|
+
* when exceeded. The `audit_log` purge-anchor logic is single-chain-only and
|
|
217
|
+
* is skipped when a `chainKey` is given.
|
|
218
|
+
*
|
|
195
219
|
* @opts
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
220
|
+
* maxRows: number, // stop after N rows per (sub-)chain (default: walk every row)
|
|
221
|
+
* chainKey: string, // partition column — verify each sub-chain independently
|
|
222
|
+
* maxChains: number, // max partitions to verify under chainKey (default 100000; fails closed)
|
|
223
|
+
* from: number, // single-chain only: verify rows with monotonicCounter >= from, anchored at the predecessor's rowHash (incremental verify after a known-good checkpoint)
|
|
224
|
+
* to: number, // single-chain only: verify rows with monotonicCounter <= to
|
|
199
225
|
*
|
|
200
226
|
* @example
|
|
201
227
|
* async function queryAll(sql) { return await myDriver.query(sql); }
|
|
202
228
|
* var result = await b.auditChain.verifyChain(queryAll, "audit_log", {});
|
|
203
229
|
* // → { ok: true, table: "audit_log", rowsVerified: 4217, lastHash: "<hex>" }
|
|
204
230
|
*/
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
var anchor;
|
|
212
|
-
try {
|
|
213
|
-
// External-only table whose LOGICAL name IS the `_blamejs_`-prefixed
|
|
214
|
-
// name (self-mapped in LOCAL_TO_EXTERNAL), passed bare so the reader's
|
|
215
|
-
// clusterStorage rewrites it; the 'audit' scope binds as a ? param.
|
|
216
|
-
// allow:hand-rolled-sql — bare logical key.
|
|
217
|
-
var anchorBuilt = sql.select("_blamejs_audit_purge_anchor", _sqlOpts()) // allow:hand-rolled-sql
|
|
218
|
-
.columns(["lastPurgedCounter", "lastPurgedRowHash"])
|
|
219
|
-
.where("scope", "audit")
|
|
220
|
-
.toSql();
|
|
221
|
-
anchor = await queryAllAsync(anchorBuilt.sql, anchorBuilt.params);
|
|
222
|
-
} catch (_e) {
|
|
223
|
-
// Anchor table may not exist on a deployment that has never been
|
|
224
|
-
// through a purge. Treat as no anchor.
|
|
225
|
-
anchor = [];
|
|
226
|
-
}
|
|
227
|
-
if (Array.isArray(anchor) && anchor.length > 0) {
|
|
228
|
-
prevHash = anchor[0].lastPurgedRowHash;
|
|
229
|
-
skipBeforeCounter = Number(anchor[0].lastPurgedCounter);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
var rowsBuilt = sql.select(tableName, _sqlOpts())
|
|
234
|
-
.orderBy("monotonicCounter", "asc")
|
|
235
|
-
.toSql();
|
|
236
|
-
var rows = await queryAllAsync(rowsBuilt.sql, rowsBuilt.params);
|
|
237
|
-
// Normalize driver shape before hashing: node-postgres returns BIGINT
|
|
238
|
-
// columns (recordedAt / monotonicCounter) as strings, which would hash
|
|
239
|
-
// differently from the numbers the chain-writer signed — the chain only
|
|
240
|
-
// verified on SQLite without this. coerceRow makes the recompute
|
|
241
|
-
// type-stable across backends (no-op on already-numeric SQLite rows).
|
|
242
|
-
rows = frameworkSchema.coerceRows(rows);
|
|
243
|
-
if (skipBeforeCounter > 0) {
|
|
244
|
-
rows = rows.filter(function (r) {
|
|
245
|
-
return Number(r.monotonicCounter) > skipBeforeCounter;
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
231
|
+
// Walk one (sub-)chain forward from startPrevHash, recomputing each row's
|
|
232
|
+
// hash. Returns the same { ok, table, rowsVerified, lastHash | breakAt... }
|
|
233
|
+
// shape verifyChain documents. Shared by the single-chain path and each
|
|
234
|
+
// per-key partition.
|
|
235
|
+
function _walkRows(rows, tableName, startPrevHash, opts) {
|
|
236
|
+
var prevHash = startPrevHash;
|
|
249
237
|
if (rows.length === 0) {
|
|
250
238
|
return { ok: true, table: tableName, rowsVerified: 0, lastHash: prevHash };
|
|
251
239
|
}
|
|
@@ -290,7 +278,148 @@ async function verifyChain(queryAllAsync, tableName, opts) {
|
|
|
290
278
|
|
|
291
279
|
if (opts.maxRows && i >= opts.maxRows - 1) break;
|
|
292
280
|
}
|
|
293
|
-
|
|
281
|
+
// Report the count ACTUALLY walked, not rows.length — under maxRows the walk
|
|
282
|
+
// stops early, so rows.length would over-report coverage (a caller reading
|
|
283
|
+
// rowsVerified to judge how much of the chain was checked must see the real
|
|
284
|
+
// number, not be told the whole table verified when only maxRows did).
|
|
285
|
+
var verifiedCount = opts.maxRows ? Math.min(rows.length, opts.maxRows) : rows.length;
|
|
286
|
+
return { ok: true, table: tableName, rowsVerified: verifiedCount, lastHash: prevHash };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function verifyChain(queryAllAsync, tableName, opts) {
|
|
290
|
+
opts = opts || {};
|
|
291
|
+
|
|
292
|
+
// Multi-chain table: verify each partition independently. Each key's
|
|
293
|
+
// sub-chain anchors at ZERO_HASH and is walked in monotonic-counter order;
|
|
294
|
+
// the first break in ANY key returns { ok:false, chainKey, ... }. maxRows is
|
|
295
|
+
// per-sub-chain; maxChains bounds the partition fan-out (fails closed when
|
|
296
|
+
// exceeded). The audit_log purge-anchor logic is single-chain-only, so it is
|
|
297
|
+
// skipped under a chainKey.
|
|
298
|
+
if (opts.chainKey) {
|
|
299
|
+
safeSql.validateIdentifier(opts.chainKey);
|
|
300
|
+
var keysBuilt = sql.select(tableName, _sqlOpts())
|
|
301
|
+
.distinct()
|
|
302
|
+
.columns([opts.chainKey])
|
|
303
|
+
.orderBy(opts.chainKey, "asc")
|
|
304
|
+
.toSql();
|
|
305
|
+
// coerce so a Postgres INTEGER/BIGINT chainKey is type-stable in the
|
|
306
|
+
// reported break-shape and the per-key WHERE bind, matching SQLite.
|
|
307
|
+
var keyRows = frameworkSchema.coerceRows(await queryAllAsync(keysBuilt.sql, keysBuilt.params));
|
|
308
|
+
var maxChains = (typeof opts.maxChains === "number" && opts.maxChains > 0) ? opts.maxChains : 100000; // allow:numeric-opt-Infinity — partition fan-out cap; non-number / <=0 falls back to the default
|
|
309
|
+
if (keyRows.length > maxChains) {
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
table: tableName,
|
|
313
|
+
rowsVerified: 0,
|
|
314
|
+
reason: "too many chains: " + keyRows.length + " partitions exceeds maxChains " + maxChains,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
var totalVerified = 0;
|
|
318
|
+
var lastHashByKey = {};
|
|
319
|
+
for (var ki = 0; ki < keyRows.length; ki++) {
|
|
320
|
+
var keyValue = keyRows[ki][opts.chainKey];
|
|
321
|
+
var rowsBuiltK = sql.select(tableName, _sqlOpts())
|
|
322
|
+
.where(opts.chainKey, keyValue)
|
|
323
|
+
.orderBy("monotonicCounter", "asc")
|
|
324
|
+
.toSql();
|
|
325
|
+
var rowsK = frameworkSchema.coerceRows(await queryAllAsync(rowsBuiltK.sql, rowsBuiltK.params));
|
|
326
|
+
var resK = _walkRows(rowsK, tableName, ZERO_HASH, opts);
|
|
327
|
+
if (!resK.ok) { resK.chainKey = keyValue; return resK; }
|
|
328
|
+
totalVerified += resK.rowsVerified;
|
|
329
|
+
lastHashByKey[String(keyValue)] = resK.lastHash;
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
ok: true,
|
|
333
|
+
table: tableName,
|
|
334
|
+
rowsVerified: totalVerified,
|
|
335
|
+
chains: keyRows.length,
|
|
336
|
+
lastHashByKey: lastHashByKey,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
var prevHash = ZERO_HASH;
|
|
341
|
+
var skipBeforeCounter = 0;
|
|
342
|
+
if (tableName === "audit_log") {
|
|
343
|
+
var anchor;
|
|
344
|
+
try {
|
|
345
|
+
// External-only table whose LOGICAL name IS the `_blamejs_`-prefixed
|
|
346
|
+
// name (self-mapped in LOCAL_TO_EXTERNAL), passed bare so the reader's
|
|
347
|
+
// clusterStorage rewrites it; the 'audit' scope binds as a ? param.
|
|
348
|
+
// allow:hand-rolled-sql — bare logical key.
|
|
349
|
+
var anchorBuilt = sql.select("_blamejs_audit_purge_anchor", _sqlOpts()) // allow:hand-rolled-sql
|
|
350
|
+
.columns(["lastPurgedCounter", "lastPurgedRowHash"])
|
|
351
|
+
.where("scope", "audit")
|
|
352
|
+
.toSql();
|
|
353
|
+
anchor = await queryAllAsync(anchorBuilt.sql, anchorBuilt.params);
|
|
354
|
+
} catch (_e) {
|
|
355
|
+
// Anchor table may not exist on a deployment that has never been
|
|
356
|
+
// through a purge. Treat as no anchor.
|
|
357
|
+
anchor = [];
|
|
358
|
+
}
|
|
359
|
+
if (Array.isArray(anchor) && anchor.length > 0) {
|
|
360
|
+
var aHash = anchor[0].lastPurgedRowHash;
|
|
361
|
+
var aCounter = Number(anchor[0].lastPurgedCounter);
|
|
362
|
+
// A corrupted / tampered purge anchor (non-hex lastPurgedRowHash or a
|
|
363
|
+
// non-numeric lastPurgedCounter) must fail CLOSED with a clear reason.
|
|
364
|
+
// Passing a garbage prevHash into _walkRows → computeRowHash would THROW
|
|
365
|
+
// ("prevHash must be a 128-char hex"), turning a defensive verify into an
|
|
366
|
+
// uncaught exception; a NaN counter would skip nothing and surface as an
|
|
367
|
+
// opaque chain-break. Detect it here and return { ok:false }.
|
|
368
|
+
if (!safeBuffer.isHex(aHash, SHA3_512_HEX_LEN) || !isFinite(aCounter) || aCounter < 0) {
|
|
369
|
+
return { ok: false, table: tableName, rowsVerified: 0, reason: "corrupted purge anchor" };
|
|
370
|
+
}
|
|
371
|
+
prevHash = aHash;
|
|
372
|
+
skipBeforeCounter = aCounter;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Incremental verify (b.audit.verify { from, to }): verify only rows whose
|
|
377
|
+
// monotonicCounter is in [from, to]. `from` must anchor on the rowHash of the
|
|
378
|
+
// row immediately BEFORE it, so the scoped walk chains correctly — otherwise
|
|
379
|
+
// the first in-range row's prevHash (= the predecessor's rowHash) wouldn't
|
|
380
|
+
// match ZERO_HASH and a good chain would falsely report a break.
|
|
381
|
+
var fromCounter = (opts.from != null && isFinite(Number(opts.from))) ? Number(opts.from) : null;
|
|
382
|
+
var toCounter = (opts.to != null && isFinite(Number(opts.to))) ? Number(opts.to) : null;
|
|
383
|
+
|
|
384
|
+
var rowsBuilt = sql.select(tableName, _sqlOpts())
|
|
385
|
+
.orderBy("monotonicCounter", "asc")
|
|
386
|
+
.toSql();
|
|
387
|
+
var rows = await queryAllAsync(rowsBuilt.sql, rowsBuilt.params);
|
|
388
|
+
// Normalize driver shape before hashing: node-postgres returns BIGINT
|
|
389
|
+
// columns (recordedAt / monotonicCounter) as strings, which would hash
|
|
390
|
+
// differently from the numbers the chain-writer signed — the chain only
|
|
391
|
+
// verified on SQLite without this. coerceRow makes the recompute
|
|
392
|
+
// type-stable across backends (no-op on already-numeric SQLite rows).
|
|
393
|
+
rows = frameworkSchema.coerceRows(rows);
|
|
394
|
+
|
|
395
|
+
// Resolve the incremental-verify anchor: the highest row strictly below
|
|
396
|
+
// `from` (derived from the already-read rows, no extra query). Raise
|
|
397
|
+
// skipBeforeCounter to it and adopt its rowHash as the chain anchor.
|
|
398
|
+
if (fromCounter != null && fromCounter > skipBeforeCounter + 1) {
|
|
399
|
+
var pred = null;
|
|
400
|
+
for (var pi = 0; pi < rows.length; pi++) {
|
|
401
|
+
var pc = Number(rows[pi].monotonicCounter);
|
|
402
|
+
if (pc < fromCounter && pc > skipBeforeCounter) pred = rows[pi]; else if (pc >= fromCounter) break;
|
|
403
|
+
}
|
|
404
|
+
if (pred) {
|
|
405
|
+
if (!safeBuffer.isHex(pred.rowHash, SHA3_512_HEX_LEN)) {
|
|
406
|
+
return { ok: false, table: tableName, rowsVerified: 0, reason: "incremental-verify anchor row has a corrupt rowHash" };
|
|
407
|
+
}
|
|
408
|
+
prevHash = pred.rowHash;
|
|
409
|
+
skipBeforeCounter = Math.max(skipBeforeCounter, Number(pred.monotonicCounter));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (skipBeforeCounter > 0 || toCounter != null) {
|
|
414
|
+
rows = rows.filter(function (r) {
|
|
415
|
+
var c = Number(r.monotonicCounter);
|
|
416
|
+
if (c <= skipBeforeCounter) return false;
|
|
417
|
+
if (toCounter != null && c > toCounter) return false;
|
|
418
|
+
return true;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return _walkRows(rows, tableName, prevHash, opts);
|
|
294
423
|
}
|
|
295
424
|
|
|
296
425
|
module.exports = {
|
|
@@ -83,9 +83,15 @@ function _defaultClassify(event) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
function _severityAtLeast(severity, threshold) {
|
|
86
|
-
var sIdx = SEVERITY_ORDER.indexOf(severity);
|
|
87
86
|
var tIdx = SEVERITY_ORDER.indexOf(threshold);
|
|
88
|
-
if (
|
|
87
|
+
if (tIdx === -1) return false; // unknown threshold (validated at config)
|
|
88
|
+
var sIdx = SEVERITY_ORDER.indexOf(severity);
|
|
89
|
+
// An UNKNOWN event severity — e.g. a custom classify(event) returning an
|
|
90
|
+
// unexpected value — must NOT silently drop the event from the review. Fail
|
|
91
|
+
// SAFE: treat it as meeting the threshold so the operator still sees the
|
|
92
|
+
// event (and notices their classify mis-returned) rather than missing a
|
|
93
|
+
// flagged event.
|
|
94
|
+
if (sIdx === -1) return true;
|
|
89
95
|
return sIdx >= tIdx;
|
|
90
96
|
}
|
|
91
97
|
|
|
@@ -164,20 +170,30 @@ function create(opts) {
|
|
|
164
170
|
throw _err("auditDailyReview/audit-query-missing",
|
|
165
171
|
"auditDailyReview.create: opts.audit.query must be a function");
|
|
166
172
|
}
|
|
167
|
-
validateOpts.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
173
|
+
validateOpts.shape(opts, {
|
|
174
|
+
audit: { methods: ["query", "safeEmit"] },
|
|
175
|
+
notify: { rule: "optional-function", code: "auditDailyReview/bad-notify" },
|
|
176
|
+
classify: { rule: "optional-function", code: "auditDailyReview/bad-classify" },
|
|
177
|
+
now: { rule: "optional-function", code: "auditDailyReview/bad-now" },
|
|
178
|
+
posture: { rule: "optional-string", code: "auditDailyReview/bad-posture" },
|
|
179
|
+
cron: { rule: "optional-string", code: "auditDailyReview/bad-cron" },
|
|
180
|
+
severityThreshold: { rule: "optional-string", code: "auditDailyReview/bad-severity" },
|
|
181
|
+
queryLimit: { rule: "optional-positive-int", code: "auditDailyReview/bad-querylimit" },
|
|
182
|
+
historyLimit: { rule: "optional-positive-int", code: "auditDailyReview/bad-historylimit" },
|
|
183
|
+
// lookbackHours — positive finite number (hours, not bytes). Bespoke
|
|
184
|
+
// check preserves the auditDailyReview/bad-lookback code the message
|
|
185
|
+
// below depends on; a positive-int token would reject fractional hours.
|
|
186
|
+
lookbackHours: function (value, label) {
|
|
187
|
+
if (value === undefined || value === null) return;
|
|
188
|
+
if (typeof value !== "number" || !isFinite(value) || value <= 0) {
|
|
189
|
+
throw _err("auditDailyReview/bad-lookback",
|
|
190
|
+
"auditDailyReview.create: lookbackHours must be a positive finite number");
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
}, "auditDailyReview", AuditDailyReviewError, "auditDailyReview/bad-opts",
|
|
194
|
+
// scheduler is forwarded to the b.scheduler instance at start() and not
|
|
195
|
+
// validated locally at create-time (start() requires it on its own).
|
|
196
|
+
{ allow: ["scheduler"] });
|
|
181
197
|
|
|
182
198
|
// lookbackHours — default 24 per PCI DSS 4.0 daily cadence. Caller can
|
|
183
199
|
// pass weekly / monthly via larger numbers.
|
|
@@ -237,6 +253,10 @@ function create(opts) {
|
|
|
237
253
|
from: fromMs,
|
|
238
254
|
to: startedAt,
|
|
239
255
|
limit: queryLimit,
|
|
256
|
+
// Newest-first: if the window holds more than queryLimit events, keep
|
|
257
|
+
// the MOST RECENT (the actionable ones) — an ascending+limit query would
|
|
258
|
+
// keep the oldest and silently drop the newest from the review.
|
|
259
|
+
order: "desc",
|
|
240
260
|
});
|
|
241
261
|
} catch (e) {
|
|
242
262
|
_emit("audit.daily_review.failed", {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// audit-emit — the stateless drop-silent audit emitter with a metadata-first,
|
|
4
|
+
// success-default signature `(action, metadata, outcome?)`. Many primitives —
|
|
5
|
+
// the mail servers and DAV bridge, the A2A task store, the compliance posture
|
|
6
|
+
// tracker, the MCP tool registry, the idempotency-key middleware — emit audit
|
|
7
|
+
// events where the per-event detail (metadata) is almost always supplied and
|
|
8
|
+
// the outcome is almost always "this happened", so they pass metadata second
|
|
9
|
+
// and let outcome default to "success". They route through the drop-silent
|
|
10
|
+
// audit.safeEmit so a misbehaving sink never crashes the request. Unlike
|
|
11
|
+
// b.audit.namespaced (gated, action-prefixed, outcome-first), this is ungated
|
|
12
|
+
// and passes the action verbatim — matching what each of them hand-rolled.
|
|
13
|
+
//
|
|
14
|
+
// This module self-lazy-requires audit so a consumer can `require("./audit-emit")`
|
|
15
|
+
// at the top of the file without re-introducing the audit load cycle that the
|
|
16
|
+
// per-file `lazyRequire(() => require("./audit"))` was guarding against.
|
|
17
|
+
|
|
18
|
+
var lazyRequire = require("./lazy-require");
|
|
19
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
20
|
+
|
|
21
|
+
// emit(action, metadata, outcome?) — drop-silent audit emit. `outcome` defaults
|
|
22
|
+
// to "success"; `metadata` defaults to `{}`.
|
|
23
|
+
function emit(action, metadata, outcome) {
|
|
24
|
+
try {
|
|
25
|
+
audit().safeEmit({
|
|
26
|
+
action: action,
|
|
27
|
+
outcome: outcome || "success",
|
|
28
|
+
metadata: metadata || {},
|
|
29
|
+
});
|
|
30
|
+
} catch (_e) { /* drop-silent — audit best-effort */ }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// emitToSink(opts, action, outcome, metadata) — drop-silent audit emit to an
|
|
34
|
+
// OPERATOR-SUPPLIED sink threaded through `opts.audit`, the no-op-when-absent
|
|
35
|
+
// variant the archive reader / tar-reader / writer share verbatim: forward the
|
|
36
|
+
// event only when `opts.audit` exposes a `safeEmit`, otherwise stay silent.
|
|
37
|
+
// Distinct from emit() (which targets the framework's GLOBAL audit()) — here the
|
|
38
|
+
// caller carries the sink in opts, so a reader/writer with no audit configured
|
|
39
|
+
// emits nothing. (Callers whose payload carries extra top-level fields — e.g.
|
|
40
|
+
// http-client's `resource` — keep their own wrapper; this is the bare
|
|
41
|
+
// action/outcome/metadata shape.)
|
|
42
|
+
function emitToSink(opts, action, outcome, metadata) {
|
|
43
|
+
if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
|
|
44
|
+
try {
|
|
45
|
+
opts.audit.safeEmit({ action: action, outcome: outcome, metadata: metadata });
|
|
46
|
+
} catch (_e) { /* drop-silent — operator audit sink must never crash the caller */ }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// gatedReasonEmitter({ audit, sink?, extra? }) — build a gated drop-silent
|
|
50
|
+
// emitter `(action, info, outcome)` that records a structured top-level `reason`
|
|
51
|
+
// (hoisted from `info.reason`) alongside the audit metadata. The gated +
|
|
52
|
+
// reason-hoisting sibling of emit() / b.audit.namespaced, for the primitives
|
|
53
|
+
// (backup / restore / scheduler / config-drift / legal-hold) that emit a reason
|
|
54
|
+
// the audit chain surfaces as a first-class field. `audit` is the gate flag
|
|
55
|
+
// (false disables); `sink` is an optional operator-supplied audit target (the
|
|
56
|
+
// no-op-without-safeEmit guard the hand-rolled `auditInstance || audit()`
|
|
57
|
+
// pattern carried); `extra(info)` optionally returns more top-level event
|
|
58
|
+
// fields (e.g. legal-hold's `resource`). Routes through b.audit.namespaced, so
|
|
59
|
+
// the same redaction + drop-silent guarantees apply.
|
|
60
|
+
function gatedReasonEmitter(opts) {
|
|
61
|
+
opts = opts || {};
|
|
62
|
+
var ns = audit().namespaced(null, { audit: opts.audit, sink: opts.sink });
|
|
63
|
+
var extra = typeof opts.extra === "function" ? opts.extra : null;
|
|
64
|
+
return function (action, info, outcome) {
|
|
65
|
+
var fields = { reason: (info && info.reason) || null };
|
|
66
|
+
if (extra) {
|
|
67
|
+
var more = extra(info);
|
|
68
|
+
if (more) {
|
|
69
|
+
for (var k in more) {
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(more, k)) fields[k] = more[k];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
ns(action, outcome, info, fields);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
emit: emit,
|
|
80
|
+
emitToSink: emitToSink,
|
|
81
|
+
gatedReasonEmitter: gatedReasonEmitter,
|
|
82
|
+
};
|