@blamejs/core 0.14.26 → 0.15.0
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 +6 -0
- package/README.md +2 -2
- package/index.js +4 -0
- package/lib/agent-envelope-mac.js +104 -0
- package/lib/agent-event-bus.js +105 -4
- package/lib/agent-posture-chain.js +8 -42
- package/lib/ai-content-detect.js +9 -10
- package/lib/api-key.js +107 -74
- package/lib/atomic-file.js +62 -4
- package/lib/audit-chain.js +47 -11
- package/lib/audit-sign.js +77 -2
- package/lib/audit-tools.js +79 -51
- package/lib/audit.js +249 -123
- package/lib/auth/openid-federation.js +108 -47
- package/lib/backup/index.js +13 -10
- package/lib/break-glass.js +202 -144
- package/lib/cache.js +174 -105
- package/lib/chain-writer.js +38 -16
- package/lib/cli.js +19 -14
- package/lib/cluster-provider-db.js +130 -104
- package/lib/cluster-storage.js +119 -22
- package/lib/cluster.js +119 -71
- package/lib/compliance.js +169 -4
- package/lib/consent.js +73 -24
- package/lib/constants.js +16 -11
- package/lib/crypto-field.js +474 -92
- package/lib/db-declare-row-policy.js +35 -22
- package/lib/db-file-lifecycle.js +3 -2
- package/lib/db-query.js +497 -255
- package/lib/db-schema.js +209 -44
- package/lib/db.js +176 -95
- package/lib/error-page.js +14 -1
- package/lib/external-db-migrate.js +229 -139
- package/lib/external-db.js +25 -15
- package/lib/file-upload.js +52 -7
- package/lib/framework-error.js +14 -1
- package/lib/framework-files.js +73 -0
- package/lib/framework-schema.js +695 -394
- package/lib/gate-contract.js +649 -1
- package/lib/guard-agent-registry.js +26 -44
- package/lib/guard-all.js +1 -0
- package/lib/guard-auth.js +42 -112
- package/lib/guard-cidr.js +33 -154
- package/lib/guard-csv.js +46 -113
- package/lib/guard-domain.js +34 -157
- package/lib/guard-dsn.js +27 -43
- package/lib/guard-email.js +47 -69
- package/lib/guard-envelope.js +19 -32
- package/lib/guard-event-bus-payload.js +24 -42
- package/lib/guard-event-bus-topic.js +25 -43
- package/lib/guard-filename.js +42 -106
- package/lib/guard-graphql.js +42 -123
- package/lib/guard-html.js +53 -108
- package/lib/guard-idempotency-key.js +24 -42
- package/lib/guard-image.js +46 -103
- package/lib/guard-imap-command.js +18 -32
- package/lib/guard-jmap.js +16 -30
- package/lib/guard-json.js +38 -108
- package/lib/guard-jsonpath.js +38 -171
- package/lib/guard-jwt.js +49 -179
- package/lib/guard-list-id.js +25 -41
- package/lib/guard-list-unsubscribe.js +27 -43
- package/lib/guard-mail-compose.js +24 -42
- package/lib/guard-mail-move.js +26 -44
- package/lib/guard-mail-query.js +28 -46
- package/lib/guard-mail-reply.js +24 -42
- package/lib/guard-mail-sieve.js +24 -42
- package/lib/guard-managesieve-command.js +17 -31
- package/lib/guard-markdown.js +37 -104
- package/lib/guard-message-id.js +26 -45
- package/lib/guard-mime.js +39 -151
- package/lib/guard-oauth.js +54 -135
- package/lib/guard-pdf.js +45 -101
- package/lib/guard-pop3-command.js +21 -31
- package/lib/guard-posture-chain.js +24 -42
- package/lib/guard-regex.js +33 -107
- package/lib/guard-saga-config.js +24 -42
- package/lib/guard-shell.js +42 -172
- package/lib/guard-smtp-command.js +48 -54
- package/lib/guard-snapshot-envelope.js +24 -42
- package/lib/guard-sql.js +1491 -0
- package/lib/guard-stream-args.js +24 -43
- package/lib/guard-svg.js +47 -65
- package/lib/guard-template.js +35 -172
- package/lib/guard-tenant-id.js +26 -45
- package/lib/guard-time.js +32 -154
- package/lib/guard-trace-context.js +25 -44
- package/lib/guard-uuid.js +32 -153
- package/lib/guard-xml.js +38 -113
- package/lib/guard-yaml.js +51 -163
- package/lib/http-client.js +37 -9
- package/lib/inbox.js +120 -107
- package/lib/legal-hold.js +107 -50
- package/lib/log-stream-cloudwatch.js +47 -31
- package/lib/log-stream-otlp.js +32 -18
- package/lib/mail-crypto-smime.js +2 -6
- package/lib/mail-greylist.js +2 -6
- package/lib/mail-helo.js +2 -6
- package/lib/mail-journal.js +85 -64
- package/lib/mail-rbl.js +2 -6
- package/lib/mail-scan.js +2 -6
- package/lib/mail-server-jmap.js +117 -12
- package/lib/mail-spam-score.js +2 -6
- package/lib/mail-store.js +287 -154
- package/lib/middleware/body-parser.js +71 -25
- package/lib/middleware/csrf-protect.js +19 -8
- package/lib/middleware/fetch-metadata.js +17 -7
- package/lib/middleware/idempotency-key.js +54 -38
- package/lib/middleware/rate-limit.js +102 -32
- package/lib/middleware/security-headers.js +21 -5
- package/lib/migrations.js +108 -66
- package/lib/network-heartbeat.js +7 -0
- package/lib/nonce-store.js +31 -9
- package/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/object-store/azure-blob.js +57 -3
- package/lib/object-store/sigv4.js +10 -0
- package/lib/observability.js +87 -0
- package/lib/otel-export.js +25 -1
- package/lib/outbox.js +136 -82
- package/lib/parsers/safe-xml.js +47 -7
- package/lib/pqc-agent.js +44 -0
- package/lib/pubsub-cluster.js +42 -20
- package/lib/queue-local.js +202 -139
- package/lib/queue-redis.js +9 -1
- package/lib/queue-sqs.js +6 -0
- package/lib/redact.js +68 -11
- package/lib/redis-client.js +160 -31
- package/lib/retention.js +82 -39
- package/lib/router.js +212 -5
- package/lib/safe-dns.js +29 -45
- package/lib/safe-ical.js +18 -33
- package/lib/safe-icap.js +27 -43
- package/lib/safe-sieve.js +21 -40
- package/lib/safe-sql.js +124 -3
- package/lib/safe-vcard.js +18 -33
- package/lib/scheduler.js +35 -12
- package/lib/seeders.js +122 -74
- package/lib/session-stores.js +42 -14
- package/lib/session.js +109 -72
- package/lib/sql.js +3885 -0
- package/lib/ssrf-guard.js +51 -4
- package/lib/static.js +177 -34
- package/lib/subject.js +55 -17
- package/lib/vault/index.js +3 -2
- package/lib/vault/passphrase-ops.js +3 -2
- package/lib/vault/rotate.js +104 -64
- package/lib/vendor-data.js +2 -0
- package/lib/websocket.js +35 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/vault/rotate.js
CHANGED
|
@@ -52,12 +52,13 @@ var nodeFs = require("node:fs");
|
|
|
52
52
|
var nodePath = require("node:path");
|
|
53
53
|
var { DatabaseSync } = require("node:sqlite");
|
|
54
54
|
var atomicFile = require("../atomic-file");
|
|
55
|
-
var
|
|
55
|
+
var sql = require("../sql");
|
|
56
56
|
var C = require("../constants");
|
|
57
57
|
var cryptoField = require("../crypto-field");
|
|
58
58
|
var bCrypto = require("../crypto");
|
|
59
59
|
var vaultAad = require("../vault-aad");
|
|
60
60
|
var dbSchema = require("../db-schema");
|
|
61
|
+
var frameworkFiles = require("../framework-files");
|
|
61
62
|
var lazyRequire = require("../lazy-require");
|
|
62
63
|
var { boot } = require("../log");
|
|
63
64
|
var numericBounds = require("../numeric-bounds");
|
|
@@ -97,18 +98,30 @@ var DEFAULT_DRIFT_SAMPLE_LIMIT = 100;
|
|
|
97
98
|
var DEFAULT_VERIFY_SAMPLE_MIN = 5;
|
|
98
99
|
var DEFAULT_VERIFY_SAMPLE_FRAC = 0.01;
|
|
99
100
|
|
|
101
|
+
// The catalog/PRAGMA statements all compose through b.sql's narrow audited
|
|
102
|
+
// catalog sub-API (b.sql.catalog / b.sql.pragma) - the only path that emits
|
|
103
|
+
// an sqlite_master reference or a PRAGMA verb, allowlisting exactly the
|
|
104
|
+
// statements the key-rotation walk needs and refusing every other internal
|
|
105
|
+
// identifier / PRAGMA verb. Each returns { sql, params }; the node:sqlite
|
|
106
|
+
// handle takes the params positionally.
|
|
107
|
+
function _all(db, built) {
|
|
108
|
+
var stmt = db.prepare(built.sql);
|
|
109
|
+
return built.params.length > 0 ? stmt.all.apply(stmt, built.params) : stmt.all();
|
|
110
|
+
}
|
|
111
|
+
function _get(db, built) {
|
|
112
|
+
var stmt = db.prepare(built.sql);
|
|
113
|
+
return built.params.length > 0 ? stmt.get.apply(stmt, built.params) : stmt.get();
|
|
114
|
+
}
|
|
115
|
+
|
|
100
116
|
function _listLiveTables(db) {
|
|
101
|
-
return db.
|
|
102
|
-
"SELECT name FROM sqlite_master " +
|
|
103
|
-
"WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
|
104
|
-
).all().map(function (r) { return r.name; });
|
|
117
|
+
return _all(db, sql.catalog.listTables()).map(function (r) { return r.name; });
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
function _listLiveColumns(db, table) {
|
|
108
121
|
// PRAGMA table_info — table name comes from sqlite_master so it's
|
|
109
|
-
// already validated as an existing identifier.
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
// already validated as an existing identifier; b.sql.catalog.tableInfo
|
|
123
|
+
// quotes it by construction.
|
|
124
|
+
return _all(db, sql.catalog.tableInfo(table)).map(function (c) { return c.name; });
|
|
112
125
|
}
|
|
113
126
|
|
|
114
127
|
function _knownColumnsFor(schema, infraColumns) {
|
|
@@ -201,12 +214,13 @@ function validateSchemaMatch(db, opts) {
|
|
|
201
214
|
}
|
|
202
215
|
if (unknown.length === 0) continue;
|
|
203
216
|
|
|
204
|
-
var
|
|
205
|
-
|
|
206
|
-
|
|
217
|
+
var sampleBuilt = sql.select(table, { dialect: "sqlite", quoteName: true })
|
|
218
|
+
.columns(unknown)
|
|
219
|
+
.limit(sampleLimit)
|
|
220
|
+
.toSql();
|
|
207
221
|
var sampled;
|
|
208
222
|
try {
|
|
209
|
-
sampled = db
|
|
223
|
+
sampled = _all(db, sampleBuilt);
|
|
210
224
|
} catch (e) {
|
|
211
225
|
warnings.push({
|
|
212
226
|
kind: "sample_failed",
|
|
@@ -312,7 +326,8 @@ function verify(opts) {
|
|
|
312
326
|
var schema = cryptoField.getSchema(table);
|
|
313
327
|
if (!schema || !Array.isArray(schema.sealedFields) || schema.sealedFields.length === 0) continue;
|
|
314
328
|
|
|
315
|
-
var totalRow = db.
|
|
329
|
+
var totalRow = _get(db, sql.select(table, { dialect: "sqlite", quoteName: true })
|
|
330
|
+
.count("*", "n").toSql());
|
|
316
331
|
var total = totalRow ? totalRow.n : 0;
|
|
317
332
|
if (total === 0) continue;
|
|
318
333
|
|
|
@@ -320,10 +335,10 @@ function verify(opts) {
|
|
|
320
335
|
if (sampleN > total) sampleN = total;
|
|
321
336
|
|
|
322
337
|
// RANDOM() is fine for a sampler — we're picking representative rows,
|
|
323
|
-
// not building cryptographic randomness.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
338
|
+
// not building cryptographic randomness. b.sql.catalog.sampleRandom is
|
|
339
|
+
// the audited ORDER BY RANDOM() form (the general builder has no random-
|
|
340
|
+
// order clause); columns omitted -> `*`.
|
|
341
|
+
var sampled = _all(db, sql.catalog.sampleRandom(table, null, { limit: sampleN }));
|
|
327
342
|
|
|
328
343
|
var foundOldFail = !oldKeys; // when no oldKeys supplied, this check is N/A
|
|
329
344
|
var verifiedRows = 0;
|
|
@@ -529,15 +544,19 @@ function _walkAndReSeal(node, oldKeys, newKeys) {
|
|
|
529
544
|
return { value: node, changed: false };
|
|
530
545
|
}
|
|
531
546
|
|
|
532
|
-
|
|
547
|
+
// Transaction-control statements only (BEGIN / COMMIT / ROLLBACK) - fixed
|
|
548
|
+
// keywords, no identifier / value, so they stay verbatim rather than route
|
|
549
|
+
// through b.sql (the builder has no transaction-control verb). The param is
|
|
550
|
+
// named `stmtText` so it does not shadow the module-level `sql` builder.
|
|
551
|
+
function _runStmt(db, stmtText) { db.prepare(stmtText).run(); }
|
|
533
552
|
|
|
534
553
|
function _rotateColumn(db, table, column, schema, roots, batchSize, progress) {
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
|
|
539
|
-
var
|
|
540
|
-
|
|
554
|
+
// Every statement composes through b.sql (sqlite dialect, quoteName so
|
|
555
|
+
// the concrete handle's table is quoted, not left bare for a cluster
|
|
556
|
+
// rewrite that does not apply here). Identifiers are validated + quoted
|
|
557
|
+
// by construction; the cursor bound (_id) + LIMIT bind as ? placeholders.
|
|
558
|
+
var total = _get(db, sql.select(table, { dialect: "sqlite", quoteName: true })
|
|
559
|
+
.count("*", "n").whereNotNull(column).toSql()).n;
|
|
541
560
|
if (total === 0) return 0;
|
|
542
561
|
|
|
543
562
|
// AAD-bound tables (registerTable({aad:true})) seal each cell under a
|
|
@@ -548,36 +567,54 @@ function _rotateColumn(db, table, column, schema, roots, batchSize, progress) {
|
|
|
548
567
|
var aadMode = !!(schema && schema.aad);
|
|
549
568
|
var rowIdField = aadMode ? schema.rowIdField : null;
|
|
550
569
|
var needRid = aadMode && rowIdField && rowIdField !== "_id";
|
|
551
|
-
var qrid = needRid ? safeSql.quoteIdentifier(rowIdField, "sqlite") : null;
|
|
552
570
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
571
|
+
// Keyset-cursor page over (_id) ascending. The projected columns are read
|
|
572
|
+
// by their REAL names off the result row (no AS alias) - the column value
|
|
573
|
+
// is row[column], the row-id value is row[rowIdField]. The SQL text is
|
|
574
|
+
// constant across the loop (only the bound _id-cursor changes; LIMIT is a
|
|
575
|
+
// builder-inlined integer literal, validated non-negative), so prepare
|
|
576
|
+
// once + re-run with the fresh cursor param positionally. The SELECT
|
|
577
|
+
// carries exactly one `?` (the _id cursor); the UPDATE carries two (the
|
|
578
|
+
// resealed value + the _id).
|
|
579
|
+
var selCols = ["_id", column];
|
|
580
|
+
if (needRid) selCols.push(rowIdField);
|
|
581
|
+
var selBuilt = sql.select(table, { dialect: "sqlite", quoteName: true })
|
|
582
|
+
.columns(selCols)
|
|
583
|
+
.whereNotNull(column)
|
|
584
|
+
.whereOp("_id", ">", "")
|
|
585
|
+
.orderBy("_id")
|
|
586
|
+
.limit(batchSize)
|
|
587
|
+
.toSql();
|
|
588
|
+
var sel = db.prepare(selBuilt.sql);
|
|
589
|
+
var updBuilt = sql.update(table, { dialect: "sqlite", quoteName: true })
|
|
590
|
+
.set(column, "")
|
|
591
|
+
.where("_id", "")
|
|
592
|
+
.toSql();
|
|
593
|
+
var upd = db.prepare(updBuilt.sql);
|
|
558
594
|
|
|
559
595
|
var processed = 0;
|
|
560
596
|
var lastId = "";
|
|
561
597
|
while (true) {
|
|
562
|
-
var rows = sel.all(lastId
|
|
598
|
+
var rows = sel.all(lastId);
|
|
563
599
|
if (rows.length === 0) break;
|
|
564
600
|
|
|
565
601
|
dbSchema.runInTransaction(db, function () {
|
|
566
602
|
for (var i = 0; i < rows.length; i++) {
|
|
567
603
|
var row = rows[i];
|
|
568
|
-
|
|
569
|
-
if (
|
|
604
|
+
var cellVal = row[column];
|
|
605
|
+
if (typeof cellVal !== "string") continue;
|
|
606
|
+
if (aadMode && vaultAad.isAadSealed(cellVal)) {
|
|
570
607
|
// Rebuild the exact AAD the seal side used. cryptoField._aadParts
|
|
571
608
|
// reads row[schema.rowIdField]; feed it the rowIdField value we
|
|
572
|
-
// selected (
|
|
609
|
+
// selected (row[rowIdField], or _id when rowIdField IS _id).
|
|
573
610
|
var rowForAad = {};
|
|
574
|
-
rowForAad[rowIdField] = needRid ? row
|
|
611
|
+
rowForAad[rowIdField] = needRid ? row[rowIdField] : row._id;
|
|
575
612
|
var aad = cryptoField._aadParts(schema, table, column, rowForAad);
|
|
576
|
-
upd.run(vaultAad.resealRoot(
|
|
577
|
-
} else if (
|
|
613
|
+
upd.run(vaultAad.resealRoot(cellVal, aad, roots.oldRootJson, roots.newRootJson), row._id);
|
|
614
|
+
} else if (cellVal.indexOf(C.VAULT_PREFIX) === 0) {
|
|
578
615
|
// Plain vault: cell (non-AAD table, or a legacy pre-AAD cell in
|
|
579
616
|
// an AAD table that the next sealRow upgrades).
|
|
580
|
-
upd.run(_reSealValue(
|
|
617
|
+
upd.run(_reSealValue(cellVal, roots.oldKeys, roots.newKeys), row._id);
|
|
581
618
|
}
|
|
582
619
|
}
|
|
583
620
|
});
|
|
@@ -589,23 +626,33 @@ function _rotateColumn(db, table, column, schema, roots, batchSize, progress) {
|
|
|
589
626
|
}
|
|
590
627
|
|
|
591
628
|
function _rotateOverflow(db, table, oldKeys, newKeys, batchSize, progress, warnings) {
|
|
592
|
-
var
|
|
593
|
-
var cols = db.prepare("PRAGMA table_info(" + qt + ")").all();
|
|
629
|
+
var cols = _all(db, sql.catalog.tableInfo(table));
|
|
594
630
|
if (!cols.some(function (c) { return c.name === "data"; })) return 0;
|
|
595
631
|
|
|
596
|
-
var total = db.
|
|
632
|
+
var total = _get(db, sql.select(table, { dialect: "sqlite", quoteName: true })
|
|
633
|
+
.count("*", "n").whereNotNull("data").toSql()).n;
|
|
597
634
|
if (total === 0) return 0;
|
|
598
635
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
636
|
+
// Same keyset cursor as _rotateColumn over the overflow `data` JSON
|
|
637
|
+
// column: one bound `?` (the _id cursor), builder-inlined LIMIT literal.
|
|
638
|
+
var selBuilt = sql.select(table, { dialect: "sqlite", quoteName: true })
|
|
639
|
+
.columns(["_id", "data"])
|
|
640
|
+
.whereNotNull("data")
|
|
641
|
+
.whereOp("_id", ">", "")
|
|
642
|
+
.orderBy("_id")
|
|
643
|
+
.limit(batchSize)
|
|
644
|
+
.toSql();
|
|
645
|
+
var sel = db.prepare(selBuilt.sql);
|
|
646
|
+
var updBuilt = sql.update(table, { dialect: "sqlite", quoteName: true })
|
|
647
|
+
.set("data", "")
|
|
648
|
+
.where("_id", "")
|
|
649
|
+
.toSql();
|
|
650
|
+
var upd = db.prepare(updBuilt.sql);
|
|
604
651
|
|
|
605
652
|
var processed = 0;
|
|
606
653
|
var lastId = "";
|
|
607
654
|
while (true) {
|
|
608
|
-
var rows = sel.all(lastId
|
|
655
|
+
var rows = sel.all(lastId);
|
|
609
656
|
if (rows.length === 0) break;
|
|
610
657
|
|
|
611
658
|
_runStmt(db, "BEGIN");
|
|
@@ -689,10 +736,10 @@ async function rotate(opts) {
|
|
|
689
736
|
var progress = opts.progressCallback;
|
|
690
737
|
var warnings = [];
|
|
691
738
|
var paths = Object.assign({
|
|
692
|
-
encryptedDb:
|
|
693
|
-
dbKeySealed:
|
|
694
|
-
vaultKeyPlain:
|
|
695
|
-
vaultKeySealed: "
|
|
739
|
+
encryptedDb: frameworkFiles.fileName("dbEnc"),
|
|
740
|
+
dbKeySealed: frameworkFiles.fileName("dbKeyEnc"),
|
|
741
|
+
vaultKeyPlain: frameworkFiles.fileName("vaultKey"),
|
|
742
|
+
vaultKeySealed: frameworkFiles.fileName("vaultKey") + ".sealed",
|
|
696
743
|
additionalSealed: [],
|
|
697
744
|
verbatimFiles: [],
|
|
698
745
|
verbatimDirs: [],
|
|
@@ -849,18 +896,15 @@ async function rotate(opts) {
|
|
|
849
896
|
|
|
850
897
|
var db = new DatabaseSync(tmpDbPath);
|
|
851
898
|
try {
|
|
852
|
-
|
|
853
|
-
|
|
899
|
+
db.prepare(sql.pragma("journal_mode", "WAL").sql).run();
|
|
900
|
+
db.prepare(sql.pragma("synchronous", "NORMAL").sql).run();
|
|
854
901
|
|
|
855
902
|
// Walk tables. For each, re-seal every column declared sealed
|
|
856
903
|
// by the field-crypto registry, plus the overflow `data` JSON
|
|
857
904
|
// column if present.
|
|
858
905
|
var tablesToRotate = Array.isArray(opts.tables) && opts.tables.length > 0
|
|
859
906
|
? opts.tables.slice()
|
|
860
|
-
: db
|
|
861
|
-
"SELECT name FROM sqlite_master " +
|
|
862
|
-
"WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
|
863
|
-
).all().map(function (r) { return r.name; });
|
|
907
|
+
: _listLiveTables(db);
|
|
864
908
|
|
|
865
909
|
// Serialized roots threaded to the AAD reseal path; oldRootJson /
|
|
866
910
|
// newRootJson match b.vault.getKeysJson() so rotated AAD cells unseal
|
|
@@ -869,15 +913,11 @@ async function rotate(opts) {
|
|
|
869
913
|
|
|
870
914
|
for (var ti = 0; ti < tablesToRotate.length; ti++) {
|
|
871
915
|
var table = tablesToRotate[ti];
|
|
872
|
-
var tableExists = db.
|
|
873
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?"
|
|
874
|
-
).get(table);
|
|
916
|
+
var tableExists = _get(db, sql.catalog.tableExists(table));
|
|
875
917
|
if (!tableExists) continue;
|
|
876
918
|
|
|
877
919
|
var schema = cryptoField.getSchema(table);
|
|
878
|
-
var liveCols = db
|
|
879
|
-
'PRAGMA table_info("' + table.replace(/"/g, '""') + '")'
|
|
880
|
-
).all().map(function (c) { return c.name; });
|
|
920
|
+
var liveCols = _listLiveColumns(db, table);
|
|
881
921
|
var liveColSet = Object.create(null);
|
|
882
922
|
for (var lc = 0; lc < liveCols.length; lc++) liveColSet[liveCols[lc]] = true;
|
|
883
923
|
|
|
@@ -894,7 +934,7 @@ async function rotate(opts) {
|
|
|
894
934
|
if (tableRows > 0) { tablesProcessed++; totalRowsProcessed += tableRows; }
|
|
895
935
|
}
|
|
896
936
|
|
|
897
|
-
|
|
937
|
+
db.prepare(sql.pragma("wal_checkpoint", "TRUNCATE").sql).run();
|
|
898
938
|
} finally {
|
|
899
939
|
db.close();
|
|
900
940
|
}
|
package/lib/vendor-data.js
CHANGED
|
@@ -130,6 +130,7 @@ function _timingSafeHexEqual(a, b) {
|
|
|
130
130
|
var KNOWN_VENDOR_DATA = Object.freeze({
|
|
131
131
|
"public-suffix-list": {
|
|
132
132
|
module: "./vendor/public-suffix-list.data",
|
|
133
|
+
// allow:hand-rolled-sql — `_blamejs_canary_*` is an in-payload tamper-canary token, not a SQL table name (no DB sink in this file)
|
|
133
134
|
canary: "_blamejs_canary_v0_9_8_.local",
|
|
134
135
|
// Canary parse check — operator-side `b.publicSuffix.isPublicSuffix(canary)`
|
|
135
136
|
// MUST return true after the PSL parser ingests the data. The check
|
|
@@ -138,6 +139,7 @@ var KNOWN_VENDOR_DATA = Object.freeze({
|
|
|
138
139
|
},
|
|
139
140
|
"common-passwords-top-10000": {
|
|
140
141
|
module: "./vendor/common-passwords-top-10000.data",
|
|
142
|
+
// allow:hand-rolled-sql — `_blamejs_canary_*` is an in-payload tamper-canary token, not a SQL table name (no DB sink in this file)
|
|
141
143
|
canary: "_blamejs_canary_password_2026_05_13_blamejs_internal_",
|
|
142
144
|
description: "Top-10000 most common passwords (SecLists). Used by b.auth.password to refuse known-breached credentials.",
|
|
143
145
|
},
|
package/lib/websocket.js
CHANGED
|
@@ -530,22 +530,32 @@ function _parseExtensionHeader(header) {
|
|
|
530
530
|
for (var i = 0; i < entries.length; i++) {
|
|
531
531
|
var parts = structuredFields.splitTopLevel(entries[i], ";").map(function (s) { return s.trim(); });
|
|
532
532
|
if (!parts[0]) continue;
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
|
|
533
|
+
// Collect [name, value] pairs, then materialize the params map via
|
|
534
|
+
// Object.fromEntries onto a null-prototype object. The extension-
|
|
535
|
+
// parameter name is taken from the client-supplied Sec-WebSocket-
|
|
536
|
+
// Extensions header, so it is never used as a computed-write key
|
|
537
|
+
// (`params[name] = value`) — that is the CWE-915 unsafe-reflection /
|
|
538
|
+
// CWE-1321 prototype-pollution sink. POISONED params (`__proto__` /
|
|
539
|
+
// `constructor` / `prototype`) are dropped, and the null-prototype
|
|
540
|
+
// accumulator means even a slipped name cannot reach Object.prototype.
|
|
541
|
+
var paramPairs = [];
|
|
537
542
|
for (var j = 1; j < parts.length; j++) {
|
|
538
543
|
var kv = parts[j].split("=");
|
|
539
544
|
var k = kv[0].trim().toLowerCase();
|
|
540
545
|
if (!k) continue;
|
|
546
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
|
|
541
547
|
var v = kv.length > 1 ? kv.slice(1).join("=").trim() : true;
|
|
542
548
|
// Strip surrounding quotes per the token-or-quoted-string grammar.
|
|
543
549
|
if (typeof v === "string") {
|
|
544
550
|
var _unq = structuredFields.unquoteSfString(v);
|
|
545
551
|
if (_unq !== null) v = _unq;
|
|
546
552
|
}
|
|
547
|
-
|
|
553
|
+
paramPairs.push([k, v]);
|
|
548
554
|
}
|
|
555
|
+
var ext = {
|
|
556
|
+
name: parts[0].toLowerCase(),
|
|
557
|
+
params: Object.assign(Object.create(null), Object.fromEntries(paramPairs)),
|
|
558
|
+
};
|
|
549
559
|
out.push(ext);
|
|
550
560
|
}
|
|
551
561
|
return out;
|
|
@@ -961,6 +971,22 @@ class WebSocketConnection extends EventEmitter {
|
|
|
961
971
|
self._transitionToClosed(1006, "abnormal closure", false, null);
|
|
962
972
|
}
|
|
963
973
|
});
|
|
974
|
+
socket.on("end", function () {
|
|
975
|
+
// Peer half-closed (TCP FIN) without sending a Close frame. HTTP
|
|
976
|
+
// 'upgrade' sockets default to allowHalfOpen=true, so this arrives
|
|
977
|
+
// as 'end' (readable side ended) while the writable side stays
|
|
978
|
+
// open — the 'close' handler above never fires and the connection
|
|
979
|
+
// would otherwise wedge open (ping timer running, no 'close' event,
|
|
980
|
+
// peer's socket never destroyed). RFC 6455 §7.1.1 treats a TCP
|
|
981
|
+
// close without a prior Close frame as abnormal closure: surface
|
|
982
|
+
// the lifecycle event and end our writable side so the socket
|
|
983
|
+
// actually tears down. _transitionToClosed is idempotent, so the
|
|
984
|
+
// native 'close' that follows is a no-op.
|
|
985
|
+
if (self._state !== STATE_CLOSED) {
|
|
986
|
+
self._transitionToClosed(1006, "abnormal closure", false, null);
|
|
987
|
+
}
|
|
988
|
+
try { socket.end(); } catch (_e) { /* socket already closing */ }
|
|
989
|
+
});
|
|
964
990
|
}
|
|
965
991
|
|
|
966
992
|
// Single state-transition method. Idempotent — repeat calls after
|
|
@@ -1541,6 +1567,10 @@ module.exports = {
|
|
|
1541
1567
|
// Server-side entrypoints
|
|
1542
1568
|
handleUpgrade: handleUpgrade, // h1 — RFC 6455 HTTP upgrade
|
|
1543
1569
|
handleExtendedConnect: handleExtendedConnect, // h2 — RFC 8441 Extended CONNECT
|
|
1570
|
+
// Internal helper exposed for tests — the Sec-WebSocket-Extensions
|
|
1571
|
+
// parser (RFC 7692 negotiation feeds off this). Underscore-prefixed so
|
|
1572
|
+
// it is not part of the public primitive surface.
|
|
1573
|
+
_parseExtensionHeader: _parseExtensionHeader,
|
|
1544
1574
|
// Constants
|
|
1545
1575
|
GUID: GUID,
|
|
1546
1576
|
REFUSED_AUTH_QUERY_PARAMS: REFUSED_AUTH_QUERY_PARAMS,
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:d63a172d-d92b-4a98-ae64-7dcb04e82076",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-06-
|
|
8
|
+
"timestamp": "2026-06-08T20:30:45.961Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.15.0",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.
|
|
25
|
+
"version": "0.15.0",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.15.0",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.
|
|
57
|
+
"ref": "@blamejs/core@0.15.0",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|