@blamejs/core 0.6.13 → 0.6.21

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/NOTICE +16 -0
  3. package/README.md +27 -18
  4. package/index.js +12 -0
  5. package/lib/archive.js +8 -7
  6. package/lib/audit.js +4 -0
  7. package/lib/auth/password.js +449 -4
  8. package/lib/bundler.js +8 -8
  9. package/lib/cache.js +105 -20
  10. package/lib/cli.js +598 -4
  11. package/lib/config-drift.js +309 -0
  12. package/lib/crypto-field.js +37 -0
  13. package/lib/crypto.js +8 -0
  14. package/lib/db-query.js +21 -2
  15. package/lib/db.js +32 -2
  16. package/lib/dual-control.js +475 -0
  17. package/lib/file-type.js +265 -0
  18. package/lib/framework-schema.js +38 -6
  19. package/lib/http-client-cookie-jar.js +117 -17
  20. package/lib/http-client.js +81 -3
  21. package/lib/internal-sha1-hibp.js +34 -0
  22. package/lib/mail.js +5 -4
  23. package/lib/middleware/csp-nonce.js +7 -4
  24. package/lib/middleware/index.js +2 -0
  25. package/lib/middleware/network-allowlist.js +199 -0
  26. package/lib/network-dns.js +564 -0
  27. package/lib/network-heartbeat.js +290 -0
  28. package/lib/network-nts.js +552 -0
  29. package/lib/network-proxy.js +246 -0
  30. package/lib/network-tls.js +326 -0
  31. package/lib/network.js +233 -0
  32. package/lib/ntp-check.js +50 -4
  33. package/lib/object-store/azure-blob.js +16 -42
  34. package/lib/pagination.js +136 -76
  35. package/lib/parsers/index.js +16 -2
  36. package/lib/parsers/safe-ini.js +273 -0
  37. package/lib/permissions.js +223 -9
  38. package/lib/pqc-agent.js +4 -4
  39. package/lib/retention.js +439 -0
  40. package/lib/security-assert.js +368 -0
  41. package/lib/session.js +138 -8
  42. package/lib/ssrf-guard.js +9 -0
  43. package/lib/vault/index.js +3 -3
  44. package/lib/vendor/MANIFEST.json +12 -0
  45. package/lib/vendor/common-passwords-top-10000.txt +10000 -0
  46. package/package.json +3 -2
  47. package/sbom.cyclonedx.json +61 -0
package/lib/cache.js CHANGED
@@ -74,11 +74,6 @@
74
74
  *
75
75
  * What is NOT in the box:
76
76
  *
77
- * - Tag invalidation on the cluster backend — invalidating tagged
78
- * entries across cluster nodes ties to a future distributed-pubsub
79
- * slice. invalidateTag against a cluster-backend cache throws
80
- * NOT_SUPPORTED today; operators wanting cluster-scope tag wipe
81
- * run their own DELETE against _blamejs_cache.
82
77
  * - maxBytes on the cluster backend — per-row size accounting against
83
78
  * a shared table would mean an aggregate query on every set. The
84
79
  * operator controls cluster-table size with their own pruning if
@@ -509,28 +504,98 @@ function _clusterBackend(cfg) {
509
504
  catch (_e) { return undefined; }
510
505
  }
511
506
 
