@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
|
@@ -82,6 +82,50 @@ var DEFAULT_CSP =
|
|
|
82
82
|
"require-trusted-types-for 'script'; " +
|
|
83
83
|
"trusted-types 'allow-duplicates' default;";
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @primitive b.middleware.securityHeaders
|
|
87
|
+
* @signature b.middleware.securityHeaders(req, res, next)
|
|
88
|
+
* @since 0.1.0
|
|
89
|
+
* @related b.middleware.cspNonce, b.middleware.cspReport, b.middleware.cors
|
|
90
|
+
*
|
|
91
|
+
* Sets the OWASP-aligned response headers every modern app should
|
|
92
|
+
* send. Constructed via `b.middleware.securityHeaders(opts)`; the
|
|
93
|
+
* resulting middleware has the `(req, res, next)` shape shown above.
|
|
94
|
+
* Headers include: HSTS (2-year max-age + includeSubDomains + preload), X-CTO
|
|
95
|
+
* nosniff, X-Frame-Options DENY, Referrer-Policy no-referrer, an
|
|
96
|
+
* extensive Permissions-Policy denylist (camera / geolocation /
|
|
97
|
+
* payment / Privacy-Sandbox attribution-reporting / bluetooth /
|
|
98
|
+
* etc.), COOP same-origin, CORP same-origin, Origin-Agent-Cluster
|
|
99
|
+
* `?1`, and a strict default CSP with `require-trusted-types-for
|
|
100
|
+
* 'script'`. Each header can be softened by passing the option
|
|
101
|
+
* value or disabled by passing `false`. Mount FIRST (after
|
|
102
|
+
* `requestId`) so headers are set before any response could be
|
|
103
|
+
* partially sent.
|
|
104
|
+
*
|
|
105
|
+
* @opts
|
|
106
|
+
* {
|
|
107
|
+
* hsts: string|false,
|
|
108
|
+
* contentTypeOptions: "nosniff"|false,
|
|
109
|
+
* frameOptions: "DENY"|"SAMEORIGIN"|false,
|
|
110
|
+
* referrerPolicy: string|false,
|
|
111
|
+
* permissionsPolicy: string|false,
|
|
112
|
+
* coop: string|false,
|
|
113
|
+
* coep: string|false,
|
|
114
|
+
* corp: string|false,
|
|
115
|
+
* originAgentCluster: "?1"|"?0"|false,
|
|
116
|
+
* dnsPrefetchControl: "off"|"on"|false,
|
|
117
|
+
* csp: string|false,
|
|
118
|
+
* reportingEndpoints: object,
|
|
119
|
+
* trustProxy: boolean|number,
|
|
120
|
+
* }
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* var b = require("@blamejs/core");
|
|
124
|
+
* var app = b.router.create();
|
|
125
|
+
* app.use(b.middleware.securityHeaders({
|
|
126
|
+
* hsts: "max-age=63072000; includeSubDomains; preload",
|
|
127
|
+
* }));
|
|
128
|
+
*/
|
|
85
129
|
function create(opts) {
|
|
86
130
|
opts = opts || {};
|
|
87
131
|
validateOpts(opts, [
|
|
@@ -56,6 +56,44 @@ function _isoFuture(s) {
|
|
|
56
56
|
return d.getTime() > Date.now();
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* @primitive b.middleware.securityTxt
|
|
61
|
+
* @signature b.middleware.securityTxt(opts)
|
|
62
|
+
* @since 0.1.0
|
|
63
|
+
* @related b.middleware.assetlinks, b.middleware.webAppManifest
|
|
64
|
+
*
|
|
65
|
+
* Serves an RFC 9116 `/.well-known/security.txt` body so security
|
|
66
|
+
* researchers find the disclosure policy. With `alsoAtRoot: true`
|
|
67
|
+
* also serves at `/security.txt`. Required fields per §2.5 are
|
|
68
|
+
* `Contact` and `Expires` — the middleware throws at create-time
|
|
69
|
+
* when either is missing OR `Expires` is in the past, and
|
|
70
|
+
* sanitizes every value against CR / LF / NUL (RFC 9116 forbids
|
|
71
|
+
* those in field values). Operators with PGP keys, hall-of-fame
|
|
72
|
+
* URLs, hiring pages, etc. populate the optional fields.
|
|
73
|
+
*
|
|
74
|
+
* @opts
|
|
75
|
+
* {
|
|
76
|
+
* contact: string[], // required, ≥1 entry
|
|
77
|
+
* expires: string, // required, future ISO timestamp
|
|
78
|
+
* encryption: string[],
|
|
79
|
+
* policy: string,
|
|
80
|
+
* ack: string,
|
|
81
|
+
* preferredLanguages: string[],
|
|
82
|
+
* hiring: string,
|
|
83
|
+
* canonical: string|string[],
|
|
84
|
+
* alsoAtRoot: boolean,
|
|
85
|
+
* audit: boolean,
|
|
86
|
+
* }
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* var b = require("@blamejs/core");
|
|
90
|
+
* var app = b.router.create();
|
|
91
|
+
* app.use(b.middleware.securityTxt({
|
|
92
|
+
* contact: ["mailto:security@example.com"],
|
|
93
|
+
* expires: "2099-01-01T00:00:00Z",
|
|
94
|
+
* policy: "https://example.com/security/policy",
|
|
95
|
+
* }));
|
|
96
|
+
*/
|
|
59
97
|
function create(opts) {
|
|
60
98
|
validateOpts.requireObject(opts, "middleware.securityTxt", SecurityTxtError);
|
|
61
99
|
validateOpts(opts, [
|
|
@@ -113,6 +113,43 @@ function _captureResponseHeaderAttrs(res, captureList, prefix) {
|
|
|
113
113
|
return out;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @primitive b.middleware.spanHttpServer
|
|
118
|
+
* @signature b.middleware.spanHttpServer(opts)
|
|
119
|
+
* @since 0.1.0
|
|
120
|
+
* @related b.middleware.tracePropagate, b.middleware.traceLogCorrelation
|
|
121
|
+
*
|
|
122
|
+
* Auto-creates a root OTel span per HTTP request, populates the
|
|
123
|
+
* `http.request.method`, `http.route`, `url.scheme`, `url.path`,
|
|
124
|
+
* `server.address`, `client.address`, `user_agent.original`, and
|
|
125
|
+
* `http.response.status_code` semconv attributes, attaches the
|
|
126
|
+
* span to `req.span`, and ends it on response close. Span kind is
|
|
127
|
+
* `server`. `ignorePaths` (strings or RegExp) keeps `/healthz` and
|
|
128
|
+
* static-asset routes out of the span volume.
|
|
129
|
+
* `captureRequestHeaders` / `captureResponseHeaders` add operator-
|
|
130
|
+
* chosen header attributes (e.g. `x-tenant-id`).
|
|
131
|
+
*
|
|
132
|
+
* @opts
|
|
133
|
+
* {
|
|
134
|
+
* tracer: object, // required
|
|
135
|
+
* onEnd: function(span): void,
|
|
136
|
+
* ignorePaths: Array<string|RegExp>,
|
|
137
|
+
* captureRequestHeaders: string[],
|
|
138
|
+
* captureResponseHeaders: string[],
|
|
139
|
+
* spanNameFn: function(req): string,
|
|
140
|
+
* audit: boolean,
|
|
141
|
+
* }
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* var b = require("@blamejs/core");
|
|
145
|
+
* var app = b.router.create();
|
|
146
|
+
* var tracer = b.observability.tracer.create({ service: "checkout" });
|
|
147
|
+
* app.use(b.middleware.tracePropagate());
|
|
148
|
+
* app.use(b.middleware.spanHttpServer({
|
|
149
|
+
* tracer: tracer,
|
|
150
|
+
* ignorePaths: ["/healthz"],
|
|
151
|
+
* }));
|
|
152
|
+
*/
|
|
116
153
|
function create(opts) {
|
|
117
154
|
validateOpts.requireObject(opts, "middleware.spanHttpServer", SpanHttpError);
|
|
118
155
|
validateOpts(opts, [
|
package/lib/middleware/sse.js
CHANGED
|
@@ -64,6 +64,42 @@ function _formatEvent(msg) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @primitive b.middleware.sse
|
|
69
|
+
* @signature b.middleware.sse(handler, opts)
|
|
70
|
+
* @since 0.1.0
|
|
71
|
+
* @related b.sse.serializeEvent, b.middleware.compression
|
|
72
|
+
*
|
|
73
|
+
* Server-Sent Events handler — one-way streaming from server to
|
|
74
|
+
* browser over a single HTTP response with `Content-Type:
|
|
75
|
+
* text/event-stream`. Browsers reconnect automatically with
|
|
76
|
+
* `Last-Event-ID` so the operator's handler can resume from the
|
|
77
|
+
* last delivered event. Refuses CRLF / NUL injection in `event` /
|
|
78
|
+
* `id` (CVE-2026-33128 / 29085 / 44217 class) — the framework
|
|
79
|
+
* does NOT silently strip; it returns a structured error.
|
|
80
|
+
* Heartbeat (default 15s) keeps corporate proxies / Heroku-style
|
|
81
|
+
* idle-timeouts from killing the stream; pass `heartbeatMs: false`
|
|
82
|
+
* to disable. SSE streams should NOT be compressed —
|
|
83
|
+
* `b.middleware.compression` already skips `text/event-stream`.
|
|
84
|
+
*
|
|
85
|
+
* The handler receives `(channel, req)`. The channel exposes
|
|
86
|
+
* `send({ id, event, data, retry })`, `ping(comment)`, `close()`,
|
|
87
|
+
* `onAbort(fn)`.
|
|
88
|
+
*
|
|
89
|
+
* @opts
|
|
90
|
+
* {
|
|
91
|
+
* heartbeatMs: number|false, // default 15000
|
|
92
|
+
* headers: object, // extra response headers
|
|
93
|
+
* }
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* var b = require("@blamejs/core");
|
|
97
|
+
* var app = b.router.create();
|
|
98
|
+
* app.get("/events", b.middleware.sse(async function (channel, req) {
|
|
99
|
+
* channel.send({ id: 1, event: "tick", data: { count: 1 } });
|
|
100
|
+
* channel.close();
|
|
101
|
+
* }, { heartbeatMs: 15000 }));
|
|
102
|
+
*/
|
|
67
103
|
function create(handler, opts) {
|
|
68
104
|
if (typeof handler !== "function") {
|
|
69
105
|
throw new Error("middleware.sse: handler must be a function (channel, req) => ...");
|
|
@@ -102,6 +102,39 @@ function _wrapLogger(baseLogger, req, opts) {
|
|
|
102
102
|
return wrapped;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* @primitive b.middleware.traceLogCorrelation
|
|
107
|
+
* @signature b.middleware.traceLogCorrelation(opts)
|
|
108
|
+
* @since 0.1.0
|
|
109
|
+
* @related b.middleware.tracePropagate, b.middleware.spanHttpServer
|
|
110
|
+
*
|
|
111
|
+
* Wraps the operator's `b.log` instance for the request lifetime
|
|
112
|
+
* so every `log() / info() / warn() / error() / debug()` call
|
|
113
|
+
* inside the handler auto-includes the canonical `trace_id` +
|
|
114
|
+
* `span_id` (and tenant attributes from W3C Baggage when present).
|
|
115
|
+
* Thin adapter — does not change levels, sinks, or the API
|
|
116
|
+
* surface; logs pass through with the trace fields injected via
|
|
117
|
+
* the meta-object second argument. When `req.trace` isn't set
|
|
118
|
+
* (operator forgot to mount `tracePropagate` first), the wrapper
|
|
119
|
+
* is a no-op pass-through.
|
|
120
|
+
*
|
|
121
|
+
* @opts
|
|
122
|
+
* {
|
|
123
|
+
* logger: object, // required b.log instance
|
|
124
|
+
* reqField: string, // default "log" → req.log
|
|
125
|
+
* includeBaggage: boolean, // default true
|
|
126
|
+
* audit: boolean,
|
|
127
|
+
* }
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* var b = require("@blamejs/core");
|
|
131
|
+
* var app = b.router.create();
|
|
132
|
+
* app.use(b.middleware.tracePropagate());
|
|
133
|
+
* app.use(b.middleware.traceLogCorrelation({
|
|
134
|
+
* logger: b.log.boot("api"),
|
|
135
|
+
* reqField: "log",
|
|
136
|
+
* }));
|
|
137
|
+
*/
|
|
105
138
|
function create(opts) {
|
|
106
139
|
validateOpts.requireObject(opts, "middleware.traceLogCorrelation", TraceLogError);
|
|
107
140
|
validateOpts(opts, [
|
|
@@ -45,6 +45,38 @@ var TracePropagateError = defineClass("TracePropagateError", { alwaysPermanent:
|
|
|
45
45
|
var observability = lazyRequire(function () { return require("../observability"); });
|
|
46
46
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @primitive b.middleware.tracePropagate
|
|
50
|
+
* @signature b.middleware.tracePropagate(opts)
|
|
51
|
+
* @since 0.1.0
|
|
52
|
+
* @related b.middleware.traceLogCorrelation, b.middleware.spanHttpServer
|
|
53
|
+
*
|
|
54
|
+
* Consumes the inbound `traceparent` header per W3C Trace Context
|
|
55
|
+
* and stamps `req.trace = { traceId, parentId, sampled,
|
|
56
|
+
* hadUpstream, tracestate }` for downstream handlers and outbound
|
|
57
|
+
* HTTP propagation. With `generateIfMissing: true` (default) the
|
|
58
|
+
* middleware synthesises a fresh trace when the inbound header is
|
|
59
|
+
* absent or malformed, and stamps `hadUpstream: false` so downstream
|
|
60
|
+
* code can tell locally-originated traces apart. `setResponseHeader:
|
|
61
|
+
* true` echoes the resolved `traceparent` on the response so the
|
|
62
|
+
* client sees what the server actually used.
|
|
63
|
+
*
|
|
64
|
+
* @opts
|
|
65
|
+
* {
|
|
66
|
+
* generateIfMissing: boolean, // default true
|
|
67
|
+
* auditOnMissing: boolean, // default false
|
|
68
|
+
* setResponseHeader: boolean, // default false
|
|
69
|
+
* audit: boolean,
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* var b = require("@blamejs/core");
|
|
74
|
+
* var app = b.router.create();
|
|
75
|
+
* app.use(b.middleware.tracePropagate({
|
|
76
|
+
* generateIfMissing: true,
|
|
77
|
+
* setResponseHeader: true,
|
|
78
|
+
* }));
|
|
79
|
+
*/
|
|
48
80
|
function create(opts) {
|
|
49
81
|
opts = opts || {};
|
|
50
82
|
validateOpts(opts, [
|
|
@@ -157,6 +157,35 @@ function _parseChecksumHeader(headerValue, allowedSet) {
|
|
|
157
157
|
return { algo: algo, nodeAlgo: nodeAlgo, digestB64: digestB64 };
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* @primitive b.middleware.tusUpload.memoryStore
|
|
162
|
+
* @signature b.middleware.tusUpload.memoryStore(opts)
|
|
163
|
+
* @since 0.1.0
|
|
164
|
+
* @related b.middleware.tusUpload
|
|
165
|
+
*
|
|
166
|
+
* In-memory upload store for the TUS middleware. Suitable for
|
|
167
|
+
* dev / single-node demos / test fixtures — every upload is kept
|
|
168
|
+
* in process memory until completion or expiry. Production
|
|
169
|
+
* operators wire a disk- or object-store-backed implementation
|
|
170
|
+
* matching the same `{ create, head, append, terminate, sweep }`
|
|
171
|
+
* shape. `maxSize` caps the per-upload byte budget; uploads above
|
|
172
|
+
* that fail-fast at PATCH time. `defaultExpirationMs` sets the
|
|
173
|
+
* retention window after creation.
|
|
174
|
+
*
|
|
175
|
+
* @opts
|
|
176
|
+
* {
|
|
177
|
+
* maxSize: number,
|
|
178
|
+
* defaultExpirationMs: number, // default 24h
|
|
179
|
+
* }
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* var b = require("@blamejs/core");
|
|
183
|
+
* var store = b.middleware.tusUpload.memoryStore({
|
|
184
|
+
* maxSize: b.constants.BYTES.gib(2),
|
|
185
|
+
* defaultExpirationMs: b.constants.TIME.hours(24),
|
|
186
|
+
* });
|
|
187
|
+
* // store is the { create, head, append, ... } object passed to tusUpload({ store })
|
|
188
|
+
*/
|
|
160
189
|
function memoryStore(opts) {
|
|
161
190
|
opts = opts || {};
|
|
162
191
|
var maxSize = opts.maxSize;
|
|
@@ -293,6 +322,49 @@ function _readChunk(req, maxChunkSize) {
|
|
|
293
322
|
});
|
|
294
323
|
}
|
|
295
324
|
|
|
325
|
+
/**
|
|
326
|
+
* @primitive b.middleware.tusUpload
|
|
327
|
+
* @signature b.middleware.tusUpload(opts)
|
|
328
|
+
* @since 0.1.0
|
|
329
|
+
* @related b.middleware.tusUpload.memoryStore, b.middleware.tusUpload.close
|
|
330
|
+
*
|
|
331
|
+
* tus.io v1.0.0 resumable-upload protocol implementation. Wires
|
|
332
|
+
* POST (creation), HEAD (offset query), PATCH (append),
|
|
333
|
+
* DELETE (termination), and OPTIONS (discovery) per the spec.
|
|
334
|
+
* Implements `creation`, `creation-with-upload`, `expiration`,
|
|
335
|
+
* `checksum`, and `termination` extensions. Concatenation (§4.6)
|
|
336
|
+
* is intentionally out of scope — operators that need parallel-
|
|
337
|
+
* chunk assembly compose it in their own store layer. Refuses
|
|
338
|
+
* checksum-mismatch with HTTP 460 per §3.5; expired uploads are
|
|
339
|
+
* swept on a periodic timer driven by the store. Hot-path PATCH
|
|
340
|
+
* lifecycle metrics route through `observability.safeEvent` rather
|
|
341
|
+
* than the audit chain.
|
|
342
|
+
*
|
|
343
|
+
* @opts
|
|
344
|
+
* {
|
|
345
|
+
* mountPath: string, // required
|
|
346
|
+
* store: object, // required
|
|
347
|
+
* maxSize: number,
|
|
348
|
+
* maxChunkSize: number,
|
|
349
|
+
* expirationSec: number,
|
|
350
|
+
* extensions: string[],
|
|
351
|
+
* checksumAlgorithms: string[],
|
|
352
|
+
* onCreate: async function(uploadId, meta): void,
|
|
353
|
+
* onComplete: async function(uploadId, meta): void,
|
|
354
|
+
* onTerminate: async function(uploadId): void,
|
|
355
|
+
* audit: boolean,
|
|
356
|
+
* }
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* var b = require("@blamejs/core");
|
|
360
|
+
* var app = b.router.create();
|
|
361
|
+
* var store = b.middleware.tusUpload.memoryStore({ maxSize: b.constants.BYTES.gib(2) });
|
|
362
|
+
* app.use(b.middleware.tusUpload({
|
|
363
|
+
* mountPath: "/uploads",
|
|
364
|
+
* store: store,
|
|
365
|
+
* maxSize: b.constants.BYTES.gib(2),
|
|
366
|
+
* }));
|
|
367
|
+
*/
|
|
296
368
|
function create(opts) {
|
|
297
369
|
validateOpts.requireObject(opts, "middleware.tusUpload", TusError);
|
|
298
370
|
validateOpts(opts, [
|
|
@@ -637,6 +709,24 @@ function create(opts) {
|
|
|
637
709
|
};
|
|
638
710
|
}
|
|
639
711
|
|
|
712
|
+
/**
|
|
713
|
+
* @primitive b.middleware.tusUpload.close
|
|
714
|
+
* @signature b.middleware.tusUpload.close(middleware)
|
|
715
|
+
* @since 0.1.0
|
|
716
|
+
* @related b.middleware.tusUpload
|
|
717
|
+
*
|
|
718
|
+
* Releases resources held by a TUS upload middleware instance —
|
|
719
|
+
* timers, periodic-sweep handles, and any store-close hook the
|
|
720
|
+
* operator wired. Operators call this on graceful shutdown so the
|
|
721
|
+
* sweep timer doesn't keep the process alive. Tolerant of
|
|
722
|
+
* middleware values that don't expose a `close` method (no-op).
|
|
723
|
+
*
|
|
724
|
+
* @example
|
|
725
|
+
* var b = require("@blamejs/core");
|
|
726
|
+
* var store = b.middleware.tusUpload.memoryStore({});
|
|
727
|
+
* var tus = b.middleware.tusUpload({ mountPath: "/uploads", store: store });
|
|
728
|
+
* b.middleware.tusUpload.close(tus);
|
|
729
|
+
*/
|
|
640
730
|
function close(middleware) {
|
|
641
731
|
// Reserved for future store-close hook; the sweep timer is the only
|
|
642
732
|
// resource currently bound, and it lives inside the middleware closure.
|
|
@@ -37,6 +37,59 @@ var observability = lazyRequire(function () { return require("../observability")
|
|
|
37
37
|
|
|
38
38
|
function _isPlainArray(x) { return Array.isArray(x); }
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @primitive b.middleware.webAppManifest
|
|
42
|
+
* @signature b.middleware.webAppManifest(opts)
|
|
43
|
+
* @since 0.1.0
|
|
44
|
+
* @related b.middleware.assetlinks, b.middleware.securityTxt
|
|
45
|
+
*
|
|
46
|
+
* Serves the W3C Web App Manifest at `/manifest.webmanifest`
|
|
47
|
+
* (and `/manifest.json` when `alsoAtJsonPath: true`). Manifest is
|
|
48
|
+
* JSON-serialized once at create-time and emitted with
|
|
49
|
+
* `Content-Type: application/manifest+json`. Throws at create-time
|
|
50
|
+
* when `name`, `start_url`, or an icons array is missing — those
|
|
51
|
+
* are the W3C-required fields for an installable PWA. Operator
|
|
52
|
+
* fields outside the W3C allowlist throw at boot so typos surface
|
|
53
|
+
* early.
|
|
54
|
+
*
|
|
55
|
+
* @opts
|
|
56
|
+
* {
|
|
57
|
+
* name: string, // required
|
|
58
|
+
* start_url: string, // required
|
|
59
|
+
* icons: Array<{ src, sizes, type }>, // required, ≥1
|
|
60
|
+
* short_name: string,
|
|
61
|
+
* description: string,
|
|
62
|
+
* scope: string,
|
|
63
|
+
* display: string,
|
|
64
|
+
* display_override: string[],
|
|
65
|
+
* orientation: string,
|
|
66
|
+
* theme_color: string,
|
|
67
|
+
* background_color: string,
|
|
68
|
+
* screenshots: array,
|
|
69
|
+
* shortcuts: array,
|
|
70
|
+
* categories: string[],
|
|
71
|
+
* lang: string,
|
|
72
|
+
* dir: string,
|
|
73
|
+
* id: string,
|
|
74
|
+
* prefer_related_applications: boolean,
|
|
75
|
+
* related_applications: array,
|
|
76
|
+
* alsoAtJsonPath: boolean,
|
|
77
|
+
* audit: boolean,
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* var b = require("@blamejs/core");
|
|
82
|
+
* var app = b.router.create();
|
|
83
|
+
* app.use(b.middleware.webAppManifest({
|
|
84
|
+
* name: "Example App",
|
|
85
|
+
* start_url: "/",
|
|
86
|
+
* display: "standalone",
|
|
87
|
+
* icons: [
|
|
88
|
+
* { src: "/icons/192.png", sizes: "192x192", type: "image/png" },
|
|
89
|
+
* { src: "/icons/512.png", sizes: "512x512", type: "image/png" },
|
|
90
|
+
* ],
|
|
91
|
+
* }));
|
|
92
|
+
*/
|
|
40
93
|
function create(opts) {
|
|
41
94
|
validateOpts.requireObject(opts, "middleware.webAppManifest", WebAppManifestError);
|
|
42
95
|
// Allowlist subset of W3C-spec attributes operators commonly set.
|
package/lib/mtls-ca.js
CHANGED
|
@@ -1,80 +1,55 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.mtlsCa
|
|
4
|
+
* @nav Crypto
|
|
5
|
+
* @title mTLS CA
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* pure-JS engine (lib/mtls-engine-default.js, backed by the vendored
|
|
9
|
-
* @peculiar/x509 + pkijs bundle); operators with custom requirements
|
|
10
|
-
* pass their own via opts.engine.
|
|
7
|
+
* @intro
|
|
8
|
+
* Mutual TLS Certificate Authority — internal CA cert issuance,
|
|
9
|
+
* mTLS gate setup, fingerprint pinning.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* vault: b.vault, // optional; required when sealed
|
|
20
|
-
* caKeySealedMode: "required", // "required" (default) | "disabled"
|
|
21
|
-
* generation: 1, // current CA generation for OU=CAv{N}
|
|
22
|
-
* engine: myCertEngine, // optional — defaults to b.mtlsEngine
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* Files (relative to dataDir):
|
|
26
|
-
* ca.crt CA certificate (PEM, plaintext on disk)
|
|
27
|
-
* ca.key CA private key (PEM, plaintext on disk)
|
|
28
|
-
* ca.key.sealed CA private key (vault.seal of PEM bytes)
|
|
29
|
-
*
|
|
30
|
-
* caKeySealedMode (defaults to "required"):
|
|
31
|
-
* "required" sealed file required; refuse plaintext (default — vault
|
|
32
|
-
* must be wired)
|
|
33
|
-
* "disabled" plaintext required; refuse sealed (dev-only opt-out;
|
|
34
|
-
* operator must justify with audited reason)
|
|
35
|
-
*
|
|
36
|
-
* The legacy "auto" mode (load whichever exists, fall back to plaintext
|
|
37
|
-
* when no sealed file is present) was removed; it defaulted to writing
|
|
38
|
-
* plaintext on a fresh install, which is the inverse of the framework's
|
|
39
|
-
* security-defaults-on posture for at-rest key material.
|
|
40
|
-
*
|
|
41
|
-
* Generation tagging: every CA cert issued by the framework embeds a
|
|
42
|
-
* "OU=CAv{N}" RDN in its subject DN. Status reads that back so an
|
|
43
|
-
* upgrade flow can detect legacy CAs (a pre-rotation CA whose key
|
|
44
|
-
* parameters are below the current bar) and prompt regeneration
|
|
45
|
-
* without breaking active mTLS clients.
|
|
11
|
+
* The framework owns storage, sealed-loading dispatch, generation
|
|
12
|
+
* tagging, and atomic commit. Cert issuance (CA generation, client
|
|
13
|
+
* cert signing, PKCS#12 packaging) delegates to a pluggable engine
|
|
14
|
+
* so the operator chooses the X.509 toolchain. The default pure-JS
|
|
15
|
+
* engine lives in `lib/mtls-engine-default.js` (backed by the
|
|
16
|
+
* vendored @peculiar/x509 + pkijs bundle); operators with custom
|
|
17
|
+
* requirements pass their own via `opts.engine`.
|
|
46
18
|
*
|
|
47
|
-
*
|
|
19
|
+
* Files relative to `dataDir`: `ca.crt` (PEM cert, plaintext),
|
|
20
|
+
* `ca.key` (PEM key, plaintext — refused under `caKeySealedMode:
|
|
21
|
+
* "required"`), `ca.key.sealed` (vault.seal of the PEM bytes — the
|
|
22
|
+
* default at-rest shape), `revocations.json` (revocation registry),
|
|
23
|
+
* `ca.crl` (signed CRL derived from the registry).
|
|
48
24
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
25
|
+
* `caKeySealedMode` defaults to "required" — sealed file required,
|
|
26
|
+
* plaintext refused. The legacy "auto" fallback was removed; it
|
|
27
|
+
* defaulted to writing plaintext on a fresh install, which is the
|
|
28
|
+
* inverse of the framework's security-defaults-on posture for
|
|
29
|
+
* at-rest key material. The "disabled" mode is a dev-only opt-out
|
|
30
|
+
* (operator must justify with audited reason).
|
|
52
31
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
32
|
+
* Generation tagging: every CA cert issued by the framework embeds
|
|
33
|
+
* an `OU=CAv{N}` RDN in its subject DN. `parseGeneration` reads that
|
|
34
|
+
* back so an upgrade flow can detect legacy CAs and prompt
|
|
35
|
+
* regeneration without breaking active mTLS clients.
|
|
55
36
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* Engine contract (default lib/mtls-engine-default.js, override via
|
|
60
|
-
* opts.engine):
|
|
61
|
-
*
|
|
62
|
-
* {
|
|
63
|
-
* async generateCa({ generation })
|
|
64
|
-
* returns { caCertPem, caKeyPem },
|
|
37
|
+
* Engine contract:
|
|
38
|
+
* async generateCa({ generation }) -> { caCertPem, caKeyPem }
|
|
65
39
|
* async signClientCert({ cn, validityDays, caCertPem, caKeyPem })
|
|
66
|
-
*
|
|
40
|
+
* -> { cert, key, ca, issuedAt, expiresAt }
|
|
67
41
|
* async packageP12({ cn, password, validityDays, caCertPem, caKeyPem })
|
|
68
|
-
*
|
|
69
|
-
*
|
|
42
|
+
* -> { p12, certPem, issuedAt, expiresAt }
|
|
43
|
+
*
|
|
44
|
+
* The engine returns the cert PEM but does NOT compute a
|
|
45
|
+
* fingerprint — the framework hashes the cert via
|
|
46
|
+
* `b.crypto.sha3Hash(certPem)` so the SHA3-512 posture stays
|
|
47
|
+
* consistent across the stack. Operators who need the X.509-
|
|
48
|
+
* conventional SHA-256 fingerprint (browser cert-details panels,
|
|
49
|
+
* openssl interop) compute it separately from the cert PEM.
|
|
70
50
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* `b.crypto.sha3Hash(certPem)` for any audit / display purpose,
|
|
74
|
-
* keeping the SHA3-512 posture consistent across the rest of the
|
|
75
|
-
* stack. Operators who want the X.509-conventional SHA-256
|
|
76
|
-
* fingerprint (for browser cert-details panels, openssl interop)
|
|
77
|
-
* compute it separately from the cert PEM.
|
|
51
|
+
* @card
|
|
52
|
+
* Mutual TLS Certificate Authority — internal CA cert issuance, mTLS gate setup, fingerprint pinning.
|
|
78
53
|
*/
|
|
79
54
|
|
|
80
55
|
var fs = require("fs");
|
|
@@ -133,10 +108,27 @@ function _resolvePaths(dataDir, paths) {
|
|
|
133
108
|
};
|
|
134
109
|
}
|
|
135
110
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
111
|
+
/**
|
|
112
|
+
* @primitive b.mtlsCa.parseGeneration
|
|
113
|
+
* @signature b.mtlsCa.parseGeneration(certPem)
|
|
114
|
+
* @since 0.7.68
|
|
115
|
+
* @related b.mtlsCa.create
|
|
116
|
+
*
|
|
117
|
+
* Read the `OU=CAv{N}` generation tag from a PEM CA certificate's
|
|
118
|
+
* subject DN. Returns the integer `N`, defaulting to `1` for untagged
|
|
119
|
+
* legacy CAs (so the first regen lifts a legacy CA to generation 2
|
|
120
|
+
* without misidentifying it as fresh) or `0` when the cert is
|
|
121
|
+
* unreadable. Operators wire this into upgrade flows that detect
|
|
122
|
+
* pre-rotation CAs whose key parameters are below the current bar.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* var pem = "-----BEGIN CERTIFICATE-----\n(invalid)\n-----END CERTIFICATE-----\n";
|
|
126
|
+
* b.mtlsCa.parseGeneration(pem);
|
|
127
|
+
* // → 0
|
|
128
|
+
*
|
|
129
|
+
* b.mtlsCa.parseGeneration(null);
|
|
130
|
+
* // → 0
|
|
131
|
+
*/
|
|
140
132
|
function parseGeneration(certPem) {
|
|
141
133
|
if (typeof certPem !== "string" && !Buffer.isBuffer(certPem)) return 0;
|
|
142
134
|
try {
|
|
@@ -149,6 +141,44 @@ function parseGeneration(certPem) {
|
|
|
149
141
|
}
|
|
150
142
|
}
|
|
151
143
|
|
|
144
|
+
/**
|
|
145
|
+
* @primitive b.mtlsCa.create
|
|
146
|
+
* @signature b.mtlsCa.create(opts)
|
|
147
|
+
* @since 0.7.68
|
|
148
|
+
* @related b.mtlsCa.parseGeneration, b.crypto.sha3Hash
|
|
149
|
+
*
|
|
150
|
+
* Build an mTLS CA handle bound to `opts.dataDir`. The handle owns
|
|
151
|
+
* sealed-loading of the CA private key, generation tagging on issued
|
|
152
|
+
* certs, atomic commit of newly generated material, and a pluggable
|
|
153
|
+
* engine for the X.509 work itself. Returns an object with
|
|
154
|
+
* `initCA()`, `generateClientCert({ cn, validityDays })`,
|
|
155
|
+
* `generateClientP12({ cn, password, validityDays })`, plus
|
|
156
|
+
* revocation helpers.
|
|
157
|
+
*
|
|
158
|
+
* Throws `MtlsCaError` at config-time on bad opts (missing dataDir,
|
|
159
|
+
* sealed-mode mismatch, missing vault when seal required).
|
|
160
|
+
*
|
|
161
|
+
* @opts
|
|
162
|
+
* dataDir: string, // required — base for cert / key / revocation files
|
|
163
|
+
* paths: { caKey, caKeySealed, caCert, revocations, crl }, // override defaults
|
|
164
|
+
* vault: object, // b.vault — required when caKeySealedMode = "required"
|
|
165
|
+
* caKeySealedMode: string, // "required" (default) | "disabled"
|
|
166
|
+
* generation: number, // current CA generation for OU=CAv{N}
|
|
167
|
+
* engine: object, // pluggable X.509 engine; default lib/mtls-engine-default
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* var fs = require("fs");
|
|
171
|
+
* var os = require("os");
|
|
172
|
+
* var path = require("path");
|
|
173
|
+
* var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-mtls-"));
|
|
174
|
+
* var ca = b.mtlsCa.create({
|
|
175
|
+
* dataDir: dir,
|
|
176
|
+
* caKeySealedMode: "disabled",
|
|
177
|
+
* generation: 1,
|
|
178
|
+
* });
|
|
179
|
+
* typeof ca.initCA;
|
|
180
|
+
* // → "function"
|
|
181
|
+
*/
|
|
152
182
|
function create(opts) {
|
|
153
183
|
opts = opts || {};
|
|
154
184
|
validateOpts(opts, [
|