🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@blamejs/core

Package Overview
Dependencies
Maintainers
1
Versions
465
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@blamejs/core - npm Package Compare versions

Comparing version
0.15.17
to
0.15.18
+54
-31
lib/middleware/dpop.js

@@ -147,29 +147,15 @@ "use strict";

function _reconstructHtu(req, mopts) {
// The proof's htu is the request URI WITHOUT query/fragment. Behind
// a reverse proxy the operator may need to override via opts.htu /
// opts.getHtu. X-Forwarded-* headers are ATTACKER-CONTROLLED when
// the origin is reachable directly; an attacker who can hit the
// origin while spoofing X-Forwarded-Proto: https can trick this
// function into building an `https` htu that the DPoP proof was
// signed for — when the origin is actually serving HTTP. RFC 9449
// §4.3 says htu MUST be the absolute URL the request was sent to.
//
// Default: ignore X-Forwarded-* and derive proto/host from the
// socket. Operators with a confirmed-trusted front proxy opt in
// via opts.trustForwardedHeaders: true.
mopts = mopts || {};
var trustForwarded = mopts.trustForwardedHeaders === true;
var proto;
if (trustForwarded && req.headers["x-forwarded-proto"]) {
proto = String(req.headers["x-forwarded-proto"]).split(",")[0].trim();
} else {
proto = req.socket && req.socket.encrypted ? "https" : "http";
}
var host;
if (trustForwarded && req.headers["x-forwarded-host"]) {
host = String(req.headers["x-forwarded-host"]).split(",")[0].trim();
} else {
host = req.headers.host;
}
function _reconstructHtu(req, protoResolver, hostResolver) {
// The proof's htu is the request URI WITHOUT query/fragment. Behind a
// reverse proxy the operator may override via opts.getHtu. RFC 9449 §4.3
// says htu MUST be the absolute URL the request was sent to — and it is
// cryptographically bound in the proof, so a forged scheme/authority lets a
// proof signed for one origin validate against another. proto + host are
// resolved through the peer-gated requestHelpers resolvers built in create():
// X-Forwarded-Proto / -Host are honored only from a declared trusted-proxy
// peer; otherwise the real TLS socket scheme + the request's own Host are
// used and forged forwarded headers are ignored.
if (!req || !req.headers) return null;
var proto = protoResolver.resolve(req);
var host = hostResolver.resolve(req);
if (!host) return null;

@@ -209,2 +195,5 @@ var path = req.url || "/";

* getHtu: function(req): string,
* trustedProxies: string|string[], // CIDRs of your reverse proxies — peer-gates X-Forwarded-Proto + X-Forwarded-Host for htu reconstruction
* protocolResolver: function(req): "http"|"https", // own the scheme decision
* hostResolver: function(req): string|null, // own the authority decision
* nonceStore: object,

@@ -233,5 +222,7 @@ * nonceWindowSec: number,

"nonceStore", "nonceWindowSec", "nonceRotateSec", "requireNonce",
// v0.9.4 — opt-in trust gate for X-Forwarded-Proto/Host when
// reconstructing htu. Default off; operators
// with a confirmed-trusted front proxy set this to `true`.
// htu reconstruction trust. trustedProxies (CIDRs) peer-gates
// X-Forwarded-Proto + X-Forwarded-Host; protocolResolver/hostResolver let
// the operator own each. trustForwardedHeaders (legacy boolean) is refused
// on its own — see the peer-gating block below.
"trustedProxies", "protocolResolver", "hostResolver",
"trustForwardedHeaders", "onDeny", "problemDetails",

@@ -288,2 +279,34 @@ ], "middleware.dpop");

