@blamejs/core 0.11.0 → 0.11.1
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/http-client.js +46 -10
- package/lib/ssrf-guard.js +71 -10
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.11.x
|
|
10
10
|
|
|
11
|
+
- v0.11.1 (2026-05-19) — **Integration suite hardening + live coverage for the v0.11.0 surface.** **`b.httpClient.request`** now skips the local SSRF DNS lookup when a proxy is configured AND the operator passes `allowInternal: true`. The proxy resolves the destination hostname in its own network context, so requiring local resolution refused legitimate intranet / docker-service-name targets routed through the proxy. The SSRF gate still runs when `allowInternal` is false / array-form (the proxy's freedom to reach internal IPs is not a blanket license; the explicit opt-in is still required). **`b.mtlsCa`** integration tests now compose with the `caKeySealedMode: "disabled"` opt for fixture purposes; production deployments continue to wire `opts.vault` for sealed-at-rest CA-key storage. **`b.mail.crypto.smime.verify`** return shape gains a `chainVerified: boolean` field reflecting whether `opts.trustAnchorCertsPem` was supplied and the leaf-to-root chain walk completed. **Integration coverage added for v0.11.0 primitives:** new `test/integration/mail-crypto-smime.test.js` round-trips S/MIME sign + verify with a real X.509 chain issued by `b.mtlsCa` (CA → leaf cert → ML-DSA-65 signer), exercises tamper / wrong-key / untrusted-anchor refusal paths, and validates the `chainVerified` return field. `test/integration/federation-auth.test.js` extends to cover SAML SLO (`buildLogoutRequest` against Keycloak's `/protocol/saml` SLO endpoint with the wire-format-parse assertion) and RFC 7592 Dynamic Client Registration Management (`registerClient` / `readClient` / `updateClient` / `deleteClient` against Keycloak's DCR endpoint).
|
|
12
|
+
|
|
11
13
|
- v0.11.0 (2026-05-19) — **Mail-crypto sign/verify + encrypt/decrypt, SAML Single Logout (Redirect / POST / SOAP) + EncryptedAssertion + Holder-of-Key, browser identity (FedCM / DBSC / VAPID), CSP3 builder, hypermedia + observability formats, OAuth DCR management + OIDC Native SSO, sectoral compliance posture growth.** **`b.cms.parseSignedData(buf)`** walks an inbound RFC 5652 SignedData ContentInfo and returns `{ digestAlgs, encapContent, certificates, signerInfos }` so consumers verify signatures without re-implementing the SignedData walker. **`b.mail.crypto.smime.sign(opts)` + `.verify(opts)`** are now live on the `b.cms` substrate: `sign` emits an RFC 8551 `multipart/signed; protocol="application/pkcs7-signature"; micalg=sha3-{256,512}` envelope; `verify` recomputes the message digest, compares it against the signed-attrs `messageDigest` attribute, refuses tamper with `mail-crypto/smime/message-digest-mismatch`, and PQC-verifies the signature against the operator-supplied signer public key. Supports ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f signers; SHA-2 family refused at `cms/bad-digest`. **`b.mail.crypto.pgp.experimental.encrypt(opts)` + `.decrypt(opts)` + `.wkd.computeUrl(email)`** ship under the `experimental` namespace because the relevant IANA codepoint registrations are still in draft: ML-KEM-1024 KEM + ChaCha20-Poly1305 AEAD multi-recipient envelope; WKD URL computer per [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/) (SHAKE256-hash localpart + zbase32 encoding; operators supply their own HTTPS fetcher). **`b.auth.saml.sp.buildLogoutRequest({...})` + `parseLogoutRequest(b64, opts)` + `buildLogoutResponse({...})`** implement SAML 2.0 Single Logout on the HTTP-Redirect binding per SAML Bindings §3.4.4.1 with PQC-signed canonical query (ML-DSA-65 / ML-DSA-87 / Ed25519); SP metadata now emits `<md:SingleLogoutService>` bindings when `singleLogoutServiceUrl` is set; tamper / wrong-key / missing-signature each refuse as typed errors. **`b.webPush.generateVapidKeypair()` + `.buildVapidAuthHeader(opts)`** sign the RFC 8292 VAPID JWT inline (ECDSA-P256 per the spec; the framework's PQC-default JWT signer refuses ES256 by design, so `b.webPush` owns the signing rather than relaxing the broader policy). **`b.fedcm`** ships the W3C FedCM 2024 IdP-side response builders: `wellKnown({ provider_urls })` / `config({ accounts_endpoint, ... })` / `accountsResponse({ accounts })` / `idAssertionResponse({ token })`. **`b.dbsc`** implements IETF Device-Bound Session Credentials: `challenge({ secretKey })` mints an HMAC-SHA3-512-signed token requiring no server-side storage; `verifyBindingAssertion(jwt, opts)` refuses HS256 / none as algorithm-confusion, validates ES256/RS256 against the embedded JWK, and returns the RFC 7638 JWK thumbprint so operators pin the binding key to a session. **`b.importmapIntegrity.build({ modules })`** emits a WICG Import Maps + SRI integrity map (SHA-384 default) so browsers refuse module bytes that don't match. **OpenMetrics 1.0 exposition** — `b.metrics` registry `exposition({ format: "openmetrics" })` emits the counter `_total` suffix, `# UNIT` lines, exemplar trace IDs on histograms, and `# EOF` terminator per the openmetrics.io 1.0 wire format. **`b.standardWebhooks.sign + verify`** implement the standardwebhooks.com consortium spec (Stripe / Svix / Okta wire format): HMAC-SHA256, multi-version signature header, 5-minute default skew tolerance. **`b.lro`** implements AIP-151 Long-Running Operations (`create({ store }) → { submit, status, list, cancel }`) with operator-supplied storage and AbortSignal-aware cancellation. **`b.jsonApi.dataResponse(data, opts) + .errorResponse(errors)`** wraps domain payloads in the JSON:API v1.1 top-level shape; refuses missing Resource Object `type`. **`b.hal.resource(payload, { links, embedded, templates })`** builds HAL responses (draft-kelly-json-hal) with an RFC 8288 link-object normaliser. **Lib-side codebase-patterns detector `number-coerce-or-zero-on-json-source`** refuses `Number(<var>['<kebab-cased-key>']) || 0` shapes — that coercion silently accepts `Infinity`, NaN, negative values, and arbitrary strings on operator-untrusted JSON-source input. **(a) `b.cms.parseSignedData(buf)`** — RFC 5652 §5.1 SignedData walker that surfaces `digestAlgs` / `encapContent` / `certificates` / `signerInfos` as structured arrays so downstream verifiers can check signatures without re-implementing the walker. **(b) `b.mail.crypto.smime.sign(opts)` + `b.mail.crypto.smime.verify(opts)` — LIVE on the b.cms substrate.** `sign` composes `b.cms.encodeSignedData` and wraps in an RFC 8551 `multipart/signed; protocol="application/pkcs7-signature"; micalg=sha3-{256,512}` envelope. `verify` parses the CMS SignedData payload, walks signed-attributes to extract the `messageDigest` attribute, recomputes the message digest, refuses tamper with `mail-crypto/smime/message-digest-mismatch`, and PQC-verifies the signature against the operator-supplied signer public key. Supports ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f signers; SHA-2 family refused at `cms/bad-digest`. **(c) `b.mail.crypto.pgp.experimental.encrypt(opts)` + `decrypt(opts)` + `wkd.computeUrl(email)`** — PQC PGP encrypt/decrypt under `experimental` namespace (RFC 9580bis PKESK ML-KEM codepoints haven't IANA-registered yet; framework-private envelope similar to v0.10.10 `b.jose.jwe.experimental`). ML-KEM-1024 KEM + ChaCha20-Poly1305 AEAD + per-recipient KEK derived via SHAKE256 bound to the literal label `pgp/experimental/chacha20-poly1305`. Multi-recipient envelopes; tamper / wrong-key refusal as typed errors. WKD URL computer per [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/) — SHAKE256-hash localpart + zbase32 encoding; returns `{ direct, advanced }` URLs (operators supply their own HTTPS fetcher). **(d) `b.auth.saml.sp.buildLogoutRequest({...})` + `parseLogoutRequest(b64, opts)` + `buildLogoutResponse({...})`** — SAML 2.0 Single Logout on the HTTP-Redirect binding per SAML Bindings §3.4.4.1 with PQC-signed canonical query string (ML-DSA-65 / ML-DSA-87 / Ed25519). `parseLogoutRequest` inflates the operator-supplied SAMLRequest, optionally verifies the redirect-binding signature against an IdP public key, and surfaces NameID / SessionIndex / Issuer. Tamper / wrong-key / missing-signature each refuse as typed errors. Closes the largest item from the v0.10.15 SAML SLO + identity-residual queued plan. **(e) Codex P2 detector `number-coerce-or-zero-on-json-source`** (lib-side) — flags `Number(<var>['<kebab-cased-key>']) || 0` shapes that silently accept Infinity / NaN / negative on operator-untrusted JSON-source numeric input. The v0.10.15 TLS-RPT fix was a one-off; this detector forces the discipline. **Additional residual closure:** `b.auth.saml.sp.buildLogoutRequestPost` / `parseLogoutRequestPost` / `buildLogoutRequestSoap` / `parseLogoutResponseSoap` add SAML SLO HTTP-POST + SOAP synchronous back-channel bindings with embedded XMLDSig-Enveloped signatures. **SignatureMethod surface** spans the W3C XMLDSig Core 1.1 + RFC 9231 vocabulary so the framework interops with deployed IdPs out of the box: `rsa-sha256` / `rsa-sha384` / `rsa-sha512` (W3C XMLDSig Core 1.1), `ecdsa-sha256` / `ecdsa-sha384` / `ecdsa-sha512` (W3C XMLDSig Core 1.1), `ed25519` (RFC 9231). Classical keys are PEM strings or `node:crypto` KeyObject instances; PQC keys remain `Uint8Array` from `b.pqcSoftware.ml_dsa_*.keygen()`. Post-quantum `ml-dsa-65` / `ml-dsa-87` are also accepted on the same surface under clearly framework-private URIs (`urn:blamejs:experimental:saml-sig-alg:ml-dsa-65` / `:ml-dsa-87`) — no IETF/W3C XMLDSig registration exists for ML-DSA yet (LAMPS WG drafts in flight). Verification refuses `'unsupported-c14n'` (only exclusive c14n; inclusive-c14n IdPs must upgrade), alg-confusion (SignatureMethod URI must match the operator-declared `idpVerifyAlg`), signature-wrapping (Reference URI must match root ID via timing-safe digest compare), and SHA-1 digest methods (CVE-2017-7525-class). **EncryptedAssertion (SAML 2.0 §2.5)** decrypts AES-128-GCM / AES-256-GCM (W3C XMLEnc 1.1 §5.2.4) content + RSA-OAEP-MGF1P / xmlenc11 RSA-OAEP key transport (SHA-256/384/512 only — SHA-1 OAEP refused as CVE-2023-49141-class). AES-CBC content encryption is **refused** under both `xmlenc#aes128-cbc` and `xmlenc#aes256-cbc` — CVE-2011-1473 padding-oracle research makes CBC mode under XMLEnc unsuitable without a per-content MAC; operators integrating with IdPs that default to CBC (older ADFS / Azure AD / Okta / Keycloak / OneLogin) switch the IdP's content-encryption setting to AES-128-GCM or AES-256-GCM. Framework-experimental URIs `urn:blamejs:experimental:xmlenc:ml-kem-1024` (key transport) and `urn:blamejs:experimental:xmlenc:xchacha20-poly1305` (content) are accepted alongside the W3C URIs. `b.mail.crypto.smime.verify({ trustAnchorCertsPem })` now walks the SignerInfo cert chain leaf-to-root via `node:crypto.X509Certificate` with notBefore/notAfter checks and refuses `mail-crypto/smime/untrusted-chain` / `cert-expired` / `cert-not-yet-valid`; revocation is operator-wired via `b.network.tls.ocsp` when freshness is required. `b.auth.saml.sp.verifyResponse({ holderOfKey: { presentedCertPem } })` honors `urn:oasis:names:tc:SAML:2.0:cm:holder-of-key` SubjectConfirmation: SHA3-512 fingerprint of the embedded KeyInfo/X509Data certificate is compared against the operator-supplied presented mTLS / possession-proof cert via timingSafeEqual; HoK and Bearer confirmations coexist. `b.auth.oauth.readClient(uri, token)` / `updateClient(uri, token, metadata)` / `deleteClient(uri, token)` implement RFC 7592 Dynamic Client Registration Management Protocol (GET/PUT/DELETE bound to the registration_access_token returned by `registerClient`); updateClient enforces the same redirect_uris-array refusal as registerClient. `b.auth.oauth.nativeSsoExchange({ deviceSecret, idToken, audience })` convenience-wraps `exchangeToken` for OpenID Connect Native SSO 1.0 §6 with `urn:openid:params:token-type:device-secret` added to the RFC 8693 §3 token-type allowlist. **`b.csp.build(directives, opts?)` + `b.csp.nonce(byteLen?)` + `b.csp.hash(scriptBody, alg?)`** ship the CSP Level 3 builder surface: refuses `'unsafe-*'` / catch-all `*` / `https:` / `data:` in non-image directives without an explicit acknowledgement opt; auto-appends `require-trusted-types-for 'script'` plus the operator-supplied `trusted-types` policy list when any script-* directive is set; emits ≥128-bit nonces by default; computes sha256/sha384/sha512 hash sources for inline scripts. `b.middleware.securityHeaders` `coep` opt now documents the W3C CR 2024-12 `credentialless` value alongside `require-corp`. **`b.compliance` posture catalog gains 17 sectoral / cybersecurity / AI-governance regimes:** `42-cfr-part-2`, `hti-1`, `uscdi-v4`, `irs-1075`, `nist-800-172-r3`, `tlp-2.0`, `soci-au`, `nis2`, `cra`, `ffiec-cat-2`, `cri-profile-v2.0`, `m-22-09`, `m-22-18`, `nist-800-53-r5-privacy`, `nist-ai-600-1-genai`, `nist-csf-2.0`, `sb-53`, `nyc-ll144-2024`. Each posture cascade pins the regime's normative floor (backupEncryptionRequired / auditChainSignedRequired / tlsMinVersion / requireVacuumAfterErase). **References:** [RFC 5652 CMS](https://www.rfc-editor.org/rfc/rfc5652.html) · [RFC 8551 S/MIME 4.0](https://www.rfc-editor.org/rfc/rfc8551.html) · [RFC 9580 OpenPGP](https://www.rfc-editor.org/rfc/rfc9580.html) · [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/) · [SAML Bindings §3.4 HTTP-Redirect + §3.5 HTTP-POST + §3.2 SOAP](https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf) · [SAML 2.0 §2.5 EncryptedAssertion](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf) · [W3C XML-Encryption 1.1](https://www.w3.org/TR/xmlenc-core1/) · [RFC 7592 Dynamic Client Management](https://www.rfc-editor.org/rfc/rfc7592.html) · [OpenID Connect Native SSO 1.0](https://openid.net/specs/openid-connect-native-sso-1_0.html) · [W3C CSP Level 3](https://www.w3.org/TR/CSP3/) · [W3C Trusted Types](https://www.w3.org/TR/trusted-types/).
|
|
12
14
|
## v0.10.x
|
|
13
15
|
|
package/lib/http-client.js
CHANGED
|
@@ -1257,14 +1257,52 @@ function _requestSingle(opts) {
|
|
|
1257
1257
|
}
|
|
1258
1258
|
}
|
|
1259
1259
|
|
|
1260
|
-
//
|
|
1261
|
-
//
|
|
1262
|
-
//
|
|
1263
|
-
//
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1260
|
+
// Proxy detection runs BEFORE the SSRF DNS lookup. When a proxy is
|
|
1261
|
+
// configured AND the operator explicitly opts into `allowInternal:
|
|
1262
|
+
// true`, the SSRF DNS lookup is skipped — the proxy resolves the
|
|
1263
|
+
// target hostname in its own network context (the proxy is the trust
|
|
1264
|
+
// boundary). Without this short-circuit, hostnames that only resolve
|
|
1265
|
+
// inside the proxy's network (e.g. corporate intranets, docker
|
|
1266
|
+
// service names) would fail DNS locally before the proxy ever sees
|
|
1267
|
+
// them.
|
|
1268
|
+
//
|
|
1269
|
+
// The `allowInternal: true` opt is the operator's affirmative waiver
|
|
1270
|
+
// of local SSRF defense; combined with a configured proxy, it
|
|
1271
|
+
// signals "trust the proxy's resolution + classification". When
|
|
1272
|
+
// `allowInternal` is false / array-form, the SSRF check still runs
|
|
1273
|
+
// even with a proxy — the proxy's freedom to reach internal IPs is
|
|
1274
|
+
// not a license for operator code to do so without the explicit opt-in.
|
|
1275
|
+
var proxyAgent = null;
|
|
1276
|
+
try { proxyAgent = networkProxy.agentFor(u); } catch (_e) { proxyAgent = null; }
|
|
1277
|
+
|
|
1278
|
+
var ssrfPromise;
|
|
1279
|
+
if (proxyAgent && opts.allowInternal === true) {
|
|
1280
|
+
// Proxy short-circuit — skip DNS resolution of the destination
|
|
1281
|
+
// hostname (the proxy resolves it in its own network context).
|
|
1282
|
+
// BUT still apply the textual cloud-metadata-IP block: addresses
|
|
1283
|
+
// like 169.254.169.254 (AWS / GCP / Azure / OpenStack / DO IMDS)
|
|
1284
|
+
// leak instance credentials and are NEVER overridable, even with
|
|
1285
|
+
// `allowInternal: true` AND a proxy configured. ssrfGuard's
|
|
1286
|
+
// textual check refuses metadata-IP literals at the hostname-text
|
|
1287
|
+
// layer so the proxy never receives the request.
|
|
1288
|
+
try {
|
|
1289
|
+
ssrfGuard.checkUrlTextual(u, { errorClass: opts.errorClass });
|
|
1290
|
+
} catch (eMeta) {
|
|
1291
|
+
return Promise.reject(eMeta);
|
|
1292
|
+
}
|
|
1293
|
+
ssrfPromise = Promise.resolve({ ips: null });
|
|
1294
|
+
} else {
|
|
1295
|
+
// SSRF gate — refuse private / loopback / link-local / cloud-metadata
|
|
1296
|
+
// / reserved IP destinations by default. The returned `ips` are
|
|
1297
|
+
// threaded into transport creation so the actual TCP connect pins
|
|
1298
|
+
// to those exact addresses, closing the DNS-rebinding TOCTOU window.
|
|
1299
|
+
ssrfPromise = ssrfGuard.checkUrl(u, {
|
|
1300
|
+
allowInternal: opts.allowInternal,
|
|
1301
|
+
errorClass: opts.errorClass,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
return ssrfPromise.then(function (ssrfResult) {
|
|
1268
1306
|
var ips = ssrfResult && ssrfResult.ips;
|
|
1269
1307
|
// Caller-supplied agent bypasses transport cache (h1 only). The
|
|
1270
1308
|
// operator owns the agent's connection pool — we still pass the
|
|
@@ -1278,8 +1316,6 @@ function _requestSingle(opts) {
|
|
|
1278
1316
|
}, u, opts);
|
|
1279
1317
|
}
|
|
1280
1318
|
|
|
1281
|
-
var proxyAgent = null;
|
|
1282
|
-
try { proxyAgent = networkProxy.agentFor(u); } catch (_e) { proxyAgent = null; }
|
|
1283
1319
|
if (proxyAgent) {
|
|
1284
1320
|
return _requestH1({
|
|
1285
1321
|
kind: "h1",
|
package/lib/ssrf-guard.js
CHANGED
|
@@ -717,15 +717,76 @@ function isCloudMetadata(ip) { return classify(ip) === "cloud-metadata"; }
|
|
|
717
717
|
*/
|
|
718
718
|
function isReserved(ip) { return classify(ip) === "reserved"; }
|
|
719
719
|
|
|
720
|
+
/**
|
|
721
|
+
* @primitive b.ssrfGuard.checkUrlTextual
|
|
722
|
+
* @signature b.ssrfGuard.checkUrlTextual(url, opts?)
|
|
723
|
+
* @since 0.11.1
|
|
724
|
+
* @status stable
|
|
725
|
+
* @related b.ssrfGuard.checkUrl
|
|
726
|
+
*
|
|
727
|
+
* Text-only SSRF check for paths where the DNS lookup is
|
|
728
|
+
* intentionally deferred to a downstream resolver (e.g. an outbound
|
|
729
|
+
* HTTP proxy resolving hostnames in its own network context, or a
|
|
730
|
+
* pinned-IP transport that already knows the destination address).
|
|
731
|
+
* The hostname is checked verbatim against the cloud-metadata IP list
|
|
732
|
+
* — those addresses (`169.254.169.254`, `169.254.170.2`,
|
|
733
|
+
* `fd00:ec2::254`) are NEVER overridable, even when
|
|
734
|
+
* `allowInternal: true` and a proxy is configured. Operators short-
|
|
735
|
+
* circuiting the DNS-resolution portion of `checkUrl` MUST still call
|
|
736
|
+
* this primitive so the unconditional metadata-IP block applies at
|
|
737
|
+
* the textual layer.
|
|
738
|
+
*
|
|
739
|
+
* Returns `{ ips: null, host }` on accept. Throws `SsrfError` with
|
|
740
|
+
* `code: "ssrf-guard/blocked-cloud-metadata"` when the hostname is
|
|
741
|
+
* an IP literal matching a known cloud-metadata IP.
|
|
742
|
+
*
|
|
743
|
+
* @opts
|
|
744
|
+
* errorClass?: typeof FrameworkError, // operator-supplied error class for typed refusal
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* b.ssrfGuard.checkUrlTextual("http://intranet-app/api");
|
|
748
|
+
* // → { ips: null, host: "intranet-app" }
|
|
749
|
+
*
|
|
750
|
+
* try { b.ssrfGuard.checkUrlTextual("http://169.254.169.254/x"); }
|
|
751
|
+
* catch (e) { e.code; }
|
|
752
|
+
* // → "ssrf-guard/blocked-cloud-metadata"
|
|
753
|
+
*/
|
|
754
|
+
function checkUrlTextual(url, opts) {
|
|
755
|
+
opts = opts || {};
|
|
756
|
+
var ErrorClass = opts.errorClass || SsrfError;
|
|
757
|
+
var parsed = url instanceof URL ? url : safeUrl.parse(String(url), {
|
|
758
|
+
allowedProtocols: safeUrl.ALLOW_HTTP_ALL,
|
|
759
|
+
errorClass: ErrorClass,
|
|
760
|
+
});
|
|
761
|
+
if (!parsed.hostname) {
|
|
762
|
+
throw new ErrorClass("URL '" + parsed.toString() + "' has no hostname",
|
|
763
|
+
"ssrf-guard/no-hostname", { url: parsed.toString() });
|
|
764
|
+
}
|
|
765
|
+
var host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
766
|
+
// If the textual hostname IS an IP literal AND matches a cloud-
|
|
767
|
+
// metadata IP, refuse — even with `allowInternal: true` and a proxy.
|
|
768
|
+
// Metadata IPs leak instance credentials (AWS IMDS, GCP, Azure) and
|
|
769
|
+
// are not a configuration knob.
|
|
770
|
+
if (net.isIP(host) && CLOUD_METADATA_IPS.indexOf(host) !== -1) {
|
|
771
|
+
throw new ErrorClass(
|
|
772
|
+
"URL '" + parsed.toString() + "' resolves to cloud-metadata IP " + host +
|
|
773
|
+
" — refused unconditionally (not overridable via allowInternal + proxy)",
|
|
774
|
+
"ssrf-guard/blocked-cloud-metadata",
|
|
775
|
+
{ url: parsed.toString(), ip: host, category: "cloud-metadata" });
|
|
776
|
+
}
|
|
777
|
+
return { ips: null, host: host };
|
|
778
|
+
}
|
|
779
|
+
|
|
720
780
|
module.exports = {
|
|
721
|
-
classify:
|
|
722
|
-
cidrContains:
|
|
723
|
-
checkUrl:
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
781
|
+
classify: classify,
|
|
782
|
+
cidrContains: cidrContains,
|
|
783
|
+
checkUrl: checkUrl,
|
|
784
|
+
checkUrlTextual: checkUrlTextual,
|
|
785
|
+
createAllowlist: createAllowlist,
|
|
786
|
+
isPrivate: isPrivate,
|
|
787
|
+
isLoopback: isLoopback,
|
|
788
|
+
isLinkLocal: isLinkLocal,
|
|
789
|
+
isCloudMetadata: isCloudMetadata,
|
|
790
|
+
isReserved: isReserved,
|
|
791
|
+
SsrfError: SsrfError,
|
|
731
792
|
};
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.6",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:7fbc6c04-e867-4e6a-b8a0-d1686fdb9dc7",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-19T15:04:27.770Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.11.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.11.1",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.11.
|
|
25
|
+
"version": "0.11.1",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.11.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.11.1",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.11.
|
|
57
|
+
"ref": "@blamejs/core@0.11.1",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|