@blamejs/core 0.8.42 → 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 +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-mime.js
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardMime
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Mime
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Media-type identifier-safety guard. Validates user-supplied
|
|
9
|
+
* RFC 6838 media-type strings destined for Accept-shape comparison,
|
|
10
|
+
* content-type allowlists, and dispatch routing. KIND="identifier"
|
|
11
|
+
* — the gate consumes `ctx.identifier` (or `ctx.mime`).
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* -
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* same flag class.
|
|
22
|
-
* - Risky types refuse list — `application/x-msdownload`, `.x-bat`,
|
|
23
|
-
* `.x-msdos-program`, `.x-sh`, `.x-csh`, `.javascript`,
|
|
24
|
-
* `.x-javascript` (when handed off to a script-host).
|
|
25
|
-
* - BIDI / zero-width / control / null-byte universal refuse.
|
|
13
|
+
* Threat catalog: shape malformation (not RFC 6838 type/subtype
|
|
14
|
+
* grammar); bad token characters (RFC 6838 §4.2 restricts type and
|
|
15
|
+
* subtype to ALPHA / DIGIT / `!#$&-^_.+` — spaces / quotes /
|
|
16
|
+
* Unicode reject); parameter injection through pass-through
|
|
17
|
+
* `text/plain; charset=...` shapes; wildcard `*/*` / `type/*`
|
|
18
|
+
* (Accept-only — refused as content-type at strict); vendor tree
|
|
19
|
+
* `application/vnd.<vendor>` and personal tree `application/prs.*`
|
|
20
|
+
* plus unregistered `x.*` flagged so operators audit the namespace;
|
|
21
|
+
* risky types refuse list (`application/x-msdownload`,
|
|
22
|
+
* `.x-msdos-program`, `.x-sh`, `.x-csh`, `application/javascript`,
|
|
23
|
+
* `text/javascript`) when handed off to a script-host;
|
|
24
|
+
* BIDI / zero-width / C0-control / null-byte universal-refuse.
|
|
26
25
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
26
|
+
* Magic-byte verification and polyglot rejection are performed by
|
|
27
|
+
* the operator-side fixture pipeline: the gate emits the asserted
|
|
28
|
+
* identifier; downstream content guards (`b.guardSvg` / `b.guardPdf`
|
|
29
|
+
* / `b.guardImage`) compare it against `inspectMagic(buffer)` and
|
|
30
|
+
* refuse mismatches.
|
|
31
|
+
*
|
|
32
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
33
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`.
|
|
34
|
+
*
|
|
35
|
+
* @card
|
|
36
|
+
* Media-type identifier-safety guard.
|
|
32
37
|
*/
|
|
33
38
|
|
|
34
39
|
var codepointClass = require("./codepoint-class");
|
|
@@ -347,6 +352,45 @@ function _detectIssues(input, opts) {
|
|
|
347
352
|
return issues;
|
|
348
353
|
}
|
|
349
354
|
|
|
355
|
+
/**
|
|
356
|
+
* @primitive b.guardMime.validate
|
|
357
|
+
* @signature b.guardMime.validate(input, opts?)
|
|
358
|
+
* @since 0.7.47
|
|
359
|
+
* @status stable
|
|
360
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
361
|
+
* @related b.guardMime.sanitize, b.guardMime.gate
|
|
362
|
+
*
|
|
363
|
+
* Inspect a media-type string against the resolved profile and
|
|
364
|
+
* return `{ ok, issues }`. Each issue carries `kind` / `severity`
|
|
365
|
+
* (`critical` | `high` | `medium` | `low`) / `ruleId` / `snippet`.
|
|
366
|
+
* Non-string input returns a single `mime.bad-input` issue rather
|
|
367
|
+
* than throwing — callers that prefer an exception use
|
|
368
|
+
* `b.guardMime.sanitize`.
|
|
369
|
+
*
|
|
370
|
+
* @opts
|
|
371
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
372
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
373
|
+
* bidiPolicy: "reject"|"strip"|"audit"|"allow",
|
|
374
|
+
* controlPolicy: "reject"|"strip"|"allow",
|
|
375
|
+
* nullBytePolicy: "reject"|"strip"|"allow",
|
|
376
|
+
* zeroWidthPolicy: "reject"|"strip"|"allow",
|
|
377
|
+
* wildcardPolicy: "reject"|"audit"|"allow",
|
|
378
|
+
* vendorTreePolicy: "reject"|"audit"|"allow",
|
|
379
|
+
* personalTreePolicy: "reject"|"audit"|"allow",
|
|
380
|
+
* unregisteredTreePolicy: "reject"|"audit"|"allow",
|
|
381
|
+
* riskyTypesPolicy: "reject"|"audit"|"allow",
|
|
382
|
+
* parameterPolicy: "reject"|"audit"|"allow",
|
|
383
|
+
* maxBytes: number, // default 256 (RFC-recommended cap)
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* var rv = b.guardMime.validate("application/json", { profile: "strict" });
|
|
387
|
+
* rv.ok; // → true
|
|
388
|
+
* rv.issues.length; // → 0
|
|
389
|
+
*
|
|
390
|
+
* var bad = b.guardMime.validate("application/x-msdownload", { profile: "strict" });
|
|
391
|
+
* bad.ok; // → false
|
|
392
|
+
* bad.issues[0].ruleId; // → "mime.risky-type"
|
|
393
|
+
*/
|
|
350
394
|
function validate(input, opts) {
|
|
351
395
|
opts = _resolveOpts(opts);
|
|
352
396
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -363,6 +407,36 @@ function validate(input, opts) {
|
|
|
363
407
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
364
408
|
}
|
|
365
409
|
|
|
410
|
+
/**
|
|
411
|
+
* @primitive b.guardMime.sanitize
|
|
412
|
+
* @signature b.guardMime.sanitize(input, opts?)
|
|
413
|
+
* @since 0.7.47
|
|
414
|
+
* @status stable
|
|
415
|
+
* @related b.guardMime.validate, b.guardMime.gate
|
|
416
|
+
*
|
|
417
|
+
* Lower-case the canonical type/subtype while preserving
|
|
418
|
+
* parameter-value case (some parameter values are case-significant —
|
|
419
|
+
* e.g. multipart `boundary` tokens). Throws `GuardMimeError` when any
|
|
420
|
+
* `critical` or `high` issue fires (risky-type, parameter-injection,
|
|
421
|
+
* BIDI / null-byte / control). Use `validate` to inspect issues
|
|
422
|
+
* without throwing.
|
|
423
|
+
*
|
|
424
|
+
* @opts
|
|
425
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
426
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
427
|
+
* ...: same shape as b.guardMime.validate opts,
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* var safe = b.guardMime.sanitize("Application/JSON; charset=UTF-8",
|
|
431
|
+
* { profile: "balanced" });
|
|
432
|
+
* safe; // → "application/json; charset=UTF-8"
|
|
433
|
+
*
|
|
434
|
+
* try {
|
|
435
|
+
* b.guardMime.sanitize("application/javascript", { profile: "strict" });
|
|
436
|
+
* } catch (e) {
|
|
437
|
+
* e.code; // → "mime.risky-type"
|
|
438
|
+
* }
|
|
439
|
+
*/
|
|
366
440
|
function sanitize(input, opts) {
|
|
367
441
|
opts = _resolveOpts(opts);
|
|
368
442
|
if (typeof input !== "string") {
|
|
@@ -385,6 +459,35 @@ function sanitize(input, opts) {
|
|
|
385
459
|
}, canonical);
|
|
386
460
|
}
|
|
387
461
|
|
|
462
|
+
/**
|
|
463
|
+
* @primitive b.guardMime.gate
|
|
464
|
+
* @signature b.guardMime.gate(opts?)
|
|
465
|
+
* @since 0.7.47
|
|
466
|
+
* @status stable
|
|
467
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
468
|
+
* @related b.guardMime.validate, b.guardMime.sanitize, b.guardAll.gate
|
|
469
|
+
*
|
|
470
|
+
* Build an async gate `(ctx) -> { ok, action, issues }` consumable
|
|
471
|
+
* by `b.guardAll`, `b.staticServe`, `b.fileUpload`, and any other
|
|
472
|
+
* host that integrates the guard contract. The gate reads
|
|
473
|
+
* `ctx.identifier` (or `ctx.mime`), runs `validate`, and maps
|
|
474
|
+
* severity to action: zero issues `serve`; only low/medium
|
|
475
|
+
* `audit-only`; any high/critical `refuse`.
|
|
476
|
+
*
|
|
477
|
+
* @opts
|
|
478
|
+
* name: string, // gate label for audit / observability
|
|
479
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
480
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
481
|
+
* ...: same shape as b.guardMime.validate opts,
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* var g = b.guardMime.gate({ profile: "strict" });
|
|
485
|
+
* var rv = await g({ identifier: "application/json" });
|
|
486
|
+
* rv.action; // → "serve"
|
|
487
|
+
*
|
|
488
|
+
* var bad = await g({ identifier: "application/x-msdownload" });
|
|
489
|
+
* bad.action; // → "refuse"
|
|
490
|
+
*/
|
|
388
491
|
function gate(opts) {
|
|
389
492
|
opts = _resolveOpts(opts);
|
|
390
493
|
return gateContract.buildGuardGate(
|
|
@@ -408,14 +511,75 @@ function gate(opts) {
|
|
|
408
511
|
});
|
|
409
512
|
}
|
|
410
513
|
|
|
514
|
+
/**
|
|
515
|
+
* @primitive b.guardMime.buildProfile
|
|
516
|
+
* @signature b.guardMime.buildProfile(opts)
|
|
517
|
+
* @since 0.7.47
|
|
518
|
+
* @status stable
|
|
519
|
+
* @related b.guardMime.gate, b.guardMime.compliancePosture
|
|
520
|
+
*
|
|
521
|
+
* Compose a derived profile from one or more named bases plus
|
|
522
|
+
* inline overrides. `opts.extends` is a profile name or array of
|
|
523
|
+
* names (later entries shadow earlier ones); inline keys win last.
|
|
524
|
+
*
|
|
525
|
+
* @opts
|
|
526
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
527
|
+
* ...: any guard-mime key, // inline override of resolved keys
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
* var custom = b.guardMime.buildProfile({
|
|
531
|
+
* extends: "balanced",
|
|
532
|
+
* vendorTreePolicy: "audit",
|
|
533
|
+
* });
|
|
534
|
+
* custom.bidiPolicy; // → "reject"
|
|
535
|
+
* custom.vendorTreePolicy; // → "audit"
|
|
536
|
+
*/
|
|
411
537
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
412
538
|
|
|
539
|
+
/**
|
|
540
|
+
* @primitive b.guardMime.compliancePosture
|
|
541
|
+
* @signature b.guardMime.compliancePosture(name)
|
|
542
|
+
* @since 0.7.47
|
|
543
|
+
* @status stable
|
|
544
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
545
|
+
* @related b.guardMime.gate, b.guardMime.buildProfile
|
|
546
|
+
*
|
|
547
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
548
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
549
|
+
* the posture object — the caller may mutate freely. Throws
|
|
550
|
+
* `GuardMimeError("mime.bad-posture")` on unknown name.
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* var posture = b.guardMime.compliancePosture("pci-dss");
|
|
554
|
+
* posture.riskyTypesPolicy; // → "reject"
|
|
555
|
+
*/
|
|
413
556
|
function compliancePosture(name) {
|
|
414
557
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
415
558
|
_err, "mime");
|
|
416
559
|
}
|
|
417
560
|
|
|
418
561
|
var _mimeRulePacks = gateContract.makeRulePackLoader(GuardMimeError, "mime");
|
|
562
|
+
/**
|
|
563
|
+
* @primitive b.guardMime.loadRulePack
|
|
564
|
+
* @signature b.guardMime.loadRulePack(pack)
|
|
565
|
+
* @since 0.7.47
|
|
566
|
+
* @status stable
|
|
567
|
+
* @related b.guardMime.gate
|
|
568
|
+
*
|
|
569
|
+
* Register an operator-supplied rule pack with the guard-mime
|
|
570
|
+
* registry. The pack is identified by `pack.id` (non-empty
|
|
571
|
+
* string) and stored for later inspection / dispatch by gates
|
|
572
|
+
* that opt in via `opts.rulePackId`. Returns the pack object
|
|
573
|
+
* unchanged on success; throws `GuardMimeError("mime.bad-opt")`
|
|
574
|
+
* when `pack` is missing or `pack.id` is not a non-empty string.
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* var pack = b.guardMime.loadRulePack({
|
|
578
|
+
* id: "operator-deny-flash",
|
|
579
|
+
* deny: ["application/x-shockwave-flash"],
|
|
580
|
+
* });
|
|
581
|
+
* pack.id; // → "operator-deny-flash"
|
|
582
|
+
*/
|
|
419
583
|
var loadRulePack = _mimeRulePacks.load;
|
|
420
584
|
|
|
421
585
|
module.exports = {
|
package/lib/guard-oauth.js
CHANGED
|
@@ -1,36 +1,82 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardOauth
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Oauth
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* OAuth 2.x / OIDC authorization-code-flow shape guard —
|
|
9
|
+
* validates user-supplied parameter bundles BEFORE the
|
|
10
|
+
* framework's `b.auth.oauth` client exchanges them with the IdP.
|
|
11
|
+
* KIND is `oauth-flow`; the gate consumes `ctx.oauthFlow` (or
|
|
12
|
+
* `ctx.flow`) shape `{ response_type, redirect_uri, state,
|
|
13
|
+
* code_challenge, code_challenge_method, scope, code, iss,
|
|
14
|
+
* _isCallback }`. The guard runs the spec-mandated refuse list
|
|
15
|
+
* so misconfigured callers can't downgrade the flow.
|
|
8
16
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - redirect_uri not in allowlist — RFC 6749 §3.1.2 + OAuth 2.1
|
|
16
|
-
* mandate exact-match (no prefix / wildcard / scheme drift).
|
|
17
|
-
* - response_type not in allowlist — refuse "token" implicit flow
|
|
18
|
-
* (deprecated in OAuth 2.1) and "id_token" outside OIDC; require
|
|
19
|
-
* operator-allowed types.
|
|
20
|
-
* - scope tampering — refuse scope values containing whitespace
|
|
21
|
-
* other than space (RFC 6749 §3.3) or non-printable bytes.
|
|
22
|
-
* - issuer (iss) missing on callback — RFC 9207 mandates iss
|
|
23
|
-
* parameter on authorization response to defeat the IdP-mix-up
|
|
24
|
-
* attack.
|
|
25
|
-
* - code reuse — operator-supplied seenCodeStore detects
|
|
26
|
-
* authorization-code replay (RFC 6749 §10.5).
|
|
27
|
-
* - excessive parameter / value length — defense against parser
|
|
28
|
-
* DoS and decompression-bomb-shaped clients.
|
|
29
|
-
* - BIDI / null / control / zero-width universal refuse.
|
|
17
|
+
* PKCE enforcement: `strict` requires `S256` (RFC 7636 + OAuth
|
|
18
|
+
* 2.1; the `plain` method is a downgrade-attack class). `balanced`
|
|
19
|
+
* accepts S256 or plain. `permissive` audits without enforcing.
|
|
20
|
+
* Missing `code_verifier` AND missing `code_challenge` always
|
|
21
|
+
* surfaces as `oauth.pkce-missing` because OAuth 2.1 mandates
|
|
22
|
+
* PKCE for every client class.
|
|
30
23
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
24
|
+
* `state` enforcement: required at strict / balanced. Without
|
|
25
|
+
* state-binding the authorization callback is open to CSRF (RFC
|
|
26
|
+
* 6749 §10.12). The guard refuses missing `state`; operator-side
|
|
27
|
+
* replay defense (rotating + comparing) is the responsibility of
|
|
28
|
+
* the caller's session layer.
|
|
29
|
+
*
|
|
30
|
+
* `nonce` is OIDC-specific replay defense — the guard's required-
|
|
31
|
+
* claims parity is enforced via the operator's
|
|
32
|
+
* `b.auth.jwt.verifyExternal` config, not in the flow shape, so
|
|
33
|
+
* nonce is documented here but checked by the verifier.
|
|
34
|
+
*
|
|
35
|
+
* `redirect_uri` exact-match: when the operator supplies
|
|
36
|
+
* `allowedRedirectUris`, every callback must be a byte-for-byte
|
|
37
|
+
* match. RFC 6749 §3.1.2 + OAuth 2.1 forbid prefix, wildcard, or
|
|
38
|
+
* scheme drift — the canonical CVE-class for this is the
|
|
39
|
+
* "redirect_uri loose-match" account-takeover bug. When no
|
|
40
|
+
* allowlist is configured the gate skips the check (operator-side
|
|
41
|
+
* misconfiguration warning lives in the startup audit, not in
|
|
42
|
+
* per-request issue lists).
|
|
43
|
+
*
|
|
44
|
+
* `response_type` allowlist: `strict` allows only `code`.
|
|
45
|
+
* `balanced` adds `code id_token`. `permissive` skips. Implicit-
|
|
46
|
+
* flow `token` and `id_token` outside OIDC are deprecated in
|
|
47
|
+
* OAuth 2.1 and refused under the strict / balanced allowlists.
|
|
48
|
+
*
|
|
49
|
+
* Scope-token discipline: every space-separated scope must
|
|
50
|
+
* conform to the RFC 6749 §3.3 charset (`%x21 / %x23-5B /
|
|
51
|
+
* %x5D-7E`). Whitespace-other-than-space, control bytes, and
|
|
52
|
+
* non-printable bytes in scope tokens are refused under strict
|
|
53
|
+
* / balanced and audited under permissive.
|
|
54
|
+
*
|
|
55
|
+
* RFC 9207 issuer-on-callback: when the request bundle is
|
|
56
|
+
* marked `_isCallback: true`, the `iss` parameter MUST be
|
|
57
|
+
* present at strict — defeats the IdP-mix-up attack class.
|
|
58
|
+
* `balanced` audits, `permissive` skips.
|
|
59
|
+
*
|
|
60
|
+
* Token-introspection bounds: `maxParamBytes` (default 2 KiB at
|
|
61
|
+
* strict / balanced) and `maxBytes` (default 8 KiB) cap each
|
|
62
|
+
* parameter and the total flow JSON. Decompression-bomb-shaped
|
|
63
|
+
* clients can't push the introspection / metadata layer past
|
|
64
|
+
* these bounds.
|
|
65
|
+
*
|
|
66
|
+
* Code-reuse defense: when the operator wires a `seenCodeStore`
|
|
67
|
+
* with `hasSeen(code)`, the guard refuses any authorization code
|
|
68
|
+
* already exchanged (RFC 6749 §10.5). The store implementation
|
|
69
|
+
* is the operator's responsibility — typically a short-TTL
|
|
70
|
+
* `b.cache` entry.
|
|
71
|
+
*
|
|
72
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
73
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. BIDI / null /
|
|
74
|
+
* control / zero-width universal-refuse applies on every string-
|
|
75
|
+
* valued top-level parameter at every profile so trojan-source
|
|
76
|
+
* codepoints can't ride a state or scope value.
|
|
77
|
+
*
|
|
78
|
+
* @card
|
|
79
|
+
* OAuth 2.x / OIDC authorization-code-flow shape guard — validates user-supplied parameter bundles BEFORE the framework's `b.auth.oauth` client exchanges them with the IdP.
|
|
34
80
|
*/
|
|
35
81
|
|
|
36
82
|
var codepointClass = require("./codepoint-class");
|
|
@@ -297,6 +343,66 @@ function _detectIssues(flow, opts) {
|
|
|
297
343
|
return issues;
|
|
298
344
|
}
|
|
299
345
|
|
|
346
|
+
/**
|
|
347
|
+
* @primitive b.guardOauth.validate
|
|
348
|
+
* @signature b.guardOauth.validate(input, opts?)
|
|
349
|
+
* @since 0.7.49
|
|
350
|
+
* @status stable
|
|
351
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
352
|
+
* @related b.guardOauth.sanitize, b.guardOauth.gate, b.auth.oauth
|
|
353
|
+
*
|
|
354
|
+
* Apply the full guard-oauth threat catalog to a flow bundle.
|
|
355
|
+
* Returns `{ ok, issues, refusal? }` per
|
|
356
|
+
* `gateContract.aggregateIssues`. Detected classes include
|
|
357
|
+
* `pkce-missing`, `pkce-method` (e.g. plain under require-s256),
|
|
358
|
+
* `state-missing`, `redirect-uri-not-allowed`,
|
|
359
|
+
* `response-type-not-allowed`, `scope-token-shape`,
|
|
360
|
+
* `issuer-missing`, `code-reused` (always critical), plus per-
|
|
361
|
+
* parameter `param-cap` and total-flow `flow-cap` bounds and
|
|
362
|
+
* codepoint-class issues on every string parameter. Operator-
|
|
363
|
+
* supplied opts are bounds-checked; bad opts throw
|
|
364
|
+
* `GuardOauthError("oauth.bad-opt")`.
|
|
365
|
+
*
|
|
366
|
+
* @opts
|
|
367
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
368
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
369
|
+
* pkcePolicy: "require-s256"|"require-any"|"audit"|"allow",
|
|
370
|
+
* statePolicy: "require"|"audit"|"allow",
|
|
371
|
+
* redirectUriPolicy: "require-exact-allowlist"|"audit"|"allow",
|
|
372
|
+
* responseTypePolicy: "require-allowlist"|"audit"|"allow",
|
|
373
|
+
* scopeTamperingPolicy: "reject"|"audit"|"allow",
|
|
374
|
+
* issuerOnCallbackPolicy: "require"|"audit"|"allow",
|
|
375
|
+
* codeReusePolicy: "reject"|"allow",
|
|
376
|
+
* allowedRedirectUris: string[],
|
|
377
|
+
* allowedResponseTypes: string[],
|
|
378
|
+
* seenCodeStore: { hasSeen: function(code): boolean },
|
|
379
|
+
* maxParamBytes: number,
|
|
380
|
+
* maxBytes: number,
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* var hostile = {
|
|
384
|
+
* response_type: "code",
|
|
385
|
+
* redirect_uri: "https://attacker.example/callback",
|
|
386
|
+
* scope: "openid",
|
|
387
|
+
* };
|
|
388
|
+
* var rv = b.guardOauth.validate(hostile, { profile: "strict" });
|
|
389
|
+
* rv.ok; // → false
|
|
390
|
+
* rv.issues[0].ruleId; // → "oauth.pkce-missing"
|
|
391
|
+
*
|
|
392
|
+
* var benign = {
|
|
393
|
+
* response_type: "code",
|
|
394
|
+
* redirect_uri: "https://app.example.com/callback",
|
|
395
|
+
* state: "csrf-rand-1",
|
|
396
|
+
* scope: "openid profile",
|
|
397
|
+
* code_challenge: "abc123def456ghi789jkl012mno345pqr678",
|
|
398
|
+
* code_challenge_method: "S256",
|
|
399
|
+
* };
|
|
400
|
+
* var ok = b.guardOauth.validate(benign, {
|
|
401
|
+
* profile: "strict",
|
|
402
|
+
* allowedRedirectUris: ["https://app.example.com/callback"],
|
|
403
|
+
* });
|
|
404
|
+
* ok.ok; // → true
|
|
405
|
+
*/
|
|
300
406
|
function validate(input, opts) {
|
|
301
407
|
opts = _resolveOpts(opts);
|
|
302
408
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -305,6 +411,36 @@ function validate(input, opts) {
|
|
|
305
411
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
306
412
|
}
|
|
307
413
|
|
|
414
|
+
/**
|
|
415
|
+
* @primitive b.guardOauth.sanitize
|
|
416
|
+
* @signature b.guardOauth.sanitize(input, opts?)
|
|
417
|
+
* @since 0.7.49
|
|
418
|
+
* @status stable
|
|
419
|
+
* @related b.guardOauth.validate, b.guardOauth.gate
|
|
420
|
+
*
|
|
421
|
+
* Pass-through-or-throw form of `validate`. OAuth flow bundles
|
|
422
|
+
* can't be partially repaired — a missing `state` or wrong
|
|
423
|
+
* `redirect_uri` is a refuse-class outcome, not something the
|
|
424
|
+
* guard can patch up safely. Returns the input unchanged when
|
|
425
|
+
* the issue list contains no `critical` / `high` entries; throws
|
|
426
|
+
* `GuardOauthError` carrying the offending `ruleId` otherwise.
|
|
427
|
+
*
|
|
428
|
+
* @opts
|
|
429
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
430
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
431
|
+
* ...: every guardOauth.validate opt is honored,
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* try {
|
|
435
|
+
* b.guardOauth.sanitize({
|
|
436
|
+
* response_type: "code",
|
|
437
|
+
* redirect_uri: "https://app.example.com/callback",
|
|
438
|
+
* scope: "openid",
|
|
439
|
+
* }, { profile: "strict" });
|
|
440
|
+
* } catch (e) {
|
|
441
|
+
* e.code; // → "oauth.pkce-missing"
|
|
442
|
+
* }
|
|
443
|
+
*/
|
|
308
444
|
function sanitize(input, opts) {
|
|
309
445
|
opts = _resolveOpts(opts);
|
|
310
446
|
// OAuth flows can't be repaired — sanitize either passes through
|
|
@@ -319,6 +455,47 @@ function sanitize(input, opts) {
|
|
|
319
455
|
return input;
|
|
320
456
|
}
|
|
321
457
|
|
|
458
|
+
/**
|
|
459
|
+
* @primitive b.guardOauth.gate
|
|
460
|
+
* @signature b.guardOauth.gate(opts?)
|
|
461
|
+
* @since 0.7.49
|
|
462
|
+
* @status stable
|
|
463
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
464
|
+
* @related b.guardOauth.validate, b.guardOauth.sanitize, b.auth.oauth
|
|
465
|
+
*
|
|
466
|
+
* Build a `gateContract.buildGuardGate`-shaped gate that pulls
|
|
467
|
+
* `ctx.oauthFlow` (or `ctx.flow`) and dispatches to `validate`.
|
|
468
|
+
* Returns `{ ok: true, action: "serve" }` when the issue list is
|
|
469
|
+
* empty, `{ ok: true, action: "audit-only", issues }` when only
|
|
470
|
+
* low-severity issues fire, and `{ ok: false, action: "refuse",
|
|
471
|
+
* issues }` on any `critical` / `high` issue. Compose into the
|
|
472
|
+
* authorization-callback handler before exchanging the code with
|
|
473
|
+
* the IdP — refusal on a hostile callback prevents the token
|
|
474
|
+
* exchange entirely.
|
|
475
|
+
*
|
|
476
|
+
* @opts
|
|
477
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
478
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
479
|
+
* name: string, // gate label for audit trails
|
|
480
|
+
* ...: every guardOauth.validate opt is honored,
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* var oauthGate = b.guardOauth.gate({
|
|
484
|
+
* profile: "strict",
|
|
485
|
+
* allowedRedirectUris: ["https://app.example.com/callback"],
|
|
486
|
+
* });
|
|
487
|
+
* var rv = await oauthGate.run({
|
|
488
|
+
* oauthFlow: {
|
|
489
|
+
* response_type: "code",
|
|
490
|
+
* redirect_uri: "https://attacker.example/callback",
|
|
491
|
+
* state: "csrf-rand-1",
|
|
492
|
+
* scope: "openid",
|
|
493
|
+
* code_challenge: "abc123def456ghi789jkl012mno345pqr678",
|
|
494
|
+
* code_challenge_method: "S256",
|
|
495
|
+
* },
|
|
496
|
+
* });
|
|
497
|
+
* rv.action; // → "refuse"
|
|
498
|
+
*/
|
|
322
499
|
function gate(opts) {
|
|
323
500
|
opts = _resolveOpts(opts);
|
|
324
501
|
return gateContract.buildGuardGate(
|
|
@@ -342,14 +519,86 @@ function gate(opts) {
|
|
|
342
519
|
});
|
|
343
520
|
}
|
|
344
521
|
|
|
522
|
+
/**
|
|
523
|
+
* @primitive b.guardOauth.buildProfile
|
|
524
|
+
* @signature b.guardOauth.buildProfile(opts)
|
|
525
|
+
* @since 0.7.49
|
|
526
|
+
* @status stable
|
|
527
|
+
* @related b.guardOauth.gate, b.guardOauth.compliancePosture
|
|
528
|
+
*
|
|
529
|
+
* Compose a derived profile from one or more named bases plus
|
|
530
|
+
* inline overrides. `opts.extends` is a profile name (`"strict"` /
|
|
531
|
+
* `"balanced"` / `"permissive"`) or an array of names; later
|
|
532
|
+
* entries shadow earlier ones, and inline `opts` keys win last.
|
|
533
|
+
* Operators stage profile overlays here so the final shape is
|
|
534
|
+
* traceable to a baseline rather than a hand-typed dictionary.
|
|
535
|
+
*
|
|
536
|
+
* @opts
|
|
537
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
538
|
+
* ...: any guardOauth key, // inline override of resolved keys
|
|
539
|
+
*
|
|
540
|
+
* @example
|
|
541
|
+
* var custom = b.guardOauth.buildProfile({
|
|
542
|
+
* extends: "balanced",
|
|
543
|
+
* pkcePolicy: "require-s256",
|
|
544
|
+
* allowedResponseTypes: ["code"],
|
|
545
|
+
* });
|
|
546
|
+
* custom.pkcePolicy; // → "require-s256"
|
|
547
|
+
* custom.allowedResponseTypes.length; // → 1
|
|
548
|
+
*/
|
|
345
549
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
346
550
|
|
|
551
|
+
/**
|
|
552
|
+
* @primitive b.guardOauth.compliancePosture
|
|
553
|
+
* @signature b.guardOauth.compliancePosture(name)
|
|
554
|
+
* @since 0.7.49
|
|
555
|
+
* @status stable
|
|
556
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
557
|
+
* @related b.guardOauth.gate, b.guardOauth.buildProfile
|
|
558
|
+
*
|
|
559
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
560
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
561
|
+
* the posture object — the caller may mutate freely. Throws
|
|
562
|
+
* `GuardOauthError("oauth.bad-posture")` on unknown name.
|
|
563
|
+
* Postures extend the strict profile (or balanced for `gdpr`)
|
|
564
|
+
* with a `forensicSnippetBytes` cap appropriate to the regime.
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* var posture = b.guardOauth.compliancePosture("pci-dss");
|
|
568
|
+
* posture.pkcePolicy; // → "require-s256"
|
|
569
|
+
* posture.forensicSnippetBytes; // → 256
|
|
570
|
+
*/
|
|
347
571
|
function compliancePosture(name) {
|
|
348
572
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
349
573
|
_err, "oauth");
|
|
350
574
|
}
|
|
351
575
|
|
|
352
576
|
var _oauthRulePacks = gateContract.makeRulePackLoader(GuardOauthError, "oauth");
|
|
577
|
+
/**
|
|
578
|
+
* @primitive b.guardOauth.loadRulePack
|
|
579
|
+
* @signature b.guardOauth.loadRulePack(pack)
|
|
580
|
+
* @since 0.7.49
|
|
581
|
+
* @status stable
|
|
582
|
+
* @related b.guardOauth.gate
|
|
583
|
+
*
|
|
584
|
+
* Register an operator-supplied rule pack with the guard-oauth
|
|
585
|
+
* registry. The pack is identified by `pack.id` (non-empty
|
|
586
|
+
* string) and stored for later inspection / dispatch by gates
|
|
587
|
+
* that opt in via `opts.rulePackId`. Returns the pack object
|
|
588
|
+
* unchanged on success; throws `GuardOauthError("oauth.bad-opt")`
|
|
589
|
+
* when `pack` is missing or `pack.id` is not a non-empty string.
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* var pack = b.guardOauth.loadRulePack({
|
|
593
|
+
* id: "scope-narrow",
|
|
594
|
+
* rules: [
|
|
595
|
+
* { id: "no-admin", severity: "high",
|
|
596
|
+
* detect: function (flow) { return /\badmin\b/.test(flow.scope || ""); },
|
|
597
|
+
* reason: "tenant forbids admin scope on user-flow callbacks" },
|
|
598
|
+
* ],
|
|
599
|
+
* });
|
|
600
|
+
* pack.id; // → "scope-narrow"
|
|
601
|
+
*/
|
|
353
602
|
var loadRulePack = _oauthRulePacks.load;
|
|
354
603
|
|
|
355
604
|
module.exports = {
|