// htu reconstruction (RFC 9449 §4.3) builds the absolute request URL —
// proto + host — that the proof's cryptographically-bound `htu` claim is
// verified against. Behind a proxy both come from forgeable X-Forwarded-*
// headers, so resolve them through the peer-gated requestHelpers primitives
// (the same fail-closed model csrf-protect / security-headers / cors use):
// X-Forwarded-Proto / -Host are honored ONLY when the immediate peer is a
// declared trusted proxy. The legacy trustForwardedHeaders:true trusted the
// headers from ANY caller — a direct attacker could forge XFP:https / a
// victim XFH to make a proof signed for one origin validate against another
// (htu confusion). It is refused on its own; migrate to trustedProxies.
var _proto = requestHelpers.trustedProtocol({
trustedProxies: opts.trustedProxies,
protocolResolver: opts.protocolResolver,
});
var _host = requestHelpers.trustedHost({
trustedProxies: opts.trustedProxies,
hostResolver: opts.hostResolver,
});
// Only refuse the spoofable legacy flag when the htu is actually
// reconstructed from the request. When the operator supplies getHtu they own
// the entire URI, _reconstructHtu (and the forwarded headers) is never
// consulted, so a leftover trustForwardedHeaders is moot — don't fail
// construction on it (the error text even offers getHtu as a migration path).
if (typeof opts.getHtu !== "function" && opts.trustForwardedHeaders === true && !_proto.peerGated) {
throw new AuthError("auth-dpop/bad-opt",
"middleware.dpop: trustForwardedHeaders is spoofable for the htu reconstruction " +
"(a direct caller can forge X-Forwarded-Proto / X-Forwarded-Host) and is no longer " +
"honored on its own. Declare your reverse proxies via trustedProxies: [\"10.0.0.0/8\", …] " +
"(peer-gates X-Forwarded-Proto + X-Forwarded-Host), or own the decision via " +
"protocolResolver(req) / hostResolver(req) / getHtu(req).");
}
function _freshNonce() { return nonceMgr ? nonceMgr.issue() : null; }

@@ -316,3 +339,3 @@

