@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-shell.js
CHANGED
|
@@ -1,34 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* @module b.guardShell
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Shell
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Shell-argument content-safety guard — refuses user-supplied
|
|
9
|
+
* strings that carry shell-injection shapes BEFORE they reach a
|
|
10
|
+
* child-process spawn. The canonical defense is "command + literal
|
|
11
|
+
* argv array, never `shell: true`" (route through `b.processSpawn`,
|
|
12
|
+
* which holds that contract); guardShell layers the metacharacter
|
|
13
|
+
* catalog on top so even operator-untrusted strings flowing through
|
|
14
|
+
* the argv slots are screened. KIND=`identifier`; the gate consumes
|
|
15
|
+
* `ctx.identifier` (or `ctx.arg`) and refuses on hostile shapes.
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* -
|
|
24
|
-
*
|
|
25
|
-
* - Operator may opt-in to `argHyphenPolicy` to refuse leading `-`
|
|
26
|
-
* arguments (defense against `-rf` / `--exec` / etc.).
|
|
27
|
-
* - BIDI / zero-width / control / null-byte universal refuse.
|
|
17
|
+
* Threat catalog: POSIX shell metacharacters
|
|
18
|
+
* (`;` `&` `|` `<` `>` `(` `)` `{` `}` `[` `]` `*` `?` `~` `!` `#`
|
|
19
|
+
* `\` and single/double quotes); backtick command substitution;
|
|
20
|
+
* `$(...)` command substitution and `${VAR}` parameter expansion;
|
|
21
|
+
* process substitution `<(...)` / `>(...)`; cmd.exe metacharacters
|
|
22
|
+
* (`&` `|` `<` `>` `^` `%` `"` `'` `(` `)` `,` `;` `=` plus
|
|
23
|
+
* whitespace + newlines); CR / LF / NUL line-splitting; bare
|
|
24
|
+
* `$VAR` parameter expansion; leading `-` arguments (`-rf` /
|
|
25
|
+
* `--exec` flag-injection class) gated by `argHyphenPolicy`; BIDI
|
|
26
|
+
* override / zero-width / C0 control / null-byte refuse at every
|
|
27
|
+
* profile.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
30
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. Operators select
|
|
31
|
+
* via `{ profile: "strict" }` or `{ compliance: "hipaa" }`;
|
|
32
|
+
* postures overlay on top of the profile baseline.
|
|
33
|
+
*
|
|
34
|
+
* Shell args cannot be repaired safely — `sanitize` either passes
|
|
35
|
+
* through clean input or throws `GuardShellError`; the gate returns
|
|
36
|
+
* `serve` / `audit-only` / `refuse` (no `sanitize` action). Pair
|
|
37
|
+
* with `b.processSpawn` so the eventual `child_process.spawn` call
|
|
38
|
+
* uses `shell: false` and the screened argv values.
|
|
39
|
+
*
|
|
40
|
+
* @card
|
|
41
|
+
* Shell-argument content-safety guard — refuses user-supplied strings that carry shell-injection shapes BEFORE they reach a child-process spawn.
|
|
32
42
|
*/
|
|
33
43
|
|
|
34
44
|
var codepointClass = require("./codepoint-class");
|
|
@@ -235,6 +245,46 @@ function _detectIssues(input, opts) {
|
|
|
235
245
|
return issues;
|
|
236
246
|
}
|
|
237
247
|
|
|
248
|
+
/**
|
|
249
|
+
* @primitive b.guardShell.validate
|
|
250
|
+
* @signature b.guardShell.validate(input, opts)
|
|
251
|
+
* @since 0.7.13
|
|
252
|
+
* @status stable
|
|
253
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
254
|
+
* @related b.guardShell.gate, b.guardShell.sanitize, b.processSpawn
|
|
255
|
+
*
|
|
256
|
+
* Inspect a single shell-argument string and return an aggregated
|
|
257
|
+
* issue list. Pure inspection — never throws on hostile input;
|
|
258
|
+
* caller decides what to do with the issues. The `ok` flag is
|
|
259
|
+
* `true` only when zero `critical` / `high` issues fire. Throws
|
|
260
|
+
* `GuardShellError("shell.bad-opt")` when a numeric opt is
|
|
261
|
+
* non-finite / negative (config-time mistake by the operator).
|
|
262
|
+
*
|
|
263
|
+
* @opts
|
|
264
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
265
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
266
|
+
* bidiPolicy: "reject"|"audit"|"allow",
|
|
267
|
+
* controlPolicy: "reject"|"audit"|"allow",
|
|
268
|
+
* nullBytePolicy: "reject"|"audit"|"allow",
|
|
269
|
+
* zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
|
|
270
|
+
* posixMetaPolicy: "reject"|"audit"|"allow",
|
|
271
|
+
* cmdMetaPolicy: "reject"|"audit"|"allow",
|
|
272
|
+
* dollarSubstPolicy: "reject"|"audit"|"allow",
|
|
273
|
+
* processSubstPolicy:"reject"|"audit"|"allow",
|
|
274
|
+
* backtickPolicy: "reject"|"audit"|"allow",
|
|
275
|
+
* newlinePolicy: "reject"|"audit"|"allow",
|
|
276
|
+
* argHyphenPolicy: "reject"|"audit"|"allow",
|
|
277
|
+
* maxBytes: number,
|
|
278
|
+
* maxRuntimeMs: number,
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* var clean = b.guardShell.validate("safe-arg-value", { profile: "strict" });
|
|
282
|
+
* clean.ok; // → true
|
|
283
|
+
*
|
|
284
|
+
* var hostile = b.guardShell.validate("safe; rm -rf /", { profile: "strict" });
|
|
285
|
+
* hostile.ok; // → false
|
|
286
|
+
* hostile.issues.some(function (i) { return i.kind === "posix-metachar"; }); // → true
|
|
287
|
+
*/
|
|
238
288
|
function validate(input, opts) {
|
|
239
289
|
opts = _resolveOpts(opts);
|
|
240
290
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -243,6 +293,47 @@ function validate(input, opts) {
|
|
|
243
293
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
244
294
|
}
|
|
245
295
|
|
|
296
|
+
/**
|
|
297
|
+
* @primitive b.guardShell.sanitize
|
|
298
|
+
* @signature b.guardShell.sanitize(input, opts)
|
|
299
|
+
* @since 0.7.13
|
|
300
|
+
* @status stable
|
|
301
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
302
|
+
* @related b.guardShell.validate, b.guardShell.gate
|
|
303
|
+
*
|
|
304
|
+
* Pass-through-or-throw. Shell arguments cannot be safely repaired
|
|
305
|
+
* (stripping a `;` inside an arg fundamentally changes operator
|
|
306
|
+
* intent); this primitive returns the input unchanged when no
|
|
307
|
+
* `critical` or `high` issue fires, otherwise throws
|
|
308
|
+
* `GuardShellError` with the offending rule id (e.g.
|
|
309
|
+
* `shell.posix-metachar`, `shell.dollar-substitution`,
|
|
310
|
+
* `shell.backtick`, `shell.newline`). Operators that need a
|
|
311
|
+
* "best-effort cleanup" semantic should use a different argv shape
|
|
312
|
+
* (path + literal arg array) rather than trying to disarm a hostile
|
|
313
|
+
* string.
|
|
314
|
+
*
|
|
315
|
+
* @opts
|
|
316
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
317
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
318
|
+
* posixMetaPolicy: "reject"|"audit"|"allow",
|
|
319
|
+
* cmdMetaPolicy: "reject"|"audit"|"allow",
|
|
320
|
+
* dollarSubstPolicy: "reject"|"audit"|"allow",
|
|
321
|
+
* processSubstPolicy:"reject"|"audit"|"allow",
|
|
322
|
+
* backtickPolicy: "reject"|"audit"|"allow",
|
|
323
|
+
* newlinePolicy: "reject"|"audit"|"allow",
|
|
324
|
+
* argHyphenPolicy: "reject"|"audit"|"allow",
|
|
325
|
+
* maxBytes: number,
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* var arg = b.guardShell.sanitize("safe-arg-value", { profile: "strict" });
|
|
329
|
+
* arg; // → "safe-arg-value"
|
|
330
|
+
*
|
|
331
|
+
* try {
|
|
332
|
+
* b.guardShell.sanitize("safe; rm -rf /", { profile: "strict" });
|
|
333
|
+
* } catch (e) {
|
|
334
|
+
* e.code; // → "shell.posix-metachar"
|
|
335
|
+
* }
|
|
336
|
+
*/
|
|
246
337
|
function sanitize(input, opts) {
|
|
247
338
|
opts = _resolveOpts(opts);
|
|
248
339
|
if (typeof input !== "string") {
|
|
@@ -260,6 +351,49 @@ function sanitize(input, opts) {
|
|
|
260
351
|
return input;
|
|
261
352
|
}
|
|
262
353
|
|
|
354
|
+
/**
|
|
355
|
+
* @primitive b.guardShell.gate
|
|
356
|
+
* @signature b.guardShell.gate(opts)
|
|
357
|
+
* @since 0.7.13
|
|
358
|
+
* @status stable
|
|
359
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
360
|
+
* @related b.guardShell.validate, b.guardShell.sanitize, b.processSpawn
|
|
361
|
+
*
|
|
362
|
+
* Build a `b.gateContract` gate that screens `ctx.identifier` (or
|
|
363
|
+
* `ctx.arg`) before each spawn. Action chain: `serve` (no issues)
|
|
364
|
+
* → `audit-only` (warn-only) → `refuse` (any `critical` or `high`).
|
|
365
|
+
* No `sanitize` action — shell args cannot be repaired. Compose
|
|
366
|
+
* with `b.processSpawn` so each argv slot is gated before reaching
|
|
367
|
+
* the OS (the spawn primitive itself enforces `shell: false`; the
|
|
368
|
+
* gate enforces metacharacter cleanliness).
|
|
369
|
+
*
|
|
370
|
+
* @opts
|
|
371
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
372
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
373
|
+
* name: string, // override gate name in audit emissions
|
|
374
|
+
* posixMetaPolicy: "reject"|"audit"|"allow",
|
|
375
|
+
* cmdMetaPolicy: "reject"|"audit"|"allow",
|
|
376
|
+
* dollarSubstPolicy: "reject"|"audit"|"allow",
|
|
377
|
+
* processSubstPolicy:"reject"|"audit"|"allow",
|
|
378
|
+
* backtickPolicy: "reject"|"audit"|"allow",
|
|
379
|
+
* newlinePolicy: "reject"|"audit"|"allow",
|
|
380
|
+
* argHyphenPolicy: "reject"|"audit"|"allow",
|
|
381
|
+
* maxBytes: number,
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* var gate = b.guardShell.gate({ profile: "strict" });
|
|
385
|
+
*
|
|
386
|
+
* // Hostile arg — gate refuses before spawn.
|
|
387
|
+
* gate({ identifier: "safe; rm -rf /" }).then(function (rv) {
|
|
388
|
+
* rv.ok; // → false
|
|
389
|
+
* rv.action; // → "refuse"
|
|
390
|
+
* });
|
|
391
|
+
*
|
|
392
|
+
* // Benign arg — gate serves.
|
|
393
|
+
* gate({ identifier: "safe-arg-value" }).then(function (rv) {
|
|
394
|
+
* rv.action; // → "serve"
|
|
395
|
+
* });
|
|
396
|
+
*/
|
|
263
397
|
function gate(opts) {
|
|
264
398
|
opts = _resolveOpts(opts);
|
|
265
399
|
return gateContract.buildGuardGate(
|
|
@@ -283,14 +417,83 @@ function gate(opts) {
|
|
|
283
417
|
});
|
|
284
418
|
}
|
|
285
419
|
|
|
420
|
+
/**
|
|
421
|
+
* @primitive b.guardShell.buildProfile
|
|
422
|
+
* @signature b.guardShell.buildProfile(opts)
|
|
423
|
+
* @since 0.7.13
|
|
424
|
+
* @status stable
|
|
425
|
+
* @related b.guardShell.gate, b.guardShell.compliancePosture
|
|
426
|
+
*
|
|
427
|
+
* Compose a derived guardShell profile from one or more named bases
|
|
428
|
+
* plus inline overrides. `opts.extends` is a profile name
|
|
429
|
+
* (`"strict"` / `"balanced"` / `"permissive"`) or an array of
|
|
430
|
+
* names; later entries shadow earlier ones. Inline `opts` keys win
|
|
431
|
+
* last. Used to keep operator-defined profiles traceable to a
|
|
432
|
+
* baseline rather than re-typing every key.
|
|
433
|
+
*
|
|
434
|
+
* @opts
|
|
435
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
436
|
+
* ...: any guardShell key, // inline override of resolved keys
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* var custom = b.guardShell.buildProfile({
|
|
440
|
+
* extends: "balanced",
|
|
441
|
+
* argHyphenPolicy: "reject",
|
|
442
|
+
* });
|
|
443
|
+
* custom.argHyphenPolicy; // → "reject"
|
|
444
|
+
* custom.dollarSubstPolicy; // → "reject"
|
|
445
|
+
*/
|
|
286
446
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
287
447
|
|
|
448
|
+
/**
|
|
449
|
+
* @primitive b.guardShell.compliancePosture
|
|
450
|
+
* @signature b.guardShell.compliancePosture(name)
|
|
451
|
+
* @since 0.7.13
|
|
452
|
+
* @status stable
|
|
453
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
454
|
+
* @related b.guardShell.gate, b.guardShell.buildProfile
|
|
455
|
+
*
|
|
456
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
457
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
458
|
+
* the posture object — the caller may mutate freely. Throws
|
|
459
|
+
* `GuardShellError("shell.bad-posture")` on unknown name.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* var posture = b.guardShell.compliancePosture("hipaa");
|
|
463
|
+
* posture.posixMetaPolicy; // → "reject"
|
|
464
|
+
* posture.forensicSnippetBytes; // → 256
|
|
465
|
+
*/
|
|
288
466
|
function compliancePosture(name) {
|
|
289
467
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
290
468
|
_err, "shell");
|
|
291
469
|
}
|
|
292
470
|
|
|
293
471
|
var _shellRulePacks = gateContract.makeRulePackLoader(GuardShellError, "shell");
|
|
472
|
+
/**
|
|
473
|
+
* @primitive b.guardShell.loadRulePack
|
|
474
|
+
* @signature b.guardShell.loadRulePack(pack)
|
|
475
|
+
* @since 0.7.13
|
|
476
|
+
* @status stable
|
|
477
|
+
* @related b.guardShell.gate
|
|
478
|
+
*
|
|
479
|
+
* Register an operator-supplied rule pack with the guardShell
|
|
480
|
+
* registry. The pack is identified by `pack.id` (non-empty string)
|
|
481
|
+
* and stored for later inspection / dispatch by gates that opt in
|
|
482
|
+
* via `opts.rulePackId`. Returns the pack object unchanged on
|
|
483
|
+
* success; throws `GuardShellError("shell.bad-opt")` when `pack`
|
|
484
|
+
* is missing or `pack.id` is not a non-empty string.
|
|
485
|
+
*
|
|
486
|
+
* @example
|
|
487
|
+
* var pack = b.guardShell.loadRulePack({
|
|
488
|
+
* id: "no-leading-dash",
|
|
489
|
+
* rules: [
|
|
490
|
+
* { id: "leading-dash", severity: "high",
|
|
491
|
+
* detect: function (arg) { return arg.charAt(0) === "-"; },
|
|
492
|
+
* reason: "argument starts with `-` flag prefix" },
|
|
493
|
+
* ],
|
|
494
|
+
* });
|
|
495
|
+
* pack.id; // → "no-leading-dash"
|
|
496
|
+
*/
|
|
294
497
|
var loadRulePack = _shellRulePacks.load;
|
|
295
498
|
|
|
296
499
|
module.exports = {
|
package/lib/guard-svg.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardSvg
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Svg
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* SVG content-safety primitive — defends against XXE / billion-laughs
|
|
9
|
+
* entity expansion, SSRF via `xlink:href`, animation-href injection
|
|
10
|
+
* (the `<animate attributeName="href" ...>` retroactive-poisoning
|
|
11
|
+
* class), embedded `<script>` / `<foreignObject>` namespace-shift
|
|
12
|
+
* escape hatches, dangerous URL schemes, CSS injection in style
|
|
13
|
+
* attributes, SVGZ compressed payloads, and Trojan-Source bidi /
|
|
14
|
+
* zero-width / null-byte threats.
|
|
11
15
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
16
|
+
* Element + attribute allowlist with strict default (text + shape
|
|
17
|
+
* primitives only). Profiles `strict` / `balanced` / `permissive`
|
|
18
|
+
* compose with compliance postures `hipaa` / `pci-dss` / `gdpr` /
|
|
19
|
+
* `soc2`. Integrates with `b.fileUpload` and `b.staticServe`'s
|
|
20
|
+
* contentSafety hook by default.
|
|
21
|
+
*
|
|
22
|
+
* Source-of-truth references: Fortinet anatomy of SVG attack
|
|
23
|
+
* surface; Angular GHSA-jrmj-c5cx-3cw6 + GHSA-v4hv-rgfq-gp49 SVG
|
|
24
|
+
* animation/href XSS; SVGO CVE-2026-29074 billion-laughs DoS;
|
|
25
|
+
* siyuan-note GHSA-5hc8-qmg8-pw27 animate-element sanitizer bypass;
|
|
26
|
+
* cure53/DOMPurify issue #233 xlink:href filtering; insertScript
|
|
27
|
+
* SVG fun-time series; svg2raster-cheatsheet SSRF guide.
|
|
15
28
|
*
|
|
16
29
|
* Threat catalog covered:
|
|
17
30
|
*
|
|
@@ -88,6 +101,9 @@
|
|
|
88
101
|
* Threat-detection regex literals are composed PROGRAMMATICALLY from
|
|
89
102
|
* numeric codepoint range tables. Source file never embeds attack
|
|
90
103
|
* characters themselves.
|
|
104
|
+
*
|
|
105
|
+
* @card
|
|
106
|
+
* SVG content-safety primitive — defends against XXE / billion-laughs entity expansion, SSRF via `xlink:href`, animation-href injection (the `<animate attributeName="href" ...>` retroactive-poisoning class), embedded `<script>` / `<foreignObject>` namespace-shift escape hatches,...
|
|
91
107
|
*/
|
|
92
108
|
|
|
93
109
|
var codepointClass = require("./codepoint-class");
|
|
@@ -890,6 +906,51 @@ function _sanitize(input, opts) {
|
|
|
890
906
|
|
|
891
907
|
// ---- Public surface ----
|
|
892
908
|
|
|
909
|
+
/**
|
|
910
|
+
* @primitive b.guardSvg.validate
|
|
911
|
+
* @signature b.guardSvg.validate(input, opts)
|
|
912
|
+
* @since 0.7.7
|
|
913
|
+
* @status stable
|
|
914
|
+
* @related b.guardSvg.sanitize, b.guardSvg.gate
|
|
915
|
+
*
|
|
916
|
+
* Inspect an SVG payload (string or Buffer) and return
|
|
917
|
+
* `{ ok, issues }` describing every threat the parser found. Never
|
|
918
|
+
* throws on hostile input — callers see the full issue list and
|
|
919
|
+
* decide whether to refuse, sanitize, or audit.
|
|
920
|
+
*
|
|
921
|
+
* Issues carry `kind` / `severity` / `ruleId` / `location` /
|
|
922
|
+
* `snippet`. Severities `critical` and `high` are the gate's
|
|
923
|
+
* refuse / sanitize signal; `warn` is audit-only.
|
|
924
|
+
*
|
|
925
|
+
* @opts
|
|
926
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
927
|
+
* compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
928
|
+
* allowedTags: Array<string>,
|
|
929
|
+
* allowedAttrs: Array<string>,
|
|
930
|
+
* urlSchemes: Array<string>,
|
|
931
|
+
* allowImageData: boolean,
|
|
932
|
+
* allowExternalRefs: boolean,
|
|
933
|
+
* allowAnimation: boolean,
|
|
934
|
+
* maxBytes: number,
|
|
935
|
+
* maxAttrValueBytes: number,
|
|
936
|
+
* maxElementCount: number,
|
|
937
|
+
* maxUseDepth: number,
|
|
938
|
+
* maxAttrsPerTag: number,
|
|
939
|
+
*
|
|
940
|
+
* @example
|
|
941
|
+
* var rv = b.guardSvg.validate(
|
|
942
|
+
* '<svg><script>alert(1)</script></svg>',
|
|
943
|
+
* { profile: "strict" });
|
|
944
|
+
* rv.ok; // → false
|
|
945
|
+
* rv.issues[0].kind; // → "dangerous-tag"
|
|
946
|
+
* rv.issues[0].severity; // → "critical"
|
|
947
|
+
*
|
|
948
|
+
* var clean = b.guardSvg.validate(
|
|
949
|
+
* '<svg><circle r="10"/></svg>',
|
|
950
|
+
* { profile: "strict" });
|
|
951
|
+
* clean.ok; // → true
|
|
952
|
+
* clean.issues.length; // → 0
|
|
953
|
+
*/
|
|
893
954
|
function validate(input, opts) {
|
|
894
955
|
opts = _resolveOpts(opts);
|
|
895
956
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -901,6 +962,46 @@ function validate(input, opts) {
|
|
|
901
962
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
902
963
|
}
|
|
903
964
|
|
|
965
|
+
/**
|
|
966
|
+
* @primitive b.guardSvg.sanitize
|
|
967
|
+
* @signature b.guardSvg.sanitize(input, opts)
|
|
968
|
+
* @since 0.7.7
|
|
969
|
+
* @status stable
|
|
970
|
+
* @related b.guardSvg.validate, b.guardSvg.gate
|
|
971
|
+
*
|
|
972
|
+
* Best-effort sanitizer. Strips dangerous tags (`<script>`,
|
|
973
|
+
* `<foreignObject>`, plugin embeds, animation elements when the
|
|
974
|
+
* profile forbids them), event-handler attributes (every
|
|
975
|
+
* `/^on[a-z]/`), URL attributes carrying `javascript:` /
|
|
976
|
+
* `vbscript:` / non-allowlisted schemes, CSS injection inside
|
|
977
|
+
* `style="..."`, DOCTYPE / `<!ENTITY>` / processing instructions /
|
|
978
|
+
* CDATA, bidi / control / null-byte / zero-width threats per the
|
|
979
|
+
* profile's char policies. Throws `GuardSvgError` (`svg.svgz`) on
|
|
980
|
+
* SVGZ input — operators must ungzip first then re-sanitize.
|
|
981
|
+
*
|
|
982
|
+
* @opts
|
|
983
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
984
|
+
* compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
985
|
+
* allowedTags: Array<string>,
|
|
986
|
+
* urlSchemes: Array<string>,
|
|
987
|
+
* allowImageData: boolean,
|
|
988
|
+
* allowExternalRefs: boolean,
|
|
989
|
+
* allowAnimation: boolean,
|
|
990
|
+
* maxBytes: number,
|
|
991
|
+
*
|
|
992
|
+
* @example
|
|
993
|
+
* var safe = b.guardSvg.sanitize(
|
|
994
|
+
* '<svg><script>alert(1)</script><circle r="10"/></svg>',
|
|
995
|
+
* { profile: "balanced" });
|
|
996
|
+
* safe;
|
|
997
|
+
* // → '<svg><circle r="10"></circle></svg>'
|
|
998
|
+
*
|
|
999
|
+
* // Event-handler attributes are stripped:
|
|
1000
|
+
* var clean = b.guardSvg.sanitize(
|
|
1001
|
+
* '<svg onload="x()"><rect width="10" height="10"/></svg>',
|
|
1002
|
+
* { profile: "strict" });
|
|
1003
|
+
* /onload/.test(clean); // → false
|
|
1004
|
+
*/
|
|
904
1005
|
function sanitize(input, opts) {
|
|
905
1006
|
opts = _resolveOpts(opts);
|
|
906
1007
|
if (typeof input !== "string" && !Buffer.isBuffer(input)) {
|
|
@@ -909,6 +1010,51 @@ function sanitize(input, opts) {
|
|
|
909
1010
|
return _sanitize(input, opts);
|
|
910
1011
|
}
|
|
911
1012
|
|
|
1013
|
+
/**
|
|
1014
|
+
* @primitive b.guardSvg.gate
|
|
1015
|
+
* @signature b.guardSvg.gate(opts)
|
|
1016
|
+
* @since 0.7.7
|
|
1017
|
+
* @status stable
|
|
1018
|
+
* @related b.guardSvg.validate, b.guardSvg.sanitize, b.fileUpload, b.staticServe
|
|
1019
|
+
*
|
|
1020
|
+
* Build a uniform gate over guard-* family contract. Returns an
|
|
1021
|
+
* async function whose verdict is `{ ok, action, issues?,
|
|
1022
|
+
* sanitized? }` where `action` is `serve` / `audit-only` /
|
|
1023
|
+
* `sanitize` / `refuse`. SVGZ inputs always refuse — operators
|
|
1024
|
+
* ungzip and re-gate the inner SVG. External `xlink:href` on
|
|
1025
|
+
* `<use>` / `<feImage>` refuses under `strict` (SSRF + XSS chain).
|
|
1026
|
+
* Sanitize path is taken when no policy is set to `reject` and the
|
|
1027
|
+
* issue set is repairable.
|
|
1028
|
+
*
|
|
1029
|
+
* @opts
|
|
1030
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
1031
|
+
* compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
1032
|
+
* mode: "enforce" | "audit-only",
|
|
1033
|
+
* audit: AuditEmitter,
|
|
1034
|
+
* observability: ObservabilityEmitter,
|
|
1035
|
+
* forensicEvidenceStore: ForensicStore,
|
|
1036
|
+
* allowedTags: Array<string>,
|
|
1037
|
+
* urlSchemes: Array<string>,
|
|
1038
|
+
* allowExternalRefs: boolean,
|
|
1039
|
+
* allowAnimation: boolean,
|
|
1040
|
+
* maxBytes: number,
|
|
1041
|
+
* maxRuntimeMs: number,
|
|
1042
|
+
*
|
|
1043
|
+
* @example
|
|
1044
|
+
* var g = b.guardSvg.gate({ profile: "strict" });
|
|
1045
|
+
* var verdict = await g({
|
|
1046
|
+
* bytes: Buffer.from('<svg><circle r="10"/></svg>', "utf8"),
|
|
1047
|
+
* });
|
|
1048
|
+
* verdict.action; // → "serve"
|
|
1049
|
+
*
|
|
1050
|
+
* // Refuses external xlink:href under strict:
|
|
1051
|
+
* var refuse = await g({
|
|
1052
|
+
* bytes: Buffer.from(
|
|
1053
|
+
* '<svg><use xlink:href="https://evil.example/x.svg#a"/></svg>',
|
|
1054
|
+
* "utf8"),
|
|
1055
|
+
* });
|
|
1056
|
+
* refuse.action; // → "refuse"
|
|
1057
|
+
*/
|
|
912
1058
|
function gate(opts) {
|
|
913
1059
|
opts = _resolveOpts(opts);
|
|
914
1060
|
return gateContract.buildGuardGate(
|
|
@@ -949,6 +1095,27 @@ function gate(opts) {
|
|
|
949
1095
|
|
|
950
1096
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
951
1097
|
|
|
1098
|
+
/**
|
|
1099
|
+
* @primitive b.guardSvg.compliancePosture
|
|
1100
|
+
* @signature b.guardSvg.compliancePosture(name)
|
|
1101
|
+
* @since 0.7.7
|
|
1102
|
+
* @status stable
|
|
1103
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
1104
|
+
* @related b.guardSvg.gate, b.compliance.set
|
|
1105
|
+
*
|
|
1106
|
+
* Look up a regulatory-posture override. Returns a shallow clone
|
|
1107
|
+
* of the named posture's option overlay; throws
|
|
1108
|
+
* `GuardSvgError` (`svg.bad-posture`) on unknown names. Operators
|
|
1109
|
+
* pass it through `gate({ compliancePosture: "hipaa" })` rather
|
|
1110
|
+
* than calling this directly — exposed for introspection and
|
|
1111
|
+
* audit-evidence collection.
|
|
1112
|
+
*
|
|
1113
|
+
* @example
|
|
1114
|
+
* var hipaa = b.guardSvg.compliancePosture("hipaa");
|
|
1115
|
+
* hipaa.bidiPolicy; // → "reject"
|
|
1116
|
+
* hipaa.allowExternalRefs; // → false
|
|
1117
|
+
* hipaa.allowAnimation; // → false
|
|
1118
|
+
*/
|
|
952
1119
|
function compliancePosture(name) {
|
|
953
1120
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "svg");
|
|
954
1121
|
}
|