@blamejs/core 0.8.43 → 0.8.49
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 +92 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/guard-template.js
CHANGED
|
@@ -1,29 +1,53 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* @module b.guardTemplate
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Template
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Server-Side Template Injection (SSTI) content-safety guard —
|
|
9
|
+
* refuses user-supplied strings that contain template-engine
|
|
10
|
+
* syntax BEFORE they're rendered. Template-injection vulnerable
|
|
11
|
+
* surfaces escape sandboxes through engine helpers (`render`,
|
|
12
|
+
* `lookup`, `with`, `attr_filter`); the safe shape is "logic-less
|
|
13
|
+
* templates only, untrusted strings are data not code". This
|
|
14
|
+
* primitive enforces that boundary by refusing engine syntax in
|
|
15
|
+
* any operator-untrusted input. Pair with logic-less Mustache
|
|
16
|
+
* helpers, Handlebars `noEscape: false`, and Liquid's strict-
|
|
17
|
+
* variables mode so the framework's defense-in-depth holds even
|
|
18
|
+
* when an operator forgets to escape. KIND=`identifier`; the
|
|
19
|
+
* gate consumes `ctx.identifier` (or `ctx.text`) and refuses on
|
|
20
|
+
* hostile shapes.
|
|
11
21
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* -
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
22
|
+
* Threat catalog (engine-shape detection): Jinja2 / Django / Twig
|
|
23
|
+
* / Liquid / Handlebars / Mustache / AngularJS — `{{...}}`
|
|
24
|
+
* expressions and `{%...%}` statements (CVE-2024-22195 Jinja
|
|
25
|
+
* `xml_attr_filter`, CVE-2024-26139 Bottle, CVE-2024-23348
|
|
26
|
+
* Pyrogram); ERB / Tornado — `<%...%>` and `<%=...%>`; Pug —
|
|
27
|
+
* `#{...}` interpolation and `!{...}` raw-HTML interpolation
|
|
28
|
+
* (prototype-pollution exit when the model is operator-fed);
|
|
29
|
+
* Mako / Velocity / Tornado / JS template-literal — `${...}`
|
|
30
|
+
* interpolation; Velocity directives (`#set`, `#if`, `#foreach`,
|
|
31
|
+
* `#parse`, `#include`); BIDI / null / C0 control / zero-width
|
|
32
|
+
* universal refuse.
|
|
23
33
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
34
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
35
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. Operators
|
|
36
|
+
* select via `{ profile: "strict" }` or
|
|
37
|
+
* `{ compliance: "hipaa" }`; postures overlay on top of the
|
|
38
|
+
* profile baseline. Jinja / ERB / Pug shape rejection holds at
|
|
39
|
+
* every profile — the SSTI class is never an operator opt-in.
|
|
40
|
+
*
|
|
41
|
+
* Template input cannot be repaired safely (stripping `{{` from
|
|
42
|
+
* `{{name}}` produces a different document); `sanitize` either
|
|
43
|
+
* passes through clean input or throws `GuardTemplateError`; the
|
|
44
|
+
* gate returns `serve` / `audit-only` / `refuse` (no `sanitize`
|
|
45
|
+
* action). The `${...}` and Velocity-directive policies default
|
|
46
|
+
* to `audit` outside `strict` because they overlap with legitimate
|
|
47
|
+
* JS / shell substrings, so operators tune via overrides.
|
|
48
|
+
*
|
|
49
|
+
* @card
|
|
50
|
+
* Server-Side Template Injection (SSTI) content-safety guard — refuses user-supplied strings that contain template-engine syntax BEFORE they're rendered.
|
|
27
51
|
*/
|
|
28
52
|
|
|
29
53
|
var codepointClass = require("./codepoint-class");
|
|
@@ -195,6 +219,45 @@ function _detectIssues(input, opts) {
|
|
|
195
219
|
return issues;
|
|
196
220
|
}
|
|
197
221
|
|
|
222
|
+
/**
|
|
223
|
+
* @primitive b.guardTemplate.validate
|
|
224
|
+
* @signature b.guardTemplate.validate(input, opts)
|
|
225
|
+
* @since 0.7.13
|
|
226
|
+
* @status stable
|
|
227
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
228
|
+
* @related b.guardTemplate.gate, b.guardTemplate.sanitize
|
|
229
|
+
*
|
|
230
|
+
* Inspect a user-supplied template-rendering input and return an
|
|
231
|
+
* aggregated issue list. Pure inspection — never throws on
|
|
232
|
+
* hostile input; caller decides what to do with the issues. The
|
|
233
|
+
* `ok` flag is `true` only when zero `critical` / `high` issues
|
|
234
|
+
* fire. Throws `GuardTemplateError("template.bad-opt")` when a
|
|
235
|
+
* numeric opt is non-finite / negative (config-time mistake by
|
|
236
|
+
* the operator).
|
|
237
|
+
*
|
|
238
|
+
* @opts
|
|
239
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
240
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
241
|
+
* bidiPolicy: "reject"|"audit"|"allow",
|
|
242
|
+
* controlPolicy: "reject"|"audit"|"allow",
|
|
243
|
+
* nullBytePolicy: "reject"|"audit"|"allow",
|
|
244
|
+
* zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
|
|
245
|
+
* jinjaPolicy: "reject"|"audit"|"allow",
|
|
246
|
+
* erbPolicy: "reject"|"audit"|"allow",
|
|
247
|
+
* pugPolicy: "reject"|"audit"|"allow",
|
|
248
|
+
* dollarBracePolicy: "reject"|"audit"|"allow",
|
|
249
|
+
* velocityDirectivePolicy: "reject"|"audit"|"allow",
|
|
250
|
+
* maxBytes: number,
|
|
251
|
+
* maxRuntimeMs: number,
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* var clean = b.guardTemplate.validate("Hello world", { profile: "strict" });
|
|
255
|
+
* clean.ok; // → true
|
|
256
|
+
*
|
|
257
|
+
* var hostile = b.guardTemplate.validate("Hello {{7*7}}", { profile: "strict" });
|
|
258
|
+
* hostile.ok; // → false
|
|
259
|
+
* hostile.issues.some(function (i) { return i.kind === "jinja-expression"; }); // → true
|
|
260
|
+
*/
|
|
198
261
|
function validate(input, opts) {
|
|
199
262
|
opts = _resolveOpts(opts);
|
|
200
263
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -203,6 +266,45 @@ function validate(input, opts) {
|
|
|
203
266
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
204
267
|
}
|
|
205
268
|
|
|
269
|
+
/**
|
|
270
|
+
* @primitive b.guardTemplate.sanitize
|
|
271
|
+
* @signature b.guardTemplate.sanitize(input, opts)
|
|
272
|
+
* @since 0.7.13
|
|
273
|
+
* @status stable
|
|
274
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
275
|
+
* @related b.guardTemplate.validate, b.guardTemplate.gate
|
|
276
|
+
*
|
|
277
|
+
* Pass-through-or-throw. Template-input strings cannot be safely
|
|
278
|
+
* repaired (stripping `{{` from `{{name}}` produces a different
|
|
279
|
+
* document and silently changes operator intent); this primitive
|
|
280
|
+
* returns the input unchanged when no `critical` or `high` issue
|
|
281
|
+
* fires, otherwise throws `GuardTemplateError` with the offending
|
|
282
|
+
* rule id (e.g. `template.jinja-expression`,
|
|
283
|
+
* `template.erb-expression`, `template.pug-interpolation`,
|
|
284
|
+
* `template.velocity-directive`). Operators that need a "best-
|
|
285
|
+
* effort cleanup" semantic should pre-escape the input through
|
|
286
|
+
* the rendering engine's own escape helper instead.
|
|
287
|
+
*
|
|
288
|
+
* @opts
|
|
289
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
290
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
291
|
+
* jinjaPolicy: "reject"|"audit"|"allow",
|
|
292
|
+
* erbPolicy: "reject"|"audit"|"allow",
|
|
293
|
+
* pugPolicy: "reject"|"audit"|"allow",
|
|
294
|
+
* dollarBracePolicy: "reject"|"audit"|"allow",
|
|
295
|
+
* velocityDirectivePolicy: "reject"|"audit"|"allow",
|
|
296
|
+
* maxBytes: number,
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* var safe = b.guardTemplate.sanitize("Hello world", { profile: "strict" });
|
|
300
|
+
* safe; // → "Hello world"
|
|
301
|
+
*
|
|
302
|
+
* try {
|
|
303
|
+
* b.guardTemplate.sanitize("Hello {{7*7}}", { profile: "strict" });
|
|
304
|
+
* } catch (e) {
|
|
305
|
+
* e.code; // → "template.jinja-expression"
|
|
306
|
+
* }
|
|
307
|
+
*/
|
|
206
308
|
function sanitize(input, opts) {
|
|
207
309
|
opts = _resolveOpts(opts);
|
|
208
310
|
if (typeof input !== "string") {
|
|
@@ -218,6 +320,46 @@ function sanitize(input, opts) {
|
|
|
218
320
|
return input;
|
|
219
321
|
}
|
|
220
322
|
|
|
323
|
+
/**
|
|
324
|
+
* @primitive b.guardTemplate.gate
|
|
325
|
+
* @signature b.guardTemplate.gate(opts)
|
|
326
|
+
* @since 0.7.13
|
|
327
|
+
* @status stable
|
|
328
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
329
|
+
* @related b.guardTemplate.validate, b.guardTemplate.sanitize
|
|
330
|
+
*
|
|
331
|
+
* Build a `b.gateContract` gate that screens `ctx.identifier` (or
|
|
332
|
+
* `ctx.text`) before any template engine renders the input.
|
|
333
|
+
* Action chain: `serve` (no issues) → `audit-only` (warn-only) →
|
|
334
|
+
* `refuse` (any `critical` or `high`). No `sanitize` action —
|
|
335
|
+
* template input cannot be repaired. Compose into form handlers /
|
|
336
|
+
* comment renderers / model fields fed to Mustache / Handlebars /
|
|
337
|
+
* Liquid so operator-untrusted strings never reach the rendering
|
|
338
|
+
* engine carrying engine syntax.
|
|
339
|
+
*
|
|
340
|
+
* @opts
|
|
341
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
342
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
343
|
+
* name: string, // override gate name in audit emissions
|
|
344
|
+
* jinjaPolicy: "reject"|"audit"|"allow",
|
|
345
|
+
* erbPolicy: "reject"|"audit"|"allow",
|
|
346
|
+
* pugPolicy: "reject"|"audit"|"allow",
|
|
347
|
+
* dollarBracePolicy: "reject"|"audit"|"allow",
|
|
348
|
+
* velocityDirectivePolicy: "reject"|"audit"|"allow",
|
|
349
|
+
* maxBytes: number,
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* var gate = b.guardTemplate.gate({ profile: "strict" });
|
|
353
|
+
*
|
|
354
|
+
* gate({ identifier: "Hello {{7*7}}" }).then(function (rv) {
|
|
355
|
+
* rv.ok; // → false
|
|
356
|
+
* rv.action; // → "refuse"
|
|
357
|
+
* });
|
|
358
|
+
*
|
|
359
|
+
* gate({ identifier: "Hello world" }).then(function (rv) {
|
|
360
|
+
* rv.action; // → "serve"
|
|
361
|
+
* });
|
|
362
|
+
*/
|
|
221
363
|
function gate(opts) {
|
|
222
364
|
opts = _resolveOpts(opts);
|
|
223
365
|
return gateContract.buildGuardGate(
|
|
@@ -243,14 +385,83 @@ function gate(opts) {
|
|
|
243
385
|
});
|
|
244
386
|
}
|
|
245
387
|
|
|
388
|
+
/**
|
|
389
|
+
* @primitive b.guardTemplate.buildProfile
|
|
390
|
+
* @signature b.guardTemplate.buildProfile(opts)
|
|
391
|
+
* @since 0.7.13
|
|
392
|
+
* @status stable
|
|
393
|
+
* @related b.guardTemplate.gate, b.guardTemplate.compliancePosture
|
|
394
|
+
*
|
|
395
|
+
* Compose a derived guardTemplate profile from one or more named
|
|
396
|
+
* bases plus inline overrides. `opts.extends` is a profile name
|
|
397
|
+
* (`"strict"` / `"balanced"` / `"permissive"`) or an array of
|
|
398
|
+
* names; later entries shadow earlier ones. Inline `opts` keys win
|
|
399
|
+
* last. Used to keep operator-defined profiles traceable to a
|
|
400
|
+
* baseline rather than re-typing every key.
|
|
401
|
+
*
|
|
402
|
+
* @opts
|
|
403
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
404
|
+
* ...: any guardTemplate key, // inline override of resolved keys
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* var custom = b.guardTemplate.buildProfile({
|
|
408
|
+
* extends: "balanced",
|
|
409
|
+
* dollarBracePolicy: "reject",
|
|
410
|
+
* });
|
|
411
|
+
* custom.dollarBracePolicy; // → "reject"
|
|
412
|
+
* custom.jinjaPolicy; // → "reject"
|
|
413
|
+
*/
|
|
246
414
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
247
415
|
|
|
416
|
+
/**
|
|
417
|
+
* @primitive b.guardTemplate.compliancePosture
|
|
418
|
+
* @signature b.guardTemplate.compliancePosture(name)
|
|
419
|
+
* @since 0.7.13
|
|
420
|
+
* @status stable
|
|
421
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
422
|
+
* @related b.guardTemplate.gate, b.guardTemplate.buildProfile
|
|
423
|
+
*
|
|
424
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
425
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
426
|
+
* the posture object — the caller may mutate freely. Throws
|
|
427
|
+
* `GuardTemplateError("template.bad-posture")` on unknown name.
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* var posture = b.guardTemplate.compliancePosture("hipaa");
|
|
431
|
+
* posture.jinjaPolicy; // → "reject"
|
|
432
|
+
* posture.forensicSnippetBytes; // → 512
|
|
433
|
+
*/
|
|
248
434
|
function compliancePosture(name) {
|
|
249
435
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
250
436
|
_err, "template");
|
|
251
437
|
}
|
|
252
438
|
|
|
253
439
|
var _tplRulePacks = gateContract.makeRulePackLoader(GuardTemplateError, "template");
|
|
440
|
+
/**
|
|
441
|
+
* @primitive b.guardTemplate.loadRulePack
|
|
442
|
+
* @signature b.guardTemplate.loadRulePack(pack)
|
|
443
|
+
* @since 0.7.13
|
|
444
|
+
* @status stable
|
|
445
|
+
* @related b.guardTemplate.gate
|
|
446
|
+
*
|
|
447
|
+
* Register an operator-supplied rule pack with the guardTemplate
|
|
448
|
+
* registry. The pack is identified by `pack.id` (non-empty string)
|
|
449
|
+
* and stored for later inspection / dispatch by gates that opt in
|
|
450
|
+
* via `opts.rulePackId`. Returns the pack object unchanged on
|
|
451
|
+
* success; throws `GuardTemplateError("template.bad-opt")` when
|
|
452
|
+
* `pack` is missing or `pack.id` is not a non-empty string.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* var pack = b.guardTemplate.loadRulePack({
|
|
456
|
+
* id: "no-prototype-keys",
|
|
457
|
+
* rules: [
|
|
458
|
+
* { id: "proto-key", severity: "critical",
|
|
459
|
+
* detect: function (text) { return /__proto__|constructor/.test(text); },
|
|
460
|
+
* reason: "input references prototype-pollution sink" },
|
|
461
|
+
* ],
|
|
462
|
+
* });
|
|
463
|
+
* pack.id; // → "no-prototype-keys"
|
|
464
|
+
*/
|
|
254
465
|
var loadRulePack = _tplRulePacks.load;
|
|
255
466
|
|
|
256
467
|
module.exports = {
|
package/lib/guard-time.js
CHANGED
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* @module b.guardTime
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Time
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* ISO 8601 / RFC 3339 datetime identifier-safety guard. Validates
|
|
9
|
+
* user-supplied datetime strings destined for audit timestamps,
|
|
10
|
+
* scheduling, retention windows, query ranges, and cross-system
|
|
11
|
+
* event correlation. KIND="identifier" — the gate consumes
|
|
12
|
+
* `ctx.identifier` / `ctx.timestamp` / `ctx.time`.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* - Excessive fractional precision — RFC 3339 allows any digits
|
|
25
|
-
* after the dot but every consuming system has a cap; flag > 9
|
|
26
|
-
* fractional digits.
|
|
27
|
-
* - Date-only / time-only — refused for full-datetime contexts.
|
|
28
|
-
* - Whitespace / control / null-byte / BIDI universal refuse.
|
|
14
|
+
* Threat catalog: shape malformation (not RFC 3339 datetime
|
|
15
|
+
* grammar); pre-epoch / far-future (year before 1970 or after
|
|
16
|
+
* the operator's ceiling, default 9999 — often a parsing bug or
|
|
17
|
+
* sentinel-leak shape); naive datetime with no offset (strict
|
|
18
|
+
* refuses — downstream interpretation depends on local timezone,
|
|
19
|
+
* breaks cross-region equality); non-UTC offset (strict accepts
|
|
20
|
+
* only `Z` / `+00:00`; balanced accepts any offset; permissive
|
|
21
|
+
* allows naive too); leap-second `60` in seconds field (RFC 3339
|
|
22
|
+
* §5.6 explicitly valid, most parsers panic — flagged-by-default
|
|
23
|
+
* with operator policy); excessive fractional precision (cap at
|
|
24
|
+
* 9 digits = nanosecond floor); date-only / time-only refused for
|
|
25
|
+
* full-datetime contexts; BIDI / zero-width / C0-control /
|
|
26
|
+
* null-byte universal-refuse.
|
|
29
27
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
28
|
+
* Far-future / pre-epoch refusal is critical-severity by default:
|
|
29
|
+
* year-2038 wrap shapes, Y10K sentinels, and `0000-01-01` poison
|
|
30
|
+
* pills routinely leak through downstream parsers as silent
|
|
31
|
+
* `NaN` / `0` rows; the guard refuses at the boundary instead.
|
|
32
|
+
*
|
|
33
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
34
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`.
|
|
35
|
+
*
|
|
36
|
+
* @card
|
|
37
|
+
* ISO 8601 / RFC 3339 datetime identifier-safety guard.
|
|
35
38
|
*/
|
|
36
39
|
|
|
37
40
|
var codepointClass = require("./codepoint-class");
|
|
@@ -327,6 +330,47 @@ function _detectIssues(input, opts) {
|
|
|
327
330
|
return issues;
|
|
328
331
|
}
|
|
329
332
|
|
|
333
|
+
/**
|
|
334
|
+
* @primitive b.guardTime.validate
|
|
335
|
+
* @signature b.guardTime.validate(input, opts?)
|
|
336
|
+
* @since 0.7.46
|
|
337
|
+
* @status stable
|
|
338
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
339
|
+
* @related b.guardTime.sanitize, b.guardTime.gate
|
|
340
|
+
*
|
|
341
|
+
* Inspect a datetime string against the resolved profile and return
|
|
342
|
+
* `{ ok, issues }`. Each issue carries `kind` / `severity`
|
|
343
|
+
* (`critical` | `high` | `medium` | `low`) / `ruleId` / `snippet`.
|
|
344
|
+
* Non-string input returns a single `time.bad-input` issue rather
|
|
345
|
+
* than throwing — callers that prefer an exception use
|
|
346
|
+
* `b.guardTime.sanitize`.
|
|
347
|
+
*
|
|
348
|
+
* @opts
|
|
349
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
350
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
351
|
+
* bidiPolicy: "reject"|"strip"|"audit"|"allow",
|
|
352
|
+
* controlPolicy: "reject"|"strip"|"allow",
|
|
353
|
+
* nullBytePolicy: "reject"|"strip"|"allow",
|
|
354
|
+
* zeroWidthPolicy: "reject"|"strip"|"allow",
|
|
355
|
+
* naiveDatetimePolicy: "reject"|"audit"|"allow",
|
|
356
|
+
* nonUtcOffsetPolicy: "reject"|"audit"|"allow",
|
|
357
|
+
* leapSecondPolicy: "reject"|"audit"|"allow",
|
|
358
|
+
* fractionalDigitsPolicy: "reject"|"truncate"|"audit"|"allow",
|
|
359
|
+
* dateOnlyPolicy: "reject"|"audit"|"allow",
|
|
360
|
+
* timeOnlyPolicy: "reject"|"audit"|"allow",
|
|
361
|
+
* minYear: number, // default 1970
|
|
362
|
+
* maxYear: number, // default 9999
|
|
363
|
+
* maxFractionalDigits: number, // default 9 (nanosecond)
|
|
364
|
+
* maxBytes: number, // default 64
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* var rv = b.guardTime.validate("2026-05-05T12:34:56Z", { profile: "strict" });
|
|
368
|
+
* rv.ok; // → true
|
|
369
|
+
*
|
|
370
|
+
* var bad = b.guardTime.validate("1969-12-31T23:59:59Z", { profile: "strict" });
|
|
371
|
+
* bad.ok; // → false
|
|
372
|
+
* bad.issues[0].ruleId; // → "time.year-out-of-range"
|
|
373
|
+
*/
|
|
330
374
|
function validate(input, opts) {
|
|
331
375
|
opts = _resolveOpts(opts);
|
|
332
376
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -343,6 +387,36 @@ function validate(input, opts) {
|
|
|
343
387
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
344
388
|
}
|
|
345
389
|
|
|
390
|
+
/**
|
|
391
|
+
* @primitive b.guardTime.sanitize
|
|
392
|
+
* @signature b.guardTime.sanitize(input, opts?)
|
|
393
|
+
* @since 0.7.46
|
|
394
|
+
* @status stable
|
|
395
|
+
* @related b.guardTime.validate, b.guardTime.gate
|
|
396
|
+
*
|
|
397
|
+
* Normalize a datetime string in-place: replace the legacy
|
|
398
|
+
* space-separator with `T`, upper-case the trailing `Z` UTC
|
|
399
|
+
* marker. Throws `GuardTimeError` when any `critical` or `high`
|
|
400
|
+
* issue fires (year out of range, leap-second under reject,
|
|
401
|
+
* naive datetime under reject). Use `validate` to inspect issues
|
|
402
|
+
* without throwing.
|
|
403
|
+
*
|
|
404
|
+
* @opts
|
|
405
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
406
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
407
|
+
* ...: same shape as b.guardTime.validate opts,
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* var safe = b.guardTime.sanitize("2026-05-05 12:34:56z",
|
|
411
|
+
* { profile: "balanced" });
|
|
412
|
+
* safe; // → "2026-05-05T12:34:56Z"
|
|
413
|
+
*
|
|
414
|
+
* try {
|
|
415
|
+
* b.guardTime.sanitize("9999-12-31T23:59:60Z", { profile: "strict" });
|
|
416
|
+
* } catch (e) {
|
|
417
|
+
* e.code; // → "time.leap-second"
|
|
418
|
+
* }
|
|
419
|
+
*/
|
|
346
420
|
function sanitize(input, opts) {
|
|
347
421
|
opts = _resolveOpts(opts);
|
|
348
422
|
if (typeof input !== "string") {
|
|
@@ -360,6 +434,35 @@ function sanitize(input, opts) {
|
|
|
360
434
|
return input.replace(/(\d) /, "$1T").replace(/z$/, "Z");
|
|
361
435
|
}
|
|
362
436
|
|
|
437
|
+
/**
|
|
438
|
+
* @primitive b.guardTime.gate
|
|
439
|
+
* @signature b.guardTime.gate(opts?)
|
|
440
|
+
* @since 0.7.46
|
|
441
|
+
* @status stable
|
|
442
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
443
|
+
* @related b.guardTime.validate, b.guardTime.sanitize, b.guardAll.gate
|
|
444
|
+
*
|
|
445
|
+
* Build an async gate `(ctx) -> { ok, action, issues }` consumable
|
|
446
|
+
* by `b.guardAll`, audit pipelines, scheduling primitives, and
|
|
447
|
+
* retention readers. The gate reads `ctx.identifier` (or
|
|
448
|
+
* `ctx.timestamp` / `ctx.time`), runs `validate`, and maps
|
|
449
|
+
* severity to action: zero issues `serve`; only low/medium
|
|
450
|
+
* `audit-only`; any high/critical `refuse`.
|
|
451
|
+
*
|
|
452
|
+
* @opts
|
|
453
|
+
* name: string, // gate label for audit / observability
|
|
454
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
455
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
456
|
+
* ...: same shape as b.guardTime.validate opts,
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* var g = b.guardTime.gate({ profile: "strict" });
|
|
460
|
+
* var rv = await g({ identifier: "2026-05-05T12:34:56Z" });
|
|
461
|
+
* rv.action; // → "serve"
|
|
462
|
+
*
|
|
463
|
+
* var bad = await g({ identifier: "2026-05-05 12:34:56" });
|
|
464
|
+
* bad.action; // → "refuse"
|
|
465
|
+
*/
|
|
363
466
|
function gate(opts) {
|
|
364
467
|
opts = _resolveOpts(opts);
|
|
365
468
|
return gateContract.buildGuardGate(
|
|
@@ -383,14 +486,77 @@ function gate(opts) {
|
|
|
383
486
|
});
|
|
384
487
|
}
|
|
385
488
|
|
|
489
|
+
/**
|
|
490
|
+
* @primitive b.guardTime.buildProfile
|
|
491
|
+
* @signature b.guardTime.buildProfile(opts)
|
|
492
|
+
* @since 0.7.46
|
|
493
|
+
* @status stable
|
|
494
|
+
* @related b.guardTime.gate, b.guardTime.compliancePosture
|
|
495
|
+
*
|
|
496
|
+
* Compose a derived profile from one or more named bases plus
|
|
497
|
+
* inline overrides. `opts.extends` is a profile name or array of
|
|
498
|
+
* names (later entries shadow earlier ones); inline keys win last.
|
|
499
|
+
*
|
|
500
|
+
* @opts
|
|
501
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
502
|
+
* ...: any guard-time key, // inline override of resolved keys
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* var custom = b.guardTime.buildProfile({
|
|
506
|
+
* extends: "balanced",
|
|
507
|
+
* leapSecondPolicy: "audit",
|
|
508
|
+
* maxYear: 2200,
|
|
509
|
+
* });
|
|
510
|
+
* custom.naiveDatetimePolicy; // → "audit"
|
|
511
|
+
* custom.maxYear; // → 2200
|
|
512
|
+
*/
|
|
386
513
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
387
514
|
|
|
515
|
+
/**
|
|
516
|
+
* @primitive b.guardTime.compliancePosture
|
|
517
|
+
* @signature b.guardTime.compliancePosture(name)
|
|
518
|
+
* @since 0.7.46
|
|
519
|
+
* @status stable
|
|
520
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
521
|
+
* @related b.guardTime.gate, b.guardTime.buildProfile
|
|
522
|
+
*
|
|
523
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
524
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
525
|
+
* the posture object — the caller may mutate freely. Throws
|
|
526
|
+
* `GuardTimeError("time.bad-posture")` on unknown name.
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* var posture = b.guardTime.compliancePosture("hipaa");
|
|
530
|
+
* posture.naiveDatetimePolicy; // → "reject"
|
|
531
|
+
*/
|
|
388
532
|
function compliancePosture(name) {
|
|
389
533
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
390
534
|
_err, "time");
|
|
391
535
|
}
|
|
392
536
|
|
|
393
537
|
var _timeRulePacks = gateContract.makeRulePackLoader(GuardTimeError, "time");
|
|
538
|
+
/**
|
|
539
|
+
* @primitive b.guardTime.loadRulePack
|
|
540
|
+
* @signature b.guardTime.loadRulePack(pack)
|
|
541
|
+
* @since 0.7.46
|
|
542
|
+
* @status stable
|
|
543
|
+
* @related b.guardTime.gate
|
|
544
|
+
*
|
|
545
|
+
* Register an operator-supplied rule pack with the guard-time
|
|
546
|
+
* registry. The pack is identified by `pack.id` (non-empty
|
|
547
|
+
* string) and stored for later inspection / dispatch by gates
|
|
548
|
+
* that opt in via `opts.rulePackId`. Throws
|
|
549
|
+
* `GuardTimeError("time.bad-opt")` when `pack` is missing or
|
|
550
|
+
* `pack.id` is not a non-empty string.
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* var pack = b.guardTime.loadRulePack({
|
|
554
|
+
* id: "audit-window",
|
|
555
|
+
* minYear: 2020,
|
|
556
|
+
* maxYear: 2030,
|
|
557
|
+
* });
|
|
558
|
+
* pack.id; // → "audit-window"
|
|
559
|
+
*/
|
|
394
560
|
var loadRulePack = _timeRulePacks.load;
|
|
395
561
|
|
|
396
562
|
module.exports = {
|