@blamejs/core 0.8.43 → 0.8.50
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 +93 -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-pdf.js
CHANGED
|
@@ -1,31 +1,71 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
3
|
+
* @module b.guardPdf
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Pdf
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* PDF content-safety guard — refuses RCE-class PDF features without
|
|
9
|
+
* vendoring a parser. Operators bring their own PDF library
|
|
10
|
+
* (pdf-lib, pdfjs-dist, vendored mupdf) and feed structural metadata
|
|
11
|
+
* to the guard. `KIND="metadata"` — consumes `ctx.metadata` shape
|
|
12
|
+
* `{ bytes?, hasJavaScript?, hasOpenAction?, hasEmbeddedFiles?,
|
|
13
|
+
* hasLaunchAction?, isEncrypted?, pageCount?, embeddedFileCount?,
|
|
14
|
+
* polyglotDetected? }`.
|
|
15
|
+
*
|
|
16
|
+
* JavaScript exec refusal: `/JS` and `/JavaScript` annotations
|
|
17
|
+
* trigger RCE in vulnerable readers (the Adobe / Foxit / nitro CVE
|
|
18
|
+
* class). `metadata.hasJavaScript === true` is refused under every
|
|
19
|
+
* profile (`javascriptPolicy: "reject"` in strict / balanced /
|
|
20
|
+
* permissive). The framework refuses to negotiate on this — there
|
|
21
|
+
* is no audit-only path for executable JavaScript inside a PDF.
|
|
22
|
+
*
|
|
23
|
+
* Embedded files refusal: `/EmbeddedFile` entries may smuggle
|
|
24
|
+
* executable payloads inside an otherwise-benign-looking PDF.
|
|
25
|
+
* `strict` refuses any embedded file (`maxEmbeddedFileCount: 0`);
|
|
26
|
+
* `balanced` audits up to 10; `permissive` audits up to 100.
|
|
27
|
+
*
|
|
28
|
+
* OpenAction refusal: `/OpenAction` runs on document open. Standalone
|
|
29
|
+
* it's a navigation hint; paired with JavaScript or LaunchAction it's
|
|
30
|
+
* a drive-by trigger. `strict` refuses; `balanced` / `permissive`
|
|
31
|
+
* audit. JavaScript / LaunchAction are refused independently so the
|
|
32
|
+
* pairing can't slip through.
|
|
33
|
+
*
|
|
34
|
+
* GoTo / Launch refusal: `/Launch` actions invoke an external
|
|
35
|
+
* program (the historical "open this .exe attached to the PDF"
|
|
36
|
+
* class). Refused under every profile (`launchActionPolicy:
|
|
37
|
+
* "reject"`). The framework keeps the exec surface closed.
|
|
38
|
+
*
|
|
39
|
+
* Stream / object caps: `maxPageCount` (strict 500, balanced 5 000,
|
|
40
|
+
* permissive 50 000), `maxBytes` (strict 64 MiB, balanced 128 MiB,
|
|
41
|
+
* permissive 512 MiB), `maxEmbeddedFileCount` (strict 0, balanced
|
|
42
|
+
* 10, permissive 100). Operator-supplied — the operator's parser
|
|
43
|
+
* reports the structural counts; the guard refuses on excess.
|
|
44
|
+
*
|
|
45
|
+
* Magic-byte check: `%PDF-` header (5 bytes `25 50 44 46 2D`).
|
|
46
|
+
* Missing magic flagged under `strict` / `balanced` (the operator
|
|
47
|
+
* may be feeding non-PDF bytes through the wrong gate).
|
|
48
|
+
*
|
|
49
|
+
* Polyglot rejection: when the operator's parser flags the buffer
|
|
50
|
+
* as polyglot (`polyglotDetected: true`), the guard refuses under
|
|
51
|
+
* every profile (`polyglotPolicy: "reject"`).
|
|
52
|
+
*
|
|
53
|
+
* Encrypted-PDF posture: many AV / sandbox tools can't scan
|
|
54
|
+
* encrypted documents. `strict` refuses; `balanced` audits;
|
|
55
|
+
* `permissive` allows.
|
|
56
|
+
*
|
|
57
|
+
* Operator-feeds-metadata pattern: the gate trusts the metadata
|
|
58
|
+
* object the operator's parser reports. The framework's no-deps
|
|
59
|
+
* stance argues against shipping a vendored PDF parser; the
|
|
60
|
+
* operator's parser is the ground truth and the guard enforces the
|
|
61
|
+
* policy boundary.
|
|
62
|
+
*
|
|
63
|
+
* Profiles `strict` / `balanced` / `permissive` and compliance
|
|
64
|
+
* postures `hipaa` / `pci-dss` / `gdpr` / `soc2` overlay on the
|
|
65
|
+
* profile baseline.
|
|
66
|
+
*
|
|
67
|
+
* @card
|
|
68
|
+
* PDF content-safety guard — refuses RCE-class PDF features without vendoring a parser.
|
|
29
69
|
*/
|
|
30
70
|
|
|
31
71
|
var lazyRequire = require("./lazy-require");
|
|
@@ -248,6 +288,57 @@ function _detectIssues(metadata, opts) {
|
|
|
248
288
|
return issues;
|
|
249
289
|
}
|
|
250
290
|
|
|
291
|
+
/**
|
|
292
|
+
* @primitive b.guardPdf.validate
|
|
293
|
+
* @signature b.guardPdf.validate(input, opts)
|
|
294
|
+
* @since 0.7.13
|
|
295
|
+
* @status stable
|
|
296
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
297
|
+
* @related b.guardPdf.sanitize, b.guardPdf.gate, b.guardPdf.inspectMagic
|
|
298
|
+
*
|
|
299
|
+
* Inspect a PDF metadata bag `{ bytes?, hasJavaScript?, hasOpenAction?,
|
|
300
|
+
* hasLaunchAction?, hasEmbeddedFiles?, isEncrypted?, pageCount?,
|
|
301
|
+
* embeddedFileCount?, polyglotDetected? }` and return `{ ok, issues }`.
|
|
302
|
+
* Detected: `magic-missing` (no `%PDF-` header), `polyglot` (operator-
|
|
303
|
+
* flagged), `javascript-action` (RCE class — universally refused),
|
|
304
|
+
* `launch-action` (universally refused), `open-action` (drive-by
|
|
305
|
+
* class), `embedded-file` / `embedded-file-count`, `encrypted`,
|
|
306
|
+
* `page-count`, `pdf-cap`. Pure inspection — never mutates input or
|
|
307
|
+
* throws on hostile metadata.
|
|
308
|
+
*
|
|
309
|
+
* @opts
|
|
310
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
311
|
+
* compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
312
|
+
* magicPolicy: "reject"|"audit"|"allow",
|
|
313
|
+
* javascriptPolicy: "reject"|"audit"|"allow", // strict refused — RCE class
|
|
314
|
+
* openActionPolicy: "reject"|"audit"|"allow",
|
|
315
|
+
* launchActionPolicy: "reject"|"audit"|"allow", // strict refused — RCE class
|
|
316
|
+
* embeddedFilePolicy: "reject"|"audit"|"allow",
|
|
317
|
+
* encryptedPolicy: "reject"|"audit"|"allow",
|
|
318
|
+
* polyglotPolicy: "reject"|"audit"|"allow",
|
|
319
|
+
* pageCountPolicy: "reject"|"audit"|"allow",
|
|
320
|
+
* embeddedFileCountPolicy: "reject"|"audit"|"allow",
|
|
321
|
+
* maxPageCount: number, // strict 500, balanced 5000, permissive 50000
|
|
322
|
+
* maxEmbeddedFileCount: number, // strict 0, balanced 10, permissive 100
|
|
323
|
+
* maxBytes: number, // strict 64 MiB, balanced 128 MiB, permissive 512 MiB
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* var rv = b.guardPdf.validate({
|
|
327
|
+
* bytes: Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x37]),
|
|
328
|
+
* hasJavaScript: true, pageCount: 1,
|
|
329
|
+
* }, { profile: "strict" });
|
|
330
|
+
* rv.ok; // → false
|
|
331
|
+
* rv.issues[0].kind; // → "javascript-action"
|
|
332
|
+
* rv.issues[0].severity; // → "critical"
|
|
333
|
+
*
|
|
334
|
+
* // LaunchAction — universally refused.
|
|
335
|
+
* var launch = b.guardPdf.validate({
|
|
336
|
+
* bytes: Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x37]),
|
|
337
|
+
* hasLaunchAction: true,
|
|
338
|
+
* }, { profile: "permissive" });
|
|
339
|
+
* launch.issues.some(function (i) { return i.kind === "launch-action"; });
|
|
340
|
+
* // → true
|
|
341
|
+
*/
|
|
251
342
|
function validate(input, opts) {
|
|
252
343
|
opts = _resolveOpts(opts);
|
|
253
344
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -258,6 +349,35 @@ function validate(input, opts) {
|
|
|
258
349
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
259
350
|
}
|
|
260
351
|
|
|
352
|
+
/**
|
|
353
|
+
* @primitive b.guardPdf.sanitize
|
|
354
|
+
* @signature b.guardPdf.sanitize(input, opts)
|
|
355
|
+
* @since 0.7.13
|
|
356
|
+
* @status stable
|
|
357
|
+
* @related b.guardPdf.validate, b.guardPdf.gate
|
|
358
|
+
*
|
|
359
|
+
* Best-effort metadata pass-through. PDF byte sanitization
|
|
360
|
+
* (stripping JavaScript actions, embedded files, OpenActions) is
|
|
361
|
+
* the operator parser's responsibility — the guard cannot rewrite
|
|
362
|
+
* the byte stream without a vendored PDF library. `sanitize`
|
|
363
|
+
* validates the metadata against the active profile and re-throws
|
|
364
|
+
* `GuardPdfError` when any issue is `critical` or `high`. Returns
|
|
365
|
+
* the input unchanged when every issue is `warn` or below.
|
|
366
|
+
*
|
|
367
|
+
* @opts
|
|
368
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
369
|
+
* compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* try {
|
|
373
|
+
* b.guardPdf.sanitize({
|
|
374
|
+
* bytes: Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D]),
|
|
375
|
+
* hasJavaScript: true,
|
|
376
|
+
* }, { profile: "strict" });
|
|
377
|
+
* } catch (e) {
|
|
378
|
+
* e.code; // → "pdf.javascript-action"
|
|
379
|
+
* }
|
|
380
|
+
*/
|
|
261
381
|
function sanitize(input, opts) {
|
|
262
382
|
opts = _resolveOpts(opts);
|
|
263
383
|
if (!input || typeof input !== "object") {
|
|
@@ -273,6 +393,39 @@ function sanitize(input, opts) {
|
|
|
273
393
|
return input;
|
|
274
394
|
}
|
|
275
395
|
|
|
396
|
+
/**
|
|
397
|
+
* @primitive b.guardPdf.gate
|
|
398
|
+
* @signature b.guardPdf.gate(opts)
|
|
399
|
+
* @since 0.7.13
|
|
400
|
+
* @status stable
|
|
401
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
402
|
+
* @related b.guardPdf.validate, b.guardPdf.sanitize, b.fileUpload, b.staticServe
|
|
403
|
+
*
|
|
404
|
+
* Build a `b.gateContract` gate suitable for `b.fileUpload({ contentSafety:
|
|
405
|
+
* { "application/pdf": gate } })` or `b.staticServe`. Operators pass
|
|
406
|
+
* `ctx.metadata` (the parser's structural report) plus the original
|
|
407
|
+
* `bytes`. Action chain: `serve` (no issues) → `audit-only`
|
|
408
|
+
* (warn-only) → `refuse` (any critical / high). No `sanitize` action
|
|
409
|
+
* — PDF byte streams can't be rewritten without a vendored parser.
|
|
410
|
+
*
|
|
411
|
+
* @opts
|
|
412
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
413
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
414
|
+
* name: string,
|
|
415
|
+
* ...: any validate opt
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* var pdfGate = b.guardPdf.gate({ profile: "strict" });
|
|
419
|
+
*
|
|
420
|
+
* var verdict = await pdfGate.check({
|
|
421
|
+
* metadata: {
|
|
422
|
+
* bytes: Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x37]),
|
|
423
|
+
* hasJavaScript: true, pageCount: 1,
|
|
424
|
+
* },
|
|
425
|
+
* });
|
|
426
|
+
* verdict.action; // → "refuse"
|
|
427
|
+
* verdict.issues[0].kind; // → "javascript-action"
|
|
428
|
+
*/
|
|
276
429
|
function gate(opts) {
|
|
277
430
|
opts = _resolveOpts(opts);
|
|
278
431
|
return gateContract.buildGuardGate(
|
|
@@ -296,17 +449,88 @@ function gate(opts) {
|
|
|
296
449
|
});
|
|
297
450
|
}
|
|
298
451
|
|
|
452
|
+
/**
|
|
453
|
+
* @primitive b.guardPdf.buildProfile
|
|
454
|
+
* @signature b.guardPdf.buildProfile(opts)
|
|
455
|
+
* @since 0.7.13
|
|
456
|
+
* @status stable
|
|
457
|
+
* @related b.guardPdf.compliancePosture, b.guardPdf.gate
|
|
458
|
+
*
|
|
459
|
+
* Resolve a named profile against the guard's PROFILES catalog and
|
|
460
|
+
* return the merged options bag. Throws
|
|
461
|
+
* `GuardPdfError("pdf.bad-profile")` on unknown name.
|
|
462
|
+
*
|
|
463
|
+
* @opts
|
|
464
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* var resolved = b.guardPdf.buildProfile({ profile: "strict" });
|
|
468
|
+
* resolved.javascriptPolicy; // → "reject"
|
|
469
|
+
* resolved.maxPageCount; // → 500
|
|
470
|
+
*/
|
|
299
471
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
300
472
|
|
|
473
|
+
/**
|
|
474
|
+
* @primitive b.guardPdf.compliancePosture
|
|
475
|
+
* @signature b.guardPdf.compliancePosture(name)
|
|
476
|
+
* @since 0.7.13
|
|
477
|
+
* @status stable
|
|
478
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
479
|
+
* @related b.guardPdf.gate, b.guardPdf.buildProfile
|
|
480
|
+
*
|
|
481
|
+
* Return the option overlay for a named compliance posture
|
|
482
|
+
* (`"hipaa"` / `"pci-dss"` / `"gdpr"` / `"soc2"`). Throws
|
|
483
|
+
* `GuardPdfError("pdf.bad-posture")` on unknown name.
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* var posture = b.guardPdf.compliancePosture("hipaa");
|
|
487
|
+
* posture.javascriptPolicy; // → "reject"
|
|
488
|
+
* posture.forensicSnippetBytes; // → 512
|
|
489
|
+
*/
|
|
301
490
|
function compliancePosture(name) {
|
|
302
491
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
303
492
|
_err, "pdf");
|
|
304
493
|
}
|
|
305
494
|
|
|
306
495
|
var _pdfRulePacks = gateContract.makeRulePackLoader(GuardPdfError, "pdf");
|
|
496
|
+
/**
|
|
497
|
+
* @primitive b.guardPdf.loadRulePack
|
|
498
|
+
* @signature b.guardPdf.loadRulePack(pack)
|
|
499
|
+
* @since 0.7.13
|
|
500
|
+
* @status stable
|
|
501
|
+
* @related b.guardPdf.gate
|
|
502
|
+
*
|
|
503
|
+
* Register an operator-supplied rule pack with the guard-pdf
|
|
504
|
+
* registry. Throws `GuardPdfError("pdf.bad-opt")` when `pack` is
|
|
505
|
+
* missing or `pack.id` is not a non-empty string.
|
|
506
|
+
*
|
|
507
|
+
* @example
|
|
508
|
+
* var pack = b.guardPdf.loadRulePack({
|
|
509
|
+
* id: "kb-2026-pdf",
|
|
510
|
+
* extraMaxPageCount: 200,
|
|
511
|
+
* });
|
|
512
|
+
* pack.id; // → "kb-2026-pdf"
|
|
513
|
+
*/
|
|
307
514
|
var loadRulePack = _pdfRulePacks.load;
|
|
308
515
|
|
|
309
|
-
|
|
516
|
+
/**
|
|
517
|
+
* @primitive b.guardPdf.inspectMagic
|
|
518
|
+
* @signature b.guardPdf.inspectMagic(bytes)
|
|
519
|
+
* @since 0.7.13
|
|
520
|
+
* @status stable
|
|
521
|
+
* @related b.guardPdf.validate, b.guardPdf.gate
|
|
522
|
+
*
|
|
523
|
+
* Return `true` when `bytes` starts with the PDF magic header
|
|
524
|
+
* (`%PDF-`, the 5 bytes `25 50 44 46 2D`); `false` otherwise. Pure
|
|
525
|
+
* inspection — never mutates input or throws.
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* var pdfBytes = Buffer.from([0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x37]);
|
|
529
|
+
* b.guardPdf.inspectMagic(pdfBytes); // → true
|
|
530
|
+
*
|
|
531
|
+
* b.guardPdf.inspectMagic(Buffer.from([0x00, 0x01, 0x02]));
|
|
532
|
+
* // → false
|
|
533
|
+
*/
|
|
310
534
|
function inspectMagic(bytes) {
|
|
311
535
|
return _hasPdfMagic(bytes);
|
|
312
536
|
}
|
package/lib/guard-regex.js
CHANGED
|
@@ -1,26 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* @module b.guardRegex
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Regex
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Regex-pattern content-safety guard — refuses user-supplied
|
|
9
|
+
* pattern strings that exhibit catastrophic-backtracking (ReDoS)
|
|
10
|
+
* shapes BEFORE the framework compiles them with `new RegExp(...)`.
|
|
11
|
+
* Operator-untrusted patterns flow into search filters, allow-lists,
|
|
12
|
+
* route matchers, and form validators; this primitive screens them
|
|
13
|
+
* so a hostile input can't pin a CPU at 100% inside the regex
|
|
14
|
+
* engine. KIND=`identifier`; the gate consumes `ctx.identifier`
|
|
15
|
+
* (or `ctx.pattern`) and refuses on hostile shapes. Composes with
|
|
16
|
+
* framework parsers (`b.safeJson` / `b.safeBuffer` / route helpers)
|
|
17
|
+
* so any operator-fed pattern hits the guard first.
|
|
9
18
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
20
|
-
* - BIDI / null / control / zero-width universal refuse.
|
|
19
|
+
* Threat catalog: nested quantifiers (`(a+)+`, `(a*)+`, `(.+)+` —
|
|
20
|
+
* the canonical ReDoS class, e.g. CVE-2024-21538 cross-spawn and
|
|
21
|
+
* CVE-2022-25929 chartjs-adapter-luxon); alternation-with-
|
|
22
|
+
* quantifier (`(a|b)+`, `(\d|\d{2})*`) where alternation overlap
|
|
23
|
+
* amplifies search paths; quantifier-inside-lookaround
|
|
24
|
+
* (`(?=.*+)`, `(?!a*)`) — catastrophic in some engines; bounded
|
|
25
|
+
* repetition with a large upper bound (gated by
|
|
26
|
+
* `maxBoundedRepeat`); per-pattern byte cap to defend against
|
|
27
|
+
* parser-stage DoS; BIDI override / zero-width / C0 control /
|
|
28
|
+
* null-byte universal refuse.
|
|
21
29
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
30
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
31
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. Operators
|
|
32
|
+
* select via `{ profile: "strict" }` or
|
|
33
|
+
* `{ compliance: "hipaa" }`; postures overlay on top of the
|
|
34
|
+
* profile baseline. Nested-quantifier rejection holds at every
|
|
35
|
+
* profile — the catastrophic class is never an operator opt-in.
|
|
36
|
+
*
|
|
37
|
+
* Pattern strings can't be repaired safely — `sanitize` either
|
|
38
|
+
* passes through clean input or throws `GuardRegexError`; the
|
|
39
|
+
* gate returns `serve` / `audit-only` / `refuse` (no `sanitize`
|
|
40
|
+
* action). Detector regexes themselves are length-bounded by
|
|
41
|
+
* `maxPatternBytes` so the screener can't be DoS'd by its own
|
|
42
|
+
* inputs.
|
|
43
|
+
*
|
|
44
|
+
* @card
|
|
45
|
+
* Regex-pattern content-safety guard — refuses user-supplied pattern strings that exhibit catastrophic-backtracking (ReDoS) shapes BEFORE the framework compiles them with `new RegExp(...)`.
|
|
24
46
|
*/
|
|
25
47
|
|
|
26
48
|
var codepointClass = require("./codepoint-class");
|
|
@@ -204,6 +226,45 @@ function _detectIssues(input, opts) {
|
|
|
204
226
|
return issues;
|
|
205
227
|
}
|
|
206
228
|
|
|
229
|
+
/**
|
|
230
|
+
* @primitive b.guardRegex.validate
|
|
231
|
+
* @signature b.guardRegex.validate(input, opts)
|
|
232
|
+
* @since 0.7.13
|
|
233
|
+
* @status stable
|
|
234
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
235
|
+
* @related b.guardRegex.gate, b.guardRegex.sanitize
|
|
236
|
+
*
|
|
237
|
+
* Inspect a user-supplied regex pattern string and return an
|
|
238
|
+
* aggregated issue list. Pure inspection — never throws on hostile
|
|
239
|
+
* patterns; caller decides what to do with the issues. The `ok`
|
|
240
|
+
* flag is `true` only when zero `critical` / `high` issues fire.
|
|
241
|
+
* Throws `GuardRegexError("regex.bad-opt")` when a numeric opt is
|
|
242
|
+
* non-finite / negative (config-time mistake by the operator).
|
|
243
|
+
*
|
|
244
|
+
* @opts
|
|
245
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
246
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
247
|
+
* bidiPolicy: "reject"|"audit"|"allow",
|
|
248
|
+
* controlPolicy: "reject"|"audit"|"allow",
|
|
249
|
+
* nullBytePolicy: "reject"|"audit"|"allow",
|
|
250
|
+
* zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
|
|
251
|
+
* nestedQuantPolicy: "reject"|"audit"|"allow",
|
|
252
|
+
* alternationQuantPolicy: "reject"|"audit"|"allow",
|
|
253
|
+
* boundedRepeatPolicy: "reject"|"audit"|"allow",
|
|
254
|
+
* lookaroundQuantPolicy: "reject"|"audit"|"allow",
|
|
255
|
+
* maxBoundedRepeat: number,
|
|
256
|
+
* maxPatternBytes: number,
|
|
257
|
+
* maxBytes: number,
|
|
258
|
+
* maxRuntimeMs: number,
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* var clean = b.guardRegex.validate("^[a-z]+$", { profile: "strict" });
|
|
262
|
+
* clean.ok; // → true
|
|
263
|
+
*
|
|
264
|
+
* var hostile = b.guardRegex.validate("(a+)+b", { profile: "strict" });
|
|
265
|
+
* hostile.ok; // → false
|
|
266
|
+
* hostile.issues.some(function (i) { return i.kind === "nested-quantifier"; }); // → true
|
|
267
|
+
*/
|
|
207
268
|
function validate(input, opts) {
|
|
208
269
|
opts = _resolveOpts(opts);
|
|
209
270
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -212,6 +273,44 @@ function validate(input, opts) {
|
|
|
212
273
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
213
274
|
}
|
|
214
275
|
|
|
276
|
+
/**
|
|
277
|
+
* @primitive b.guardRegex.sanitize
|
|
278
|
+
* @signature b.guardRegex.sanitize(input, opts)
|
|
279
|
+
* @since 0.7.13
|
|
280
|
+
* @status stable
|
|
281
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
282
|
+
* @related b.guardRegex.validate, b.guardRegex.gate
|
|
283
|
+
*
|
|
284
|
+
* Pass-through-or-throw. Regex patterns cannot be safely repaired
|
|
285
|
+
* (stripping a `+` from a quantifier silently changes match
|
|
286
|
+
* semantics); this primitive returns the input unchanged when no
|
|
287
|
+
* `critical` or `high` issue fires, otherwise throws
|
|
288
|
+
* `GuardRegexError` with the offending rule id (e.g.
|
|
289
|
+
* `regex.nested-quantifier`, `regex.lookaround-quantifier`,
|
|
290
|
+
* `regex.bounded-repeat-cap`). Operators that need a "best-effort
|
|
291
|
+
* cleanup" semantic should reject the input at the boundary
|
|
292
|
+
* instead.
|
|
293
|
+
*
|
|
294
|
+
* @opts
|
|
295
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
296
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
297
|
+
* nestedQuantPolicy: "reject"|"audit"|"allow",
|
|
298
|
+
* alternationQuantPolicy: "reject"|"audit"|"allow",
|
|
299
|
+
* boundedRepeatPolicy: "reject"|"audit"|"allow",
|
|
300
|
+
* lookaroundQuantPolicy: "reject"|"audit"|"allow",
|
|
301
|
+
* maxBoundedRepeat: number,
|
|
302
|
+
* maxPatternBytes: number,
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* var safe = b.guardRegex.sanitize("^[a-z]+$", { profile: "strict" });
|
|
306
|
+
* safe; // → "^[a-z]+$"
|
|
307
|
+
*
|
|
308
|
+
* try {
|
|
309
|
+
* b.guardRegex.sanitize("(a+)+b", { profile: "strict" });
|
|
310
|
+
* } catch (e) {
|
|
311
|
+
* e.code; // → "regex.nested-quantifier"
|
|
312
|
+
* }
|
|
313
|
+
*/
|
|
215
314
|
function sanitize(input, opts) {
|
|
216
315
|
opts = _resolveOpts(opts);
|
|
217
316
|
if (typeof input !== "string") {
|
|
@@ -227,6 +326,45 @@ function sanitize(input, opts) {
|
|
|
227
326
|
return input;
|
|
228
327
|
}
|
|
229
328
|
|
|
329
|
+
/**
|
|
330
|
+
* @primitive b.guardRegex.gate
|
|
331
|
+
* @signature b.guardRegex.gate(opts)
|
|
332
|
+
* @since 0.7.13
|
|
333
|
+
* @status stable
|
|
334
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
335
|
+
* @related b.guardRegex.validate, b.guardRegex.sanitize
|
|
336
|
+
*
|
|
337
|
+
* Build a `b.gateContract` gate that screens `ctx.identifier` (or
|
|
338
|
+
* `ctx.pattern`) before any compilation step. Action chain:
|
|
339
|
+
* `serve` (no issues) → `audit-only` (warn-only) → `refuse` (any
|
|
340
|
+
* `critical` or `high`). No `sanitize` action — pattern strings
|
|
341
|
+
* cannot be repaired. Compose into framework parsers / form
|
|
342
|
+
* validators / route matchers so operator-fed patterns hit the
|
|
343
|
+
* guard before reaching `new RegExp()`.
|
|
344
|
+
*
|
|
345
|
+
* @opts
|
|
346
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
347
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
348
|
+
* name: string, // override gate name in audit emissions
|
|
349
|
+
* nestedQuantPolicy: "reject"|"audit"|"allow",
|
|
350
|
+
* alternationQuantPolicy: "reject"|"audit"|"allow",
|
|
351
|
+
* boundedRepeatPolicy: "reject"|"audit"|"allow",
|
|
352
|
+
* lookaroundQuantPolicy: "reject"|"audit"|"allow",
|
|
353
|
+
* maxBoundedRepeat: number,
|
|
354
|
+
* maxPatternBytes: number,
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* var gate = b.guardRegex.gate({ profile: "strict" });
|
|
358
|
+
*
|
|
359
|
+
* gate({ identifier: "(a+)+b" }).then(function (rv) {
|
|
360
|
+
* rv.ok; // → false
|
|
361
|
+
* rv.action; // → "refuse"
|
|
362
|
+
* });
|
|
363
|
+
*
|
|
364
|
+
* gate({ identifier: "^[a-z]+$" }).then(function (rv) {
|
|
365
|
+
* rv.action; // → "serve"
|
|
366
|
+
* });
|
|
367
|
+
*/
|
|
230
368
|
function gate(opts) {
|
|
231
369
|
opts = _resolveOpts(opts);
|
|
232
370
|
return gateContract.buildGuardGate(
|
|
@@ -252,14 +390,84 @@ function gate(opts) {
|
|
|
252
390
|
});
|
|
253
391
|
}
|
|
254
392
|
|
|
393
|
+
/**
|
|
394
|
+
* @primitive b.guardRegex.buildProfile
|
|
395
|
+
* @signature b.guardRegex.buildProfile(opts)
|
|
396
|
+
* @since 0.7.13
|
|
397
|
+
* @status stable
|
|
398
|
+
* @related b.guardRegex.gate, b.guardRegex.compliancePosture
|
|
399
|
+
*
|
|
400
|
+
* Compose a derived guardRegex profile from one or more named
|
|
401
|
+
* bases plus inline overrides. `opts.extends` is a profile name
|
|
402
|
+
* (`"strict"` / `"balanced"` / `"permissive"`) or an array of
|
|
403
|
+
* names; later entries shadow earlier ones. Inline `opts` keys win
|
|
404
|
+
* last. Used to keep operator-defined profiles traceable to a
|
|
405
|
+
* baseline rather than re-typing every key.
|
|
406
|
+
*
|
|
407
|
+
* @opts
|
|
408
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
409
|
+
* ...: any guardRegex key, // inline override of resolved keys
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* var custom = b.guardRegex.buildProfile({
|
|
413
|
+
* extends: "balanced",
|
|
414
|
+
* maxBoundedRepeat: 50,
|
|
415
|
+
* boundedRepeatPolicy: "reject",
|
|
416
|
+
* });
|
|
417
|
+
* custom.maxBoundedRepeat; // → 50
|
|
418
|
+
* custom.nestedQuantPolicy; // → "reject"
|
|
419
|
+
*/
|
|
255
420
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
256
421
|
|
|
422
|
+
/**
|
|
423
|
+
* @primitive b.guardRegex.compliancePosture
|
|
424
|
+
* @signature b.guardRegex.compliancePosture(name)
|
|
425
|
+
* @since 0.7.13
|
|
426
|
+
* @status stable
|
|
427
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
428
|
+
* @related b.guardRegex.gate, b.guardRegex.buildProfile
|
|
429
|
+
*
|
|
430
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
431
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
432
|
+
* the posture object — the caller may mutate freely. Throws
|
|
433
|
+
* `GuardRegexError("regex.bad-posture")` on unknown name.
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* var posture = b.guardRegex.compliancePosture("hipaa");
|
|
437
|
+
* posture.nestedQuantPolicy; // → "reject"
|
|
438
|
+
* posture.forensicSnippetBytes; // → 256
|
|
439
|
+
*/
|
|
257
440
|
function compliancePosture(name) {
|
|
258
441
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
259
442
|
_err, "regex");
|
|
260
443
|
}
|
|
261
444
|
|
|
262
445
|
var _regexRulePacks = gateContract.makeRulePackLoader(GuardRegexError, "regex");
|
|
446
|
+
/**
|
|
447
|
+
* @primitive b.guardRegex.loadRulePack
|
|
448
|
+
* @signature b.guardRegex.loadRulePack(pack)
|
|
449
|
+
* @since 0.7.13
|
|
450
|
+
* @status stable
|
|
451
|
+
* @related b.guardRegex.gate
|
|
452
|
+
*
|
|
453
|
+
* Register an operator-supplied rule pack with the guardRegex
|
|
454
|
+
* registry. The pack is identified by `pack.id` (non-empty string)
|
|
455
|
+
* and stored for later inspection / dispatch by gates that opt in
|
|
456
|
+
* via `opts.rulePackId`. Returns the pack object unchanged on
|
|
457
|
+
* success; throws `GuardRegexError("regex.bad-opt")` when `pack`
|
|
458
|
+
* is missing or `pack.id` is not a non-empty string.
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* var pack = b.guardRegex.loadRulePack({
|
|
462
|
+
* id: "no-empty-alternation",
|
|
463
|
+
* rules: [
|
|
464
|
+
* { id: "empty-alt", severity: "high",
|
|
465
|
+
* detect: function (pattern) { return /\(\|/.test(pattern); },
|
|
466
|
+
* reason: "alternation with empty branch" },
|
|
467
|
+
* ],
|
|
468
|
+
* });
|
|
469
|
+
* pack.id; // → "no-empty-alternation"
|
|
470
|
+
*/
|
|
263
471
|
var loadRulePack = _regexRulePacks.load;
|
|
264
472
|
|
|
265
473
|
module.exports = {
|