@blamejs/core 0.8.87 → 0.8.88
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 +1 -0
- package/index.js +2 -0
- package/lib/auth/fal.js +13 -3
- package/lib/early-hints.js +192 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,7 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.8.x
|
|
10
10
|
|
|
11
|
+
- v0.8.88 (2026-05-11) — **Hotfix: `b.auth.fal.meets()` authorization-correctness bug + new `b.earlyHints` RFC 8297 helper**. **Hotfix (PRIMARY)**: `b.auth.fal.meets(actualBand, requiredBand)` previously compared raw ranks (`_bandRank(actual) >= _bandRank(required)`) without validating either input. Unknown bands mapped to rank `0`, so `meets("FAL1", "FALX")` returned `true` (because `1 >= 0`) and `meets("bad", "bad")` returned `true` (because `0 >= 0`) — both contradicting the documented contract that invalid bands MUST return `false`. Operators calling `meets()` directly for authorization decisions could grant access on malformed input pairs. The new implementation validates both bands via `isValidBand()` first; any invalid band on either side returns `false`. The `requireFal()` guard was already correct (it used `meets()` after a separate `isValidBand(actualBand)` check, but a defense-in-depth pass into `meets()` itself now catches direct callers too). Tests added: 7 invalid-input shapes (`FALX` actual, `FALX` required, `bad`/`bad`, `FALX`/`FALX`, null on either side, both null). **New**: `b.earlyHints.send(res, { link })` — RFC 8297 103 Early Hints interim-response helper. Wraps Node 18.11+'s built-in `res.writeEarlyHints()` with: link-header validation (RFC 8288 form with one of `preload` / `preconnect` / `prefetch` / `dns-prefetch` / `modulepreload` / `prerender` / `next` / `prev`); silent no-op when the response object lacks `writeEarlyHints` (HTTP/1.0, mocks, older Node); refusal of per-request-state headers per RFC 8297 §3 (`set-cookie`, `authorization`, `content-length`, `content-type`, etc.). Operators use it to start browser-side preload of CSS / JS / fonts / preconnect origins in parallel with the server-side composition of the final response.
|
|
11
12
|
- v0.8.87 (2026-05-11) — **NIST 800-63-4 FAL classifier + RFC 7505 Null-MX helper + Gmail FBL Feedback-ID builder + vendor-update.sh stale-entry cleanup**. **`b.auth.fal`** lands as the federation-side counterpart to the existing `b.auth.aal` band classifier. `fromAssertion({ channel, encrypted?, replayProtected?, hokBinding? })` classifies an incoming federation assertion as `"FAL1"` / `"FAL2"` / `"FAL3"` per NIST 800-63C-4: Holder-of-Key (mTLS / DPoP / SAML HoK) with replay-protection → FAL3; back-channel OR encrypted front-channel with replay-protection → FAL2; bare bearer front-channel → FAL1. Conservative: missing replay-protection on a back-channel assertion downgrades to FAL1 because §5.2 requires nonce / jti binding before back-channel can claim FAL2. `requireFal(minimumBand)` builds a band-check guard that throws `auth/fal-insufficient` for stale-band requests; compose with the request-scope auth state to gate sensitive operations. **`b.network.dns.isNullMx(records)`** lands as the RFC 7505 Null-MX classifier: returns `true` when an operator-supplied MX-record array signals "this domain does not accept email" (single record, priority 0, exchange `.` per RFC 7505 §3). Operators send-side check this before delivery to skip domains that have explicitly opted out — `node:dns.resolveMx` returns `exchange: ""` for the same RDATA, so the classifier accepts both shapes. **`b.mail.feedbackId({ campaignId, customerId, mailType, senderId })`** builds a Gmail Feedback-Loop (FBL) Feedback-ID header value as the canonical 4-tuple `CampaignID:CustomerID:MailType:SenderID`. Refuses missing / empty fields, fields containing `:` (would corrupt the field separator), fields >64 chars (Gmail FBL truncation threshold), and control-char content (CR/LF header-injection defense). Setting Feedback-ID on outbound mail lets Gmail Postmaster Tools surface per-campaign abuse-rate metrics keyed by the operator's vocabulary instead of by SMTP envelope-sender alone. **vendor-update.sh cleanup**: `scripts/vendor-update.sh --check` removed the stale `argon2` entry from `VENDORED_PACKAGES`. argon2 was removed from `lib/vendor/` back in v0.4.x when Node 24's built-in `crypto.argon2*` API replaced the third-party prebuilds (per `lib/argon2-builtin.js`); the script still listed it in the check array, producing a false "UPDATE AVAILABLE" line for an unvendored package. The case-block error path that still says "argon2 is no longer vendored" stays so anyone running `./scripts/vendor-update.sh argon2` gets the operator-friendly explanation.
|
|
12
13
|
- v0.8.86 (2026-05-11) — **Sectoral + cybersecurity posture sweep + HTTP-hygiene primitives + npm-publish hotfix**. **npm-publish hotfix**: the v0.8.85 `npm audit signatures` step failed with `npm error found no installed dependencies to audit` because the framework's zero-runtime-deps posture produces an empty install tree; the gate now treats that specific message as success while keeping every other failure mode loud (v0.8.85 npm tarball never published — operators upgrade `0.8.83 → 0.8.86` to pick up the carried v0.8.84 + v0.8.85 surface plus the new v0.8.86 primitives). **10 new compliance postures**: `cmmc-2.0` (DoD Cybersecurity Maturity Model Certification 2.0), `cjis-v6` (FBI CJIS Security Policy v6.0), `iso-27001-2022` + `iso-27002-2022` + `iso-27017` + `iso-27018` + `iso-27701` (ISO/IEC 27001 family), `nist-800-66-r2` (HIPAA Security Rule implementation guidance), `ehds` (European Health Data Space), `circia` (US Cyber Incident Reporting for Critical Infrastructure Act). Cascade defaults set encrypted-backup + signed-audit-chain + TLS 1.3 + vacuum-after-erase for the data-tier postures; `iso-27002-2022` + `circia` defer the data-tier mandate to operator choice. **`b.cacheStatus`** — RFC 9211 Cache-Status response-header builder + parser. `append(prev, entry)` chains the operator's current cache decision onto whatever upstream caches wrote; `entry({...})` formats a single entry; `parse(headerValue)` returns the parsed chain as `[{ cache, params }]` records with `hit`/`stored`/`collapsed` as booleans, `ttl`/`fwdStatus` as numbers, `fwd` as the RFC 9211 §2 enum string, `key`/`detail` as unquoted sf-strings. Operators diagnose CDN/reverse-proxy/app-cache decision chains by reading the header instead of guessing from elapsed-time metrics. **`b.serverTiming`** — W3C Server-Timing response-header builder. `create()` returns a per-request collector with `mark(name, durationMs?, description?)` / `measure(name, fn)` async-timing wrapper / `toHeader()` serializer. Surfaces server-side latency in the browser's Performance API. **`b.middleware.noCache`** — RFC 9111 §5.2.2.5 `Cache-Control: no-store` middleware for auth-gated / individualized response paths. Sets `Cache-Control: no-store`, `Pragma: no-cache` (HTTP/1.0 compatibility), `Vary: Cookie, Authorization` so intermediate caches don't store personalized responses keyed by URL alone. Optional `opts.when(req)` predicate for conditional application; `opts.skipExisting:true` skips when `Cache-Control` is already set.
|
|
13
14
|
- v0.8.85 (2026-05-11) — **MCP tool registry + tool-call signing + A2A v1 task-exchange surface**. Closes the substantial agent-protocol gaps surfaced by the 2026-05-11 audit's MITRE ATLAS v5.3.0 + A2A v1 cross-walk. **MCP tool registry** lands as `b.mcp.toolRegistry.create({ tools, signingKey, verifyingKey?, alg?, ttlMs? })` — every registered tool gets a signed descriptor blob `{ tool, alg, signature }` (defense against compromised MCP server / descriptor drift) and a `descriptorsManifest()` produces a signed `{ body, signature }` document for operator-side attestation. The registry's `signCall({ toolName, args, nonce?, ttlMs? })` builds + signs an outbound tool-call envelope `{ tool, argsHash, nonce, iat, exp }` (defense against MCP middleman / indirect-prompt-injection synthesizing tool calls); `verifyCall(signed, { args?, seen?, nowMs? })` runs the inverse on inbound — refuses signature mismatch (`mcp/call-verify-failed`), expired envelopes (`mcp/call-expired`), replayed nonces via operator-supplied `seen(nonce)` callback (`mcp/call-replay`), unregistered tools (`mcp/call-unregistered-tool`), and args-hash mismatch when raw args supplied (`mcp/call-args-mismatch`). Default algorithm ML-DSA-87 per the framework's PQC-first rule; Ed25519 / ECDSA / SLH-DSA also available. **A2A v1 task-exchange surface** lands as `b.a2a.tasks.{send, get, cancel}` (client-side JSON-RPC dispatchers — `send` posts `tasks/send` to the peer URL with task validation + https-only refusal; `get` polls `tasks/get`; `cancel` requests `tasks/cancel`) plus `b.a2a.middleware.tasks({ scopes, handler, maxBytes? })` (server-side connect-style middleware — parses inbound JSON-RPC 2.0, enforces method allowlist `[tasks/send, tasks/get, tasks/cancel]` with -32601 method-not-found, enforces per-skill scopes via `req.a2aScopes` with -32001 scope-denied, dispatches to operator handler, maps errors to JSON-RPC -32603, refuses non-POST with 405 + non-JSON content-type with 415) plus `b.a2a.middleware.agentCard({ card, maxAgeSec? })` (serves operator's signed Agent Card at `/.well-known/agent.json` per A2A v1 discovery — 405 on non-GET, Cache-Control max-age operator-tunable).
|
package/index.js
CHANGED
|
@@ -146,6 +146,7 @@ var dataAct = require("./lib/data-act");
|
|
|
146
146
|
var problemDetails = require("./lib/problem-details");
|
|
147
147
|
var cacheStatus = require("./lib/cache-status");
|
|
148
148
|
var serverTiming = require("./lib/server-timing");
|
|
149
|
+
var earlyHints = require("./lib/early-hints");
|
|
149
150
|
var gateContract = require("./lib/gate-contract");
|
|
150
151
|
var guardCsv = require("./lib/guard-csv");
|
|
151
152
|
var guardHtml = require("./lib/guard-html");
|
|
@@ -377,6 +378,7 @@ module.exports = {
|
|
|
377
378
|
problemDetails: problemDetails,
|
|
378
379
|
cacheStatus: cacheStatus,
|
|
379
380
|
serverTiming: serverTiming,
|
|
381
|
+
earlyHints: earlyHints,
|
|
380
382
|
gateContract: gateContract,
|
|
381
383
|
guardCsv: guardCsv,
|
|
382
384
|
guardHtml: guardHtml,
|
package/lib/auth/fal.js
CHANGED
|
@@ -81,13 +81,23 @@ function isValidBand(band) {
|
|
|
81
81
|
*
|
|
82
82
|
* Predicate returning `true` when `actualBand` satisfies the
|
|
83
83
|
* `requiredBand` floor (FAL3 ≥ FAL2 ≥ FAL1). Invalid band strings
|
|
84
|
-
* on either argument return `false
|
|
84
|
+
* on either argument return `false` — operators using `meets`
|
|
85
|
+
* directly for authorization decisions never get a "true" verdict
|
|
86
|
+
* out of a malformed input pair.
|
|
85
87
|
*
|
|
86
88
|
* @example
|
|
87
|
-
* b.auth.fal.meets("FAL3", "FAL2");
|
|
88
|
-
* b.auth.fal.meets("FAL1", "FAL2");
|
|
89
|
+
* b.auth.fal.meets("FAL3", "FAL2"); // → true
|
|
90
|
+
* b.auth.fal.meets("FAL1", "FAL2"); // → false
|
|
91
|
+
* b.auth.fal.meets("FAL1", "FALX"); // → false (invalid required band)
|
|
92
|
+
* b.auth.fal.meets("bad", "bad"); // → false (both invalid)
|
|
89
93
|
*/
|
|
90
94
|
function meets(actualBand, requiredBand) {
|
|
95
|
+
// Validate BOTH inputs before comparing ranks. The previous
|
|
96
|
+
// implementation compared raw ranks (`>=`) — unknown bands mapped
|
|
97
|
+
// to rank 0 and `0 >= 0` returned true, contradicting the
|
|
98
|
+
// documented contract and producing false-positive authorization
|
|
99
|
+
// decisions for operators using meets() directly.
|
|
100
|
+
if (!isValidBand(actualBand) || !isValidBand(requiredBand)) return false;
|
|
91
101
|
return _bandRank(actualBand) >= _bandRank(requiredBand);
|
|
92
102
|
}
|
|
93
103
|
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.earlyHints
|
|
4
|
+
* @nav HTTP
|
|
5
|
+
* @title RFC 8297 103 Early Hints
|
|
6
|
+
* @order 320
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* RFC 8297 103 Early Hints — interim informational response the
|
|
10
|
+
* server sends BEFORE the final response, telling the browser
|
|
11
|
+
* which subresources it should start preloading while the server
|
|
12
|
+
* is still composing the final HTML / JSON. Browsers (Chrome 103+,
|
|
13
|
+
* Edge 103+, Firefox 120+) honor `Link: rel=preload` /
|
|
14
|
+
* `rel=preconnect` headers in the 103 to kick off resource fetches
|
|
15
|
+
* in parallel with the main render.
|
|
16
|
+
*
|
|
17
|
+
* Operators reach for early-hints when the server has slow upstream
|
|
18
|
+
* dependencies (DB query, downstream API) but already knows the
|
|
19
|
+
* final response will reference specific CSS / JS / fonts /
|
|
20
|
+
* API origins. The 103 turns a single-RTT-bound page load into a
|
|
21
|
+
* parallel resource-prefetch chain.
|
|
22
|
+
*
|
|
23
|
+
* `b.earlyHints.send(res, { link, ... })` writes the interim 103
|
|
24
|
+
* with the supplied headers. The framework wraps Node's built-in
|
|
25
|
+
* `res.writeEarlyHints()` (Node 18.11+) and adds:
|
|
26
|
+
*
|
|
27
|
+
* - input validation (link entries must be RFC 8288 Link-header
|
|
28
|
+
* form: `<uri>; rel=preload[; as=script][; crossorigin=...]`)
|
|
29
|
+
* - silent no-op when the operator-supplied `res` is not an
|
|
30
|
+
* HTTP/1.1+ socket-backed response (HTTP/1.0 clients don't
|
|
31
|
+
* understand 103; serializing one would corrupt the stream)
|
|
32
|
+
* - validation of cacheable header set per RFC 8297 section 3
|
|
33
|
+
* (only headers that hint about the FINAL response are
|
|
34
|
+
* honored; Set-Cookie / authentication-related headers are
|
|
35
|
+
* refused)
|
|
36
|
+
*
|
|
37
|
+
* The 103 does NOT replace the final response — the operator's
|
|
38
|
+
* handler still writes the regular 200/400/etc. status + body.
|
|
39
|
+
* Multiple 103s before the final response are permitted (Node's
|
|
40
|
+
* writeEarlyHints can be called repeatedly).
|
|
41
|
+
*
|
|
42
|
+
* @card
|
|
43
|
+
* RFC 8297 103 Early Hints helper — operator-friendly wrapper around Node's response writeEarlyHints API for browser-side parallel resource-prefetch hints.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
var validateOpts = require("./validate-opts");
|
|
47
|
+
var { defineClass } = require("./framework-error");
|
|
48
|
+
|
|
49
|
+
var EarlyHintsError = defineClass("EarlyHintsError", { alwaysPermanent: true });
|
|
50
|
+
|
|
51
|
+
// Headers that are SAFE to surface in a 103 are the ones describing
|
|
52
|
+
// the upcoming final response. Refused header names mostly carry
|
|
53
|
+
// per-request state (cookies, auth) that a 103 would prematurely
|
|
54
|
+
// leak or that a 103 cannot honor (Content-Length, Transfer-
|
|
55
|
+
// Encoding, Content-Type all describe THIS interim response, not
|
|
56
|
+
// the final).
|
|
57
|
+
var REFUSED_HEADERS = Object.freeze([
|
|
58
|
+
"set-cookie",
|
|
59
|
+
"authorization",
|
|
60
|
+
"www-authenticate",
|
|
61
|
+
"content-length",
|
|
62
|
+
"content-type",
|
|
63
|
+
"transfer-encoding",
|
|
64
|
+
"connection",
|
|
65
|
+
"upgrade",
|
|
66
|
+
"trailer",
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
var LINK_RELATION_RE = /^(preload|preconnect|prefetch|dns-prefetch|modulepreload|prerender|next|prev)$/i;
|
|
70
|
+
var LINK_MAX_BYTES = 4096; // allow:raw-byte-literal — per-link length cap, not bytes
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @primitive b.earlyHints.send
|
|
74
|
+
* @signature b.earlyHints.send(res, opts)
|
|
75
|
+
* @since 0.8.88
|
|
76
|
+
* @status stable
|
|
77
|
+
*
|
|
78
|
+
* Write an RFC 8297 103 Early Hints interim response to `res`.
|
|
79
|
+
* Returns `true` when the 103 was written, `false` when the
|
|
80
|
+
* underlying response does not support early hints (HTTP/1.0, a
|
|
81
|
+
* non-HTTP-shaped object, or the response writeEarlyHints API is
|
|
82
|
+
* missing).
|
|
83
|
+
*
|
|
84
|
+
* `link` is either a single Link-header value string OR an array
|
|
85
|
+
* of strings. Each must follow the RFC 8288 Link-header grammar
|
|
86
|
+
* with a `rel=` parameter naming one of: `preload`, `preconnect`,
|
|
87
|
+
* `prefetch`, `dns-prefetch`, `modulepreload`, `prerender`, `next`,
|
|
88
|
+
* `prev`. Refused: per-link size > 4 KiB, missing `rel=`, unknown
|
|
89
|
+
* relation. Other operator-supplied header keys must NOT be in
|
|
90
|
+
* `REFUSED_HEADERS` (set-cookie / authorization / content-length
|
|
91
|
+
* / etc.) — those carry per-request state a 103 must not surface.
|
|
92
|
+
*
|
|
93
|
+
* @opts
|
|
94
|
+
* link: string | string[], // RFC 8288 Link-header values (REQUIRED)
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* b.earlyHints.send(res, {
|
|
98
|
+
* link: [
|
|
99
|
+
* "</style.css>; rel=preload; as=style",
|
|
100
|
+
* "</app.js>; rel=preload; as=script",
|
|
101
|
+
* "<https://cdn.example.com>; rel=preconnect",
|
|
102
|
+
* ],
|
|
103
|
+
* });
|
|
104
|
+
* res.statusCode = 200;
|
|
105
|
+
* res.setHeader("Content-Type", "text/html");
|
|
106
|
+
* res.end(html);
|
|
107
|
+
*/
|
|
108
|
+
function send(res, opts) {
|
|
109
|
+
if (!res || typeof res !== "object") {
|
|
110
|
+
throw new EarlyHintsError("early-hints/bad-res",
|
|
111
|
+
"earlyHints.send: res must be an HTTP response object", true);
|
|
112
|
+
}
|
|
113
|
+
if (typeof res.writeEarlyHints !== "function") {
|
|
114
|
+
// Node < 18.11 OR HTTP/1.0 OR mock res — silent no-op so
|
|
115
|
+
// operator code stays the same across deployments.
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (!opts || typeof opts !== "object" || Array.isArray(opts)) {
|
|
119
|
+
throw new EarlyHintsError("early-hints/bad-opts",
|
|
120
|
+
"earlyHints.send: opts required (link + optional header pairs)", true);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
var headers = {};
|
|
124
|
+
|
|
125
|
+
if (opts.link === undefined || opts.link === null) {
|
|
126
|
+
throw new EarlyHintsError("early-hints/no-link",
|
|
127
|
+
"earlyHints.send: opts.link is required (RFC 8297 §2 requires at least one Link header)", true);
|
|
128
|
+
}
|
|
129
|
+
var linkArr = Array.isArray(opts.link) ? opts.link : [opts.link];
|
|
130
|
+
if (linkArr.length === 0) {
|
|
131
|
+
throw new EarlyHintsError("early-hints/no-link",
|
|
132
|
+
"earlyHints.send: opts.link must contain at least one Link-header value", true);
|
|
133
|
+
}
|
|
134
|
+
for (var i = 0; i < linkArr.length; i += 1) {
|
|
135
|
+
_validateLink(linkArr[i], i);
|
|
136
|
+
}
|
|
137
|
+
headers.link = linkArr;
|
|
138
|
+
|
|
139
|
+
var keys = Object.keys(opts);
|
|
140
|
+
for (var k = 0; k < keys.length; k += 1) {
|
|
141
|
+
var name = keys[k];
|
|
142
|
+
if (name === "link") continue;
|
|
143
|
+
var lower = name.toLowerCase();
|
|
144
|
+
if (REFUSED_HEADERS.indexOf(lower) !== -1) {
|
|
145
|
+
throw new EarlyHintsError("early-hints/refused-header",
|
|
146
|
+
"earlyHints.send: header '" + name + "' refused — RFC 8297 §3 prohibits " +
|
|
147
|
+
"per-request state in interim responses (refused set: " + REFUSED_HEADERS.join(", ") + ")");
|
|
148
|
+
}
|
|
149
|
+
if (typeof opts[name] !== "string" && !Array.isArray(opts[name])) {
|
|
150
|
+
throw new EarlyHintsError("early-hints/bad-header-value",
|
|
151
|
+
"earlyHints.send: header '" + name + "' must be a string or string[]", true);
|
|
152
|
+
}
|
|
153
|
+
headers[lower] = opts[name];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
res.writeEarlyHints(headers);
|
|
158
|
+
return true;
|
|
159
|
+
} catch (writeErr) {
|
|
160
|
+
throw new EarlyHintsError("early-hints/write-failed",
|
|
161
|
+
"earlyHints.send: writeEarlyHints failed: " + (writeErr.message || writeErr));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function _validateLink(linkValue, idx) {
|
|
166
|
+
validateOpts.requireNonEmptyString(linkValue, "earlyHints.send.link[" + idx + "]",
|
|
167
|
+
EarlyHintsError, "early-hints/bad-link");
|
|
168
|
+
if (linkValue.length > LINK_MAX_BYTES) {
|
|
169
|
+
throw new EarlyHintsError("early-hints/bad-link",
|
|
170
|
+
"link[" + idx + "] exceeds " + LINK_MAX_BYTES + " bytes");
|
|
171
|
+
}
|
|
172
|
+
var relMatch = /;\s*rel\s*=\s*"?([a-zA-Z0-9-]+)"?/i.exec(linkValue);
|
|
173
|
+
if (!relMatch) {
|
|
174
|
+
throw new EarlyHintsError("early-hints/bad-link",
|
|
175
|
+
"link[" + idx + "] missing rel= parameter (RFC 8288)");
|
|
176
|
+
}
|
|
177
|
+
if (relMatch[1].length > 32 || !LINK_RELATION_RE.test(relMatch[1])) { // allow:raw-byte-literal — rel-token length cap, not bytes
|
|
178
|
+
throw new EarlyHintsError("early-hints/bad-link",
|
|
179
|
+
"link[" + idx + "].rel '" + relMatch[1] + "' must be one of: " +
|
|
180
|
+
"preload, preconnect, prefetch, dns-prefetch, modulepreload, prerender, next, prev");
|
|
181
|
+
}
|
|
182
|
+
if (linkValue.charAt(0) !== "<" || linkValue.indexOf(">") < 1) {
|
|
183
|
+
throw new EarlyHintsError("early-hints/bad-link",
|
|
184
|
+
"link[" + idx + "] must start with angle-bracketed URI per RFC 8288 (e.g. <https://x.com>; rel=preload)");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
send: send,
|
|
190
|
+
REFUSED_HEADERS: REFUSED_HEADERS,
|
|
191
|
+
EarlyHintsError: EarlyHintsError,
|
|
192
|
+
};
|
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:75038048-e27a-4af6-a354-0cabce037597",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-11T18:13:26.544Z",
|
|
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.8.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.8.88",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.88",
|
|
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.8.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.8.88",
|
|
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.8.
|
|
57
|
+
"ref": "@blamejs/core@0.8.88",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|