@blamejs/core 0.7.18 → 0.7.19
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 +425 -423
- package/README.md +150 -150
- package/bin/blamejs.js +0 -0
- package/index.js +310 -308
- package/lib/api-key.js +660 -660
- package/lib/api-snapshot.js +338 -338
- package/lib/app-shutdown.js +385 -385
- package/lib/app.js +365 -365
- package/lib/archive.js +250 -250
- package/lib/atomic-file.js +544 -544
- package/lib/audit-chain.js +177 -177
- package/lib/audit-sign.js +344 -344
- package/lib/audit-tools.js +677 -677
- package/lib/audit.js +766 -766
- package/lib/auth/jwt-external.js +365 -0
- package/lib/auth/jwt.js +337 -311
- package/lib/auth/lockout.js +436 -436
- package/lib/auth/oauth.js +721 -721
- package/lib/auth/passkey.js +181 -181
- package/lib/auth/password.js +628 -594
- package/lib/backup/bundle.js +217 -217
- package/lib/backup/crypto.js +176 -176
- package/lib/backup/index.js +515 -515
- package/lib/backup/manifest.js +282 -282
- package/lib/break-glass.js +1338 -1338
- package/lib/bundler.js +441 -441
- package/lib/cache-redis.js +256 -256
- package/lib/cache.js +1206 -1206
- package/lib/canonical-json.js +115 -115
- package/lib/chain-writer.js +234 -234
- package/lib/cli-helpers.js +206 -206
- package/lib/cli.js +2334 -2334
- package/lib/cluster-provider-db.js +317 -317
- package/lib/cluster-storage.js +226 -226
- package/lib/cluster.js +703 -703
- package/lib/config-drift.js +301 -301
- package/lib/consent.js +222 -222
- package/lib/constants.js +191 -191
- package/lib/cookies.js +315 -315
- package/lib/credential-hash.js +322 -322
- package/lib/crypto.js +266 -266
- package/lib/csv.js +275 -275
- package/lib/db-declare-row-policy.js +267 -267
- package/lib/db-declare-view.js +420 -420
- package/lib/db-query.js +406 -406
- package/lib/db-schema.js +319 -319
- package/lib/db.js +1288 -1288
- package/lib/deprecate.js +222 -222
- package/lib/dev.js +335 -335
- package/lib/dual-control.js +473 -473
- package/lib/error-page.js +420 -420
- package/lib/external-db-migrate.js +441 -441
- package/lib/external-db.js +1061 -1061
- package/lib/file-type.js +273 -273
- package/lib/forms.js +422 -422
- package/lib/framework-error.js +293 -293
- package/lib/framework-schema.js +717 -717
- package/lib/handlers.js +350 -350
- package/lib/http-client-cookie-jar.js +508 -508
- package/lib/http-client.js +1195 -1195
- package/lib/i18n.js +878 -878
- package/lib/jobs.js +185 -185
- package/lib/log-stream-cloudwatch.js +369 -369
- package/lib/log-stream-local.js +146 -146
- package/lib/log-stream-otlp-grpc.js +410 -410
- package/lib/log-stream-otlp.js +286 -286
- package/lib/log-stream-syslog.js +302 -302
- package/lib/log-stream-webhook.js +199 -199
- package/lib/log-stream.js +330 -330
- package/lib/log.js +500 -500
- package/lib/mail-bounce.js +528 -528
- package/lib/mail-dkim.js +369 -369
- package/lib/mail.js +981 -981
- package/lib/metrics.js +683 -683
- package/lib/middleware/api-encrypt.js +936 -936
- package/lib/middleware/attach-user.js +157 -157
- package/lib/middleware/bearer-auth.js +152 -0
- package/lib/middleware/body-parser.js +1170 -1170
- package/lib/middleware/bot-guard.js +178 -178
- package/lib/middleware/compression.js +452 -452
- package/lib/middleware/cors.js +314 -314
- package/lib/middleware/csp-nonce.js +348 -348
- package/lib/middleware/csrf-protect.js +316 -316
- package/lib/middleware/db-role-for.js +264 -264
- package/lib/middleware/health.js +392 -392
- package/lib/middleware/index.js +82 -79
- package/lib/middleware/rate-limit.js +358 -358
- package/lib/middleware/request-id.js +61 -61
- package/lib/middleware/request-log.js +168 -168
- package/lib/middleware/require-auth.js +104 -104
- package/lib/middleware/security-headers.js +116 -116
- package/lib/middleware/sse.js +166 -166
- package/lib/migrations.js +383 -383
- package/lib/mtls-ca.js +518 -518
- package/lib/mtls-engine-default.js +481 -481
- package/lib/network-dns.js +632 -632
- package/lib/network-heartbeat.js +290 -290
- package/lib/network-nts.js +574 -574
- package/lib/network-proxy.js +265 -265
- package/lib/network-tls.js +328 -328
- package/lib/network.js +233 -233
- package/lib/notify.js +612 -612
- package/lib/ntp-check.js +229 -229
- package/lib/numeric-bounds.js +111 -111
- package/lib/object-store/azure-blob-bucket-ops.js +349 -349
- package/lib/object-store/azure-blob.js +488 -488
- package/lib/object-store/gcs-bucket-ops.js +351 -351
- package/lib/object-store/gcs.js +519 -519
- package/lib/object-store/http-put.js +153 -153
- package/lib/object-store/index.js +197 -197
- package/lib/object-store/sigv4-bucket-ops.js +1092 -1092
- package/lib/object-store/sigv4.js +903 -903
- package/lib/observability.js +151 -151
- package/lib/otel-export.js +269 -269
- package/lib/pagination.js +464 -464
- package/lib/parsers/index.js +80 -80
- package/lib/parsers/safe-env.js +642 -642
- package/lib/parsers/safe-ini.js +292 -292
- package/lib/parsers/safe-toml.js +784 -784
- package/lib/parsers/safe-xml.js +390 -390
- package/lib/parsers/safe-yaml.js +1015 -1015
- package/lib/permissions.js +708 -708
- package/lib/pqc-agent.js +87 -87
- package/lib/pqc-gate.js +279 -279
- package/lib/protobuf-encoder.js +190 -190
- package/lib/protocol-dispatcher.js +161 -161
- package/lib/pubsub-redis.js +167 -167
- package/lib/pubsub.js +429 -429
- package/lib/queue-local.js +476 -476
- package/lib/queue-redis.js +745 -745
- package/lib/queue-sqs.js +319 -319
- package/lib/queue.js +695 -695
- package/lib/redis-client.js +519 -519
- package/lib/request-helpers.js +340 -340
- package/lib/restore-bundle.js +237 -237
- package/lib/restore-rollback.js +259 -259
- package/lib/restore.js +409 -409
- package/lib/retry.js +376 -376
- package/lib/router.js +748 -748
- package/lib/safe-async.js +735 -735
- package/lib/safe-buffer.js +237 -237
- package/lib/safe-json.js +541 -541
- package/lib/safe-schema.js +1266 -1266
- package/lib/safe-url.js +159 -159
- package/lib/scheduler.js +706 -706
- package/lib/security-assert.js +373 -373
- package/lib/seeders.js +618 -618
- package/lib/session.js +535 -478
- package/lib/slug.js +269 -269
- package/lib/ssrf-guard.js +401 -401
- package/lib/storage.js +471 -471
- package/lib/subject.js +281 -281
- package/lib/template.js +791 -791
- package/lib/testing.js +798 -798
- package/lib/time.js +310 -310
- package/lib/totp.js +302 -302
- package/lib/tracing.js +494 -494
- package/lib/uuid.js +132 -132
- package/lib/validate-opts.js +340 -340
- package/lib/vault/index.js +308 -308
- package/lib/vault/rotate.js +784 -784
- package/lib/vault/wrap.js +296 -296
- package/lib/vendor/noble-ciphers.cjs +9 -9
- package/lib/webhook.js +595 -595
- package/lib/websocket.js +1048 -1048
- package/package.json +77 -77
- package/sbom.cyclonedx.json +7 -7
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* require-auth middleware — gates routes that require an authenticated
|
|
4
|
-
* user. Mount AFTER attachUser; this middleware reads `req.user` and
|
|
5
|
-
* either lets the request through or rejects it.
|
|
6
|
-
*
|
|
7
|
-
* Rejection shape:
|
|
8
|
-
* - JSON-preferring caller (Accept includes application/json, or
|
|
9
|
-
* X-Requested-With: XMLHttpRequest):
|
|
10
|
-
* 401 application/json with { error: "Authentication required." }
|
|
11
|
-
* - Browser-preferring caller, when opts.redirectTo is set:
|
|
12
|
-
* 302 with Location header
|
|
13
|
-
* - Otherwise:
|
|
14
|
-
* 401 text/plain
|
|
15
|
-
*
|
|
16
|
-
* Note: the Content-Type of the REQUEST is intentionally NOT a signal.
|
|
17
|
-
* A server-to-server POST with `Content-Type: application/json` and no
|
|
18
|
-
* `Accept` header should get the same response shape as any other
|
|
19
|
-
* unauthenticated request — Content-Type describes what the client
|
|
20
|
-
* SENT, not what they want back. Operators with a non-default
|
|
21
|
-
* preference contract supply opts.prefersJson.
|
|
22
|
-
*
|
|
23
|
-
* Always emits `auth.required.denied` audit event on rejection (when
|
|
24
|
-
* opts.audit !== false). The event records request method + path +
|
|
25
|
-
* client IP — keys-only, no body content.
|
|
26
|
-
*
|
|
27
|
-
* Options:
|
|
28
|
-
* {
|
|
29
|
-
* redirectTo: null (optional URL for browser redirects;
|
|
30
|
-
* when set, prefersJson()=false rejections
|
|
31
|
-
* produce 302 instead of 401 text/plain)
|
|
32
|
-
* prefersJson: function (optional override; defaults to checking
|
|
33
|
-
* Accept / X-Requested-With / Content-Type)
|
|
34
|
-
* errorMessage: 'Authentication required.'
|
|
35
|
-
* audit: true (emit auth.required.denied on reject)
|
|
36
|
-
* }
|
|
37
|
-
*/
|
|
38
|
-
var lazyRequire = require("../lazy-require");
|
|
39
|
-
var requestHelpers = require("../request-helpers");
|
|
40
|
-
var validateOpts = require("../validate-opts");
|
|
41
|
-
var audit = lazyRequire(function () { return require("../audit"); });
|
|
42
|
-
|
|
43
|
-
function _defaultPrefersJson(req) {
|
|
44
|
-
var h = req.headers || {};
|
|
45
|
-
if (typeof h.accept === "string" && h.accept.indexOf("application/json") !== -1) return true;
|
|
46
|
-
if (h["x-requested-with"] === "XMLHttpRequest") return true;
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function create(opts) {
|
|
51
|
-
opts = opts || {};
|
|
52
|
-
validateOpts(opts, [
|
|
53
|
-
"redirectTo", "prefersJson", "errorMessage", "audit",
|
|
54
|
-
], "middleware.requireAuth");
|
|
55
|
-
var redirectTo = opts.redirectTo || null;
|
|
56
|
-
var prefersJson = typeof opts.prefersJson === "function"
|
|
57
|
-
? opts.prefersJson
|
|
58
|
-
: _defaultPrefersJson;
|
|
59
|
-
var msg = opts.errorMessage || "Authentication required.";
|
|
60
|
-
var auditOn = opts.audit !== false;
|
|
61
|
-
|
|
62
|
-
return function requireAuth(req, res, next) {
|
|
63
|
-
if (req.user) return next();
|
|
64
|
-
|
|
65
|
-
if (auditOn) {
|
|
66
|
-
try {
|
|
67
|
-
audit().emit({
|
|
68
|
-
action: "auth.required.denied",
|
|
69
|
-
outcome: "denied",
|
|
70
|
-
actor: requestHelpers.extractActorContext(req),
|
|
71
|
-
reason: "no authenticated user on request",
|
|
72
|
-
metadata: { method: req.method, path: req.pathname || (req.url || "").split("?")[0] },
|
|
73
|
-
});
|
|
74
|
-
} catch (_e) { /* audit best-effort */ }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (prefersJson(req)) {
|
|
78
|
-
if (typeof res.writeHead === "function") {
|
|
79
|
-
res.writeHead(requestHelpers.HTTP_STATUS.UNAUTHORIZED,
|
|
80
|
-
{ "Content-Type": "application/json" });
|
|
81
|
-
res.end(JSON.stringify({ error: msg }));
|
|
82
|
-
}
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (redirectTo) {
|
|
86
|
-
if (typeof res.writeHead === "function") {
|
|
87
|
-
// 302 Found — RFC 7231 §6.4.3. Not in HTTP_STATUS table.
|
|
88
|
-
res.writeHead(302, { "Location": redirectTo });
|
|
89
|
-
res.end();
|
|
90
|
-
}
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (typeof res.writeHead === "function") {
|
|
94
|
-
res.writeHead(requestHelpers.HTTP_STATUS.UNAUTHORIZED,
|
|
95
|
-
{ "Content-Type": "text/plain" });
|
|
96
|
-
res.end(msg);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
module.exports = {
|
|
102
|
-
create: create,
|
|
103
|
-
_defaultPrefersJson: _defaultPrefersJson,
|
|
104
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* require-auth middleware — gates routes that require an authenticated
|
|
4
|
+
* user. Mount AFTER attachUser; this middleware reads `req.user` and
|
|
5
|
+
* either lets the request through or rejects it.
|
|
6
|
+
*
|
|
7
|
+
* Rejection shape:
|
|
8
|
+
* - JSON-preferring caller (Accept includes application/json, or
|
|
9
|
+
* X-Requested-With: XMLHttpRequest):
|
|
10
|
+
* 401 application/json with { error: "Authentication required." }
|
|
11
|
+
* - Browser-preferring caller, when opts.redirectTo is set:
|
|
12
|
+
* 302 with Location header
|
|
13
|
+
* - Otherwise:
|
|
14
|
+
* 401 text/plain
|
|
15
|
+
*
|
|
16
|
+
* Note: the Content-Type of the REQUEST is intentionally NOT a signal.
|
|
17
|
+
* A server-to-server POST with `Content-Type: application/json` and no
|
|
18
|
+
* `Accept` header should get the same response shape as any other
|
|
19
|
+
* unauthenticated request — Content-Type describes what the client
|
|
20
|
+
* SENT, not what they want back. Operators with a non-default
|
|
21
|
+
* preference contract supply opts.prefersJson.
|
|
22
|
+
*
|
|
23
|
+
* Always emits `auth.required.denied` audit event on rejection (when
|
|
24
|
+
* opts.audit !== false). The event records request method + path +
|
|
25
|
+
* client IP — keys-only, no body content.
|
|
26
|
+
*
|
|
27
|
+
* Options:
|
|
28
|
+
* {
|
|
29
|
+
* redirectTo: null (optional URL for browser redirects;
|
|
30
|
+
* when set, prefersJson()=false rejections
|
|
31
|
+
* produce 302 instead of 401 text/plain)
|
|
32
|
+
* prefersJson: function (optional override; defaults to checking
|
|
33
|
+
* Accept / X-Requested-With / Content-Type)
|
|
34
|
+
* errorMessage: 'Authentication required.'
|
|
35
|
+
* audit: true (emit auth.required.denied on reject)
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
38
|
+
var lazyRequire = require("../lazy-require");
|
|
39
|
+
var requestHelpers = require("../request-helpers");
|
|
40
|
+
var validateOpts = require("../validate-opts");
|
|
41
|
+
var audit = lazyRequire(function () { return require("../audit"); });
|
|
42
|
+
|
|
43
|
+
function _defaultPrefersJson(req) {
|
|
44
|
+
var h = req.headers || {};
|
|
45
|
+
if (typeof h.accept === "string" && h.accept.indexOf("application/json") !== -1) return true;
|
|
46
|
+
if (h["x-requested-with"] === "XMLHttpRequest") return true;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function create(opts) {
|
|
51
|
+
opts = opts || {};
|
|
52
|
+
validateOpts(opts, [
|
|
53
|
+
"redirectTo", "prefersJson", "errorMessage", "audit",
|
|
54
|
+
], "middleware.requireAuth");
|
|
55
|
+
var redirectTo = opts.redirectTo || null;
|
|
56
|
+
var prefersJson = typeof opts.prefersJson === "function"
|
|
57
|
+
? opts.prefersJson
|
|
58
|
+
: _defaultPrefersJson;
|
|
59
|
+
var msg = opts.errorMessage || "Authentication required.";
|
|
60
|
+
var auditOn = opts.audit !== false;
|
|
61
|
+
|
|
62
|
+
return function requireAuth(req, res, next) {
|
|
63
|
+
if (req.user) return next();
|
|
64
|
+
|
|
65
|
+
if (auditOn) {
|
|
66
|
+
try {
|
|
67
|
+
audit().emit({
|
|
68
|
+
action: "auth.required.denied",
|
|
69
|
+
outcome: "denied",
|
|
70
|
+
actor: requestHelpers.extractActorContext(req),
|
|
71
|
+
reason: "no authenticated user on request",
|
|
72
|
+
metadata: { method: req.method, path: req.pathname || (req.url || "").split("?")[0] },
|
|
73
|
+
});
|
|
74
|
+
} catch (_e) { /* audit best-effort */ }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (prefersJson(req)) {
|
|
78
|
+
if (typeof res.writeHead === "function") {
|
|
79
|
+
res.writeHead(requestHelpers.HTTP_STATUS.UNAUTHORIZED,
|
|
80
|
+
{ "Content-Type": "application/json" });
|
|
81
|
+
res.end(JSON.stringify({ error: msg }));
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (redirectTo) {
|
|
86
|
+
if (typeof res.writeHead === "function") {
|
|
87
|
+
// 302 Found — RFC 7231 §6.4.3. Not in HTTP_STATUS table.
|
|
88
|
+
res.writeHead(302, { "Location": redirectTo });
|
|
89
|
+
res.end();
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (typeof res.writeHead === "function") {
|
|
94
|
+
res.writeHead(requestHelpers.HTTP_STATUS.UNAUTHORIZED,
|
|
95
|
+
{ "Content-Type": "text/plain" });
|
|
96
|
+
res.end(msg);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
create: create,
|
|
103
|
+
_defaultPrefersJson: _defaultPrefersJson,
|
|
104
|
+
};
|
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Security headers middleware. Sets the headers every modern app should
|
|
4
|
-
* send, regardless of content. Deliberately strict by default — operators
|
|
5
|
-
* who need to soften a header opt in explicitly per option.
|
|
6
|
-
*
|
|
7
|
-
* Strict-Transport-Security — force HTTPS (HSTS); 2-year max-age + includeSubDomains + preload
|
|
8
|
-
* X-Content-Type-Options: nosniff — block MIME sniffing
|
|
9
|
-
* X-Frame-Options: DENY — prevent clickjacking via iframes
|
|
10
|
-
* Referrer-Policy: no-referrer — don't leak full URL to outbound links
|
|
11
|
-
* Permissions-Policy — disable common-attack APIs (camera, geolocation, payment, etc.)
|
|
12
|
-
* Cross-Origin-Opener-Policy: same-origin
|
|
13
|
-
* Cross-Origin-Embedder-Policy: require-corp (off by default — breaks images from CDNs)
|
|
14
|
-
* Cross-Origin-Resource-Policy: same-origin
|
|
15
|
-
* Origin-Agent-Cluster: ?1 — origin-keyed agent cluster; extra process isolation
|
|
16
|
-
* X-DNS-Prefetch-Control: off — don't pre-resolve DNS for off-page links
|
|
17
|
-
* Content-Security-Policy — operator-supplied; framework provides a safe default that
|
|
18
|
-
* only allows same-origin and prevents inline scripts
|
|
19
|
-
*
|
|
20
|
-
* These are the OWASP-aligned defaults. Apps that need different policies
|
|
21
|
-
* (e.g. allow-list for analytics scripts, embed iframes from a known
|
|
22
|
-
* partner) override per-option without losing the others.
|
|
23
|
-
*
|
|
24
|
-
* Options:
|
|
25
|
-
* {
|
|
26
|
-
* hsts: '<value>' or false to disable
|
|
27
|
-
* contentTypeOptions: 'nosniff' or false
|
|
28
|
-
* frameOptions: 'DENY' | 'SAMEORIGIN' or false
|
|
29
|
-
* referrerPolicy: '<value>' or false
|
|
30
|
-
* permissionsPolicy: '<value>' or false
|
|
31
|
-
* coop / coep / corp: '<value>' or false
|
|
32
|
-
* originAgentCluster: '?1' (default) or '?0' or false
|
|
33
|
-
* dnsPrefetchControl: 'off' (default) or 'on' or false
|
|
34
|
-
* csp: '<full CSP string>' or false to disable
|
|
35
|
-
* }
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
var requestHelpers = require("../request-helpers");
|
|
39
|
-
var validateOpts = require("../validate-opts");
|
|
40
|
-
|
|
41
|
-
var DEFAULT_PERMISSIONS = [
|
|
42
|
-
"accelerometer=()", "ambient-light-sensor=()", "autoplay=()",
|
|
43
|
-
"camera=()", "display-capture=()", "encrypted-media=()", "fullscreen=(self)",
|
|
44
|
-
"geolocation=()", "gyroscope=()", "magnetometer=()", "microphone=()",
|
|
45
|
-
"midi=()", "payment=()", "picture-in-picture=()", "publickey-credentials-get=()",
|
|
46
|
-
"screen-wake-lock=()", "sync-xhr=()", "usb=()", "web-share=()", "xr-spatial-tracking=()",
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
// Strict CSP — no 'unsafe-inline' on script-src OR style-src. Operators
|
|
50
|
-
// with inline styles / scripts wire `b.middleware.cspNonce()` and use
|
|
51
|
-
// `{{ cspNonce }}` in their views (or req.cspNonce in handlers); the
|
|
52
|
-
// nonce hooks into the policy via the `csp` opt or a custom value.
|
|
53
|
-
// Operators in transitional environments who genuinely need inline
|
|
54
|
-
// styles set `csp` explicitly to the loose form.
|
|
55
|
-
var DEFAULT_CSP =
|
|
56
|
-
"default-src 'self'; " +
|
|
57
|
-
"script-src 'self'; " +
|
|
58
|
-
"style-src 'self'; " +
|
|
59
|
-
"img-src 'self' data:; " +
|
|
60
|
-
"font-src 'self'; " +
|
|
61
|
-
"connect-src 'self'; " +
|
|
62
|
-
"frame-ancestors 'none'; " +
|
|
63
|
-
"base-uri 'self'; " +
|
|
64
|
-
"form-action 'self'; " +
|
|
65
|
-
"object-src 'none';";
|
|
66
|
-
|
|
67
|
-
function create(opts) {
|
|
68
|
-
opts = opts || {};
|
|
69
|
-
validateOpts(opts, [
|
|
70
|
-
"hsts", "contentTypeOptions", "frameOptions", "referrerPolicy",
|
|
71
|
-
"permissionsPolicy", "coop", "coep", "corp",
|
|
72
|
-
"originAgentCluster", "dnsPrefetchControl", "csp", "trustProxy",
|
|
73
|
-
], "middleware.securityHeaders");
|
|
74
|
-
var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
|
|
75
|
-
? opts.trustProxy : false;
|
|
76
|
-
var hsts = opts.hsts === undefined ? "max-age=63072000; includeSubDomains; preload" : opts.hsts;
|
|
77
|
-
var ctOpts = opts.contentTypeOptions === undefined ? "nosniff" : opts.contentTypeOptions;
|
|
78
|
-
var frameOpts = opts.frameOptions === undefined ? "DENY" : opts.frameOptions;
|
|
79
|
-
var refPolicy = opts.referrerPolicy === undefined ? "no-referrer" : opts.referrerPolicy;
|
|
80
|
-
var permPolicy = opts.permissionsPolicy === undefined ? DEFAULT_PERMISSIONS.join(", ") : opts.permissionsPolicy;
|
|
81
|
-
var coop = opts.coop === undefined ? "same-origin" : opts.coop;
|
|
82
|
-
var coep = opts.coep === undefined ? false : opts.coep;
|
|
83
|
-
var corp = opts.corp === undefined ? "same-origin" : opts.corp;
|
|
84
|
-
var oac = opts.originAgentCluster === undefined ? "?1" : opts.originAgentCluster;
|
|
85
|
-
var dpc = opts.dnsPrefetchControl === undefined ? "off" : opts.dnsPrefetchControl;
|
|
86
|
-
var csp = opts.csp === undefined ? DEFAULT_CSP : opts.csp;
|
|
87
|
-
|
|
88
|
-
return function securityHeaders(req, res, next) {
|
|
89
|
-
if (typeof res.setHeader !== "function") return next();
|
|
90
|
-
// RFC 6797 §7.2: HSTS over plain HTTP is meaningless (UAs ignore
|
|
91
|
-
// it). Skip the header on non-TLS requests so dev-over-HTTP doesn't
|
|
92
|
-
// surface confusing "Strict-Transport-Security on http://" lines.
|
|
93
|
-
// requestProtocol respects trustProxy — operators behind a TLS
|
|
94
|
-
// terminator opt in to read X-Forwarded-Proto.
|
|
95
|
-
if (hsts && requestHelpers.requestProtocol(req, { trustProxy: trustProxy }) === "https") {
|
|
96
|
-
res.setHeader("Strict-Transport-Security", hsts);
|
|
97
|
-
}
|
|
98
|
-
if (ctOpts) res.setHeader("X-Content-Type-Options", ctOpts);
|
|
99
|
-
if (frameOpts) res.setHeader("X-Frame-Options", frameOpts);
|
|
100
|
-
if (refPolicy) res.setHeader("Referrer-Policy", refPolicy);
|
|
101
|
-
if (permPolicy) res.setHeader("Permissions-Policy", permPolicy);
|
|
102
|
-
if (coop) res.setHeader("Cross-Origin-Opener-Policy", coop);
|
|
103
|
-
if (coep) res.setHeader("Cross-Origin-Embedder-Policy", coep);
|
|
104
|
-
if (corp) res.setHeader("Cross-Origin-Resource-Policy", corp);
|
|
105
|
-
if (oac) res.setHeader("Origin-Agent-Cluster", oac);
|
|
106
|
-
if (dpc) res.setHeader("X-DNS-Prefetch-Control", dpc);
|
|
107
|
-
if (csp) res.setHeader("Content-Security-Policy", csp);
|
|
108
|
-
next();
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = {
|
|
113
|
-
create: create,
|
|
114
|
-
DEFAULT_PERMISSIONS: DEFAULT_PERMISSIONS,
|
|
115
|
-
DEFAULT_CSP: DEFAULT_CSP,
|
|
116
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Security headers middleware. Sets the headers every modern app should
|
|
4
|
+
* send, regardless of content. Deliberately strict by default — operators
|
|
5
|
+
* who need to soften a header opt in explicitly per option.
|
|
6
|
+
*
|
|
7
|
+
* Strict-Transport-Security — force HTTPS (HSTS); 2-year max-age + includeSubDomains + preload
|
|
8
|
+
* X-Content-Type-Options: nosniff — block MIME sniffing
|
|
9
|
+
* X-Frame-Options: DENY — prevent clickjacking via iframes
|
|
10
|
+
* Referrer-Policy: no-referrer — don't leak full URL to outbound links
|
|
11
|
+
* Permissions-Policy — disable common-attack APIs (camera, geolocation, payment, etc.)
|
|
12
|
+
* Cross-Origin-Opener-Policy: same-origin
|
|
13
|
+
* Cross-Origin-Embedder-Policy: require-corp (off by default — breaks images from CDNs)
|
|
14
|
+
* Cross-Origin-Resource-Policy: same-origin
|
|
15
|
+
* Origin-Agent-Cluster: ?1 — origin-keyed agent cluster; extra process isolation
|
|
16
|
+
* X-DNS-Prefetch-Control: off — don't pre-resolve DNS for off-page links
|
|
17
|
+
* Content-Security-Policy — operator-supplied; framework provides a safe default that
|
|
18
|
+
* only allows same-origin and prevents inline scripts
|
|
19
|
+
*
|
|
20
|
+
* These are the OWASP-aligned defaults. Apps that need different policies
|
|
21
|
+
* (e.g. allow-list for analytics scripts, embed iframes from a known
|
|
22
|
+
* partner) override per-option without losing the others.
|
|
23
|
+
*
|
|
24
|
+
* Options:
|
|
25
|
+
* {
|
|
26
|
+
* hsts: '<value>' or false to disable
|
|
27
|
+
* contentTypeOptions: 'nosniff' or false
|
|
28
|
+
* frameOptions: 'DENY' | 'SAMEORIGIN' or false
|
|
29
|
+
* referrerPolicy: '<value>' or false
|
|
30
|
+
* permissionsPolicy: '<value>' or false
|
|
31
|
+
* coop / coep / corp: '<value>' or false
|
|
32
|
+
* originAgentCluster: '?1' (default) or '?0' or false
|
|
33
|
+
* dnsPrefetchControl: 'off' (default) or 'on' or false
|
|
34
|
+
* csp: '<full CSP string>' or false to disable
|
|
35
|
+
* }
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
var requestHelpers = require("../request-helpers");
|
|
39
|
+
var validateOpts = require("../validate-opts");
|
|
40
|
+
|
|
41
|
+
var DEFAULT_PERMISSIONS = [
|
|
42
|
+
"accelerometer=()", "ambient-light-sensor=()", "autoplay=()",
|
|
43
|
+
"camera=()", "display-capture=()", "encrypted-media=()", "fullscreen=(self)",
|
|
44
|
+
"geolocation=()", "gyroscope=()", "magnetometer=()", "microphone=()",
|
|
45
|
+
"midi=()", "payment=()", "picture-in-picture=()", "publickey-credentials-get=()",
|
|
46
|
+
"screen-wake-lock=()", "sync-xhr=()", "usb=()", "web-share=()", "xr-spatial-tracking=()",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Strict CSP — no 'unsafe-inline' on script-src OR style-src. Operators
|
|
50
|
+
// with inline styles / scripts wire `b.middleware.cspNonce()` and use
|
|
51
|
+
// `{{ cspNonce }}` in their views (or req.cspNonce in handlers); the
|
|
52
|
+
// nonce hooks into the policy via the `csp` opt or a custom value.
|
|
53
|
+
// Operators in transitional environments who genuinely need inline
|
|
54
|
+
// styles set `csp` explicitly to the loose form.
|
|
55
|
+
var DEFAULT_CSP =
|
|
56
|
+
"default-src 'self'; " +
|
|
57
|
+
"script-src 'self'; " +
|
|
58
|
+
"style-src 'self'; " +
|
|
59
|
+
"img-src 'self' data:; " +
|
|
60
|
+
"font-src 'self'; " +
|
|
61
|
+
"connect-src 'self'; " +
|
|
62
|
+
"frame-ancestors 'none'; " +
|
|
63
|
+
"base-uri 'self'; " +
|
|
64
|
+
"form-action 'self'; " +
|
|
65
|
+
"object-src 'none';";
|
|
66
|
+
|
|
67
|
+
function create(opts) {
|
|
68
|
+
opts = opts || {};
|
|
69
|
+
validateOpts(opts, [
|
|
70
|
+
"hsts", "contentTypeOptions", "frameOptions", "referrerPolicy",
|
|
71
|
+
"permissionsPolicy", "coop", "coep", "corp",
|
|
72
|
+
"originAgentCluster", "dnsPrefetchControl", "csp", "trustProxy",
|
|
73
|
+
], "middleware.securityHeaders");
|
|
74
|
+
var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
|
|
75
|
+
? opts.trustProxy : false;
|
|
76
|
+
var hsts = opts.hsts === undefined ? "max-age=63072000; includeSubDomains; preload" : opts.hsts;
|
|
77
|
+
var ctOpts = opts.contentTypeOptions === undefined ? "nosniff" : opts.contentTypeOptions;
|
|
78
|
+
var frameOpts = opts.frameOptions === undefined ? "DENY" : opts.frameOptions;
|
|
79
|
+
var refPolicy = opts.referrerPolicy === undefined ? "no-referrer" : opts.referrerPolicy;
|
|
80
|
+
var permPolicy = opts.permissionsPolicy === undefined ? DEFAULT_PERMISSIONS.join(", ") : opts.permissionsPolicy;
|
|
81
|
+
var coop = opts.coop === undefined ? "same-origin" : opts.coop;
|
|
82
|
+
var coep = opts.coep === undefined ? false : opts.coep;
|
|
83
|
+
var corp = opts.corp === undefined ? "same-origin" : opts.corp;
|
|
84
|
+
var oac = opts.originAgentCluster === undefined ? "?1" : opts.originAgentCluster;
|
|
85
|
+
var dpc = opts.dnsPrefetchControl === undefined ? "off" : opts.dnsPrefetchControl;
|
|
86
|
+
var csp = opts.csp === undefined ? DEFAULT_CSP : opts.csp;
|
|
87
|
+
|
|
88
|
+
return function securityHeaders(req, res, next) {
|
|
89
|
+
if (typeof res.setHeader !== "function") return next();
|
|
90
|
+
// RFC 6797 §7.2: HSTS over plain HTTP is meaningless (UAs ignore
|
|
91
|
+
// it). Skip the header on non-TLS requests so dev-over-HTTP doesn't
|
|
92
|
+
// surface confusing "Strict-Transport-Security on http://" lines.
|
|
93
|
+
// requestProtocol respects trustProxy — operators behind a TLS
|
|
94
|
+
// terminator opt in to read X-Forwarded-Proto.
|
|
95
|
+
if (hsts && requestHelpers.requestProtocol(req, { trustProxy: trustProxy }) === "https") {
|
|
96
|
+
res.setHeader("Strict-Transport-Security", hsts);
|
|
97
|
+
}
|
|
98
|
+
if (ctOpts) res.setHeader("X-Content-Type-Options", ctOpts);
|
|
99
|
+
if (frameOpts) res.setHeader("X-Frame-Options", frameOpts);
|
|
100
|
+
if (refPolicy) res.setHeader("Referrer-Policy", refPolicy);
|
|
101
|
+
if (permPolicy) res.setHeader("Permissions-Policy", permPolicy);
|
|
102
|
+
if (coop) res.setHeader("Cross-Origin-Opener-Policy", coop);
|
|
103
|
+
if (coep) res.setHeader("Cross-Origin-Embedder-Policy", coep);
|
|
104
|
+
if (corp) res.setHeader("Cross-Origin-Resource-Policy", corp);
|
|
105
|
+
if (oac) res.setHeader("Origin-Agent-Cluster", oac);
|
|
106
|
+
if (dpc) res.setHeader("X-DNS-Prefetch-Control", dpc);
|
|
107
|
+
if (csp) res.setHeader("Content-Security-Policy", csp);
|
|
108
|
+
next();
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
create: create,
|
|
114
|
+
DEFAULT_PERMISSIONS: DEFAULT_PERMISSIONS,
|
|
115
|
+
DEFAULT_CSP: DEFAULT_CSP,
|
|
116
|
+
};
|