@blamejs/core 0.14.6 → 0.14.8

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 (110) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +3 -2
  3. package/lib/a2a-tasks.js +6 -6
  4. package/lib/agent-event-bus.js +4 -4
  5. package/lib/agent-idempotency.js +6 -6
  6. package/lib/agent-orchestrator.js +9 -9
  7. package/lib/agent-posture-chain.js +10 -10
  8. package/lib/agent-saga.js +6 -7
  9. package/lib/agent-snapshot.js +8 -8
  10. package/lib/agent-stream.js +3 -3
  11. package/lib/agent-tenant.js +4 -4
  12. package/lib/agent-trace.js +5 -5
  13. package/lib/ai-disclosure.js +3 -3
  14. package/lib/ai-input.js +1 -1
  15. package/lib/app.js +2 -2
  16. package/lib/archive-read.js +1 -1
  17. package/lib/archive-tar-read.js +1 -1
  18. package/lib/archive-wrap.js +5 -5
  19. package/lib/audit-tools.js +65 -5
  20. package/lib/audit.js +2 -2
  21. package/lib/auth/acr-vocabulary.js +1 -1
  22. package/lib/auth/ciba.js +4 -4
  23. package/lib/auth/dpop.js +1 -1
  24. package/lib/auth/fal.js +1 -1
  25. package/lib/auth/fido-mds3.js +2 -3
  26. package/lib/auth/jwt-external.js +2 -2
  27. package/lib/auth/oauth.js +10 -10
  28. package/lib/auth/oid4vci.js +8 -8
  29. package/lib/auth/oid4vp.js +1 -1
  30. package/lib/auth/openid-federation.js +6 -6
  31. package/lib/auth/passkey.js +6 -6
  32. package/lib/auth/saml.js +1 -1
  33. package/lib/auth/sd-jwt-vc.js +3 -6
  34. package/lib/backup/index.js +18 -18
  35. package/lib/breach-deadline.js +3 -3
  36. package/lib/cache.js +4 -4
  37. package/lib/calendar.js +7 -7
  38. package/lib/circuit-breaker.js +1 -1
  39. package/lib/cms-codec.js +2 -2
  40. package/lib/compliance.js +14 -14
  41. package/lib/content-credentials.js +3 -3
  42. package/lib/crypto-field.js +58 -21
  43. package/lib/crypto.js +5 -6
  44. package/lib/db-query.js +131 -9
  45. package/lib/db.js +106 -22
  46. package/lib/ddl-change-control.js +2 -2
  47. package/lib/did.js +2 -2
  48. package/lib/dsr.js +4 -4
  49. package/lib/external-db.js +65 -17
  50. package/lib/framework-schema.js +4 -4
  51. package/lib/guard-cidr.js +1 -1
  52. package/lib/guard-image.js +1 -1
  53. package/lib/guard-list-id.js +2 -2
  54. package/lib/guard-list-unsubscribe.js +2 -3
  55. package/lib/guard-time.js +1 -1
  56. package/lib/guard-xml.js +1 -1
  57. package/lib/http-client-cache.js +1 -1
  58. package/lib/iab-tcf.js +4 -4
  59. package/lib/incident-report.js +150 -0
  60. package/lib/json-schema.js +1 -1
  61. package/lib/jtd.js +1 -1
  62. package/lib/mail-auth.js +1 -1
  63. package/lib/mail-bimi.js +1 -1
  64. package/lib/mail-crypto-smime.js +2 -2
  65. package/lib/mail-deploy.js +3 -3
  66. package/lib/mail-server-managesieve.js +2 -2
  67. package/lib/mail-server-mx.js +1 -1
  68. package/lib/mail-server-pop3.js +2 -2
  69. package/lib/mail-server-rate-limit.js +1 -1
  70. package/lib/mail-server-submission.js +1 -1
  71. package/lib/mail-store.js +1 -1
  72. package/lib/mcp.js +7 -7
  73. package/lib/mdoc.js +1 -1
  74. package/lib/metrics.js +10 -10
  75. package/lib/middleware/compose-pipeline.js +1 -1
  76. package/lib/middleware/csrf-protect.js +1 -1
  77. package/lib/middleware/dpop.js +5 -5
  78. package/lib/middleware/idempotency-key.js +21 -22
  79. package/lib/middleware/protected-resource-metadata.js +2 -2
  80. package/lib/network-dns-resolver.js +2 -2
  81. package/lib/network-dns.js +1 -2
  82. package/lib/network-dnssec.js +2 -2
  83. package/lib/network-smtp-policy.js +1 -1
  84. package/lib/network-tls.js +1 -2
  85. package/lib/network-tsig.js +3 -3
  86. package/lib/outbox.js +1 -1
  87. package/lib/pqc-agent.js +1 -1
  88. package/lib/retention.js +1 -1
  89. package/lib/retry.js +1 -1
  90. package/lib/rfc3339.js +2 -2
  91. package/lib/safe-archive.js +2 -2
  92. package/lib/safe-decompress.js +1 -1
  93. package/lib/safe-ical.js +2 -2
  94. package/lib/safe-mime.js +1 -1
  95. package/lib/self-update-standalone-verifier.js +1 -1
  96. package/lib/self-update.js +2 -2
  97. package/lib/standard-webhooks.js +3 -3
  98. package/lib/static.js +1 -1
  99. package/lib/stream-throttle.js +2 -2
  100. package/lib/structured-fields.js +1 -1
  101. package/lib/subject.js +2 -2
  102. package/lib/vault/index.js +64 -1
  103. package/lib/vault/rotate.js +19 -0
  104. package/lib/vault/seal-pem-file.js +1 -1
  105. package/lib/vendor-data.js +1 -1
  106. package/lib/web-push-vapid.js +1 -1
  107. package/lib/webhook.js +1 -1
  108. package/lib/websocket.js +1 -1
  109. package/package.json +1 -1
  110. package/sbom.cdx.json +6 -6
