@blamejs/core 0.5.4 → 0.5.6
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 +2 -0
- package/lib/break-glass.js +14 -3
- package/lib/middleware/security-headers.js +7 -1
- package/lib/ssrf-guard.js +31 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.5.x
|
|
10
10
|
|
|
11
|
+
- **0.5.5** (2026-04-30) — strict default CSP + IPv6 special-range expansion
|
|
12
|
+
- **0.5.4** (2026-04-30) — close SSRF DNS-rebinding window with pinned outbound DNS
|
|
11
13
|
- **0.5.3** (2026-04-30) — security cleanup: trustProxy primitive + Vary merge + HSTS gate
|
|
12
14
|
- **0.5.2** (2026-04-30) — b.breakGlass: passkey factor + service-account bypass + admin tools
|
|
13
15
|
- **0.5.1** (2026-04-30) — b.breakGlass: per-cell encryption + context binding + migrate
|
package/lib/break-glass.js
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
var audit = require("./audit");
|
|
41
41
|
var C = require("./constants");
|
|
42
|
+
var cache = require("./cache");
|
|
42
43
|
var clusterStorage = require("./cluster-storage");
|
|
43
44
|
var { generateBytes, generateToken, kdf, sha3Hash, encryptPacked, decryptPacked } = require("./crypto");
|
|
44
45
|
var cryptoField = require("./crypto-field");
|
|
@@ -76,6 +77,10 @@ var ALLOWED_REASON_STORAGE = ["cleartext", "hmac", "both"];
|
|
|
76
77
|
// Populated on first access per-table; invalidated on policy.set/delete.
|
|
77
78
|
var policyCache = new Map(); // table -> policy
|
|
78
79
|
var initialized = false;
|
|
80
|
+
// Framework-wide trustProxy setting (set at init). When true, the
|
|
81
|
+
// break-glass primitive consults X-Forwarded-For to populate the
|
|
82
|
+
// grant row's `ip` field — same trust boundary as middleware.
|
|
83
|
+
var _trustProxy = false;
|
|
79
84
|
|
|
80
85
|
// Factor lockout — wrap auth.lockout so a hostile actor brute-forcing
|
|
81
86
|
// TOTP codes against break-glass gets shut out after a few failures.
|
|
@@ -85,7 +90,6 @@ var _factorLockout = null;
|
|
|
85
90
|
var _factorLockoutCache = null;
|
|
86
91
|
function _ensureFactorLockout() {
|
|
87
92
|
if (_factorLockout) return _factorLockout;
|
|
88
|
-
var cache = require("./cache");
|
|
89
93
|
_factorLockoutCache = cache.create({
|
|
90
94
|
namespace: "breakglass.factor",
|
|
91
95
|
backend: "memory",
|
|
@@ -305,10 +309,12 @@ async function migrate(table, opts) {
|
|
|
305
309
|
|
|
306
310
|
function init(opts) {
|
|
307
311
|
opts = opts || {};
|
|
308
|
-
validateOpts(opts, ["now"], "breakGlass.init");
|
|
312
|
+
validateOpts(opts, ["now", "trustProxy"], "breakGlass.init");
|
|
309
313
|
initialized = true;
|
|
310
314
|
policyCache.clear();
|
|
311
315
|
_factorLockout = null;
|
|
316
|
+
_trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
|
|
317
|
+
? opts.trustProxy : false;
|
|
312
318
|
}
|
|
313
319
|
|
|
314
320
|
function _resetForTest() {
|
|
@@ -320,6 +326,7 @@ function _resetForTest() {
|
|
|
320
326
|
}
|
|
321
327
|
_factorLockout = null;
|
|
322
328
|
_factorLockoutCache = null;
|
|
329
|
+
_trustProxy = false;
|
|
323
330
|
}
|
|
324
331
|
|
|
325
332
|
function _requireInit() {
|
|
@@ -695,7 +702,11 @@ async function grant(opts) {
|
|
|
695
702
|
var nowMs = Date.now();
|
|
696
703
|
var grantId = "bg-" + generateToken(16);
|
|
697
704
|
var sessionId = (opts.req && opts.req.session && opts.req.session.id) || null;
|
|
698
|
-
|
|
705
|
+
// Honor the framework-wide trustProxy setting from init() — same
|
|
706
|
+
// boundary as middleware. Without trustProxy, X-Forwarded-For is
|
|
707
|
+
// ignored as attacker-forgeable, and the grant pins to the socket
|
|
708
|
+
// remoteAddress only.
|
|
709
|
+
var ipFromReq = requestHelpers.clientIp(opts.req, { trustProxy: _trustProxy });
|
|
699
710
|
|
|
700
711
|
var grantRow = {
|
|
701
712
|
_id: grantId,
|
|
@@ -46,10 +46,16 @@ var DEFAULT_PERMISSIONS = [
|
|
|
46
46
|
"screen-wake-lock=()", "sync-xhr=()", "usb=()", "web-share=()", "xr-spatial-tracking=()",
|
|
47
47
|
];
|
|
48
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.
|
|
49
55
|
var DEFAULT_CSP =
|
|
50
56
|
"default-src 'self'; " +
|
|
51
57
|
"script-src 'self'; " +
|
|
52
|
-
"style-src 'self'
|
|
58
|
+
"style-src 'self'; " +
|
|
53
59
|
"img-src 'self' data:; " +
|
|
54
60
|
"font-src 'self'; " +
|
|
55
61
|
"connect-src 'self'; " +
|
package/lib/ssrf-guard.js
CHANGED
|
@@ -94,6 +94,23 @@ var IPV6_PRIVATE_PREFIX = _ipv6ToBytes("fc00::");
|
|
|
94
94
|
var IPV6_LINK_LOCAL_PREFIX = _ipv6ToBytes("fe80::");
|
|
95
95
|
var IPV6_DOC_PREFIX = _ipv6ToBytes("2001:db8::"); // documentation
|
|
96
96
|
var IPV6_V4_MAPPED_PREFIX = _ipv6ToBytes("::ffff:0:0"); // IPv4-mapped (::ffff:0:0/96)
|
|
97
|
+
// Multicast ff00::/8 — RFC 4291 §2.7. Refused for outbound HTTP same as
|
|
98
|
+
// IPv4 multicast 224/4 — there's no legitimate fetch from a multicast
|
|
99
|
+
// destination on a production gateway.
|
|
100
|
+
var IPV6_MULTICAST_PREFIX = _ipv6ToBytes("ff00::");
|
|
101
|
+
// NAT64 well-known prefix 64:ff9b::/96 — RFC 6052. Translates to IPv4
|
|
102
|
+
// embedded in the lower 32 bits; the framework refuses since the
|
|
103
|
+
// underlying v4 address is what gets contacted and might be private.
|
|
104
|
+
// (Operators with NAT64 deployments flip allowInternal.)
|
|
105
|
+
var IPV6_NAT64_PREFIX = _ipv6ToBytes("64:ff9b::");
|
|
106
|
+
// 6to4 2002::/16 — RFC 3056. Carries an embedded IPv4 in bytes 2-5;
|
|
107
|
+
// hostile use is to tunnel through to a v4 destination the v4 guard
|
|
108
|
+
// would have refused. Refused.
|
|
109
|
+
var IPV6_6TO4_PREFIX = _ipv6ToBytes("2002::");
|
|
110
|
+
// Discard prefix 100::/64 — RFC 6666. Dropped by routers; fetching
|
|
111
|
+
// from it is operationally meaningless and a likely sign of mis-config
|
|
112
|
+
// or attempted exfil to a sinkhole.
|
|
113
|
+
var IPV6_DISCARD_PREFIX = _ipv6ToBytes("100::");
|
|
97
114
|
|
|
98
115
|
// ---- Cloud metadata addresses (string-equality, exact match) ----
|
|
99
116
|
var CLOUD_METADATA_IPS = [
|
|
@@ -235,6 +252,20 @@ function classify(ip) {
|
|
|
235
252
|
if (_ipv6PrefixMatch(IPV6_LINK_LOCAL_PREFIX, 10, bytes)) return "link-local";
|
|
236
253
|
if (_ipv6PrefixMatch(IPV6_PRIVATE_PREFIX, 7, bytes)) return "private";
|
|
237
254
|
if (_ipv6PrefixMatch(IPV6_DOC_PREFIX, 32, bytes)) return "reserved";
|
|
255
|
+
if (_ipv6PrefixMatch(IPV6_MULTICAST_PREFIX, 8, bytes)) return "reserved";
|
|
256
|
+
if (_ipv6PrefixMatch(IPV6_DISCARD_PREFIX, 64, bytes)) return "reserved";
|
|
257
|
+
// 6to4 (2002::/16) embeds a v4 address in bytes 2–5; classify the
|
|
258
|
+
// embedded v4 so a 6to4-wrapped private/metadata address is refused
|
|
259
|
+
// for the same reason its v4 form would be.
|
|
260
|
+
if (_ipv6PrefixMatch(IPV6_6TO4_PREFIX, 16, bytes)) {
|
|
261
|
+
var v4From6to4 = bytes[2] + "." + bytes[3] + "." + bytes[4] + "." + bytes[5];
|
|
262
|
+
return classify(v4From6to4) || "reserved";
|
|
263
|
+
}
|
|
264
|
+
// NAT64 well-known prefix (64:ff9b::/96) embeds v4 in bytes 12–15.
|
|
265
|
+
if (_ipv6PrefixMatch(IPV6_NAT64_PREFIX, 96, bytes)) {
|
|
266
|
+
var v4FromNat64 = bytes[12] + "." + bytes[13] + "." + bytes[14] + "." + bytes[15];
|
|
267
|
+
return classify(v4FromNat64) || "reserved";
|
|
268
|
+
}
|
|
238
269
|
// IPv4-mapped addresses (::ffff:a.b.c.d/96): re-classify the v4 portion.
|
|
239
270
|
if (_ipv6PrefixMatch(IPV6_V4_MAPPED_PREFIX, 96, bytes)) {
|
|
240
271
|
var mappedV4 = bytes[12] + "." + bytes[13] + "." + bytes[14] + "." + bytes[15];
|