@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-uuid.js
CHANGED
|
@@ -1,38 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardUuid
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Uuid
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (
|
|
7
|
+
* @intro
|
|
8
|
+
* UUID identifier-safety guard. Validates user-supplied UUID
|
|
9
|
+
* strings per RFC 9562 (May 2024 — obsoletes RFC 4122) and
|
|
10
|
+
* refuses non-RFC shapes that downstream parsers routinely
|
|
11
|
+
* misinterpret. KIND="identifier" — the gate consumes
|
|
12
|
+
* `ctx.identifier` (or `ctx.uuid`).
|
|
8
13
|
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* - Max UUID (RFC 9562 §5.10 — all FF) — sentinel value with the
|
|
24
|
-
* same semantic risk as nil.
|
|
25
|
-
* - urn:uuid: prefix (RFC 4122 §3) — when not requested by the
|
|
26
|
-
* caller, can disguise a UUID inside a URN-shape parser.
|
|
27
|
-
* - Microsoft GUID braces `{...}` — disguise a UUID inside a
|
|
28
|
-
* COM-style serialization parser.
|
|
29
|
-
* - BIDI / zero-width / control / null-byte — universal-refuse.
|
|
14
|
+
* Threat catalog: wrong length / shape (canonical 36-char
|
|
15
|
+
* hyphenated, 32-char hyphenless, 38-char braced, or
|
|
16
|
+
* `urn:uuid:` prefixed — anything else is malformed); wrong
|
|
17
|
+
* character class (non-hex anywhere); invalid version field
|
|
18
|
+
* (RFC 9562 §4.2 defines 1-8; 0 and 9-F are reserved /
|
|
19
|
+
* unassigned and indicate hand-rolled or attacker-shaped IDs);
|
|
20
|
+
* variant bits (RFC 9562 §4.1 — only 10xx is the canonical
|
|
21
|
+
* variant; NCS-reserved 0xxx, Microsoft 110x, future 111x often
|
|
22
|
+
* indicate non-UUID payloads coerced into the slot); nil UUID
|
|
23
|
+
* (§5.9 all zeros — usually "no UUID set", masks missing-key
|
|
24
|
+
* bugs when passed through); max UUID (§5.10 all FF — sentinel
|
|
25
|
+
* with the same semantic risk as nil); `urn:uuid:` prefix
|
|
26
|
+
* smuggling; Microsoft GUID braces `{...}` smuggling;
|
|
27
|
+
* BIDI / zero-width / C0-control / null-byte universal-refuse.
|
|
30
28
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
29
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
30
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`.
|
|
31
|
+
*
|
|
32
|
+
* @card
|
|
33
|
+
* UUID identifier-safety guard.
|
|
36
34
|
*/
|
|
37
35
|
|
|
38
36
|
var codepointClass = require("./codepoint-class");
|
|
@@ -292,6 +290,46 @@ function _detectIssues(input, opts) {
|
|
|
292
290
|
return issues;
|
|
293
291
|
}
|
|
294
292
|
|
|
293
|
+
/**
|
|
294
|
+
* @primitive b.guardUuid.validate
|
|
295
|
+
* @signature b.guardUuid.validate(input, opts?)
|
|
296
|
+
* @since 0.7.44
|
|
297
|
+
* @status stable
|
|
298
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
299
|
+
* @related b.guardUuid.sanitize, b.guardUuid.gate, b.uuid.v4, b.uuid.v7
|
|
300
|
+
*
|
|
301
|
+
* Inspect a UUID string against the resolved profile and return
|
|
302
|
+
* `{ ok, issues }`. Each issue carries `kind` / `severity`
|
|
303
|
+
* (`critical` | `high` | `medium` | `low`) / `ruleId` / `snippet`.
|
|
304
|
+
* Non-string input returns a single `uuid.bad-input` issue rather
|
|
305
|
+
* than throwing — callers that prefer an exception use
|
|
306
|
+
* `b.guardUuid.sanitize`.
|
|
307
|
+
*
|
|
308
|
+
* @opts
|
|
309
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
310
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
311
|
+
* bidiPolicy: "reject"|"strip"|"audit"|"allow",
|
|
312
|
+
* controlPolicy: "reject"|"strip"|"allow",
|
|
313
|
+
* nullBytePolicy: "reject"|"strip"|"allow",
|
|
314
|
+
* zeroWidthPolicy: "reject"|"strip"|"allow",
|
|
315
|
+
* formatPolicy: "hyphenated"|"hyphenless"|"braced"|"urn"|"hyphenated-only"|"any",
|
|
316
|
+
* versionPolicy: "reject-unassigned"|"audit"|"allow",
|
|
317
|
+
* variantPolicy: "reject-non-rfc"|"audit"|"allow",
|
|
318
|
+
* nilPolicy: "reject"|"audit"|"allow",
|
|
319
|
+
* maxPolicy: "reject"|"audit"|"allow",
|
|
320
|
+
* urnPolicy: "reject"|"audit"|"allow",
|
|
321
|
+
* maxBytes: number,
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* var rv = b.guardUuid.validate("550e8400-e29b-41d4-a716-446655440000",
|
|
325
|
+
* { profile: "strict" });
|
|
326
|
+
* rv.ok; // → true
|
|
327
|
+
*
|
|
328
|
+
* var bad = b.guardUuid.validate("00000000-0000-0000-0000-000000000000",
|
|
329
|
+
* { profile: "strict" });
|
|
330
|
+
* bad.ok; // → false
|
|
331
|
+
* bad.issues[0].ruleId; // → "uuid.nil"
|
|
332
|
+
*/
|
|
295
333
|
function validate(input, opts) {
|
|
296
334
|
opts = _resolveOpts(opts);
|
|
297
335
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -308,6 +346,36 @@ function validate(input, opts) {
|
|
|
308
346
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
309
347
|
}
|
|
310
348
|
|
|
349
|
+
/**
|
|
350
|
+
* @primitive b.guardUuid.sanitize
|
|
351
|
+
* @signature b.guardUuid.sanitize(input, opts?)
|
|
352
|
+
* @since 0.7.44
|
|
353
|
+
* @status stable
|
|
354
|
+
* @related b.guardUuid.validate, b.guardUuid.gate
|
|
355
|
+
*
|
|
356
|
+
* Normalize a UUID to canonical hyphenated lowercase form. Strips
|
|
357
|
+
* Microsoft GUID braces `{...}` and the `urn:uuid:` prefix. Throws
|
|
358
|
+
* `GuardUuidError` when any `critical` or `high` issue fires
|
|
359
|
+
* (nil / max sentinel under reject, unassigned version, non-RFC
|
|
360
|
+
* variant). Use `validate` to inspect issues without throwing.
|
|
361
|
+
*
|
|
362
|
+
* @opts
|
|
363
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
364
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
365
|
+
* ...: same shape as b.guardUuid.validate opts,
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* var safe = b.guardUuid.sanitize("urn:uuid:550E8400-E29B-41D4-A716-446655440000",
|
|
369
|
+
* { profile: "balanced" });
|
|
370
|
+
* safe; // → "550e8400-e29b-41d4-a716-446655440000"
|
|
371
|
+
*
|
|
372
|
+
* try {
|
|
373
|
+
* b.guardUuid.sanitize("ffffffff-ffff-ffff-ffff-ffffffffffff",
|
|
374
|
+
* { profile: "strict" });
|
|
375
|
+
* } catch (e) {
|
|
376
|
+
* e.code; // → "uuid.max"
|
|
377
|
+
* }
|
|
378
|
+
*/
|
|
311
379
|
function sanitize(input, opts) {
|
|
312
380
|
opts = _resolveOpts(opts);
|
|
313
381
|
if (typeof input !== "string") {
|
|
@@ -330,6 +398,35 @@ function sanitize(input, opts) {
|
|
|
330
398
|
hex.slice(20); // allow:raw-byte-literal — UUID hex slice positions
|
|
331
399
|
}
|
|
332
400
|
|
|
401
|
+
/**
|
|
402
|
+
* @primitive b.guardUuid.gate
|
|
403
|
+
* @signature b.guardUuid.gate(opts?)
|
|
404
|
+
* @since 0.7.44
|
|
405
|
+
* @status stable
|
|
406
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
407
|
+
* @related b.guardUuid.validate, b.guardUuid.sanitize, b.guardAll.gate
|
|
408
|
+
*
|
|
409
|
+
* Build an async gate `(ctx) -> { ok, action, issues }` consumable
|
|
410
|
+
* by `b.guardAll`, ID validators, and any host that handles
|
|
411
|
+
* UUID-shaped tokens. The gate reads `ctx.identifier` (or
|
|
412
|
+
* `ctx.uuid`), runs `validate`, and maps severity to action: zero
|
|
413
|
+
* issues `serve`; only low/medium `audit-only`; any high/critical
|
|
414
|
+
* `refuse`.
|
|
415
|
+
*
|
|
416
|
+
* @opts
|
|
417
|
+
* name: string, // gate label for audit / observability
|
|
418
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
419
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
420
|
+
* ...: same shape as b.guardUuid.validate opts,
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* var g = b.guardUuid.gate({ profile: "strict" });
|
|
424
|
+
* var rv = await g({ identifier: "550e8400-e29b-41d4-a716-446655440000" });
|
|
425
|
+
* rv.action; // → "serve"
|
|
426
|
+
*
|
|
427
|
+
* var bad = await g({ identifier: "{550e8400-e29b-41d4-a716-446655440000}" });
|
|
428
|
+
* bad.action; // → "refuse"
|
|
429
|
+
*/
|
|
333
430
|
function gate(opts) {
|
|
334
431
|
opts = _resolveOpts(opts);
|
|
335
432
|
return gateContract.buildGuardGate(
|
|
@@ -353,14 +450,76 @@ function gate(opts) {
|
|
|
353
450
|
});
|
|
354
451
|
}
|
|
355
452
|
|
|
453
|
+
/**
|
|
454
|
+
* @primitive b.guardUuid.buildProfile
|
|
455
|
+
* @signature b.guardUuid.buildProfile(opts)
|
|
456
|
+
* @since 0.7.44
|
|
457
|
+
* @status stable
|
|
458
|
+
* @related b.guardUuid.gate, b.guardUuid.compliancePosture
|
|
459
|
+
*
|
|
460
|
+
* Compose a derived profile from one or more named bases plus
|
|
461
|
+
* inline overrides. `opts.extends` is a profile name or array of
|
|
462
|
+
* names (later entries shadow earlier ones); inline keys win last.
|
|
463
|
+
*
|
|
464
|
+
* @opts
|
|
465
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
466
|
+
* ...: any guard-uuid key, // inline override of resolved keys
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* var custom = b.guardUuid.buildProfile({
|
|
470
|
+
* extends: "balanced",
|
|
471
|
+
* formatPolicy: "hyphenated-only",
|
|
472
|
+
* nilPolicy: "audit",
|
|
473
|
+
* });
|
|
474
|
+
* custom.formatPolicy; // → "hyphenated-only"
|
|
475
|
+
* custom.nilPolicy; // → "audit"
|
|
476
|
+
*/
|
|
356
477
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
357
478
|
|
|
479
|
+
/**
|
|
480
|
+
* @primitive b.guardUuid.compliancePosture
|
|
481
|
+
* @signature b.guardUuid.compliancePosture(name)
|
|
482
|
+
* @since 0.7.44
|
|
483
|
+
* @status stable
|
|
484
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
485
|
+
* @related b.guardUuid.gate, b.guardUuid.buildProfile
|
|
486
|
+
*
|
|
487
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
488
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
489
|
+
* the posture object — the caller may mutate freely. Throws
|
|
490
|
+
* `GuardUuidError("uuid.bad-posture")` on unknown name.
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* var posture = b.guardUuid.compliancePosture("hipaa");
|
|
494
|
+
* posture.nilPolicy; // → "reject"
|
|
495
|
+
*/
|
|
358
496
|
function compliancePosture(name) {
|
|
359
497
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
360
498
|
_err, "uuid");
|
|
361
499
|
}
|
|
362
500
|
|
|
363
501
|
var _uuidRulePacks = gateContract.makeRulePackLoader(GuardUuidError, "uuid");
|
|
502
|
+
/**
|
|
503
|
+
* @primitive b.guardUuid.loadRulePack
|
|
504
|
+
* @signature b.guardUuid.loadRulePack(pack)
|
|
505
|
+
* @since 0.7.44
|
|
506
|
+
* @status stable
|
|
507
|
+
* @related b.guardUuid.gate
|
|
508
|
+
*
|
|
509
|
+
* Register an operator-supplied rule pack with the guard-uuid
|
|
510
|
+
* registry. The pack is identified by `pack.id` (non-empty
|
|
511
|
+
* string) and stored for later inspection / dispatch by gates
|
|
512
|
+
* that opt in via `opts.rulePackId`. Throws
|
|
513
|
+
* `GuardUuidError("uuid.bad-opt")` when `pack` is missing or
|
|
514
|
+
* `pack.id` is not a non-empty string.
|
|
515
|
+
*
|
|
516
|
+
* @example
|
|
517
|
+
* var pack = b.guardUuid.loadRulePack({
|
|
518
|
+
* id: "v7-only",
|
|
519
|
+
* allowedVersions: [7],
|
|
520
|
+
* });
|
|
521
|
+
* pack.id; // → "v7-only"
|
|
522
|
+
*/
|
|
364
523
|
var loadRulePack = _uuidRulePacks.load;
|
|
365
524
|
|
|
366
525
|
module.exports = {
|
package/lib/guard-xml.js
CHANGED
|
@@ -1,45 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardXml
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Xml
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - CVE-2025-32415 libxml2 schema heap under-read
|
|
14
|
-
* - CVE-2025-27113 libxml2 NULL deref in pattern.c
|
|
15
|
-
* - CVE-2024-8176 libexpat stack overflow (recursive entity expansion)
|
|
7
|
+
* @intro
|
|
8
|
+
* XML content-safety guard — defends against the XXE / billion-
|
|
9
|
+
* laughs / external-entity / XSLT-exec catalog that has remained
|
|
10
|
+
* active for 20+ years and continues to ship CVEs through 2025-
|
|
11
|
+
* 2026. XML attack surface centers on the DOCTYPE subset, where
|
|
12
|
+
* entity declarations and external references convert a benign-
|
|
13
|
+
* looking XML document into a file-disclosure / SSRF / RCE / DoS
|
|
14
|
+
* primitive depending on the parser.
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* XXE / external entity (XML External Entity) defense:
|
|
17
|
+
* `<!ENTITY xxe SYSTEM "file:///etc/passwd">` and `SYSTEM` /
|
|
18
|
+
* `PUBLIC` identifiers pointing at `file://` / `http://` /
|
|
19
|
+
* `https://` / `ftp://` / `gopher://` / `jar://` / `netdoc://`
|
|
20
|
+
* are refused regardless of profile. CVE-2026-24400 AssertJ
|
|
21
|
+
* `toXmlDocument` default parser, CVE-2025-3225 sitemap parser,
|
|
22
|
+
* CVE-2024-1455 LangChain XXE, and CVE-2024-25062 libxml2 UAF
|
|
23
|
+
* with DTD + XInclude all fit this shape.
|
|
20
24
|
*
|
|
21
|
-
*
|
|
25
|
+
* Billion-laughs / entity-expansion DoS: `<!ENTITY lol "lol">` +
|
|
26
|
+
* `<!ENTITY lol2 "&lol;&lol;...">` recursive declarations expand
|
|
27
|
+
* exponentially when the parser dereferences. Refused via the
|
|
28
|
+
* blanket `<!ENTITY>` rule; parameter entities (`<!ENTITY %>`
|
|
29
|
+
* prefix) get an additional out-of-band exfil tag. CVE-2024-8176
|
|
30
|
+
* libexpat stack overflow on recursive entity expansion +
|
|
31
|
+
* CVE-2025-24928 libxml2 stack overflow on DTD validation track
|
|
32
|
+
* the family.
|
|
22
33
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
34
|
+
* DTD external-entity refusal: every `<!DOCTYPE>` declaration is
|
|
35
|
+
* refused unconditionally — there is no safe DTD subset that
|
|
36
|
+
* defenders can enumerate against the parser-quirk landscape, so
|
|
37
|
+
* the only stable posture is to reject the surface entirely.
|
|
38
|
+
*
|
|
39
|
+
* XSLT / processing-instruction exec defense: `<?xml-stylesheet
|
|
40
|
+
* href="...">` and other `<?PI ?>` shapes can route the document
|
|
41
|
+
* through an XSLT processor with `document()` / `xsl:include` /
|
|
42
|
+
* `xsl:import` — full file-disclosure + SSRF surface. Flagged
|
|
43
|
+
* under balanced; refused under strict (after the standard
|
|
44
|
+
* `<?xml ... ?>` declaration is stripped).
|
|
45
|
+
*
|
|
46
|
+
* XInclude (`<xi:include href="...">`) and `xsi:schemaLocation` /
|
|
47
|
+
* `xsi:noNamespaceSchemaLocation` are operator-controlled fetch
|
|
48
|
+
* surfaces; XML signature elements (`xmldsig`) require operator
|
|
49
|
+
* defense against signature-wrapping attacks. CDATA sections
|
|
50
|
+
* often hide payloads from naive scanners.
|
|
51
|
+
*
|
|
52
|
+
* Anti-DoS caps: total document size (`maxBytes`), nesting depth
|
|
53
|
+
* (`maxDepth`), element count (`maxElements`), attribute count per
|
|
54
|
+
* element (`maxAttrsPerElement`), and attribute value length
|
|
55
|
+
* (`maxAttrValueBytes`).
|
|
56
|
+
*
|
|
57
|
+
* Bidi / null / control / zero-width character threats route
|
|
58
|
+
* through the shared lib/codepoint-class detector.
|
|
59
|
+
*
|
|
60
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
61
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. Even under
|
|
62
|
+
* `permissive`, DOCTYPE / ENTITY / external-entity refusal stays
|
|
63
|
+
* on — the billion-laughs and XXE classes have no safe permissive
|
|
64
|
+
* posture.
|
|
65
|
+
*
|
|
66
|
+
* @card
|
|
67
|
+
* XML content-safety guard — defends against the XXE / billion- laughs / external-entity / XSLT-exec catalog that has remained active for 20+ years and continues to ship CVEs through 2025- 2026.
|
|
43
68
|
*/
|
|
44
69
|
|
|
45
70
|
var codepointClass = require("./codepoint-class");
|
|
@@ -299,6 +324,59 @@ function _detectIssues(input, opts) {
|
|
|
299
324
|
|
|
300
325
|
// ---- Public surface ----
|
|
301
326
|
|
|
327
|
+
/**
|
|
328
|
+
* @primitive b.guardXml.validate
|
|
329
|
+
* @signature b.guardXml.validate(input, opts?)
|
|
330
|
+
* @since 0.7.15
|
|
331
|
+
* @status stable
|
|
332
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
333
|
+
* @related b.guardXml.sanitize, b.guardXml.gate
|
|
334
|
+
*
|
|
335
|
+
* Inspect `input` (string of XML source) for the full guard-xml
|
|
336
|
+
* threat catalog without invoking a parser. Returns
|
|
337
|
+
* `{ ok, issues, severities }` where `issues` enumerates every
|
|
338
|
+
* DOCTYPE declaration, `<!ENTITY>` definition (including parameter
|
|
339
|
+
* entities), SYSTEM/PUBLIC external-entity reference, XInclude
|
|
340
|
+
* directive, xsi:schemaLocation hint, processing instruction (after
|
|
341
|
+
* the standard `<?xml ?>` declaration), CDATA section, XML signature
|
|
342
|
+
* element, and codepoint-class threat. Element / depth caps are
|
|
343
|
+
* estimated via tag-count + nesting heuristics — strict-mode rejects
|
|
344
|
+
* exceeding the configured caps without requiring a full parse.
|
|
345
|
+
*
|
|
346
|
+
* Profile-driven (`strict` / `balanced` / `permissive`) and posture-
|
|
347
|
+
* driven (`hipaa` / `pci-dss` / `gdpr` / `soc2`). Note that
|
|
348
|
+
* DOCTYPE / `<!ENTITY>` / external-entity refusal stays on under
|
|
349
|
+
* every profile — there is no safe permissive posture for the XXE
|
|
350
|
+
* + billion-laughs class.
|
|
351
|
+
*
|
|
352
|
+
* @opts
|
|
353
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
354
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
355
|
+
* doctypePolicy: "reject"|"audit"|"allow",
|
|
356
|
+
* entityPolicy: "reject"|"audit"|"allow",
|
|
357
|
+
* externalEntityPolicy: "reject"|"audit"|"allow",
|
|
358
|
+
* xincludePolicy: "reject"|"audit"|"allow",
|
|
359
|
+
* schemaLocationPolicy: "reject"|"audit"|"allow",
|
|
360
|
+
* processingInstrPolicy: "reject"|"audit"|"allow",
|
|
361
|
+
* cdataPolicy: "reject"|"audit"|"allow",
|
|
362
|
+
* xmlDsigPolicy: "audit"|"allow",
|
|
363
|
+
* bidiPolicy: "reject"|"strip"|"audit"|"allow",
|
|
364
|
+
* controlPolicy: "reject"|"strip"|"allow",
|
|
365
|
+
* nullBytePolicy: "reject"|"strip"|"allow",
|
|
366
|
+
* zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
|
|
367
|
+
* maxBytes: number, // total source byte cap
|
|
368
|
+
* maxDepth: number, // estimated nesting depth cap
|
|
369
|
+
* maxElements: number, // total open-tag count cap
|
|
370
|
+
* maxAttrsPerElement: number, // attribute count cap per element
|
|
371
|
+
* maxAttrValueBytes: number, // per-attr-value length cap
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* var hostile = '<?xml version="1.0"?>\n' +
|
|
375
|
+
* '<!DOCTYPE r [<!ENTITY xx "yy">]>\n<r/>';
|
|
376
|
+
* var rv = b.guardXml.validate(hostile, { profile: "strict" });
|
|
377
|
+
* rv.ok; // → false
|
|
378
|
+
* rv.issues.some(function (i) { return i.kind === "doctype"; }); // → true
|
|
379
|
+
*/
|
|
302
380
|
function validate(input, opts) {
|
|
303
381
|
opts = _resolveOpts(opts);
|
|
304
382
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -315,6 +393,44 @@ function validate(input, opts) {
|
|
|
315
393
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
316
394
|
}
|
|
317
395
|
|
|
396
|
+
/**
|
|
397
|
+
* @primitive b.guardXml.sanitize
|
|
398
|
+
* @signature b.guardXml.sanitize(input, opts?)
|
|
399
|
+
* @since 0.7.15
|
|
400
|
+
* @status stable
|
|
401
|
+
* @related b.guardXml.validate, b.guardXml.gate
|
|
402
|
+
*
|
|
403
|
+
* Best-effort cleanup of `input` (string of XML source): strips
|
|
404
|
+
* codepoint-class threats per policy (BOM, bidi when
|
|
405
|
+
* `bidiPolicy: "strip"`, C0 controls when `controlPolicy: "strip"`,
|
|
406
|
+
* null bytes when `nullBytePolicy: "strip"`, zero-width characters
|
|
407
|
+
* when `zeroWidthPolicy: "strip"`). Throws `GuardXmlError` on any
|
|
408
|
+
* critical issue — DOCTYPE / `<!ENTITY>` / external-entity / param-
|
|
409
|
+
* entity shapes have no safe sanitization (the only correct response
|
|
410
|
+
* is refusal). The error code matches the triggering rule
|
|
411
|
+
* (`xml.doctype`, `xml.entity`, `xml.external-entity`, etc.).
|
|
412
|
+
*
|
|
413
|
+
* Sanitize is intentionally narrow: it cleans the character-class
|
|
414
|
+
* surface but never rewrites structural XML. Use `b.guardXml.gate`
|
|
415
|
+
* for the full sanitize-or-refuse action chain inside a request
|
|
416
|
+
* pipeline.
|
|
417
|
+
*
|
|
418
|
+
* @opts
|
|
419
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
420
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
421
|
+
* bidiPolicy: "reject"|"strip"|"audit"|"allow",
|
|
422
|
+
* controlPolicy: "reject"|"strip"|"allow",
|
|
423
|
+
* nullBytePolicy: "reject"|"strip"|"allow",
|
|
424
|
+
* zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* // Build hostile input programmatically so the source stays ASCII.
|
|
428
|
+
* var ZWSP = String.fromCharCode(0x200B);
|
|
429
|
+
* var clean = b.guardXml.sanitize("<root>hello" + ZWSP + "</root>", {
|
|
430
|
+
* profile: "balanced",
|
|
431
|
+
* });
|
|
432
|
+
* clean.indexOf(ZWSP) === -1; // → true
|
|
433
|
+
*/
|
|
318
434
|
function sanitize(input, opts) {
|
|
319
435
|
opts = _resolveOpts(opts);
|
|
320
436
|
if (typeof input !== "string") {
|
|
@@ -334,6 +450,43 @@ function sanitize(input, opts) {
|
|
|
334
450
|
return codepointClass.applyCharStripPolicies(input, opts);
|
|
335
451
|
}
|
|
336
452
|
|
|
453
|
+
/**
|
|
454
|
+
* @primitive b.guardXml.gate
|
|
455
|
+
* @signature b.guardXml.gate(opts?)
|
|
456
|
+
* @since 0.7.15
|
|
457
|
+
* @status stable
|
|
458
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
459
|
+
* @related b.guardXml.validate, b.guardXml.sanitize, b.staticServe.create, b.fileUpload.create
|
|
460
|
+
*
|
|
461
|
+
* Build a `b.gateContract` gate suitable for plugging into
|
|
462
|
+
* `b.staticServe({ contentSafety: { ".xml": gate } })`,
|
|
463
|
+
* `b.fileUpload({ contentSafety: { "application/xml": gate } })`,
|
|
464
|
+
* or any host primitive that consumes the gate-contract shape.
|
|
465
|
+
* Action chain on validation: `serve` (no issues) → `audit-only`
|
|
466
|
+
* (warn-only issues) → `sanitize` (high/critical when DOCTYPE /
|
|
467
|
+
* ENTITY / external-entity policies are not `reject`, which strips
|
|
468
|
+
* codepoint-class threats only) → `refuse` (any of those structural
|
|
469
|
+
* policies is reject and a critical issue fired, or sanitize threw).
|
|
470
|
+
*
|
|
471
|
+
* Under strict and balanced both, DOCTYPE / ENTITY / external-entity
|
|
472
|
+
* are reject — so the gate jumps from `audit-only` straight to
|
|
473
|
+
* `refuse` for the XXE / billion-laughs class. Permissive allows
|
|
474
|
+
* downgrading XInclude / schemaLocation / PI / CDATA to `audit`,
|
|
475
|
+
* but never DOCTYPE / ENTITY / external-entity.
|
|
476
|
+
*
|
|
477
|
+
* @opts
|
|
478
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
479
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
480
|
+
* name: string, // gate identity for audit / observability
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* var xmlGate = b.guardXml.gate({ profile: "strict" });
|
|
484
|
+
* var hostile = Buffer.from(
|
|
485
|
+
* '<?xml version="1.0"?>\n<!DOCTYPE r [<!ENTITY a "b">]>\n<r/>',
|
|
486
|
+
* "utf8");
|
|
487
|
+
* var verdict = await xmlGate.check({ bytes: hostile });
|
|
488
|
+
* verdict.action; // → "refuse"
|
|
489
|
+
*/
|
|
337
490
|
function gate(opts) {
|
|
338
491
|
opts = _resolveOpts(opts);
|
|
339
492
|
return gateContract.buildGuardGate(
|
|
@@ -365,13 +518,83 @@ function gate(opts) {
|
|
|
365
518
|
});
|
|
366
519
|
}
|
|
367
520
|
|
|
521
|
+
/**
|
|
522
|
+
* @primitive b.guardXml.buildProfile
|
|
523
|
+
* @signature b.guardXml.buildProfile(opts)
|
|
524
|
+
* @since 0.7.15
|
|
525
|
+
* @status stable
|
|
526
|
+
* @related b.guardXml.gate, b.guardXml.compliancePosture
|
|
527
|
+
*
|
|
528
|
+
* Compose a derived profile from one or more named bases plus
|
|
529
|
+
* inline overrides. `opts.extends` is a profile name (`"strict"` /
|
|
530
|
+
* `"balanced"` / `"permissive"`) or an array of names; later entries
|
|
531
|
+
* shadow earlier ones. Inline `opts` keys win last. Used to keep
|
|
532
|
+
* operator-defined profiles traceable to a baseline rather than re-
|
|
533
|
+
* typing every key.
|
|
534
|
+
*
|
|
535
|
+
* @opts
|
|
536
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
537
|
+
* ...: any guard-xml key, // inline override of resolved keys
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* var custom = b.guardXml.buildProfile({
|
|
541
|
+
* extends: "balanced",
|
|
542
|
+
* cdataPolicy: "reject",
|
|
543
|
+
* maxElements: 4096,
|
|
544
|
+
* });
|
|
545
|
+
* custom.cdataPolicy; // → "reject"
|
|
546
|
+
* custom.maxElements; // → 4096
|
|
547
|
+
*/
|
|
368
548
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
369
549
|
|
|
550
|
+
/**
|
|
551
|
+
* @primitive b.guardXml.compliancePosture
|
|
552
|
+
* @signature b.guardXml.compliancePosture(name)
|
|
553
|
+
* @since 0.7.15
|
|
554
|
+
* @status stable
|
|
555
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
556
|
+
* @related b.guardXml.gate, b.guardXml.buildProfile
|
|
557
|
+
*
|
|
558
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
559
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of the
|
|
560
|
+
* posture object — the caller may mutate freely. Throws
|
|
561
|
+
* `GuardXmlError("xml.bad-posture")` on unknown name.
|
|
562
|
+
*
|
|
563
|
+
* @example
|
|
564
|
+
* var posture = b.guardXml.compliancePosture("hipaa");
|
|
565
|
+
* posture.doctypePolicy; // → "reject"
|
|
566
|
+
* posture.forensicSnippetBytes; // → 256
|
|
567
|
+
*/
|
|
370
568
|
function compliancePosture(name) {
|
|
371
569
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "xml");
|
|
372
570
|
}
|
|
373
571
|
|
|
374
572
|
var _xmlRulePacks = gateContract.makeRulePackLoader(GuardXmlError, "xml");
|
|
573
|
+
/**
|
|
574
|
+
* @primitive b.guardXml.loadRulePack
|
|
575
|
+
* @signature b.guardXml.loadRulePack(pack)
|
|
576
|
+
* @since 0.7.15
|
|
577
|
+
* @status stable
|
|
578
|
+
* @related b.guardXml.gate
|
|
579
|
+
*
|
|
580
|
+
* Register an operator-supplied rule pack with the guard-xml
|
|
581
|
+
* registry. The pack is identified by `pack.id` (non-empty string)
|
|
582
|
+
* and stored for later inspection / dispatch by gates that opt in
|
|
583
|
+
* via `opts.rulePackId`. Returns the pack object unchanged on
|
|
584
|
+
* success; throws `GuardXmlError("xml.bad-opt")` when `pack` is
|
|
585
|
+
* missing or `pack.id` is not a non-empty string.
|
|
586
|
+
*
|
|
587
|
+
* @example
|
|
588
|
+
* var pack = b.guardXml.loadRulePack({
|
|
589
|
+
* id: "soap-envelope",
|
|
590
|
+
* rules: [
|
|
591
|
+
* { id: "must-have-envelope", severity: "high",
|
|
592
|
+
* detect: function (text) { return text.indexOf("<soap:Envelope") === -1; },
|
|
593
|
+
* reason: "SOAP request missing soap:Envelope root" },
|
|
594
|
+
* ],
|
|
595
|
+
* });
|
|
596
|
+
* pack.id; // → "soap-envelope"
|
|
597
|
+
*/
|
|
375
598
|
var loadRulePack = _xmlRulePacks.load;
|
|
376
599
|
|
|
377
600
|
module.exports = {
|