512
- async function set(key, value, expiresAt) {
507
+ async function set(key, value, expiresAt, meta) {
513
508
  var json = JSON.stringify(value);
514
509
  var storedExpires = (expiresAt === Infinity) ? Number.MAX_SAFE_INTEGER : expiresAt;
515
510
  var now = clock();
511
+ var ck = _composedKey(key);
516
512
  // SQLite + Postgres both honor ON CONFLICT (cacheKey) DO UPDATE.
517
513
  await clusterStorage.execute(
518
514
  "INSERT INTO _blamejs_cache (cacheKey, valueJson, expiresAt, updatedAt) " +
519
515
  "VALUES (?, ?, ?, ?) " +
520
516
  "ON CONFLICT (cacheKey) DO UPDATE SET " +
521
517
  "valueJson = ?, expiresAt = ?, updatedAt = ?",
522
- [_composedKey(key), json, storedExpires, now, json, storedExpires, now]
518
+ [ck, json, storedExpires, now, json, storedExpires, now]
519
+ );
520
+ // Tag handling: drop any prior tags for this key (tags can change
521
+ // across sets), then INSERT the new ones. The PRIMARY KEY on
522
+ // (cacheKey, tag) makes the INSERT idempotent if duplicate tags
523
+ // sneak in.
524
+ var tags = meta && Array.isArray(meta.tags) ? meta.tags : null;
525
+ await clusterStorage.execute(
526
+ "DELETE FROM _blamejs_cache_tags WHERE cacheKey = ?",
527
+ [ck]
523
528
  );
529
+ if (tags && tags.length > 0) {
530
+ for (var i = 0; i < tags.length; i++) {
531
+ await clusterStorage.execute(
532
+ "INSERT INTO _blamejs_cache_tags (cacheKey, tag) VALUES (?, ?) " +
533
+ "ON CONFLICT (cacheKey, tag) DO NOTHING",
534
+ [ck, tags[i]]
535
+ );
536
+ }
537
+ }
524
538
  }
525
539
 
526
540
  async function del(key) {
541
+ var ck = _composedKey(key);
527
542
  var result = await clusterStorage.execute(
528
543
  "DELETE FROM _blamejs_cache WHERE cacheKey = ?",
529
- [_composedKey(key)]
544
+ [ck]
530
545
  );
546
+ // Drop any matching tag rows. Best-effort: a stale tag row pointing
547
+ // at a non-existent cacheKey is dropped on the next invalidateTag
548
+ // sweep (by the JOIN-shape DELETE) anyway.
549
+ await clusterStorage.execute(
550
+ "DELETE FROM _blamejs_cache_tags WHERE cacheKey = ?",
551
+ [ck]
552
+ ).catch(function () { /* best-effort */ });
531
553
  return !!(result && result.rowCount && result.rowCount > 0);
532
554
  }
533
555
 
556
+ async function invalidateTag(tag) {
557
+ // Find every cacheKey carrying the tag (namespace-scoped via the LIKE
558
+ // on the composed key), delete from the cache table + the junction.
559
+ var like = namespace + ":%";
560
+ var keysResult = await clusterStorage.execute(
561
+ "SELECT cacheKey FROM _blamejs_cache_tags WHERE tag = ? AND cacheKey LIKE ?",
562
+ [tag, like]
563
+ );
564
+ var keys = (keysResult && keysResult.rows) || [];
565
+ if (keys.length === 0) {
566
+ // Nothing to invalidate; still drop any orphan tag rows for
567
+ // this tag scoped to our namespace.
568
+ await clusterStorage.execute(
569
+ "DELETE FROM _blamejs_cache_tags WHERE tag = ? AND cacheKey LIKE ?",
570
+ [tag, like]
571
+ );
572
+ return 0;
573
+ }
574
+ var purged = 0;
575
+ for (var i = 0; i < keys.length; i++) {
576
+ var ck = keys[i].cacheKey;
577
+ var r = await clusterStorage.execute(
578
+ "DELETE FROM _blamejs_cache WHERE cacheKey = ?",
579
+ [ck]
580
+ );
581
+ if (r && r.rowCount > 0) purged += r.rowCount;
582
+ await clusterStorage.execute(
583
+ "DELETE FROM _blamejs_cache_tags WHERE cacheKey = ?",
584
+ [ck]
585
+ );
586
+ }
587
+ return purged;
588
+ }
589
+
590
+ async function getTags(key) {
591
+ var result = await clusterStorage.execute(
592
+ "SELECT tag FROM _blamejs_cache_tags WHERE cacheKey = ?",
593
+ [_composedKey(key)]
594
+ );
595
+ if (!result || !result.rows) return [];
596
+ return result.rows.map(function (r) { return r.tag; });
597
+ }
598
+
534
599
  async function has(key) {
535
600
  // Existence check without recency bump — cluster backend doesn't
536
601
  // track LRU at all, so "without bumping" is automatic. Honors
@@ -551,6 +616,11 @@ function _clusterBackend(cfg) {
551
616
  "DELETE FROM _blamejs_cache WHERE cacheKey LIKE ?",
552
617
  [like]
553
618
  );
619
+ // Drop matching tag rows in the same namespace.
620
+ await clusterStorage.execute(
621
+ "DELETE FROM _blamejs_cache_tags WHERE cacheKey LIKE ?",
622
+ [like]
623
+ ).catch(function () { /* best-effort */ });
554
624
  return (result && result.rowCount) || 0;
555
625
  }
556
626
 
@@ -568,10 +638,24 @@ function _clusterBackend(cfg) {
568
638
  async function _sweep() {
569
639
  var now = clock();
570
640
  var like = namespace + ":%";
641
+ // Capture the to-be-purged keys first so we can drop matching tag
642
+ // rows in the same sweep — keeps the junction table free of orphans
643
+ // pointing at expired cacheKeys.
644
+ var expiredResult = await clusterStorage.execute(
645
+ "SELECT cacheKey FROM _blamejs_cache WHERE cacheKey LIKE ? AND expiresAt <= ?",
646
+ [like, now]
647
+ );
648
+ var expiredKeys = (expiredResult && expiredResult.rows) || [];
571
649
  await clusterStorage.execute(
572
650
  "DELETE FROM _blamejs_cache WHERE cacheKey LIKE ? AND expiresAt <= ?",
573
651
  [like, now]
574
652
  );
653
+ for (var i = 0; i < expiredKeys.length; i++) {
654
+ await clusterStorage.execute(
655
+ "DELETE FROM _blamejs_cache_tags WHERE cacheKey = ?",
656
+ [expiredKeys[i].cacheKey]
657
+ ).catch(function () { /* best-effort */ });
658
+ }
575
659
  }
576
660
 
577
661
  function _startSweep(intervalMs) {
@@ -583,15 +667,17 @@ function _clusterBackend(cfg) {
583
667
  }
584
668
 
585
669
  return {
586
- name: "cluster",
587
- get: get,
588
- set: set,
589
- del: del,
590
- has: has,
591
- clear: clear,
592
- size: size,
593
- close: close,
594
- _startSweep: _startSweep,
670
+ name: "cluster",
671
+ get: get,
672
+ set: set,
673
+ del: del,
674
+ has: has,
675
+ clear: clear,
676
+ size: size,
677
+ close: close,
678
+ invalidateTag: invalidateTag,
679
+ getTags: getTags,
680
+ _startSweep: _startSweep,
595
681
  };
596
682
  }
597
683
 
@@ -865,9 +951,8 @@ function create(opts) {
865
951
  if (typeof backend.invalidateTag !== "function") {
866
952
  throw _err("NOT_SUPPORTED",
867
953
  "cache.invalidateTag: backend '" + (backend.name || "custom") +
868
- "' does not support tag invalidation. Tags are memory-backend only " +
869
- "in this release; the cluster backend gains tag support alongside " +
870
- "the distributed-invalidation slice.");
954
+ "' does not implement invalidateTag. Operator-supplied custom backends " +
955
+ "must export invalidateTag(tag) number to participate in tag-based wipes.");
871
956
  }
872
957
  var purged;
873
958
  try { purged = await backend.invalidateTag(tag); }