@@ -57,7 +57,7 @@ function _emitMetric(name, value, labels) {
57
57
  catch (_e) { /* hot-path observability sink — drop silent by design */ }
58
58
  }
59
59
 
60
- // Statement-class classifier for auth-failure forensics (D-M2). Inspects
60
+ // Statement-class classifier for auth-failure forensics. Inspects
61
61
  // the leading keyword only so an attacker-controlled trailing fragment
62
62
  // can't smuggle a false classification. Skips leading whitespace plus
63
63
  // SQL line / block comments before reading the keyword.
@@ -83,8 +83,49 @@ function _classifyStatement(sql) {
83
83
  return _STATEMENT_CLASS_MAP[m[1].toUpperCase()] || "OTHER";
84
84
  }
85
85
 
86
+ // Best-effort target-relation extractor for auth-failure forensics: the
87
+ // table the denied role attempted to touch, so the audit row records
88
+ // the OBJECT (SOC2 CC7.2 / NIST SP 800-53 AU-3 "what was accessed"),
89
+ // not just the statement class. Defensive reader — returns null on
90
+ // anything unparseable and NEVER throws: it runs in the live failure
91
+ // path and must not mask the real 28000 / 42501 error. The extracted
92
+ // identifier is captured into audit METADATA (a JSON string, never
93
+ // re-executed as SQL), so the only sink risk is log-injection: any
94
+ // segment carrying a control / NUL character is refused. Spaces and
95
+ // ordinary punctuation inside a quoted identifier are kept so a
96
+ // legitimately-quoted relation name still surfaces in the audit row.
97
+ var _RELATION_RE = /\b(?:FROM|INTO|UPDATE|JOIN|TABLE|COPY)\s+((?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*))?)/ig;
98
+ function _hasControlChar(s) {
99
+ for (var i = 0; i < s.length; i += 1) {
100
+ var c = s.charCodeAt(i);
101
+ if (c < 0x20 || c === 0x7f) return true;
102
+ }
103
+ return false;
104
+ }
105
+ function _extractTargetRelation(sql) {
106
+ if (typeof sql !== "string" || sql.length === 0) return null;
107
+ var clean = sql.replace(/--[^\n]*/g, " ").replace(/\/\*[\s\S]*?\*\//g, " ");
108
+ _RELATION_RE.lastIndex = 0;
109
+ var m = _RELATION_RE.exec(clean);
110
+ if (!m) return null;
111
+ var segs = m[1].split(".").map(function (s) { return s.replace(/^["`]|["`]$/g, ""); });
112
+ for (var i = 0; i < segs.length; i += 1) {
113
+ if (segs[i].length === 0 || _hasControlChar(segs[i])) return null;
114
+ }
115
+ return segs.join(".");
116
+ }
117
+
118
+ function _countTargetRelations(sql) {
119
+ if (typeof sql !== "string") return 0;
120
+ var clean = sql.replace(/--[^\n]*/g, " ").replace(/\/\*[\s\S]*?\*\//g, " ");
121
+ _RELATION_RE.lastIndex = 0;
122
+ var n = 0;
123
+ while (_RELATION_RE.exec(clean) !== null) n += 1;
124
+ return n;
125
+ }
126
+
86
127
  // Postgres SQLSTATE classes that indicate authentication / authorization
87
- // failure at the DB level. SOC2 forensic gap (D-M2) — every match emits
128
+ // failure at the DB level. SOC2 forensic gap — every match emits
88
129
  // db.auth.failed with the SQL identity attempted, the database, and
89
130
  // the statement class.
90
131
  var _AUTH_FAILURE_CODES = Object.freeze({
@@ -97,18 +138,24 @@ function _emitAuthFailureAudit(backend, role, sql, e) {
97
138
  if (!e || !e.code) return;
98
139
  var kind = _AUTH_FAILURE_CODES[e.code];
99
140
  if (!kind) return;
141
+ var attemptedTable = _extractTargetRelation(sql);
142
+ var relationCount = _countTargetRelations(sql);
143
+ var resource = { kind: "db.backend", id: backend.name };
144
+ if (attemptedTable !== null) resource.attemptedTable = attemptedTable;
100
145
  audit().safeEmit({
101
146
  action: "db.auth.failed",
102
147
  actor: {},
103
- resource: { kind: "db.backend", id: backend.name },
148
+ resource: resource,
104
149
  outcome: "denied",
105
150
  reason: kind,
106
151
  metadata: {
107
- backend: backend.name,
108
- dialect: backend.dialect,
109
- sqlIdentity: role || null,
110
- sqlstate: e.code,
111
- statementClass: _classifyStatement(sql),
152
+ backend: backend.name,
153
+ dialect: backend.dialect,
154
+ sqlIdentity: role || null,
155
+ sqlstate: e.code,
156
+ statementClass: _classifyStatement(sql),
157
+ attemptedTable: attemptedTable,
158
+ attemptedRelationCount: relationCount,
112
159
  },
113
160
  });
114
161
  _emitMetric("db.auth.failed", 1, {
@@ -118,7 +165,7 @@ function _emitAuthFailureAudit(backend, role, sql, e) {
118
165
  });
119
166
  }
120
167
 
121
- // Slow-query bucket emitter (D-L7). Single-shot per query — highest
168
+ // Slow-query bucket emitter. Single-shot per query — highest
122
169
  // matched bucket wins. Operators dashboard on the `bucket` label
123
170
  // rather than separate counters per threshold.
124
171
  var _SLOW_QUERY_BUCKETS = Object.freeze([
@@ -628,7 +675,7 @@ async function query(sql, params, opts) {
628
675
  _emitMetric("db.role.denied", 1,
629
676
  { backend: b.name, role: role || "(none)" });
630
677
  }
631
- // D-M2 — DB-auth audit visibility. Every 28000 / 28P01 / 42501
678
+ // DB-auth audit visibility. Every 28000 / 28P01 / 42501
632
679
  // surfaces an auditable db.auth.failed row tagged with the SQL
633
680
  // identity and the statement class so SOC2 reviewers can
634
681
  // reconstruct the denial timeline.
@@ -693,13 +740,13 @@ async function transaction(fn, opts) {
693
740
  var prebuiltGucs = _buildSessionGucsStatements(opts.sessionGucs);
694
741
 
695
742
  var t0 = Date.now();
696
- // D-H4 — per-statement timeout. SET LOCAL statement_timeout binds
697
- // the query-cancel ceiling to this transaction; D-M7 wires
743
+ // Per-statement timeout. SET LOCAL statement_timeout binds
744
+ // the query-cancel ceiling to this transaction; this wires
698
745
  // idle_in_transaction_session_timeout from the same opt. Both
699
746
  // emit at SET LOCAL scope so the next pool checkout starts clean.
700
747
  var stmtTimeoutMs = opts.statementTimeoutMs;
701
748
  var idleTimeoutMs = opts.idleInTransactionTimeoutMs;
702
- // D-M8 — deadlock-retry policy. 40P01 (deadlock_detected) and 40001
749
+ // Deadlock-retry policy. 40P01 (deadlock_detected) and 40001
703
750
  // (serialization_failure) are transient — retry with capped attempts
704
751
  // and a small jittered backoff. Operators tune retries via opts.deadlockRetries (default 3).
705
752
  // numeric-bounds doesn't have a non-negative-int helper; use a
@@ -756,7 +803,7 @@ async function transaction(fn, opts) {
756
803
  _emitMetric("externaldb.transaction.retry", 1,
757
804
  { backend: b.name, code: txErr.code, attempt: String(attempt) });
758
805
  var jitter = bCrypto.randomInt(0, 6); // 0-5ms jitter
759
- await safeAsync.sleep(attempt * 5 + jitter); // allow:raw-time-literal — sub-second backoff
806
+ await safeAsync.sleep(attempt * 5 + jitter);
760
807
  continue;
761
808
  }
762
809
  var failureMs = Date.now() - t0;
@@ -771,7 +818,7 @@ async function transaction(fn, opts) {
771
818
  _emitMetric("db.role.denied", 1,
772
819
  { backend: b.name, role: role || "(none)" });
773
820
  }
774
- // D-M2 — DB-auth audit visibility on transaction-shaped denials.
821
+ // DB-auth audit visibility on transaction-shaped denials.
775
822
  // Statement class always reads as "TX" since the failure
776
823
  // surface inside a transaction body could be any statement;
777
824
  // operators correlate via the transaction's audit row.
@@ -1017,7 +1064,7 @@ function _requireInit() {
1017
1064
 
1018
1065
  var REPLICA_UNHEALTHY_COOLDOWN_MS = C.TIME.seconds(30);
1019
1066
 
1020
- // F-CBT-2 — replica residency-tag compatibility.
1067
+ // Replica residency-tag compatibility.
1021
1068
  //
1022
1069
  // A primary tagged "EU" replicating to a "US" replica is a GDPR
1023
1070
  // Article 46 cross-border transfer; without an explicit operator
@@ -1194,7 +1241,7 @@ async function _readQuery(sql, params, opts) {
1194
1241
  _emitMetric("db.role.denied", 1,
1195
1242
  { backend: b.name, role: role || "(none)" });
1196
1243
  }
1197
- // D-M2 — DB-auth audit visibility for read-replica denials too.
1244
+ // DB-auth audit visibility for read-replica denials too.
1198
1245
  _emitAuthFailureAudit(b, role, sql, e);
1199
1246
  // Fallback to primary on a failed replica read when allowed.
1200
1247
  if (b.replicaFallbackToPrimary) {
@@ -1874,4 +1921,5 @@ module.exports = {
1874
1921
  migrate: externalDbMigrate,
1875
1922
  Pool: Pool,
1876
1923
  _resetForTest: _resetForTest,
1924
+ _extractTargetRelation: _extractTargetRelation,
1877
1925
  };
@@ -648,8 +648,8 @@ function _breakGlassPoliciesDDL(dialect) {
648
648
  }
649
649
 
650
650
  // _blamejs_break_glass_grants — issued grants. One row per successful
651
- // step-up. Default maxRowsPerGrant=1 enforces row-by-row auth per the
652
- // operator-confirmed shape ("each row access = its own grant").
651
+ // step-up. Default maxRowsPerGrant=1 enforces row-by-row auth
652
+ // ("each row access = its own grant").
653
653
  function _breakGlassGrantsDDL(dialect) {
654
654
  var t = _types(dialect);
655
655
  var name = LOCAL_TO_EXTERNAL._blamejs_break_glass_grants;
@@ -766,7 +766,7 @@ async function ensureSchema(opts) {
766
766
  created.push(d.create.match(/CREATE TABLE IF NOT EXISTS\s+(\S+)/)[1]);
767
767
  }
768
768
 
769
- // D-M11 — append-only WORM enforcement on audit_log / consent_log /
769
+ // Append-only WORM enforcement on audit_log / consent_log /
770
770
  // audit_checkpoints in cluster mode. Local-SQLite path already
771
771
  // installs CREATE TRIGGER IF NOT EXISTS via lib/db.js's
772
772
  // _installAppendOnlyTriggers; Postgres needs equivalent rules
@@ -779,7 +779,7 @@ async function ensureSchema(opts) {
779
779
  return { tables: created };
780
780
  }
781
781
 
782
- // D-M11 — WORM enforcement helper. Idempotent: rebuilding triggers
782
+ // WORM enforcement helper. Idempotent: rebuilding triggers
783
783
  // per boot is cheap and any operator-applied DROP TRIGGER is restored
784
784
  // at the next ensureSchema pass.
785
785
  async function _installWormTriggers(backend, dialect) {
package/lib/guard-cidr.js CHANGED
@@ -73,7 +73,7 @@ var IPV4_RESERVED = Object.freeze([
73
73
  { net: _ipv4ToUint32([127, 0, 0, 0]), prefix: 8, label: "loopback" }, // IPv4 octets
74
74
  { net: _ipv4ToUint32([169, 254, 0, 0]), prefix: 16, label: "link-local" }, // IPv4 octets
75
75
  { net: _ipv4ToUint32([224, 0, 0, 0]), prefix: 4, label: "multicast" }, // IPv4 octets
76
- { net: _ipv4ToUint32([240, 0, 0, 0]), prefix: 4, label: "reserved-class-e" }, // allow:raw-time-literal — 240 is an IPv4 octet not seconds
76
+ { net: _ipv4ToUint32([240, 0, 0, 0]), prefix: 4, label: "reserved-class-e" }, // allow:raw-time-literal — IPv4 octet 240; coincidental multiple-of-60, not a duration, C.TIME N/A
77
77
  { net: _ipv4ToUint32([192, 0, 2, 0]), prefix: 24, label: "documentation-test-net-1" }, // IPv4 octets
78
78
  { net: _ipv4ToUint32([198, 51, 100, 0]), prefix: 24, label: "documentation-test-net-2" }, // IPv4 octets
79
79
  { net: _ipv4ToUint32([203, 0, 113, 0]), prefix: 24, label: "documentation-test-net-3" }, // IPv4 octets
@@ -111,7 +111,7 @@ var PROFILES = Object.freeze({
111
111
  framesPolicy: "reject",
112
112
  maxWidth: C.BYTES.bytes(8192), // pixel cap, repurposing bytes() for clarity
113
113
  maxHeight: C.BYTES.bytes(8192),
114
- maxFrames: 60, // allow:raw-time-literal — animation frame count, not seconds
114
+ maxFrames: 60, // allow:raw-time-literal — max-frame count 60; coincidental multiple-of-60, not a duration, C.TIME N/A
115
115
  maxBytes: C.BYTES.mib(32),
116
116
  maxRuntimeMs: C.TIME.seconds(5),
117
117
  },
@@ -203,8 +203,8 @@ function validate(headerValue, opts) {
203
203
  // recover the boundary without Public Suffix List awareness
204
204
  // (`team.example.com` could be label=team / ns=example.com OR
205
205
  // label=team.example / ns=com). The earlier last-2-segment
206
- // heuristic produced empty `label` for 2-label IDs (Codex P1 on
207
- // PR #64), which violates RFC 2919 §2's required label "."
206
+ // heuristic produced empty `label` for 2-label IDs
207
+ // which violates RFC 2919 §2's required label "."
208
208
  // namespace decomposition.
209
209
  //
210
210
  // Drop the heuristic split — surface only the raw `listId` (and
@@ -349,8 +349,7 @@ function _extractUris(raw, maxUris) {
349
349
  // bracket pairs directly via String.matchAll so URIs containing
350
350
  // commas (legitimate, e.g. `<https://x/u?tags=a,b>`) parse
351
351
  // correctly. Earlier split(",")-based scan misclassified such
352
- // URIs as "no <URI> elements" and refused legitimate mail
353
- // (Codex P1 on PR #63).
352
+ // URIs as "no <URI> elements" and refused legitimate mail.
354
353
  var matches = raw.matchAll(/<([^<>]*)>/g); // allow:regex-no-length-cap — input length-bounded by maxBytes check upstream
355
354
  var uris = [];
356
355
  for (var m of matches) {
@@ -372,7 +371,7 @@ function _hasControlChar(s) {
372
371
 
373
372
  function _trunc(s) {
374
373
  if (s.length <= 64) return s; // error-message truncation
375
- return s.slice(0, 60) + "…"; // allow:raw-time-literal — char count for error-message truncation, not seconds
374
+ return s.slice(0, 60) + "…"; // allow:raw-time-literal — truncation char-count 60; coincidental multiple-of-60, not a duration, C.TIME N/A
376
375
  }
377
376
 
378
377
  function _verdict(action, reason, extra) {
package/lib/guard-time.js CHANGED
@@ -254,7 +254,7 @@ function _detectIssues(input, opts) {
254
254
  snippet: "minute " + minute + " > 59",
255
255
  });
256
256
  }
257
- if (second > 60) { // allow:raw-time-literal — leap-second ceiling, RFC 3339 §5.6 (not seconds-of-time)
257
+ if (second > 60) { // allow:raw-time-literal — leap-second ceiling literal 60 (RFC 3339 5.6); coincidental multiple-of-60, not a duration, C.TIME N/A
258
258
  issues.push({
259
259
  kind: "second-range", severity: "high",
260
260
  ruleId: "time.second-range",
package/lib/guard-xml.js CHANGED
@@ -303,7 +303,7 @@ function _detectIssues(input, opts) {
303
303
  // get the NCR cap disabled with them. The `maxNumericCharRefs` opt
304
304
  // is validated by `numericBounds.requireAllPositiveFiniteIntIfPresent`
305
305
  // at the public-surface boundary above.
306
- var ncrCap = opts.maxNumericCharRefs; // allow:numeric-opt-no-bounds-check — validated at public boundary
306
+ var ncrCap = opts.maxNumericCharRefs;
307
307
  if (ncrCap !== undefined && ncrCap !== null) {
308
308
  var ncrMatches = input.match(NUMERIC_CHAR_REF_RE); // allow:regex-no-length-cap — input bounded by maxBytes above
309
309
  var ncrCount = ncrMatches === null ? 0 : ncrMatches.length;
@@ -66,7 +66,7 @@ var HEURISTIC_MAX_AGE_MS = C.TIME.hours(24);
66
66
  // Statuses RFC 9110 designates as heuristically cacheable. (Plus 200/206
67
67
  // which are universally cacheable when a freshness lifetime is given.)
68
68
  var CACHEABLE_STATUSES = new Set([
69
- 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501, // allow:raw-time-literal — same line, status codes not seconds
69
+ 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501, // allow:raw-time-literal — RFC 9111 cacheable status-code set; coincidental multiple-of-60 entries, not durations, C.TIME N/A
70
70
  ]);
71
71
 
72
72
  // Headers that MUST not be forwarded when serving a 304-updated entry.
package/lib/iab-tcf.js CHANGED
@@ -108,7 +108,7 @@ var TCF_V23_POLICY_VERSION = 4;
108
108
  var SEGMENT_TYPE_DISCLOSED_VENDORS = 1; // TCF segment-type marker, not bytes
109
109
  var SEGMENT_TYPE_ALLOWED_VENDORS = 2; // TCF segment-type marker, not bytes
110
110
  var SEGMENT_TYPE_PUBLISHER_TC = 3; // TCF segment-type marker, not bytes
111
- var MAX_TC_STRING_BYTES = 64 * 1024; // allow:raw-byte-literal — request-payload cap
111
+ var MAX_TC_STRING_BYTES = 64 * 1024;
112
112
 
113
113
  // base64url decode (no padding) → Buffer.
114
114
  function _b64urlDecode(s) {
@@ -186,8 +186,8 @@ function _parseCore(buf) {
186
186
  var publisherRestrictions = _parsePublisherRestrictions(r);
187
187
  return {
188
188
  version: version,
189
- createdAt: createdRaw * 100, // allow:raw-time-literal — TCF spec deciseconds → ms
190
- lastUpdatedAt: lastUpdatedRaw * 100, // allow:raw-time-literal — TCF spec deciseconds → ms
189
+ createdAt: createdRaw * 100,
190
+ lastUpdatedAt: lastUpdatedRaw * 100,
191
191
  cmpId: cmpId,
192
192
  cmpVersion: cmpVersion,
193
193
  consentScreen: consentScreen,
@@ -545,7 +545,7 @@ function _idRuns(ids) {
545
545
  function _decisec(t) {
546
546
  var ms = t instanceof Date ? t.getTime() : (t == null ? Date.now() : Number(t));
547
547
  if (!isFinite(ms) || ms < 0) throw IabTcfError.factory("iab-tcf/bad-value", "iabTcf.encode: timestamp must be a Date or non-negative epoch-ms");
548
- return Math.round(ms / 100); // allow:raw-time-literal — ms → TCF deciseconds
548
+ return Math.round(ms / 100);
549
549
  }
550
550
 
551
551
  // Bit writer mirroring _bitReader: bits are packed high-bit-first, then the
@@ -305,8 +305,158 @@ function create(opts) {
305
305
  };
306
306
  }
307
307
 
308
+ // Breach detection -> notification running clock. The reporter
309
+ // (`create`) computes the static per-stage deadlines; this clock turns
310
+ // them into a live escalation loop: it tracks open incident records and,
311
+ // on each tick, fires "approaching" warnings as a stage's deadline nears
312
+ // and a "passed" alert when it elapses — once per (incident, stage,
313
+ // state) so a busy tick interval can't storm the operator. It re-uses
314
+ // the reporter's REGIME_DEADLINES / dueBy timestamps and re-encodes no
315
+ // jurisdiction hour-counts (GDPR Art.33 72h, NIS2 Art.23(4) 24h, DORA
316
+ // Art.19 + RTS 2024/1772 4h, CRA Art.14, HIPAA 45 CFR 164.404/408).
317
+ // `approachThresholds` are unitless proportions of detected-to-due.
318
+ function createDeadlineClock(opts) {
319
+ opts = opts || {};
320
+ validateOpts(opts, [
321
+ "audit", "notify", "approachThresholds", "intervalMs", "autoStart", "now",
322
+ ], "incident.report.createDeadlineClock");
323
+
324
+ var auditOn = opts.audit !== false;
325
+ var notify = (opts.notify && typeof opts.notify.send === "function") ? opts.notify : null;
326
+ var thresholds = Array.isArray(opts.approachThresholds) ? opts.approachThresholds.slice() : [0.5, 0.75, 0.9];
327
+ for (var ti = 0; ti < thresholds.length; ti += 1) {
328
+ if (typeof thresholds[ti] !== "number" || !(thresholds[ti] > 0 && thresholds[ti] < 1)) {
329
+ throw new IncidentReportError("incident-report/bad-threshold",
330
+ "createDeadlineClock: approachThresholds must be numbers strictly between 0 and 1");
331
+ }
332
+ }
333
+ thresholds.sort(function (a, b) { return a - b; });
334
+ var now = typeof opts.now === "function" ? opts.now : function () { return Date.now(); };
335
+ var intervalMs = (typeof opts.intervalMs === "number" && isFinite(opts.intervalMs) && opts.intervalMs > 0)
336
+ ? opts.intervalMs : C.TIME.minutes(1);
337
+ var autoStart = opts.autoStart !== false;
338
+
339
+ var tracked = new Map(); // incidentId -> { detectedAt, dueBy, regime, acked, fired }
340
+ var timer = null;
341
+
342
+ function _emit(action, outcome, metadata) {
343
+ if (!auditOn) return;
344
+ try {
345
+ audit().safeEmit({ action: "incident.report.clock." + action, outcome: outcome, metadata: metadata || {} });
346
+ } catch (_e) { /* drop-silent — by design */ }
347
+ }
348
+ function _notify(payload) {
349
+ if (!notify) return;
350
+ try {
351
+ var r = notify.send(payload);
352
+ if (r && typeof r.then === "function") r.then(null, function () {});
353
+ } catch (_e) { /* drop-silent — escalation is best-effort, never crashes a tick */ }
354
+ }
355
+
356
+ function track(record) {
357
+ if (!record || typeof record !== "object" || typeof record.id !== "string" || record.id.length === 0) {
358
+ throw new IncidentReportError("incident-report/bad-record",
359
+ "createDeadlineClock.track: record must be an incident.report record with a string id");
360
+ }
361
+ if (!record.dueBy || typeof record.dueBy !== "object" ||
362
+ typeof record.detectedAt !== "number") {
363
+ throw new IncidentReportError("incident-report/bad-record",
364
+ "createDeadlineClock.track: record must carry detectedAt + dueBy { initial, intermediate, final }");
365
+ }
366
+ tracked.set(record.id, {
367
+ detectedAt: record.detectedAt,
368
+ dueBy: record.dueBy,
369
+ regime: record.regime || null,
370
+ acked: {},
371
+ fired: {},
372
+ });
373
+ return record.id;
374
+ }
375
+
376
+ function untrack(id) { return tracked.delete(id); }
377
+
378
+ function acknowledgeSubmission(id, stage, info) {
379
+ if (!VALID_STAGES[stage]) {
380
+ throw new IncidentReportError("incident-report/bad-stage",
381
+ "createDeadlineClock.acknowledgeSubmission: stage must be one of " + Object.keys(VALID_STAGES).join(", "));
382
+ }
383
+ var t = tracked.get(id);
384
+ if (!t) {
385
+ throw new IncidentReportError("incident-report/unknown-incident",
386
+ "createDeadlineClock.acknowledgeSubmission: no tracked incident '" + id + "'");
387
+ }
388
+ t.acked[stage] = true;
389
+ _emit("submission_acknowledged", "success", { incidentId: id, regime: t.regime, stage: stage, info: info || null });
390
+ return true;
391
+ }
392
+
393
+ // Pure evaluation seam — operators (and tests) can pass an explicit
394
+ // nowMs. Each (incident, stage, state) fires AT MOST once; a stage
395
+ // that has been acknowledged is skipped entirely.
396
+ function tick(nowMsArg) {
397
+ var nowMs = typeof nowMsArg === "number" ? nowMsArg : now();
398
+ tracked.forEach(function (t, id) {
399
+ var stages = ["initial", "intermediate", "final"];
400
+ for (var si = 0; si < stages.length; si += 1) {
401
+ var stage = stages[si];
402
+ if (t.acked[stage]) continue;
403
+ var due = t.dueBy[stage];
404
+ if (typeof due !== "number") continue;
405
+ var span = due - t.detectedAt;
406
+ if (span <= 0) continue;
407
+ if (nowMs >= due) {
408
+ var pk = stage + ":passed";
409
+ if (!t.fired[pk]) {
410
+ t.fired[pk] = true;
411
+ _emit("deadline_passed", "failure", { incidentId: id, regime: t.regime, stage: stage, dueBy: due });
412
+ _notify({ kind: "deadline_passed", incidentId: id, regime: t.regime, stage: stage, dueBy: due });
413
+ }
414
+ continue;
415
+ }
416
+ var proportion = (nowMs - t.detectedAt) / span;
417
+ for (var thi = thresholds.length - 1; thi >= 0; thi -= 1) {
418
+ if (proportion >= thresholds[thi]) {
419
+ var ak = stage + ":approaching:" + thresholds[thi];
420
+ if (!t.fired[ak]) {
421
+ t.fired[ak] = true;
422
+ _emit("deadline_approaching", "warning",
423
+ { incidentId: id, regime: t.regime, stage: stage, dueBy: due, threshold: thresholds[thi] });
424
+ _notify({ kind: "deadline_approaching", incidentId: id, regime: t.regime, stage: stage, dueBy: due, threshold: thresholds[thi] });
425
+ }
426
+ break;
427
+ }
428
+ }
429
+ }
430
+ });
431
+ }
432
+
433
+ function start() {
434
+ if (timer) return;
435
+ timer = setInterval(function () { tick(); }, intervalMs);
436
+ if (timer && typeof timer.unref === "function") timer.unref();
437
+ }
438
+ function stop() {
439
+ if (timer) { clearInterval(timer); timer = null; }
440
+ }
441
+ function status() {
442
+ return { tracked: tracked.size, running: timer !== null, intervalMs: intervalMs };
443
+ }
444
+
445
+ if (autoStart) start();
446
+ return {
447
+ track: track,
448
+ untrack: untrack,
449
+ acknowledgeSubmission: acknowledgeSubmission,
450
+ tick: tick,
451
+ start: start,
452
+ stop: stop,
453
+ status: status,
454
+ };
455
+ }
456
+
308
457
  module.exports = {
309
458
  create: create,
459
+ createDeadlineClock: createDeadlineClock,
310
460
  IncidentReportError: IncidentReportError,
311
461
  REGIME_DEADLINES: REGIME_DEADLINES,
312
462
  DEFAULT_DEADLINES: DEFAULT_DEADLINES,
@@ -487,7 +487,7 @@ function _typeMatches(typeKw, instance, actual) {
487
487
  function _checkNumber(schema, n, emit) {
488
488
  if (typeof schema.multipleOf === "number") {
489
489
  var q = n / schema.multipleOf;
490
- if (!isFinite(q) || Math.abs(q - Math.round(q)) > 1e-9 * Math.max(1, Math.abs(q))) { // allow:raw-time-literal — float tolerance for multipleOf
490
+ if (!isFinite(q) || Math.abs(q - Math.round(q)) > 1e-9 * Math.max(1, Math.abs(q))) {
491
491
  // Exact check for integers; tolerance only bridges float error.
492
492
  if (n % schema.multipleOf !== 0) emit("multipleOf", "value is not a multiple of " + schema.multipleOf);
493
493
  }
package/lib/jtd.js CHANGED
@@ -33,7 +33,7 @@ var rfc3339 = require("./rfc3339");
33
33
 
34
34
  var JtdError = defineClass("JtdError", { alwaysPermanent: true });
35
35
 
36
- var MAX_DEPTH = 10000; // allow:raw-time-literal — recursion cap for self-referential refs
36
+ var MAX_DEPTH = 10000;
37
37
 
38
38
  var TYPES = {
39
39
  boolean: 1, string: 1, timestamp: 1, float32: 1, float64: 1,
package/lib/mail-auth.js CHANGED
@@ -1819,7 +1819,7 @@ function authResultsEmit(opts) {
1819
1819
  // // }
1820
1820
 
1821
1821
  var DMARC_RUA_MAX_REPORT_BYTES = C.BYTES.mib(8);
1822
- var DMARC_RUA_MAX_RECORDS_PER_REPORT = 10000; // allow:raw-time-literal — record cap, not seconds
1822
+ var DMARC_RUA_MAX_RECORDS_PER_REPORT = 10000;
1823
1823
 
1824
1824
  function _arrayOf(value) {
1825
1825
  if (value === undefined || value === null) return [];
package/lib/mail-bimi.js CHANGED
@@ -444,7 +444,7 @@ function validateTinyPsSvg(svgBytes) {
444
444
  if (lname === "href" || lname === "xlink:href") {
445
445
  if (aval.length > 0 && aval.charAt(0) !== "#") {
446
446
  _vio("external-ref-forbidden",
447
- "external reference in `" + aname + "='" + aval.slice(0, 60) /* allow:raw-time-literal — display truncation chars, not seconds */ + "...'` " +
447
+ "external reference in `" + aname + "='" + aval.slice(0, 60) /* allow:raw-time-literal — display truncation char-count 60; coincidental multiple-of-60, not a duration, C.TIME N/A */ + "...'` " +
448
448
  "is forbidden in Tiny-PS (only `#fragment` permitted)");
449
449
  }
450
450
  }
@@ -754,7 +754,7 @@ function checkCert(opts) {
754
754
  if (pub && pub.asymmetricKeyType === "rsa") {
755
755
  var jwk = pub.export({ format: "jwk" });
756
756
  var nBytes = Buffer.from(jwk.n, "base64url");
757
- var bits = nBytes.length * 8; // allow:raw-time-literal — RFC 5280 in comment, not seconds
757
+ var bits = nBytes.length * 8; // allow:raw-time-literal — byte-length*8 bit count; coincidental multiple-of-60 product, not a duration, C.TIME N/A
758
758
  if (bits < RSA_MIN_BITS) {
759
759
  throw new MailCryptoError("mail-crypto/smime/rsa-too-small",
760
760
  "cert public key is " + bits + " RSA bits; minimum is " + RSA_MIN_BITS +
@@ -763,7 +763,7 @@ function checkCert(opts) {
763
763
  }
764
764
 
765
765
  // Validity window — refuse certs outside their notBefore / notAfter
766
- // window. Codex P1: checkCert's docstring promises this throws
766
+ // window. checkCert's docstring promises this throws
767
767
  // `mail-crypto/smime/expired-cert` but the impl was missing, letting
768
768
  // expired or not-yet-valid signing certs pass boot-time preflight
769
769
  // and fail interop later when peers verify signatures against the
@@ -720,7 +720,7 @@ function _validateTlsRptReport(raw, ctx) {
720
720
  "parseTlsRptReport: report has " + policies.length +
721
721
  " policies (cap " + TLSRPT_MAX_POLICIES + ")");
722
722
  }
723
- // Codex P2 (v0.10.15) — validate summary counts as finite non-negative
723
+ // Validate summary counts as finite non-negative
724
724
  // integers before summing. `Number(...) || 0` would accept
725
725
  // `Infinity` (from JSON literal `1e309` or string "Infinity"),
726
726
  // negative values, and arbitrary strings (coerced to NaN→0). Each
@@ -882,7 +882,7 @@ function tlsRptReportSchema() {
882
882
  * posture-aware payload (organization-name, report-id,
883
883
  * policy-domain set, session totals).
884
884
  *
885
- * Authentication discipline (Codex P2 v0.10.15):
885
+ * Authentication discipline:
886
886
  * - `trustedReporters` is a CONTENT-SIDE soft filter — it compares
887
887
  * the reporter's self-declared `organization-name` field (the
888
888
  * report body, operator-untrusted) against the operator's
@@ -962,7 +962,7 @@ function tlsRptIngestHttp(opts) {
962
962
  res.end("RFC 8460 §6.4-6.5 media types required\n");
963
963
  return;
964
964
  }
965
- // Codex P2 (v0.10.15) — real-authentication boundary BEFORE body
965
+ // Real-authentication boundary BEFORE body
966
966
  // collection. The operator-supplied `authenticate(req)` hook
967
967
  // routes to mTLS peer-cert / IP-allowlist / signed-header /
968
968
  // reverse-proxy header inspection. Sync-or-async; falsy → 401.
@@ -43,7 +43,7 @@
43
43
  * `opts.auth.mechanisms`. The framework hardcodes no defaults; an
44
44
  * operator who omits `mechanisms` gets a listener that refuses
45
45
  * every AUTHENTICATE attempt with "mechanism not advertised"
46
- * (avoids the IMAP v0.9.49 Codex P2 class — advertising AUTH=PLAIN
46
+ * (otherwise advertising AUTH=PLAIN
47
47
  * when authConfig is null sets clients up to attempt PLAIN against
48
48
  * a listener that hasn't wired the verifier).
49
49
  *
@@ -482,7 +482,7 @@ function create(opts) {
482
482
  }
483
483
  socket.write('"SIEVE" "' + sieveCaps.join(" ") + '"\r\n');
484
484
  // Advertise SASL mechanisms — ONLY the mechs the operator wired
485
- // in opts.auth.mechanisms (Codex P2 IMAP lesson: don't hardcode).
485
+ // in opts.auth.mechanisms (do not hardcode the advertised mechanisms).
486
486
  if (authConfig && Array.isArray(authConfig.mechanisms) && authConfig.mechanisms.length > 0) {
487
487
  var mechs = authConfig.mechanisms.map(function (m) {
488
488
  return String(m).toUpperCase();
@@ -998,7 +998,7 @@ function create(opts) {
998
998
  });
999
999
  var deadline = Date.now() + timeoutMs;
1000
1000
  while (connections.size > 0 && Date.now() < deadline) {
1001
- await safeAsync.sleep(100); // allow:raw-time-literal — close-drain poll interval (sub-second; operator-bounded by timeoutMs)
1001
+ await safeAsync.sleep(100);
1002
1002
  }
1003
1003
  connections.forEach(function (sock) {
1004
1004
  try { sock.destroy(); } catch (_e) { /* best-effort */ }
@@ -368,8 +368,8 @@ function create(opts) {
368
368
  socket.write("UIDL\r\n");
369
369
  socket.write("RESP-CODES\r\n");
370
370
  if (!state.tls) socket.write("STLS\r\n");
371
- // Advertise AUTH mechanisms ONLY when wired (Codex P2 IMAP lesson:
372
- // don't hardcode SASL mechs in caps).
371
+ // Advertise AUTH mechanisms ONLY when wired
372
+ // (do not hardcode SASL mechs in caps).
373
373
  if (authConfig && Array.isArray(authConfig.mechanisms) && authConfig.mechanisms.length > 0) {
374
374
  var mechs = authConfig.mechanisms.map(function (m) {
375
375
  return String(m).toUpperCase();
@@ -96,7 +96,7 @@ var MailServerRateLimitError = defineClass("MailServerRateLimitError", { alwaysP
96
96
 
97
97
  var DEFAULTS = Object.freeze({
98
98
  maxConcurrentConnectionsPerIp: 10,
99
- connectionsPerIpPerMinute: 60, // allow:raw-time-literal — connection count, not a time value
99
+ connectionsPerIpPerMinute: 60, // allow:raw-time-literal — per-minute connection rate 60; time-derived (per-minute) single source of truth, C.TIME N/A as a count
100
100
  authFailuresPerIpPer15Min: 10,
101
101
  minBytesPerSecond: 100, // slow-loris byte-rate floor
102
102
  // RCPT-TO recipient-failure cap defends against the 550-vs-250
@@ -1347,7 +1347,7 @@ function create(opts) {
1347
1347
  });
1348
1348
  var deadline = Date.now() + timeoutMs;
1349
1349
  while (connections.size > 0 && Date.now() < deadline) {
1350
- await safeAsync.sleep(100); // allow:raw-time-literal — sub-second drain poll
1350
+ await safeAsync.sleep(100);
1351
1351
  }
1352
1352
  connections.forEach(function (sock) {
1353
1353
  try { sock.destroy(); } catch (_e) { /* best-effort */ }
package/lib/mail-store.js CHANGED
@@ -753,7 +753,7 @@ function _setFlags(args) {
753
753
  // Per-message modseq bump — without this, queryByModseq filters
754
754
  // `messages.modseq > sinceModseq` and misses the flag change. CONDSTORE
755
755
  // (RFC 7162) / JMAP Email/changes both depend on the per-message
756
- // modseq being current. Per Codex P1 on PR #49.
756
+ // modseq being current.
757
757
  if (args.objectids.length > 0 && (setFlags.length > 0 || unsetFlags.length > 0)) {
758
758
  // Bulk-update via IN-clause. SQLite caps IN-clause at 32766 (max
759
759
  // bound parameters); chunk for very large operands.