@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-auth.js
CHANGED
|
@@ -1,30 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardAuth
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Auth
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* }
|
|
7
|
+
* @intro
|
|
8
|
+
* Composite auth-bundle safety primitive (KIND="auth-bundle"). One
|
|
9
|
+
* gate that sequences `b.guardJwt` (bearer token), `b.guardOauth`
|
|
10
|
+
* (authorization-code / token-exchange flow shape), `b.cookies.parseSafe`
|
|
11
|
+
* (Cookie header), and a light request-header threat scan
|
|
12
|
+
* (Content-Length + Transfer-Encoding header smuggling per RFC 9112
|
|
13
|
+
* §6.1) into a single check operators wire into the request
|
|
14
|
+
* lifecycle. Consumes `ctx.authBundle`:
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* {
|
|
17
|
+
* jwtToken?: string, // routed to guardJwt
|
|
18
|
+
* oauthFlow?: object, // routed to guardOauth
|
|
19
|
+
* cookieHeader?: string, // routed to b.cookies.parseSafe
|
|
20
|
+
* requestHeaders?: object, // routed through threat detection
|
|
21
|
+
* }
|
|
19
22
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Each sub-validator runs independently; aggregated issues carry a
|
|
24
|
+
* `source` field (`"jwt"` / `"oauth"` / `"cookies"` / `"headers"` /
|
|
25
|
+
* `"auth"`) tagging which sub-guard raised them so operators see
|
|
26
|
+
* the full failure surface in one verdict.
|
|
27
|
+
*
|
|
28
|
+
* Refusal posture: stale-token / alg=none JWT / unknown OAuth grant /
|
|
29
|
+
* CL+TE header smuggling all surface as high-severity issues. Strict
|
|
30
|
+
* profile requires at least one auth input via `requireAtLeastOne` —
|
|
31
|
+
* a bundle with no jwtToken / oauthFlow / cookieHeader / requestHeaders
|
|
32
|
+
* is refused so operators don't accidentally ship an unauthenticated
|
|
33
|
+
* request through a gate they thought was active.
|
|
26
34
|
*
|
|
27
|
-
*
|
|
35
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance postures:
|
|
36
|
+
* `hipaa` / `pci-dss` / `gdpr` / `soc2`. Operators select via
|
|
37
|
+
* `{ profile: "strict" }` or `{ compliance: "hipaa" }`; postures
|
|
38
|
+
* overlay on the profile baseline.
|
|
39
|
+
*
|
|
40
|
+
* @card
|
|
41
|
+
* Composite auth-bundle safety primitive (KIND="auth-bundle").
|
|
28
42
|
*/
|
|
29
43
|
|
|
30
44
|
var lazyRequire = require("./lazy-require");
|
|
@@ -183,6 +197,41 @@ function _detectIssues(bundle, opts) {
|
|
|
183
197
|
return issues;
|
|
184
198
|
}
|
|
185
199
|
|
|
200
|
+
/**
|
|
201
|
+
* @primitive b.guardAuth.validate
|
|
202
|
+
* @signature b.guardAuth.validate(input, opts?)
|
|
203
|
+
* @since 0.7.41
|
|
204
|
+
* @status stable
|
|
205
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
206
|
+
* @related b.guardAuth.sanitize, b.guardAuth.gate, b.guardJwt.validate, b.guardOauth.validate
|
|
207
|
+
*
|
|
208
|
+
* Inspect an auth-bundle object and return `{ ok, issues, summary }`.
|
|
209
|
+
* Each issue carries `{ kind, severity, ruleId, source, snippet }`
|
|
210
|
+
* with severity in `"warn"|"high"|"critical"` and `source` tagging
|
|
211
|
+
* the sub-guard that raised it (`"jwt"` / `"oauth"` / `"cookies"` /
|
|
212
|
+
* `"headers"` / `"auth"`). Pure inspection — never mutates input or
|
|
213
|
+
* throws on hostile bundles.
|
|
214
|
+
*
|
|
215
|
+
* Strict profile sets `requireAtLeastOne: true` so an empty bundle
|
|
216
|
+
* (no jwtToken / oauthFlow / cookieHeader / requestHeaders) emits a
|
|
217
|
+
* `no-auth-input` issue — guards against an operator wiring a gate
|
|
218
|
+
* onto a request that ships no credentials at all.
|
|
219
|
+
*
|
|
220
|
+
* @opts
|
|
221
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
222
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
223
|
+
* childProfile: "strict"|"balanced"|"permissive", // forwarded to guardJwt / guardOauth
|
|
224
|
+
* requireAtLeastOne: boolean,
|
|
225
|
+
* allowedRedirectUris: string[], // forwarded to guardOauth
|
|
226
|
+
* maxBytes: number, // bundle JSON-byte cap
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* var rv = b.guardAuth.validate({
|
|
230
|
+
* jwtToken: "eyJhbGciOiJub25lIn0.eyJzdWIiOiJ4In0.",
|
|
231
|
+
* }, { profile: "strict" });
|
|
232
|
+
* rv.ok; // → false
|
|
233
|
+
* rv.issues.some(function (i) { return i.source === "jwt"; }); // → true
|
|
234
|
+
*/
|
|
186
235
|
function validate(input, opts) {
|
|
187
236
|
opts = _resolveOpts(opts);
|
|
188
237
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -191,6 +240,32 @@ function validate(input, opts) {
|
|
|
191
240
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
192
241
|
}
|
|
193
242
|
|
|
243
|
+
/**
|
|
244
|
+
* @primitive b.guardAuth.sanitize
|
|
245
|
+
* @signature b.guardAuth.sanitize(input, opts?)
|
|
246
|
+
* @since 0.7.41
|
|
247
|
+
* @status stable
|
|
248
|
+
* @related b.guardAuth.validate, b.guardAuth.gate
|
|
249
|
+
*
|
|
250
|
+
* Strict pass-through validator. The auth-bundle is composed of values
|
|
251
|
+
* the framework cannot safely mutate (forging a JWT alg / rewriting an
|
|
252
|
+
* OAuth state parameter / dropping cookies would be silently dangerous
|
|
253
|
+
* — sanitize must never disarm an actual attack token), so this
|
|
254
|
+
* function refuses (throws `GuardAuthError`) on any critical or high
|
|
255
|
+
* issue and returns the input unchanged when clean.
|
|
256
|
+
*
|
|
257
|
+
* @opts
|
|
258
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
259
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* var clean = b.guardAuth.sanitize({
|
|
263
|
+
* jwtToken: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." +
|
|
264
|
+
* "eyJpc3MiOiJleGFtcGxlIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE3MDAwMDAwMDB9.sig",
|
|
265
|
+
* cookieHeader: "sid=abc123",
|
|
266
|
+
* }, { profile: "balanced" });
|
|
267
|
+
* clean.cookieHeader; // → "sid=abc123"
|
|
268
|
+
*/
|
|
194
269
|
function sanitize(input, opts) {
|
|
195
270
|
opts = _resolveOpts(opts);
|
|
196
271
|
if (!input || typeof input !== "object") {
|
|
@@ -207,6 +282,36 @@ function sanitize(input, opts) {
|
|
|
207
282
|
return input;
|
|
208
283
|
}
|
|
209
284
|
|
|
285
|
+
/**
|
|
286
|
+
* @primitive b.guardAuth.gate
|
|
287
|
+
* @signature b.guardAuth.gate(opts?)
|
|
288
|
+
* @since 0.7.41
|
|
289
|
+
* @status stable
|
|
290
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
291
|
+
* @related b.guardAuth.validate, b.guardAuth.sanitize, b.middleware.bearerAuth
|
|
292
|
+
*
|
|
293
|
+
* Build a `b.gateContract` gate that consumes `ctx.authBundle` (or
|
|
294
|
+
* `ctx.auth`) and dispatches to guardJwt / guardOauth / cookies /
|
|
295
|
+
* header-smuggling detection. Action chain on validation:
|
|
296
|
+
* `serve` (no bundle, or bundle clean) → `audit-only` (warn-only
|
|
297
|
+
* issues) → `refuse` (any critical or high issue from any
|
|
298
|
+
* sub-validator). No `sanitize` action — the auth bundle isn't
|
|
299
|
+
* repairable in transit.
|
|
300
|
+
*
|
|
301
|
+
* @opts
|
|
302
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
303
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
304
|
+
* name: string, // gate identity for audit / observability
|
|
305
|
+
* childProfile: "strict"|"balanced"|"permissive",
|
|
306
|
+
* allowedRedirectUris: string[],
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* var authGate = b.guardAuth.gate({ profile: "strict" });
|
|
310
|
+
* var verdict = await authGate.check({ authBundle: {
|
|
311
|
+
* jwtToken: "eyJhbGciOiJub25lIn0.eyJzdWIiOiJ4In0.",
|
|
312
|
+
* } });
|
|
313
|
+
* verdict.action; // → "refuse"
|
|
314
|
+
*/
|
|
210
315
|
function gate(opts) {
|
|
211
316
|
opts = _resolveOpts(opts);
|
|
212
317
|
return gateContract.buildGuardGate(
|
|
@@ -230,14 +335,82 @@ function gate(opts) {
|
|
|
230
335
|
});
|
|
231
336
|
}
|
|
232
337
|
|
|
338
|
+
/**
|
|
339
|
+
* @primitive b.guardAuth.buildProfile
|
|
340
|
+
* @signature b.guardAuth.buildProfile(opts)
|
|
341
|
+
* @since 0.7.41
|
|
342
|
+
* @status stable
|
|
343
|
+
* @related b.guardAuth.gate, b.guardAuth.compliancePosture
|
|
344
|
+
*
|
|
345
|
+
* Compose a derived profile from one or more named bases plus inline
|
|
346
|
+
* overrides. `opts.extends` is a profile name (`"strict"` /
|
|
347
|
+
* `"balanced"` / `"permissive"`) or an array of names; later entries
|
|
348
|
+
* shadow earlier ones. Inline `opts` keys win last. Used to keep
|
|
349
|
+
* operator-defined profiles traceable to a baseline rather than
|
|
350
|
+
* re-typing every key.
|
|
351
|
+
*
|
|
352
|
+
* @opts
|
|
353
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* var custom = b.guardAuth.buildProfile({
|
|
357
|
+
* extends: "balanced",
|
|
358
|
+
* requireAtLeastOne: true,
|
|
359
|
+
* });
|
|
360
|
+
* custom.requireAtLeastOne; // → true
|
|
361
|
+
* custom.bidiPolicy; // → "reject"
|
|
362
|
+
*/
|
|
233
363
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
234
364
|
|
|
365
|
+
/**
|
|
366
|
+
* @primitive b.guardAuth.compliancePosture
|
|
367
|
+
* @signature b.guardAuth.compliancePosture(name)
|
|
368
|
+
* @since 0.7.41
|
|
369
|
+
* @status stable
|
|
370
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
371
|
+
* @related b.guardAuth.gate, b.guardAuth.buildProfile
|
|
372
|
+
*
|
|
373
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
374
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of the
|
|
375
|
+
* posture object — the caller may mutate freely. Throws
|
|
376
|
+
* `GuardAuthError("auth.bad-posture")` on unknown name.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* var posture = b.guardAuth.compliancePosture("hipaa");
|
|
380
|
+
* posture.forensicSnippetBytes; // → 512
|
|
381
|
+
* posture.bidiPolicy; // → "reject"
|
|
382
|
+
*/
|
|
235
383
|
function compliancePosture(name) {
|
|
236
384
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
237
385
|
_err, "auth");
|
|
238
386
|
}
|
|
239
387
|
|
|
240
388
|
var _authRulePacks = gateContract.makeRulePackLoader(GuardAuthError, "auth");
|
|
389
|
+
/**
|
|
390
|
+
* @primitive b.guardAuth.loadRulePack
|
|
391
|
+
* @signature b.guardAuth.loadRulePack(pack)
|
|
392
|
+
* @since 0.7.41
|
|
393
|
+
* @status stable
|
|
394
|
+
* @related b.guardAuth.gate
|
|
395
|
+
*
|
|
396
|
+
* Register an operator-supplied rule pack with the guard-auth
|
|
397
|
+
* registry. The pack is identified by `pack.id` (non-empty string)
|
|
398
|
+
* and stored for later inspection / dispatch by gates that opt in
|
|
399
|
+
* via `opts.rulePackId`. Returns the pack object unchanged on
|
|
400
|
+
* success; throws `GuardAuthError("auth.bad-opt")` when `pack` is
|
|
401
|
+
* missing or `pack.id` is not a non-empty string.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* var pack = b.guardAuth.loadRulePack({
|
|
405
|
+
* id: "tenant-bearer-prefix",
|
|
406
|
+
* rules: [
|
|
407
|
+
* { id: "tenant-prefix", severity: "high",
|
|
408
|
+
* detect: function (b2) { return b2.jwtToken && b2.jwtToken.indexOf("tenant_") !== 0; },
|
|
409
|
+
* reason: "JWT does not carry the required tenant_ prefix" },
|
|
410
|
+
* ],
|
|
411
|
+
* });
|
|
412
|
+
* pack.id; // → "tenant-bearer-prefix"
|
|
413
|
+
*/
|
|
241
414
|
var loadRulePack = _authRulePacks.load;
|
|
242
415
|
|
|
243
416
|
module.exports = {
|
package/lib/guard-cidr.js
CHANGED
|
@@ -1,36 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardCidr
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Cidr
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* CIDR identifier-safety primitive (KIND="identifier"). Validates
|
|
9
|
+
* user-supplied CIDR notation strings (IPv4 + IPv6) destined for
|
|
10
|
+
* network allowlists, ACLs, security-group rules, and tenant-
|
|
11
|
+
* boundary configuration. Consumes `ctx.identifier` (or
|
|
12
|
+
* `ctx.cidr`).
|
|
9
13
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Often a typo that produces unexpected match semantics.
|
|
18
|
-
* - Reserved IPv4 ranges (RFC 1918 private 10/8, 172.16/12,
|
|
19
|
-
* 192.168/16; loopback 127/8; link-local 169.254/16; multicast
|
|
20
|
-
* 224/4; reserved 240/4; documentation 192.0.2/24, 198.51.100/24,
|
|
21
|
-
* 203.0.113/24; benchmarking 198.18/15; CGNAT 100.64/10).
|
|
22
|
-
* - Reserved IPv6 ranges — loopback `::1`, unspecified `::/128`,
|
|
23
|
-
* ULA `fc00::/7`, link-local `fe80::/10`, multicast `ff00::/8`,
|
|
24
|
-
* IPv4-mapped `::ffff:0:0/96`, documentation `2001:db8::/32`,
|
|
25
|
-
* teredo `2001::/32`, deprecated 6to4 `2002::/16`.
|
|
26
|
-
* - IPv4-mapped IPv6 confusion (CVE-2021-22931 IPv6 / IPv4 dual-
|
|
27
|
-
* stack class) — `::ffff:192.168.1.1` represents the IPv4 address
|
|
28
|
-
* in IPv6 namespace and trips dual-stack allowlist matchers.
|
|
29
|
-
* - BIDI / zero-width / control / null-byte universal refuse.
|
|
14
|
+
* Shape and prefix-bound enforcement: every CIDR splits into
|
|
15
|
+
* `address/mask`. IPv4 must be strict dotted-decimal (no leading
|
|
16
|
+
* zeros — octal-form `0177.0.0.1` is refused at the parser; that
|
|
17
|
+
* class is owned by `b.guardDomain`). IPv4 mask is `[0-32]`; IPv6
|
|
18
|
+
* mask is `[0-128]`; out-of-range and non-numeric masks refuse.
|
|
19
|
+
* IPv6 supports `::` zero-group compression with the standard
|
|
20
|
+
* "at most one `::`" rule.
|
|
30
21
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
22
|
+
* Reserved-block awareness: IPv4 ranges per RFC 1918 (private 10/8,
|
|
23
|
+
* 172.16/12, 192.168/16), loopback 127/8, link-local 169.254/16,
|
|
24
|
+
* multicast 224/4, reserved class-E 240/4, documentation 192.0.2/24,
|
|
25
|
+
* 198.51.100/24, 203.0.113/24, benchmarking 198.18/15, and CGNAT
|
|
26
|
+
* 100.64/10. IPv6 ranges: loopback `::1`, unspecified `::/128`,
|
|
27
|
+
* ULA `fc00::/7`, link-local `fe80::/10`, multicast `ff00::/8`,
|
|
28
|
+
* IPv4-mapped `::ffff:0:0/96`, documentation `2001:db8::/32`,
|
|
29
|
+
* teredo `2001::/32`, deprecated 6to4 `2002::/16`. IPv4-mapped IPv6
|
|
30
|
+
* trips dual-stack allowlist confusion (CVE-2021-22931 class) and
|
|
31
|
+
* refuses under strict.
|
|
32
|
+
*
|
|
33
|
+
* Network-address alignment: `10.0.0.1/24` has host bits set under
|
|
34
|
+
* a /24 mask when the canonical network is `10.0.0.0/24`. Common
|
|
35
|
+
* typo class — refused under strict, audited under balanced.
|
|
36
|
+
* BIDI / control / null-byte / zero-width are universal-refuse at
|
|
37
|
+
* every profile (codepoint-class catalog).
|
|
38
|
+
*
|
|
39
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
40
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`.
|
|
41
|
+
*
|
|
42
|
+
* @card
|
|
43
|
+
* CIDR identifier-safety primitive (KIND="identifier").
|
|
34
44
|
*/
|
|
35
45
|
|
|
36
46
|
var codepointClass = require("./codepoint-class");
|
|
@@ -418,6 +428,41 @@ function _detectIssues(input, opts) {
|
|
|
418
428
|
return issues;
|
|
419
429
|
}
|
|
420
430
|
|
|
431
|
+
/**
|
|
432
|
+
* @primitive b.guardCidr.validate
|
|
433
|
+
* @signature b.guardCidr.validate(input, opts?)
|
|
434
|
+
* @since 0.7.41
|
|
435
|
+
* @status stable
|
|
436
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
437
|
+
* @related b.guardCidr.sanitize, b.guardCidr.gate
|
|
438
|
+
*
|
|
439
|
+
* Inspect a CIDR notation string and return `{ ok, issues, summary }`.
|
|
440
|
+
* Each issue carries `{ kind, severity, ruleId, snippet }` with
|
|
441
|
+
* severity in `"warn"|"high"|"critical"`. Detected: malformed address
|
|
442
|
+
* shape, octet-out-of-range, mask-out-of-range, network-address
|
|
443
|
+
* misalignment, reserved-range membership, IPv4-mapped-IPv6
|
|
444
|
+
* confusion, family mismatch, bare IP without `/mask`, BIDI / control
|
|
445
|
+
* / null-byte / zero-width codepoints. Pure inspection — never
|
|
446
|
+
* mutates input or throws.
|
|
447
|
+
*
|
|
448
|
+
* @opts
|
|
449
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
450
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
451
|
+
* family: "either"|"ipv4-only"|"ipv6-only",
|
|
452
|
+
* networkAlignmentPolicy: "reject"|"audit"|"allow",
|
|
453
|
+
* reservedRangesPolicy: "reject"|"audit"|"allow",
|
|
454
|
+
* ipv4MappedIpv6Policy: "reject"|"audit"|"allow",
|
|
455
|
+
* requireMaskPolicy: "reject-bare-ip"|"audit-bare-ip"|"allow-bare-ip",
|
|
456
|
+
* maxBytes: number, // CIDR string byte cap (default 64)
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* var rv = b.guardCidr.validate("10.0.0.0/8", { profile: "strict" });
|
|
460
|
+
* rv.ok; // → false
|
|
461
|
+
* rv.issues.some(function (i) { return i.kind === "reserved-range"; }); // → true
|
|
462
|
+
*
|
|
463
|
+
* var clean = b.guardCidr.validate("8.8.8.0/24", { profile: "strict" });
|
|
464
|
+
* clean.ok; // → true
|
|
465
|
+
*/
|
|
421
466
|
function validate(input, opts) {
|
|
422
467
|
opts = _resolveOpts(opts);
|
|
423
468
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -434,6 +479,31 @@ function validate(input, opts) {
|
|
|
434
479
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
435
480
|
}
|
|
436
481
|
|
|
482
|
+
/**
|
|
483
|
+
* @primitive b.guardCidr.sanitize
|
|
484
|
+
* @signature b.guardCidr.sanitize(input, opts?)
|
|
485
|
+
* @since 0.7.41
|
|
486
|
+
* @status stable
|
|
487
|
+
* @related b.guardCidr.validate, b.guardCidr.gate
|
|
488
|
+
*
|
|
489
|
+
* Normalize a CIDR string when no critical/high issues fire. Throws
|
|
490
|
+
* `GuardCidrError` on any high/critical refusal (reserved-range,
|
|
491
|
+
* misalignment under strict, BIDI / null-byte / control bytes).
|
|
492
|
+
* Safe transforms applied otherwise: lowercase IPv6 hex groups,
|
|
493
|
+
* preserve mask form. IPv4 is returned unchanged (no canonical
|
|
494
|
+
* casing).
|
|
495
|
+
*
|
|
496
|
+
* @opts
|
|
497
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
498
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* var safe = b.guardCidr.sanitize("2001:DB8::/32", { profile: "permissive" });
|
|
502
|
+
* safe; // → "2001:db8::/32"
|
|
503
|
+
*
|
|
504
|
+
* var v4 = b.guardCidr.sanitize("8.8.8.0/24", { profile: "strict" });
|
|
505
|
+
* v4; // → "8.8.8.0/24"
|
|
506
|
+
*/
|
|
437
507
|
function sanitize(input, opts) {
|
|
438
508
|
opts = _resolveOpts(opts);
|
|
439
509
|
if (typeof input !== "string") {
|
|
@@ -454,6 +524,32 @@ function sanitize(input, opts) {
|
|
|
454
524
|
return mask === null ? addr.toLowerCase() : addr.toLowerCase() + "/" + mask;
|
|
455
525
|
}
|
|
456
526
|
|
|
527
|
+
/**
|
|
528
|
+
* @primitive b.guardCidr.gate
|
|
529
|
+
* @signature b.guardCidr.gate(opts?)
|
|
530
|
+
* @since 0.7.41
|
|
531
|
+
* @status stable
|
|
532
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
533
|
+
* @related b.guardCidr.validate, b.guardCidr.sanitize
|
|
534
|
+
*
|
|
535
|
+
* Build a `b.gateContract` gate that consumes `ctx.identifier` (or
|
|
536
|
+
* `ctx.cidr`) and dispatches `serve` (no input or clean) →
|
|
537
|
+
* `audit-only` (warn-only issues) → `refuse` (any critical or high
|
|
538
|
+
* issue). No `sanitize` action — CIDR sanitization is caller-driven
|
|
539
|
+
* via `b.guardCidr.sanitize`; an allowlist gate that silently rewrote
|
|
540
|
+
* the operator's network range would be its own bug class.
|
|
541
|
+
*
|
|
542
|
+
* @opts
|
|
543
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
544
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
545
|
+
* name: string, // gate identity for audit / observability
|
|
546
|
+
* family: "either"|"ipv4-only"|"ipv6-only",
|
|
547
|
+
*
|
|
548
|
+
* @example
|
|
549
|
+
* var cidrGate = b.guardCidr.gate({ profile: "strict", family: "ipv4-only" });
|
|
550
|
+
* var verdict = await cidrGate.check({ identifier: "10.0.0.0/8" });
|
|
551
|
+
* verdict.action; // → "refuse"
|
|
552
|
+
*/
|
|
457
553
|
function gate(opts) {
|
|
458
554
|
opts = _resolveOpts(opts);
|
|
459
555
|
return gateContract.buildGuardGate(
|
|
@@ -477,14 +573,80 @@ function gate(opts) {
|
|
|
477
573
|
});
|
|
478
574
|
}
|
|
479
575
|
|
|
576
|
+
/**
|
|
577
|
+
* @primitive b.guardCidr.buildProfile
|
|
578
|
+
* @signature b.guardCidr.buildProfile(opts)
|
|
579
|
+
* @since 0.7.41
|
|
580
|
+
* @status stable
|
|
581
|
+
* @related b.guardCidr.gate, b.guardCidr.compliancePosture
|
|
582
|
+
*
|
|
583
|
+
* Compose a derived profile from one or more named bases plus inline
|
|
584
|
+
* overrides. `opts.extends` is a profile name (`"strict"` /
|
|
585
|
+
* `"balanced"` / `"permissive"`) or an array of names; later entries
|
|
586
|
+
* shadow earlier ones. Inline `opts` keys win last.
|
|
587
|
+
*
|
|
588
|
+
* @opts
|
|
589
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* var custom = b.guardCidr.buildProfile({
|
|
593
|
+
* extends: "balanced",
|
|
594
|
+
* reservedRangesPolicy: "reject",
|
|
595
|
+
* });
|
|
596
|
+
* custom.reservedRangesPolicy; // → "reject"
|
|
597
|
+
* custom.bidiPolicy; // → "reject"
|
|
598
|
+
*/
|
|
480
599
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
481
600
|
|
|
601
|
+
/**
|
|
602
|
+
* @primitive b.guardCidr.compliancePosture
|
|
603
|
+
* @signature b.guardCidr.compliancePosture(name)
|
|
604
|
+
* @since 0.7.41
|
|
605
|
+
* @status stable
|
|
606
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
607
|
+
* @related b.guardCidr.gate, b.guardCidr.buildProfile
|
|
608
|
+
*
|
|
609
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
610
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of the
|
|
611
|
+
* posture object — the caller may mutate freely. Throws
|
|
612
|
+
* `GuardCidrError("cidr.bad-posture")` on unknown name.
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* var posture = b.guardCidr.compliancePosture("hipaa");
|
|
616
|
+
* posture.reservedRangesPolicy; // → "reject"
|
|
617
|
+
* posture.forensicSnippetBytes; // → 128
|
|
618
|
+
*/
|
|
482
619
|
function compliancePosture(name) {
|
|
483
620
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
484
621
|
_err, "cidr");
|
|
485
622
|
}
|
|
486
623
|
|
|
487
624
|
var _cidrRulePacks = gateContract.makeRulePackLoader(GuardCidrError, "cidr");
|
|
625
|
+
/**
|
|
626
|
+
* @primitive b.guardCidr.loadRulePack
|
|
627
|
+
* @signature b.guardCidr.loadRulePack(pack)
|
|
628
|
+
* @since 0.7.41
|
|
629
|
+
* @status stable
|
|
630
|
+
* @related b.guardCidr.gate
|
|
631
|
+
*
|
|
632
|
+
* Register an operator-supplied rule pack with the guard-cidr
|
|
633
|
+
* registry. The pack is identified by `pack.id` (non-empty string)
|
|
634
|
+
* and stored for later inspection / dispatch by gates that opt in
|
|
635
|
+
* via `opts.rulePackId`. Returns the pack object unchanged on
|
|
636
|
+
* success; throws `GuardCidrError("cidr.bad-opt")` when `pack` is
|
|
637
|
+
* missing or `pack.id` is not a non-empty string.
|
|
638
|
+
*
|
|
639
|
+
* @example
|
|
640
|
+
* var pack = b.guardCidr.loadRulePack({
|
|
641
|
+
* id: "tenant-private-only",
|
|
642
|
+
* rules: [
|
|
643
|
+
* { id: "external-allowlisted", severity: "high",
|
|
644
|
+
* detect: function (cidr) { return cidr.indexOf("10.") !== 0; },
|
|
645
|
+
* reason: "tenant policy: only 10.0.0.0/8 ranges permitted" },
|
|
646
|
+
* ],
|
|
647
|
+
* });
|
|
648
|
+
* pack.id; // → "tenant-private-only"
|
|
649
|
+
*/
|
|
488
650
|
var loadRulePack = _cidrRulePacks.load;
|
|
489
651
|
|
|
490
652
|
module.exports = {
|