var htu = (typeof opts.getHtu === "function" ? opts.getHtu(req) : _reconstructHtu(req, opts));
var htu = (typeof opts.getHtu === "function" ? opts.getHtu(req) : _reconstructHtu(req, _proto, _host));
if (!htu) {

@@ -319,0 +342,0 @@ return _writeUnauthorized(req, res, "invalid_dpop_proof", "could not reconstruct htu", null, onDeny, problemMode);

@@ -71,2 +71,7 @@ "use strict";

function _scheme(req) {
// Display-only: the OTel url.scheme span attribute reflects the scheme the
// client used (forwarded), NOT a Secure/HSTS/origin trust decision. Routing
// through trustedProtocol would drop the forwarded scheme from spans behind a
// proxy (less accurate telemetry) for no security gain.
// allow:raw-xfp — telemetry label, not a trust sink (see above).
var x = req.headers && (req.headers["x-forwarded-proto"] || "");

@@ -81,2 +86,4 @@ if (typeof x === "string" && x.length > 0) {

function _serverAddress(req) {
// allow:raw-xfp — display-only: server.address span attribute (telemetry),
// not an authority trust decision. Same rationale as _scheme above.
var hostHeader = req.headers && (req.headers["x-forwarded-host"] || req.headers.host);

@@ -83,0 +90,0 @@ if (typeof hostHeader === "string" && hostHeader.length > 0) {

@@ -639,2 +639,90 @@ "use strict";

/**
* @primitive b.requestHelpers.trustedHost
* @signature b.requestHelpers.trustedHost(opts?)
* @since 0.15.18
* @related b.requestHelpers.requestHost, b.requestHelpers.trustedProtocol
*
* Peer-gated companion to trustedProtocol for the request authority (host).
* Reconstructing the absolute request URL — the DPoP `htu`, an origin/issuer
* string, a redirect base — depends on the host the client addressed; behind a
* proxy that comes from X-Forwarded-Host, which is forgeable unless the
* immediate peer is a trusted proxy. Returns `{ resolve(req)=>string|null,
* peerGated }`. With `trustedProxies` (CIDRs) X-Forwarded-Host is honored only
* from a trusted peer; with `hostResolver(req)` the operator owns it; with
* neither only the request's own Host header is used (forwarded host ignored).
*
* @opts
* trustedProxies: string | string[],
* hostResolver: function(req): string|null,
*
* @example
* var th = b.requestHelpers.trustedHost({ trustedProxies: ["10.0.0.0/8"] });
* th.resolve(req); // X-Forwarded-Host only when it came via a trusted peer
*/
function trustedHost(opts) {
opts = opts || {};
var resolver = opts.hostResolver;
if (resolver != null && typeof resolver !== "function") {
throw new TypeError("trustedHost: hostResolver must be a function(req) => string|null");
}
var predicate = _trustedProxyPredicate(_normTrustedProxies(opts), "trustedHost");
return {
peerGated: !!(resolver || predicate),
resolve: function (req) {
if (resolver) return resolver(req);
if (predicate) return requestHost(req, { trustProxy: predicate });
return requestHost(req, { trustProxy: false });
},
};
}
/**
* @primitive b.requestHelpers.requestHost
* @signature b.requestHelpers.requestHost(req, opts?)
* @since 0.15.18
* @related b.requestHelpers.requestProtocol, b.requestHelpers.trustedHost
*
* Resolve the inbound authority (host[:port]). Default returns the request's
* own `Host` header. Behind a trusted reverse proxy that rewrites the host,
* pass `trustProxy` as a PREDICATE `function(addr)=>boolean` (build it via
* `b.requestHelpers.trustedHost`): `X-Forwarded-Host` is then honored only when
* the immediate peer is a trusted proxy, so a direct caller can't forge it. The
* legacy `trustProxy: true` reads the leftmost forwarded hop without checking
* the peer — forgeable. Returns the host string, or `null` when absent.
*
* @opts
* trustProxy: boolean | function // false (default) | predicate (peer-gated) | legacy true
*
* @example
* b.requestHelpers.requestHost({ headers: { host: "app.example.com" } });
* // → "app.example.com"
*/
function requestHost(req, opts) {
if (!req || !req.headers) return null;
var trust = opts && opts.trustProxy;
if (trust) {
var fwd = req.headers["x-forwarded-host"];
if (typeof fwd === "string" && fwd.length > 0) {
var hops = parseListHeader(fwd);
if (hops.length > 0) {
if (typeof trust === "function") {
// Peer-gated: honor X-Forwarded-Host only when the immediate TCP peer
// is a trusted proxy. A direct caller's forged header is ignored —
// fall through to the request's own Host header.
var peer =
(req.socket && typeof req.socket.remoteAddress === "string" && req.socket.remoteAddress) ? req.socket.remoteAddress
: (req.connection && typeof req.connection.remoteAddress === "string" && req.connection.remoteAddress) ? req.connection.remoteAddress
: null;
if (peer && trust(peer)) return hops[0];
// peer not a trusted proxy → ignore forgeable header, fall through
} else {
return hops[0]; // legacy true — spoofable, see docstring
}
}
}
}
return typeof req.headers.host === "string" ? req.headers.host : null;
}
// RFC 9110 §5.6.2 token grammar — letters, digits, and the

@@ -1225,2 +1313,4 @@ // punctuation set `!#$%&'*+-.^_`|~`. Used by header-list parsers

trustedProtocol: trustedProtocol,
requestHost: requestHost,
trustedHost: trustedHost,
appendVary: appendVary,

@@ -1227,0 +1317,0 @@ // CVE-2026-21710 wrap — safe alternative to req.headersDistinct

{
"name": "@blamejs/core",
"version": "0.15.17",
"version": "0.15.18",
"description": "The Node framework that owns its stack.",

@@ -5,0 +5,0 @@ "license": "Apache-2.0",

@@ -5,6 +5,6 @@ {

"specVersion": "1.5",
"serialNumber": "urn:uuid:39056bfa-d578-4577-96e0-d3ec1956c873",
"serialNumber": "urn:uuid:c814ff7e-59d1-4c8b-bb90-fe001973e26d",
"version": 1,
"metadata": {
"timestamp": "2026-06-22T18:34:01.428Z",
"timestamp": "2026-06-23T03:53:58.941Z",
"lifecycles": [

@@ -23,10 +23,10 @@ {

"component": {
"bom-ref": "@blamejs/core@0.15.17",
"bom-ref": "@blamejs/core@0.15.18",
"type": "application",
"name": "blamejs",
"version": "0.15.17",
"version": "0.15.18",
"scope": "required",
"author": "blamejs contributors",
"description": "The Node framework that owns its stack.",
"purl": "pkg:npm/%40blamejs/core@0.15.17",
"purl": "pkg:npm/%40blamejs/core@0.15.18",
"properties": [],

@@ -59,3 +59,3 @@ "externalReferences": [

{
"ref": "@blamejs/core@0.15.17",
"ref": "@blamejs/core@0.15.18",
"dependsOn": []

@@ -62,0 +62,0 @@ }

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display