@blamejs/core 0.16.28 → 0.16.30
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 +4 -0
- package/NOTICE +26 -1
- package/lib/backup/bundle.js +13 -3
- package/lib/backup/index.js +16 -5
- package/lib/backup/manifest.js +10 -5
- package/lib/guard-cidr.js +5 -0
- package/lib/ip-utils.js +11 -1
- package/lib/middleware/require-bound-key.js +1 -1
- package/lib/restore-bundle.js +10 -2
- package/lib/vendor/MANIFEST.json +11 -11
- package/lib/vendor/public-suffix-list.dat +30 -7
- package/lib/vendor/public-suffix-list.data.js +5560 -5553
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.16.x
|
|
10
10
|
|
|
11
|
+
- v0.16.30 (2026-07-13) — **Reject a malformed IPv6 address whose '::' compresses no groups (and a leading-zero embedded-IPv4 tail) in the shared IP validator and the CIDR guard, and complete third-party license attribution for the vendored elliptic-curve library and the Public Suffix List.** The shared IPv6 text parser accepted an address where a '::' sat next to a full eight explicit groups (for example 1:2:3:4:5:6:7:8::, ::1:2:3:4:5:6:7:8, or 1:2:3:4:5:6:7::8). RFC 4291 §2.2 defines '::' as an abbreviation for one or more all-zero groups, so those forms compress nothing and are not valid IPv6 text -- Node's built-in net.isIP rejects every one of them. The parser computed the number of groups to insert but only rejected a negative count, so a zero-insert '::' passed. Because each accepted spelling still normalizes to the same address as its canonical form, no allow/deny decision was bypassed, but the validator disagreed with net.isIP and with any strict peer parser on a whole class of inputs. b.mail (SPF/DMARC/RBL/greylist/HELO) and b.guardCidr both parse IPv6 through the affected code, so both now reject these forms. The embedded IPv4 tail of an IPv4-mapped address is also now validated with the framework's strict dotted-quad check, so a leading-zero, octal-ambiguous octet such as ::ffff:01.2.3.4 is refused rather than silently reinterpreted. Separately, the third-party NOTICE file was missing two vendored components that ship in the package -- the @noble/curves elliptic-curve library (MIT, used by the OPRF primitive) and the Mozilla Public Suffix List (MPL-2.0) -- both are now attributed, and a repository supply-chain policy records why the framework's inherent network, configuration, vendored-bundle, and data-file characteristics are expected. **Changed:** *Complete third-party license attribution for the vendored @noble/curves library and the Public Suffix List, and refresh the vendored Public Suffix List* — The NOTICE file, which attributes every third-party component vendored under lib/vendor/, was missing two that ship in the package: the @noble/curves elliptic-curve library (MIT, used by the OPRF primitive) and the Mozilla Public Suffix List (MPL-2.0, used for organizational-domain derivation in DMARC alignment, BIMI scoping, and cookie-scope confinement). Both are now attributed; the Public Suffix List entry records that it is vendored verbatim under MPL-2.0 with its canonical source URL, satisfying MPL-2.0 §3.2. A stale vendored-library version in the same file was also corrected. The vendored Public Suffix List itself was refreshed to the current upstream snapshot so organizational-domain derivation reflects the latest registry delegations. A repository supply-chain policy also documents why the framework's inherent network egress, configuration reads, vendored minified bundles, and permissively- or open-data-licensed data files are expected characteristics rather than findings. **Security:** *b.mail and b.guardCidr reject a malformed IPv6 address whose '::' compresses zero groups, matching RFC 4291 and net.isIP* — The shared IPv6 text parser inserted 8 - (left groups) - (right groups) zero groups for a '::' and rejected only a negative result, so an address with a full eight explicit groups adjacent to a '::' -- 1:2:3:4:5:6:7:8::, ::1:2:3:4:5:6:7:8, 1:2:3:4:5:6:7::8, 1:2:3:4::5:6:7:8 -- was accepted even though RFC 4291 §2.2 requires '::' to stand for at least one all-zero group and net.isIP rejects all of them. The parser now requires a '::' to insert at least one group. Every affected spelling normalized to the same address as its canonical form, so no CIDR or SPF/DMARC allow/deny decision was bypassed; the fix removes a parser divergence from the platform and peer parsers on a whole class of inputs. The same zero-group check was applied to the separate IPv6 parser inside b.guardCidr, and the embedded IPv4 tail of an IPv4-mapped IPv6 address is now validated with the framework's strict dotted-quad check so a leading-zero / octal-ambiguous octet (for example ::ffff:01.2.3.4) is refused instead of reinterpreted. Legitimate compressed and IPv4-mapped addresses are unchanged.
|
|
12
|
+
|
|
13
|
+
- v0.16.29 (2026-07-13) — **Reject a backup manifest's absolute, drive-letter, or NTFS-stream path at validation and resolve every path through the framework path-safety primitive on both backup and restore, and stop a bound-key auth middleware from hanging when a required peer certificate is absent.** Two defects surfaced while covering the backup/restore path handling and the bound-key auth middleware. A backup manifest's file paths were checked only for a leading separator and .., so a Windows drive-letter path such as C:\Windows\evil, or a colon-bearing NTFS alternate-data-stream marker such as db.enc:evil, passed b.backupManifest.validate, and the backup and restore steps joined the caller- or manifest-declared relativePath and encryptedPath directly, without the framework's path-safety primitive -- so a path built from untrusted input could read a file outside dataDir on backup, or aim a restored file (or a blob read) outside the staging directory on restore. validate now rejects a colon anywhere (both the drive-letter prefix and the alternate-data-stream marker) alongside .. and a leading separator, and both b.backupBundle.create and b.restoreBundle resolve every manifest-declared path through b.safePath (which refuses traversal, absolute, drive-letter, UNC, NTFS alternate-data-stream, and reserved-name paths), so each sink is contained even if a path slips past the first-line check. Separately, b.middleware.requireBoundKey dereferenced req.peerCert.raw in its peer-certificate pinning branch when a fingerprint had been pre-attached by upstream mTLS but the certificate object was absent, throwing an uncaught TypeError that rejected the middleware promise -- the request hung with no response instead of a clean fail-closed denial; the branch now guards the certificate and denies. **Fixed:** *b.middleware.requireBoundKey fails closed instead of hanging when a required peer certificate is absent* — In the peer-certificate pinning branch, the middleware read req.peerCert.raw without checking that req.peerCert was present. When an upstream mTLS layer had pre-attached a peer fingerprint (req.peerFingerprint) but no certificate object, that read threw an uncaught TypeError, which rejected the middleware's promise -- the request received no response and hung, rather than a clean fail-closed 401/403. The branch now checks for the certificate before dereferencing it and denies when it is missing, so a peer-cert-pinned key without a usable certificate is refused, not stalled. **Security:** *Backup and restore resolve every filesystem path through b.safePath, and b.backupManifest.validate rejects an absolute, drive-letter, or NTFS-stream path* — A backup manifest's per-file relativePath and encryptedPath were validated only against .. and a leading / or \, so a Windows drive-absolute path (C:\...) or an NTFS alternate-data-stream marker (db.enc:evil) passed b.backupManifest.validate, and the backup, restore, and storage-adapter steps built their filesystem paths with a plain path join of the caller- or manifest-supplied values -- meaning a path built from untrusted input could read a file outside dataDir on backup, aim a decrypted-file write or an encrypted-blob read outside the intended directory on restore, or escape the storage root through the filesystem adapter. validate() now rejects a colon anywhere (covering both the drive-letter prefix and the alternate-data-stream marker) alongside .. and a leading separator, and every filesystem sink -- b.backupBundle.create (the source read), b.restoreBundle (the encrypted-blob read and the restore destination), and the b.backup.bundleAdapterStorage.fsAdapter key resolver -- resolves its path through b.safePath, which refuses traversal, absolute, drive-letter, UNC, NTFS alternate-data-stream, and Windows reserved-name components and contains the result under its base. (The object-store storage adapter builds an object key, not a filesystem path, and keeps its traversal/NUL check -- a colon is a legal object-key character.) Legitimate relative paths back up and restore unchanged.
|
|
14
|
+
|
|
11
15
|
- v0.16.28 (2026-07-13) — **Fix a Sieve filter bypass where an explicit :comparator silently disabled a header/address/envelope test, plus a JSONPath normalized-path round-trip fault and a rejected daemon cwd option.** Three defects surfaced while covering the Sieve interpreter, JSONPath engine, and daemon supervisor. In b.mail.sieve, an explicit :comparator on a header, address, or envelope test silently disabled the whole test: the Sieve parser did not bind the comparator name to its tag, so the comparator string was consumed as the first positional argument (the header/address name) and every real argument shifted by one -- the test looked up a non-existent header, never matched, and the message fell through to implicit keep, so a discard / fileinto / redirect that should have fired instead delivered the message. The parser now binds :comparator to its tag (RFC 5228 §2.7.3), so the comparator is applied and the arguments stay aligned. b.jsonPath.paths emitted a normalized path containing raw control characters (only ' and \ were escaped), which the query parser then rejected, so a path returned by paths() did not round-trip through query(); control characters are now escaped per RFC 9535 §2.7. And b.daemon.start rejected its own documented cwd option with an unknown-option error; cwd is now accepted and forwarded as the detached child's working directory. **Fixed:** *b.jsonPath.paths emits a normalized path that round-trips through b.jsonPath.query* — A normalized path returned by paths() escaped only the single-quote and backslash characters inside a member name, leaving raw control characters (newline, tab, and the rest of U+0000 through U+001F) in the output. The query parser rejects an unescaped control character in a name selector, so such a path could not be fed back into query(). Control characters in a normalized-path name are now escaped as their short form (\b \t \n \f \r) or \uXXXX, per RFC 9535 §2.7, so paths() output round-trips. · *b.daemon.start accepts and applies the documented cwd option* — The start() options validator did not list cwd among the accepted options, so passing the documented cwd (the working directory for the detached child) was rejected with a daemon/bad-opts unknown-option error and the feature was unusable. cwd is now an accepted optional string and is forwarded to the detached child spawn as its working directory. **Security:** *b.mail.sieve applies an explicit :comparator instead of silently disabling the test* — A Sieve script that used an explicit :comparator on a header, address, or envelope test -- for example header :comparator "i;octet" :is "Subject" "..." -- silently stopped filtering. The parser did not attach the comparator's value string to the :comparator tag, so it was consumed as the first positional argument (the header or address name) and shifted the real name and key list by one position. The test then queried a non-existent header, never matched, and evaluation fell through to implicit keep, so a discard, fileinto, or reject the operator intended was not applied and the message was delivered anyway -- a filter bypass for any rule with an explicit comparator. The parser now binds :comparator to its tag per RFC 5228 §2.7.3, so the chosen comparator (i;octet exact vs the default i;ascii-casemap case-insensitive) is applied and the positional arguments stay aligned; a :comparator with no following comparator-name string is refused at parse time.
|
|
12
16
|
|
|
13
17
|
- v0.16.27 (2026-07-12) — **Fix b.pubsub pattern subscriptions silently dropping every message for a single-wildcard pattern such as orders.*.created.** b.pubsub.subscribePattern with a * wildcard between dotted segments never matched any channel, so a pattern subscriber received nothing. The documented flagship pattern orders.*.created silently dropped orders.eu.created, and the same held for a.*.c, a.*.b.*.c, and any pattern whose wildcard sits between literal segments -- the source @example itself did not work. The segment matcher required a literal that follows a * to fit entirely before the next dot, but a trailing literal like .created contains a dot and legitimately begins before that boundary, so the match was rejected. The matcher now lets a post-wildcard literal begin at or before the next dot and matches it verbatim, while a single-segment * still never spans a dot -- so orders.*.created matches orders.eu.created but not orders.eu.fr.created or orders.created, and a pattern subscriber never receives a message from an extra path segment. Surfaced while covering the pub/sub matcher's branches. **Fixed:** *b.pubsub.subscribePattern matches a single-segment wildcard between literal segments* — A pattern with a * between dotted segments (the documented orders.*.created, or any a.*.c / a.*.b.*.c) matched no channel at all, so the pattern subscriber's handler never fired. The matcher rejected the literal that follows a wildcard unless it fit entirely before the next dot, but a trailing literal such as .created contains a dot and starts before that boundary, so a legitimate match was dropped. The literal after a wildcard is now allowed to begin at or before the next dot and is matched verbatim; the wildcard still matches exactly one non-dot segment, so orders.*.created matches orders.eu.created but not orders.eu.fr.created (two segments) or orders.created (none) -- a pattern subscription never widens to an extra path segment. Behaviour is now consistent with the documented example.
|
package/NOTICE
CHANGED
|
@@ -25,6 +25,16 @@ Used for: XChaCha20-Poly1305 authenticated encryption (lib/crypto.js,
|
|
|
25
25
|
lib/vault-wrap.js, via lib/vendor/noble-ciphers.cjs).
|
|
26
26
|
Thank you to Paul Miller for the audited noble-ciphers suite.
|
|
27
27
|
--------------------------------------------------------------------------------
|
|
28
|
+
Component: @noble/curves
|
|
29
|
+
Version: 2.2.0
|
|
30
|
+
Source: https://github.com/paulmillr/noble-curves
|
|
31
|
+
License: MIT
|
|
32
|
+
Copyright: Copyright (c) 2022 Paul Miller (https://paulmillr.com)
|
|
33
|
+
Used for: RFC 9497 Oblivious Pseudo-Random Function (OPRF / VOPRF / POPRF)
|
|
34
|
+
over ristretto255 / P-256 / P-384 / P-521 (lib/crypto-oprf.js,
|
|
35
|
+
via lib/vendor/noble-curves.cjs). Thank you to Paul Miller for the
|
|
36
|
+
audited noble-curves suite.
|
|
37
|
+
--------------------------------------------------------------------------------
|
|
28
38
|
Component: @noble/post-quantum
|
|
29
39
|
Version: 0.6.1
|
|
30
40
|
Source: https://github.com/paulmillr/noble-post-quantum
|
|
@@ -41,7 +51,7 @@ Used for: FIPS 203 ML-KEM (ml_kem_512 / ml_kem_768 / ml_kem_1024),
|
|
|
41
51
|
reference implementation.
|
|
42
52
|
--------------------------------------------------------------------------------
|
|
43
53
|
Component: @simplewebauthn/server
|
|
44
|
-
Version: 13.3.
|
|
54
|
+
Version: 13.3.2
|
|
45
55
|
Source: https://github.com/MasterKale/SimpleWebAuthn
|
|
46
56
|
License: MIT
|
|
47
57
|
Copyright: Copyright (c) Matthew Miller
|
|
@@ -79,3 +89,18 @@ Used for: Top-10000 most-common (breach-derived) passwords. Loaded by
|
|
|
79
89
|
project maintainers for keeping a curated, freely-redistributable
|
|
80
90
|
baseline.
|
|
81
91
|
--------------------------------------------------------------------------------
|
|
92
|
+
Component: publicsuffix-list (Mozilla Public Suffix List)
|
|
93
|
+
Version: master snapshot (bundled 2026-07-13)
|
|
94
|
+
Source: https://publicsuffix.org/list/public_suffix_list.dat
|
|
95
|
+
License: MPL-2.0
|
|
96
|
+
Copyright: Copyright (c) Mozilla Foundation and Public Suffix List contributors
|
|
97
|
+
Used for: Canonical catalog of effective top-level domains. Loaded at module
|
|
98
|
+
init by b.publicSuffix to derive organizational domains for
|
|
99
|
+
DMARCbis (psd= / np=) alignment, BIMI issuer scoping, cookie-scope
|
|
100
|
+
confinement, and same-site policy (lib/public-suffix.js, via
|
|
101
|
+
lib/vendor/public-suffix-list.dat). This is data, not a code
|
|
102
|
+
bundle. The list is vendored verbatim and unmodified and remains
|
|
103
|
+
licensed under the Mozilla Public License 2.0; its complete source
|
|
104
|
+
is available at the canonical URL above, satisfying MPL-2.0 §3.2.
|
|
105
|
+
The full MPL-2.0 text is at https://www.mozilla.org/MPL/2.0/.
|
|
106
|
+
--------------------------------------------------------------------------------
|
package/lib/backup/bundle.js
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
var nodeFs = require("node:fs");
|
|
52
52
|
var nodePath = require("node:path");
|
|
53
53
|
var atomicFile = require("../atomic-file");
|
|
54
|
+
var safePath = require("../safe-path");
|
|
54
55
|
var bCrypto = require("./crypto");
|
|
55
56
|
var backupManifest = require("./manifest");
|
|
56
57
|
var validateOpts = require("../validate-opts");
|
|
@@ -122,11 +123,20 @@ async function create(opts) {
|
|
|
122
123
|
throw new BackupBundleError("backup-bundle/bad-include",
|
|
123
124
|
"create: files[" + i + "] requires { relativePath: string }");
|
|
124
125
|
}
|
|
125
|
-
if (entry.relativePath.indexOf("..") !== -1 || /^[/\\]/.test(entry.relativePath)
|
|
126
|
+
if (entry.relativePath.indexOf("..") !== -1 || /^[/\\]/.test(entry.relativePath) ||
|
|
127
|
+
entry.relativePath.indexOf(":") !== -1) {
|
|
128
|
+
// A colon anywhere is refused alongside '..' and a leading separator: it
|
|
129
|
+
// covers both a Windows drive prefix (C:\..., absolute) and an NTFS
|
|
130
|
+
// alternate-data-stream marker (db.enc:evil). This friendly pre-screen
|
|
131
|
+
// mirrors b.backupManifest.validate; safePath.resolve below is the
|
|
132
|
+
// authoritative sink for the residual classes (UNC, reserved name, etc.).
|
|
126
133
|
throw new BackupBundleError("backup-bundle/bad-include",
|
|
127
|
-
"create: files[" + i + "].relativePath must be a relative path (got '" + entry.relativePath + "')");
|
|
134
|
+
"create: files[" + i + "].relativePath must be a relative path without '..', a leading separator, or a colon (got '" + entry.relativePath + "')");
|
|
128
135
|
}
|
|
129
|
-
|
|
136
|
+
// Resolve the source path THROUGH dataDir with safePath so a relativePath
|
|
137
|
+
// built from untrusted input can't read a file outside dataDir even if it
|
|
138
|
+
// slips the first-line check (UNC, reserved name, encoded separator, bidi).
|
|
139
|
+
var srcPath = safePath.resolve(dataDir, entry.relativePath);
|
|
130
140
|
if (!nodeFs.existsSync(srcPath)) {
|
|
131
141
|
if (entry.required) {
|
|
132
142
|
throw new BackupBundleError("backup-bundle/missing-required",
|
package/lib/backup/index.js
CHANGED
|
@@ -55,6 +55,7 @@ var os = require("node:os");
|
|
|
55
55
|
var nodePath = require("node:path");
|
|
56
56
|
var bCrypto = require("../crypto");
|
|
57
57
|
var atomicFile = require("../atomic-file");
|
|
58
|
+
var safePath = require("../safe-path");
|
|
58
59
|
var C = require("../constants");
|
|
59
60
|
var backupBundle = require("./bundle");
|
|
60
61
|
var frameworkFiles = require("../framework-files");
|
|
@@ -2197,13 +2198,23 @@ bundleAdapterStorage.fsAdapter = function (fsOpts) {
|
|
|
2197
2198
|
atomicFile.ensureDir(root);
|
|
2198
2199
|
|
|
2199
2200
|
function _keyPath(key) {
|
|
2200
|
-
//
|
|
2201
|
-
//
|
|
2202
|
-
|
|
2201
|
+
// Gate the key under Windows path semantics REGARDLESS of the host, then
|
|
2202
|
+
// resolve the real on-disk path with the host's semantics. An fsAdapter
|
|
2203
|
+
// store is portable — a bundle written on one OS is often read on another —
|
|
2204
|
+
// and win32 splits on both `/` and `\`, so a key like `..\evil` (a harmless
|
|
2205
|
+
// literal filename under POSIX, but a climb out of root when the same key is
|
|
2206
|
+
// later interpreted on Windows) is refused everywhere. The gate is a strict
|
|
2207
|
+
// superset of the host's, also refusing an absolute / drive-letter / UNC
|
|
2208
|
+
// prefix, an NTFS alternate-data-stream marker, a Windows reserved name, and
|
|
2209
|
+
// NUL / control bytes; defense in depth even though the storage layer also
|
|
2210
|
+
// checks. A hand-rolled '..' + NUL substring check (the prior shape) missed
|
|
2211
|
+
// the drive / ADS / reserved-name class the shared primitive handles, and a
|
|
2212
|
+
// host-only resolve missed the cross-platform backslash-traversal class.
|
|
2213
|
+
if (safePath.resolveOrNull(root, key, { platform: "win32" }) === null) {
|
|
2203
2214
|
throw new BackupError("backup/bad-key",
|
|
2204
|
-
"fsAdapter: key contains invalid characters: " + JSON.stringify(key));
|
|
2215
|
+
"fsAdapter: key contains invalid or unsafe path characters: " + JSON.stringify(key));
|
|
2205
2216
|
}
|
|
2206
|
-
return
|
|
2217
|
+
return safePath.resolve(root, key);
|
|
2207
2218
|
}
|
|
2208
2219
|
|
|
2209
2220
|
return {
|
package/lib/backup/manifest.js
CHANGED
|
@@ -121,14 +121,19 @@ function _validateFileEntry(f, idx, errors) {
|
|
|
121
121
|
}
|
|
122
122
|
if (typeof f.relativePath !== "string" || f.relativePath.length === 0) {
|
|
123
123
|
errors.push("files[" + idx + "].relativePath: required non-empty string");
|
|
124
|
-
} else if (f.relativePath.indexOf("..") !== -1 || /^[/\\]/.test(f.relativePath)) {
|
|
125
|
-
// No traversal — when restored, relativePath joins under dataDir
|
|
126
|
-
|
|
124
|
+
} else if (f.relativePath.indexOf("..") !== -1 || /^[/\\]/.test(f.relativePath) || f.relativePath.indexOf(":") !== -1) {
|
|
125
|
+
// No traversal — when restored, relativePath joins under dataDir. A colon
|
|
126
|
+
// anywhere is refused alongside '..' and a leading separator: it covers
|
|
127
|
+
// both a Windows drive prefix (C:\..., absolute — path.resolve would honor
|
|
128
|
+
// it and escape dataDir) and an NTFS alternate-data-stream marker
|
|
129
|
+
// (db.enc:evil), so validate() fails closed before restore, matching the
|
|
130
|
+
// safePath sink b.restoreBundle resolves both paths through.
|
|
131
|
+
errors.push("files[" + idx + "].relativePath: must be a relative path without '..', a leading separator, or a colon (drive letter / NTFS data-stream marker)");
|
|
127
132
|
}
|
|
128
133
|
if (typeof f.encryptedPath !== "string" || f.encryptedPath.length === 0) {
|
|
129
134
|
errors.push("files[" + idx + "].encryptedPath: required non-empty string");
|
|
130
|
-
} else if (f.encryptedPath.indexOf("..") !== -1 || /^[/\\]/.test(f.encryptedPath)) {
|
|
131
|
-
errors.push("files[" + idx + "].encryptedPath: must be a relative path without '..'
|
|
135
|
+
} else if (f.encryptedPath.indexOf("..") !== -1 || /^[/\\]/.test(f.encryptedPath) || f.encryptedPath.indexOf(":") !== -1) {
|
|
136
|
+
errors.push("files[" + idx + "].encryptedPath: must be a relative path without '..', a leading separator, or a colon (drive letter / NTFS data-stream marker)");
|
|
132
137
|
}
|
|
133
138
|
if (typeof f.size !== "number" || !Number.isInteger(f.size) || f.size < 0) {
|
|
134
139
|
errors.push("files[" + idx + "].size: required non-negative integer");
|
package/lib/guard-cidr.js
CHANGED
|
@@ -182,6 +182,11 @@ function _parseIpv6(s) {
|
|
|
182
182
|
if (left === null || right === null) return null;
|
|
183
183
|
var pad = IPV6_GROUPS - left.length - right.length;
|
|
184
184
|
if (pad < 0) return null;
|
|
185
|
+
// RFC 4291 §2.2 — "::" must compress at least one all-zero group. Eight
|
|
186
|
+
// explicit groups adjacent to a "::" (pad === 0) compress nothing and are
|
|
187
|
+
// malformed IPv6 text (net.isIP rejects them), so reject rather than accept
|
|
188
|
+
// a non-canonical spelling into CIDR matching.
|
|
189
|
+
if (pad === 0) return null;
|
|
185
190
|
var zeros = [];
|
|
186
191
|
for (var z = 0; z < pad; z += 1) zeros.push("0000"); // IPv6 zero group
|
|
187
192
|
groups = left.concat(zeros).concat(right);
|
package/lib/ip-utils.js
CHANGED
|
@@ -30,8 +30,12 @@ function expandIpv6Hex(ip) {
|
|
|
30
30
|
// RFC 4291 §2.5.5.2 IPv4-mapped / dual-stack: accept ".d.d.d.d" tail.
|
|
31
31
|
var dual = ip.match(/^(.*?):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/); // allow:regex-no-length-cap — dotted-quad has fixed shape; LHS bounded by IPv6 group cap below
|
|
32
32
|
if (dual) {
|
|
33
|
+
// The embedded IPv4 tail must be a strict RFC 791 dotted-quad. Reuse the
|
|
34
|
+
// canonical isIPv4 (rejects octets > 255 AND leading-zero / octal-ambiguous
|
|
35
|
+
// octets like "01") so this mapped-form parse doesn't diverge from net.isIP
|
|
36
|
+
// or from the standalone isIPv4 validator on the same dotted-quad.
|
|
37
|
+
if (!isIPv4(dual[2])) return null;
|
|
33
38
|
var v4 = dual[2].split(".").map(Number);
|
|
34
|
-
if (v4.some(function (o) { return !(o >= 0 && o <= 255); })) return null; // IPv4 octet range
|
|
35
39
|
var hi = (v4[0] << 8) | v4[1]; // 16-bit group pack
|
|
36
40
|
var lo = (v4[2] << 8) | v4[3]; // 16-bit group pack
|
|
37
41
|
ip = dual[1] + ":" + hi.toString(16) + ":" + lo.toString(16);
|
|
@@ -43,6 +47,12 @@ function expandIpv6Hex(ip) {
|
|
|
43
47
|
if (dblColon.length === 1 && leftGroups.length !== 8) return null; // RFC 4291 IPv6 group count
|
|
44
48
|
var fillCount = 8 - leftGroups.length - rightGroups.length; // RFC 4291 IPv6 group count
|
|
45
49
|
if (fillCount < 0) return null;
|
|
50
|
+
// RFC 4291 §2.2 — "::" abbreviates ONE OR MORE groups of all-zero 16-bit
|
|
51
|
+
// fields, so when a "::" is present it must insert at least one group. Eight
|
|
52
|
+
// explicit groups plus a "::" (e.g. 1:2:3:4:5:6:7:8:: or 1:2:3:4:5:6:7::8)
|
|
53
|
+
// compresses zero groups and is not valid IPv6 text; net.isIP rejects these,
|
|
54
|
+
// so reject them here too rather than diverge from the kernel/peer parser.
|
|
55
|
+
if (dblColon.length === 2 && fillCount === 0) return null;
|
|
46
56
|
var fill = [];
|
|
47
57
|
for (var f = 0; f < fillCount; f += 1) fill.push("0");
|
|
48
58
|
var groups = leftGroups.concat(fill).concat(rightGroups);
|
|
@@ -289,7 +289,7 @@ function create(opts) {
|
|
|
289
289
|
keyId: record.id || null,
|
|
290
290
|
});
|
|
291
291
|
}
|
|
292
|
-
} else if (!bCrypto().isCertRevoked(req.peerCert.raw, pinned)) {
|
|
292
|
+
} else if (!(req.peerCert && req.peerCert.raw) || !bCrypto().isCertRevoked(req.peerCert.raw, pinned)) {
|
|
293
293
|
// isCertRevoked returns true on MATCH against the deny-list
|
|
294
294
|
// shape; we use it here as a fingerprint-set membership test
|
|
295
295
|
// because it does the same constant-time hex/colon comparison
|
package/lib/restore-bundle.js
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
var nodeFs = require("node:fs");
|
|
51
51
|
var nodePath = require("node:path");
|
|
52
52
|
var atomicFile = require("./atomic-file");
|
|
53
|
+
var safePath = require("./safe-path");
|
|
53
54
|
var C = require("./constants");
|
|
54
55
|
var backupCrypto = require("./backup/crypto");
|
|
55
56
|
var backupManifest = require("./backup/manifest");
|
|
@@ -226,7 +227,11 @@ async function extract(opts) {
|
|
|
226
227
|
continue;
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
|
|
230
|
+
// Resolve the manifest-declared encrypted-blob path THROUGH the base with
|
|
231
|
+
// safePath -- a tampered manifest could otherwise carry a traversal /
|
|
232
|
+
// absolute / drive-letter / NTFS-ADS encryptedPath and read a file outside
|
|
233
|
+
// the bundle. manifest.validate is a first-line check; this is the sink.
|
|
234
|
+
var blobPath = safePath.resolve(bundleDir, entry.encryptedPath);
|
|
230
235
|
// Cap the read to the manifest's declared encryptedSize so an oversize-on-
|
|
231
236
|
// disk blob is refused BEFORE it is read into memory (was: read fully, then
|
|
232
237
|
// compare → an OOM window for a huge swapped blob). A valid blob is exactly
|
|
@@ -282,7 +287,10 @@ async function extract(opts) {
|
|
|
282
287
|
" — bundle is corrupted or manifest tampered");
|
|
283
288
|
}
|
|
284
289
|
|
|
285
|
-
|
|
290
|
+
// Resolve the restore destination THROUGH the staging base with safePath
|
|
291
|
+
// so a tampered relativePath (traversal / absolute / drive-letter / ADS)
|
|
292
|
+
// cannot escape stagingDir when the decrypted file is written.
|
|
293
|
+
var destPath = safePath.resolve(stagingDir, entry.relativePath);
|
|
286
294
|
atomicFile.ensureDir(nodePath.dirname(destPath));
|
|
287
295
|
atomicFile.writeSync(destPath, plaintext, { fileMode: 0o600 });
|
|
288
296
|
|
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"hashes": {
|
|
19
19
|
"server": "sha256:5d539dfc9ef47121d4c09bd7256d76448a1f5ac47ee09ac44c78ff6a062af9ab"
|
|
20
20
|
},
|
|
21
|
-
"refreshedAt": "2026-07-
|
|
21
|
+
"refreshedAt": "2026-07-13T22:58:54.983Z"
|
|
22
22
|
},
|
|
23
23
|
"@noble/curves": {
|
|
24
24
|
"version": "2.2.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"hashes": {
|
|
41
41
|
"server": "sha256:ebf254d5eb56aef8705a1c4af9603f47987b4870a9bb5e657e06907b701e2731"
|
|
42
42
|
},
|
|
43
|
-
"refreshedAt": "2026-07-
|
|
43
|
+
"refreshedAt": "2026-07-13T22:58:54.983Z"
|
|
44
44
|
},
|
|
45
45
|
"@noble/post-quantum": {
|
|
46
46
|
"version": "0.6.1",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"hashes": {
|
|
72
72
|
"server": "sha256:f9190309daadca4c2e2cc2b76beaa6b96e463429cc3c390bd9f0ceaf7b588c68"
|
|
73
73
|
},
|
|
74
|
-
"refreshedAt": "2026-07-
|
|
74
|
+
"refreshedAt": "2026-07-13T22:58:54.983Z"
|
|
75
75
|
},
|
|
76
76
|
"@simplewebauthn/server": {
|
|
77
77
|
"version": "13.3.2",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"hashes": {
|
|
95
95
|
"server": "sha256:49411d893f5e9b0e2fcaa564b4ec7921f73a9a06229b5e53d49c1453ea1a365c"
|
|
96
96
|
},
|
|
97
|
-
"refreshedAt": "2026-07-
|
|
97
|
+
"refreshedAt": "2026-07-13T22:58:54.983Z"
|
|
98
98
|
},
|
|
99
99
|
"SecLists-common-passwords-top-10000": {
|
|
100
100
|
"version": "10k-most-common (master)",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
},
|
|
115
115
|
"runtime_artifact": "lib/vendor/common-passwords-top-10000.data.js",
|
|
116
116
|
"integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
|
|
117
|
-
"refreshedAt": "2026-07-
|
|
117
|
+
"refreshedAt": "2026-07-13T22:58:54.983Z"
|
|
118
118
|
},
|
|
119
119
|
"bimi-trust-anchors": {
|
|
120
120
|
"version": "operator-managed",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
},
|
|
140
140
|
"runtime_artifact": "lib/vendor/bimi-trust-anchors.data.js",
|
|
141
141
|
"integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
|
|
142
|
-
"refreshedAt": "2026-07-
|
|
142
|
+
"refreshedAt": "2026-07-13T22:58:54.983Z"
|
|
143
143
|
},
|
|
144
144
|
"publicsuffix-list": {
|
|
145
145
|
"version": "master",
|
|
@@ -152,14 +152,14 @@
|
|
|
152
152
|
"data_js": "lib/vendor/public-suffix-list.data.js"
|
|
153
153
|
},
|
|
154
154
|
"bundler": "curl https://publicsuffix.org/list/public_suffix_list.dat",
|
|
155
|
-
"bundledAt": "2026-
|
|
155
|
+
"bundledAt": "2026-07-13T00:00:00Z",
|
|
156
156
|
"hashes": {
|
|
157
|
-
"server": "sha256:
|
|
158
|
-
"data_js": "sha256:
|
|
157
|
+
"server": "sha256:b6270a246d7bffbe85ad1fb2908abad74828ac5b95bad81cd0cde236955ce444",
|
|
158
|
+
"data_js": "sha256:13b7fa0a62d7b86e5a46468d5d252addd4e54f20143ef3377a5f042b36abf22e"
|
|
159
159
|
},
|
|
160
160
|
"runtime_artifact": "lib/vendor/public-suffix-list.data.js",
|
|
161
161
|
"integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
|
|
162
|
-
"refreshedAt": "2026-07-
|
|
162
|
+
"refreshedAt": "2026-07-13T22:58:54.983Z"
|
|
163
163
|
},
|
|
164
164
|
"peculiar-pki": {
|
|
165
165
|
"version": "2.0.0+pkijs-3.4.0",
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
"hashes": {
|
|
191
191
|
"server": "sha256:9bbc191afaaa2b1e5757f00480457c08134cdc2c55d541df18d9155bba9cbf77"
|
|
192
192
|
},
|
|
193
|
-
"refreshedAt": "2026-07-
|
|
193
|
+
"refreshedAt": "2026-07-13T22:58:54.983Z"
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat,
|
|
6
6
|
// rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported.
|
|
7
7
|
|
|
8
|
-
// VERSION: 2026-07-
|
|
9
|
-
// COMMIT:
|
|
8
|
+
// VERSION: 2026-07-13_21-14-34_UTC
|
|
9
|
+
// COMMIT: 8eb9e60139cb2c62ccec664554adae3767dc1374
|
|
10
10
|
|
|
11
11
|
// Instructions on pulling and using this list can be found at https://publicsuffix.org/list/.
|
|
12
12
|
|
|
@@ -1176,12 +1176,16 @@ gov.gd
|
|
|
1176
1176
|
// Confirmed by registry <info@nic.ge> 2024-11-20
|
|
1177
1177
|
ge
|
|
1178
1178
|
com.ge
|
|
1179
|
+
cyb.ge
|
|
1179
1180
|
edu.ge
|
|
1180
1181
|
gov.ge
|
|
1182
|
+
llc.ge
|
|
1181
1183
|
net.ge
|
|
1184
|
+
online.ge
|
|
1182
1185
|
org.ge
|
|
1183
1186
|
pvt.ge
|
|
1184
1187
|
school.ge
|
|
1188
|
+
tnx.ge
|
|
1185
1189
|
|
|
1186
1190
|
// gf : https://www.iana.org/domains/root/db/gf.html
|
|
1187
1191
|
gf
|
|
@@ -1464,11 +1468,14 @@ tv.im
|
|
|
1464
1468
|
// Please note, that nic.in is not an official eTLD, but used by most
|
|
1465
1469
|
// government institutions.
|
|
1466
1470
|
// Confirmed by Gaurav Kansal <gaurav.kansal@nic.in> 2025-11-06
|
|
1471
|
+
// Added aero.in, alumni.in, school.in and ub.in by Gaurav Kansal <gaurav.kansal@nic.in> 2026-06-25
|
|
1467
1472
|
in
|
|
1468
1473
|
5g.in
|
|
1469
1474
|
6g.in
|
|
1470
1475
|
ac.in
|
|
1476
|
+
aero.in
|
|
1471
1477
|
ai.in
|
|
1478
|
+
alumni.in
|
|
1472
1479
|
am.in
|
|
1473
1480
|
bank.in
|
|
1474
1481
|
bihar.in
|
|
@@ -1503,8 +1510,10 @@ pg.in
|
|
|
1503
1510
|
post.in
|
|
1504
1511
|
pro.in
|
|
1505
1512
|
res.in
|
|
1513
|
+
school.in
|
|
1506
1514
|
travel.in
|
|
1507
1515
|
tv.in
|
|
1516
|
+
ub.in
|
|
1508
1517
|
uk.in
|
|
1509
1518
|
up.in
|
|
1510
1519
|
us.in
|
|
@@ -4336,14 +4345,18 @@ no
|
|
|
4336
4345
|
fhs.no
|
|
4337
4346
|
folkebibl.no
|
|
4338
4347
|
fylkesbibl.no
|
|
4348
|
+
gielda.no
|
|
4349
|
+
herad.no
|
|
4339
4350
|
idrett.no
|
|
4351
|
+
kommune.no
|
|
4340
4352
|
museum.no
|
|
4341
4353
|
priv.no
|
|
4354
|
+
suohkan.no
|
|
4355
|
+
tjielte.no
|
|
4356
|
+
uenorge.no
|
|
4342
4357
|
vgs.no
|
|
4343
4358
|
// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/
|
|
4344
4359
|
dep.no
|
|
4345
|
-
herad.no
|
|
4346
|
-
kommune.no
|
|
4347
4360
|
mil.no
|
|
4348
4361
|
stat.no
|
|
4349
4362
|
// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/
|
|
@@ -4484,7 +4497,7 @@ askøy.no
|
|
|
4484
4497
|
askvoll.no
|
|
4485
4498
|
asnes.no
|
|
4486
4499
|
åsnes.no
|
|
4487
|
-
|
|
4500
|
+
audnedal.no
|
|
4488
4501
|
aukra.no
|
|
4489
4502
|
aure.no
|
|
4490
4503
|
aurland.no
|
|
@@ -4595,7 +4608,6 @@ forsand.no
|
|
|
4595
4608
|
fosnes.no
|
|
4596
4609
|
fræna.no
|
|
4597
4610
|
frana.no
|
|
4598
|
-
frei.no
|
|
4599
4611
|
frogn.no
|
|
4600
4612
|
froland.no
|
|
4601
4613
|
frosta.no
|
|
@@ -4646,6 +4658,7 @@ halden.no
|
|
|
4646
4658
|
halsa.no
|
|
4647
4659
|
hamar.no
|
|
4648
4660
|
hamaroy.no
|
|
4661
|
+
hamarøy.no
|
|
4649
4662
|
hammarfeasta.no
|
|
4650
4663
|
hámmárfeasta.no
|
|
4651
4664
|
hammerfest.no
|
|
@@ -4700,6 +4713,7 @@ karasjohka.no
|
|
|
4700
4713
|
kárášjohka.no
|
|
4701
4714
|
karasjok.no
|
|
4702
4715
|
karlsoy.no
|
|
4716
|
+
karlsøy.no
|
|
4703
4717
|
karmoy.no
|
|
4704
4718
|
karmøy.no
|
|
4705
4719
|
kautokeino.no
|
|
@@ -4888,6 +4902,7 @@ ralingen.no
|
|
|
4888
4902
|
rana.no
|
|
4889
4903
|
randaberg.no
|
|
4890
4904
|
rauma.no
|
|
4905
|
+
re.no
|
|
4891
4906
|
rendalen.no
|
|
4892
4907
|
rennebu.no
|
|
4893
4908
|
rennesoy.no
|
|
@@ -5045,6 +5060,7 @@ tysvær.no
|
|
|
5045
5060
|
tysvar.no
|
|
5046
5061
|
ullensaker.no
|
|
5047
5062
|
ullensvang.no
|
|
5063
|
+
ulstein.no
|
|
5048
5064
|
ulvik.no
|
|
5049
5065
|
unjarga.no
|
|
5050
5066
|
unjárga.no
|
|
@@ -14231,6 +14247,12 @@ iopsys.se
|
|
|
14231
14247
|
// Submitted by Matthew Hardeman <mhardeman@ipifony.com>
|
|
14232
14248
|
ipifony.net
|
|
14233
14249
|
|
|
14250
|
+
// IPv64.net : https://ipv64.net/
|
|
14251
|
+
// Submitted by Dennis Schröder <info@ipv64.net>
|
|
14252
|
+
home64.de
|
|
14253
|
+
ipv64.de
|
|
14254
|
+
ipv64.net
|
|
14255
|
+
|
|
14234
14256
|
// ir.md : https://nic.ir.md
|
|
14235
14257
|
// Submitted by Ali Soizi <info@nic.ir.md>
|
|
14236
14258
|
ir.md
|
|
@@ -15636,9 +15658,10 @@ dedibox.fr
|
|
|
15636
15658
|
schokokeks.net
|
|
15637
15659
|
|
|
15638
15660
|
// Scottish Government : https://www.gov.scot
|
|
15639
|
-
// Submitted by Martin Ellis <
|
|
15661
|
+
// Submitted by Martin Ellis <digital-publishing@gov.scot>
|
|
15640
15662
|
gov.scot
|
|
15641
15663
|
service.gov.scot
|
|
15664
|
+
mygov.scot
|
|
15642
15665
|
|
|
15643
15666
|
// Scry Security : http://www.scrysec.com
|
|
15644
15667
|
// Submitted by Shante Adam <shante@skyhat.io>
|