@daloyjs/core 0.43.0 → 0.44.0
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/dist/adapters/node.d.ts +11 -1
- package/dist/adapters/node.js +19 -3
- package/dist/fetch-guard.d.ts +47 -9
- package/dist/fetch-guard.js +114 -12
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/package.json +3 -2
package/dist/adapters/node.d.ts
CHANGED
|
@@ -8,7 +8,17 @@ import type { App } from "../app.js";
|
|
|
8
8
|
export interface NodeServerOptions {
|
|
9
9
|
port?: number;
|
|
10
10
|
hostname?: string;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Connection-level timeout in ms, applied to BOTH `headersTimeout` and
|
|
13
|
+
* `requestTimeout`. A client that has not finished sending its request
|
|
14
|
+
* headers (or whole request) within this window is hung up on with `408`.
|
|
15
|
+
* This is the slowloris / slow-header defense: the adapter also lowers
|
|
16
|
+
* Node's `connectionsCheckingInterval` to a fraction of this value so the
|
|
17
|
+
* timeout is actually *enforced* near when it elapses, rather than only at
|
|
18
|
+
* Node's default 30s sweep (which would let a stalled or byte-trickling
|
|
19
|
+
* connection hold a socket open far past the configured timeout). Set `0`
|
|
20
|
+
* to disable timeouts entirely. Default: 30000.
|
|
21
|
+
*/
|
|
12
22
|
connectionTimeoutMs?: number;
|
|
13
23
|
/** Drain timeout for graceful shutdown. Default: 10000. */
|
|
14
24
|
shutdownTimeoutMs?: number;
|
package/dist/adapters/node.js
CHANGED
|
@@ -13,7 +13,23 @@ export function serve(app, opts = {}) {
|
|
|
13
13
|
const bufferedBodyMaxBytes = typeof opts.bufferedBodyMaxBytes === "number" && opts.bufferedBodyMaxBytes >= 0
|
|
14
14
|
? opts.bufferedBodyMaxBytes
|
|
15
15
|
: DEFAULT_BUFFERED_BODY_MAX_BYTES;
|
|
16
|
-
const
|
|
16
|
+
const connectionTimeoutMs = opts.connectionTimeoutMs ?? 30_000;
|
|
17
|
+
// Node only *enforces* `headersTimeout` / `requestTimeout` when its periodic
|
|
18
|
+
// connection checker runs, and that checker's default interval is 30s. With
|
|
19
|
+
// the default interval a slowloris that never completes its headers — idle
|
|
20
|
+
// OR trickling a byte at a time — survives until the next 30s sweep, far
|
|
21
|
+
// past the configured timeout, holding a socket open the whole time. Polling
|
|
22
|
+
// at a fraction of the timeout (bounded to [1s, 5s]) makes the configured
|
|
23
|
+
// timeout actually honored, so a stalled connection is reaped with `408`
|
|
24
|
+
// close to when it should be. Skipped only when timeouts are disabled
|
|
25
|
+
// (`connectionTimeoutMs: 0`), where there is nothing to reap.
|
|
26
|
+
const connectionsCheckingInterval = connectionTimeoutMs > 0
|
|
27
|
+
? Math.max(1_000, Math.min(5_000, Math.floor(connectionTimeoutMs / 2)))
|
|
28
|
+
: undefined;
|
|
29
|
+
const server = createServer({
|
|
30
|
+
maxHeaderSize: opts.maxHeaderBytes ?? 16 * 1024,
|
|
31
|
+
...(connectionsCheckingInterval !== undefined ? { connectionsCheckingInterval } : {}),
|
|
32
|
+
}, (req, res) => {
|
|
17
33
|
// GET/HEAD: no body work, dispatch directly. Keep this first so the GET
|
|
18
34
|
// hot path doesn't pay for any of the buffering bookkeeping below.
|
|
19
35
|
const method = req.method;
|
|
@@ -33,8 +49,8 @@ export function serve(app, opts = {}) {
|
|
|
33
49
|
}
|
|
34
50
|
dispatchToApp(app, req, res, trustProxy, undefined);
|
|
35
51
|
});
|
|
36
|
-
server.requestTimeout =
|
|
37
|
-
server.headersTimeout =
|
|
52
|
+
server.requestTimeout = connectionTimeoutMs;
|
|
53
|
+
server.headersTimeout = connectionTimeoutMs;
|
|
38
54
|
server.keepAliveTimeout = 5_000;
|
|
39
55
|
// Native parser-level header-count cap. Drops header-count floods (the
|
|
40
56
|
// "HTTP/2 Bomb" amplification dimension) before they become a Request.
|
package/dist/fetch-guard.d.ts
CHANGED
|
@@ -52,18 +52,21 @@
|
|
|
52
52
|
* its **own** DNS lookup when it opens the socket. An attacker that
|
|
53
53
|
* controls a TTL=0 record can return a public IP at validation time and
|
|
54
54
|
* a `127.0.0.1` / `169.254.169.254` at connect time, slipping past the
|
|
55
|
-
* library-level check.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
55
|
+
* library-level check. To close the window:
|
|
56
|
+
*
|
|
57
|
+
* 0. **Built-in, `http:` only** (recommended for cloud-metadata defense):
|
|
58
|
+
* set {@link FetchGuardOptions.pinDns} `: true`. On Node, `http:`
|
|
59
|
+
* requests are then dispatched through `node:http` with the socket
|
|
60
|
+
* pinned to the validated IP (and the original `Host` header
|
|
61
|
+
* preserved), so there is no connect-time re-resolution to rebind.
|
|
62
|
+
* `https:` is not pinned by this knob — see its docs — so the items
|
|
63
|
+
* below still matter for TLS upstreams.
|
|
61
64
|
* 1. **Operator-side** (recommended): block egress to RFC1918 /
|
|
62
65
|
* loopback / link-local at the VPC / firewall layer. This neutralises
|
|
63
66
|
* the rebinding even if the application is naïve.
|
|
64
|
-
* 2. **Caller-side, Node only
|
|
65
|
-
* `fetch` that wires a dispatcher with a pinned-IP
|
|
66
|
-
* e.g.
|
|
67
|
+
* 2. **Caller-side, Node only** (covers `https:` too): install `undici`
|
|
68
|
+
* and pass a custom `fetch` that wires a dispatcher with a pinned-IP
|
|
69
|
+
* `connect.lookup`, e.g.
|
|
67
70
|
* ```ts
|
|
68
71
|
* import { Agent, fetch as undiciFetch } from "undici";
|
|
69
72
|
* import * as dns from "node:dns/promises";
|
|
@@ -177,6 +180,41 @@ export interface FetchGuardOptions {
|
|
|
177
180
|
* without `--allow-net`) or to enforce an in-memory test fixture.
|
|
178
181
|
*/
|
|
179
182
|
resolve?: (hostname: string) => Promise<readonly string[]>;
|
|
183
|
+
/**
|
|
184
|
+
* Close the residual DNS-rebinding (TOCTOU) window for **`http:`** requests
|
|
185
|
+
* by connecting the socket to the exact IP that was validated, instead of
|
|
186
|
+
* letting the underlying client re-resolve the hostname at connect time.
|
|
187
|
+
*
|
|
188
|
+
* When `true` (default `false`), a request to a hostname that resolves to a
|
|
189
|
+
* validated address is dispatched through Node's built-in `node:http` with
|
|
190
|
+
* the connection pinned to that address and the original `Host` header
|
|
191
|
+
* preserved — so virtual-host routing still works while an attacker's
|
|
192
|
+
* TTL=0 rebinding to `127.0.0.1` / `169.254.169.254` can no longer take
|
|
193
|
+
* effect between validation and connect.
|
|
194
|
+
*
|
|
195
|
+
* **Scope and caveats** (read before enabling):
|
|
196
|
+
*
|
|
197
|
+
* - **`http:` only.** `https:` is intentionally NOT pinned here: pinning a
|
|
198
|
+
* TLS connection to an IP while keeping hostname-based SNI / certificate
|
|
199
|
+
* validation needs more machinery than this knob provides, so `https:`
|
|
200
|
+
* requests fall through to the normal validated-then-fetch path and retain
|
|
201
|
+
* the documented TOCTOU caveat. The prime rebinding target — cloud
|
|
202
|
+
* metadata at `http://169.254.169.254` — is `http:`, so this still closes
|
|
203
|
+
* the highest-value vector.
|
|
204
|
+
* - **Node only.** It uses `node:http`; on runtimes without it (Workers,
|
|
205
|
+
* some edge sandboxes) an `http:` pinned dispatch throws a clear error so
|
|
206
|
+
* the misconfiguration is loud rather than a silent no-op.
|
|
207
|
+
* - **Bypasses `options.fetch`** for the pinned `http:` path (it must own the
|
|
208
|
+
* socket), and negotiates no response compression (`Accept-Encoding:
|
|
209
|
+
* identity`) so body semantics match a plain `fetch`.
|
|
210
|
+
*
|
|
211
|
+
* Requests to a literal-IP host or an `allowHosts` entry are never pinned
|
|
212
|
+
* (the former already connects to an exact IP; the latter is an explicit
|
|
213
|
+
* operator trust). Default `false`.
|
|
214
|
+
*
|
|
215
|
+
* @since 0.44.0
|
|
216
|
+
*/
|
|
217
|
+
pinDns?: boolean;
|
|
180
218
|
}
|
|
181
219
|
/**
|
|
182
220
|
* Wrap `fetch` with an SSRF-hardened guard. The returned function has
|
package/dist/fetch-guard.js
CHANGED
|
@@ -52,18 +52,21 @@
|
|
|
52
52
|
* its **own** DNS lookup when it opens the socket. An attacker that
|
|
53
53
|
* controls a TTL=0 record can return a public IP at validation time and
|
|
54
54
|
* a `127.0.0.1` / `169.254.169.254` at connect time, slipping past the
|
|
55
|
-
* library-level check.
|
|
56
|
-
* the `fetch()` boundary without injecting a custom HTTP dispatcher
|
|
57
|
-
* (e.g. `undici.Agent({ connect: { lookup } })`) — and Daloy
|
|
58
|
-
* deliberately ships **zero** runtime dependencies, so we do not bundle
|
|
59
|
-
* `undici`. To fully close the window:
|
|
55
|
+
* library-level check. To close the window:
|
|
60
56
|
*
|
|
57
|
+
* 0. **Built-in, `http:` only** (recommended for cloud-metadata defense):
|
|
58
|
+
* set {@link FetchGuardOptions.pinDns} `: true`. On Node, `http:`
|
|
59
|
+
* requests are then dispatched through `node:http` with the socket
|
|
60
|
+
* pinned to the validated IP (and the original `Host` header
|
|
61
|
+
* preserved), so there is no connect-time re-resolution to rebind.
|
|
62
|
+
* `https:` is not pinned by this knob — see its docs — so the items
|
|
63
|
+
* below still matter for TLS upstreams.
|
|
61
64
|
* 1. **Operator-side** (recommended): block egress to RFC1918 /
|
|
62
65
|
* loopback / link-local at the VPC / firewall layer. This neutralises
|
|
63
66
|
* the rebinding even if the application is naïve.
|
|
64
|
-
* 2. **Caller-side, Node only
|
|
65
|
-
* `fetch` that wires a dispatcher with a pinned-IP
|
|
66
|
-
* e.g.
|
|
67
|
+
* 2. **Caller-side, Node only** (covers `https:` too): install `undici`
|
|
68
|
+
* and pass a custom `fetch` that wires a dispatcher with a pinned-IP
|
|
69
|
+
* `connect.lookup`, e.g.
|
|
67
70
|
* ```ts
|
|
68
71
|
* import { Agent, fetch as undiciFetch } from "undici";
|
|
69
72
|
* import * as dns from "node:dns/promises";
|
|
@@ -175,6 +178,7 @@ export function fetchGuard(options = {}) {
|
|
|
175
178
|
throw new Error("fetchGuard(): no global fetch available; pass options.fetch.");
|
|
176
179
|
}
|
|
177
180
|
const resolveFn = options.resolve ?? createDefaultResolver();
|
|
181
|
+
const pinDns = options.pinDns === true;
|
|
178
182
|
for (const c of ALWAYS_DENY)
|
|
179
183
|
hardDenyMatchers.push(compileCidrMatcher(c));
|
|
180
184
|
for (const c of options.denyAddresses ?? [])
|
|
@@ -206,6 +210,17 @@ export function fetchGuard(options = {}) {
|
|
|
206
210
|
return true;
|
|
207
211
|
return !softDenyMatchers.some((m) => matchesMatcher(parsed, m));
|
|
208
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Validate a URL's destination against the deny/allow policy.
|
|
215
|
+
*
|
|
216
|
+
* @returns the single validated IP the connection should be **pinned** to
|
|
217
|
+
* (the first resolved address), or `null` when pinning does not apply —
|
|
218
|
+
* the host is an `allowHosts` entry (explicit operator trust) or already a
|
|
219
|
+
* literal IP (the socket connects to that exact address, so there is no
|
|
220
|
+
* re-resolution window to close).
|
|
221
|
+
* @throws {SsrfBlockedError} when the protocol, host, or any resolved
|
|
222
|
+
* address is not permitted.
|
|
223
|
+
*/
|
|
209
224
|
async function validateUrl(url) {
|
|
210
225
|
const proto = url.protocol.toLowerCase();
|
|
211
226
|
if (!allowProtocols.has(proto)) {
|
|
@@ -217,13 +232,13 @@ export function fetchGuard(options = {}) {
|
|
|
217
232
|
throw new SsrfBlockedError(url.toString(), "invalid-url");
|
|
218
233
|
}
|
|
219
234
|
if (allowHosts.has(hostname.toLowerCase()))
|
|
220
|
-
return;
|
|
235
|
+
return null;
|
|
221
236
|
const literal = parseIp(hostname);
|
|
222
237
|
if (literal) {
|
|
223
238
|
if (!isAddressAllowed(literal)) {
|
|
224
239
|
throw new SsrfBlockedError(url.toString(), "address-not-allowed", hostname);
|
|
225
240
|
}
|
|
226
|
-
return;
|
|
241
|
+
return null;
|
|
227
242
|
}
|
|
228
243
|
let addrs;
|
|
229
244
|
try {
|
|
@@ -244,6 +259,7 @@ export function fetchGuard(options = {}) {
|
|
|
244
259
|
throw new SsrfBlockedError(url.toString(), "address-not-allowed", a);
|
|
245
260
|
}
|
|
246
261
|
}
|
|
262
|
+
return addrs[0];
|
|
247
263
|
}
|
|
248
264
|
const guarded = async (input, init) => {
|
|
249
265
|
let request = new Request(input, init);
|
|
@@ -258,10 +274,17 @@ export function fetchGuard(options = {}) {
|
|
|
258
274
|
throw new SsrfBlockedError(String(input), "invalid-url");
|
|
259
275
|
}
|
|
260
276
|
for (let hop = 0;; hop++) {
|
|
261
|
-
await validateUrl(currentUrl);
|
|
277
|
+
const pinnedIp = await validateUrl(currentUrl);
|
|
262
278
|
const dispatchInit = { redirect: "manual" };
|
|
263
279
|
const dispatchReq = new Request(request, dispatchInit);
|
|
264
|
-
|
|
280
|
+
// When DNS pinning is enabled, dispatch `http:` requests through a
|
|
281
|
+
// socket bound to the exact validated IP so the underlying client cannot
|
|
282
|
+
// re-resolve the hostname to an internal address between validation and
|
|
283
|
+
// connect (DNS-rebinding TOCTOU). `https:` keeps the normal path — see
|
|
284
|
+
// the `pinDns` option docs for why.
|
|
285
|
+
const res = pinDns && pinnedIp !== null && currentUrl.protocol === "http:"
|
|
286
|
+
? await pinnedHttpFetch(dispatchReq, pinnedIp)
|
|
287
|
+
: await baseFetch(dispatchReq);
|
|
265
288
|
if (!isRedirect(res.status))
|
|
266
289
|
return res;
|
|
267
290
|
if (userRedirect === "error") {
|
|
@@ -307,6 +330,85 @@ export function fetchGuard(options = {}) {
|
|
|
307
330
|
function isRedirect(status) {
|
|
308
331
|
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
309
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Dispatch an `http:` request with the TCP connection **pinned** to a single,
|
|
335
|
+
* already-validated IP address — closing the DNS-rebinding (TOCTOU) window that
|
|
336
|
+
* exists when the underlying `fetch` re-resolves the hostname at connect time.
|
|
337
|
+
*
|
|
338
|
+
* Uses Node's built-in `node:http` (no runtime dependency) so the `Host` header
|
|
339
|
+
* can be set to the original authority — preserving virtual-host routing while
|
|
340
|
+
* the socket targets `ip`, never a re-resolved address. The response body is
|
|
341
|
+
* streamed (not buffered) so large downloads keep the same memory profile as a
|
|
342
|
+
* plain `fetch`. `Accept-Encoding: identity` is forced so the returned body is
|
|
343
|
+
* not silently left compressed (Node's `http` does not auto-decode).
|
|
344
|
+
*
|
|
345
|
+
* @param req - The request to dispatch (its URL supplies the path + `Host`).
|
|
346
|
+
* @param ip - The validated address to connect to.
|
|
347
|
+
* @returns A web {@link Response} mirroring the upstream reply.
|
|
348
|
+
* @throws when `node:http` is unavailable (non-Node runtime) or the socket errors.
|
|
349
|
+
*/
|
|
350
|
+
async function pinnedHttpFetch(req, ip) {
|
|
351
|
+
let httpRequest;
|
|
352
|
+
let toWeb;
|
|
353
|
+
try {
|
|
354
|
+
({ request: httpRequest } = await import("node:http"));
|
|
355
|
+
({ toWeb } = (await import("node:stream")).Readable);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
throw new Error("fetchGuard({ pinDns: true }): node:http is unavailable on this runtime; " +
|
|
359
|
+
"DNS pinning for http: requires Node. Disable pinDns or run on Node.");
|
|
360
|
+
}
|
|
361
|
+
const url = new URL(req.url);
|
|
362
|
+
const headers = {};
|
|
363
|
+
req.headers.forEach((value, key) => {
|
|
364
|
+
headers[key] = value;
|
|
365
|
+
});
|
|
366
|
+
// Preserve the original authority so virtual-host routing still works even
|
|
367
|
+
// though the socket connects to a raw IP.
|
|
368
|
+
headers["host"] = url.host;
|
|
369
|
+
// Do not negotiate compression: node:http won't auto-decode it, and a
|
|
370
|
+
// silently-compressed body would break `res.text()` / `res.json()` callers.
|
|
371
|
+
headers["accept-encoding"] = "identity";
|
|
372
|
+
const method = req.method.toUpperCase();
|
|
373
|
+
const bodyBytes = method === "GET" || method === "HEAD" ? undefined : new Uint8Array(await req.arrayBuffer());
|
|
374
|
+
return await new Promise((resolve, reject) => {
|
|
375
|
+
const clientReq = httpRequest({
|
|
376
|
+
host: ip,
|
|
377
|
+
port: url.port ? Number(url.port) : 80,
|
|
378
|
+
method,
|
|
379
|
+
path: `${url.pathname}${url.search}`,
|
|
380
|
+
headers,
|
|
381
|
+
}, (res) => {
|
|
382
|
+
const responseHeaders = new Headers();
|
|
383
|
+
for (const [key, value] of Object.entries(res.headers)) {
|
|
384
|
+
if (Array.isArray(value)) {
|
|
385
|
+
for (const v of value)
|
|
386
|
+
responseHeaders.append(key, v);
|
|
387
|
+
}
|
|
388
|
+
else if (typeof value === "string") {
|
|
389
|
+
responseHeaders.set(key, value);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const status = res.statusCode ?? 502;
|
|
393
|
+
// 204/304 and HEAD responses must carry a null body per the spec.
|
|
394
|
+
const nullBody = status === 204 || status === 304 || method === "HEAD";
|
|
395
|
+
if (nullBody) {
|
|
396
|
+
res.resume(); // drain so the socket can be released
|
|
397
|
+
resolve(new Response(null, { status, statusText: res.statusMessage, headers: responseHeaders }));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
resolve(new Response(toWeb(res), {
|
|
401
|
+
status,
|
|
402
|
+
statusText: res.statusMessage,
|
|
403
|
+
headers: responseHeaders,
|
|
404
|
+
}));
|
|
405
|
+
});
|
|
406
|
+
clientReq.on("error", reject);
|
|
407
|
+
if (bodyBytes && bodyBytes.byteLength > 0)
|
|
408
|
+
clientReq.write(bodyBytes);
|
|
409
|
+
clientReq.end();
|
|
410
|
+
});
|
|
411
|
+
}
|
|
310
412
|
function stripBodyHeaders(headers) {
|
|
311
413
|
const out = new Headers(headers);
|
|
312
414
|
out.delete("content-length");
|
package/dist/sbom.cdx.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.5",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:b8d26872-a6bb-52d0-b454-592bdf7f82cc",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-06-
|
|
7
|
+
"timestamp": "2026-06-20T23:19:34.215Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.44.0"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"authors": [
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
],
|
|
20
20
|
"component": {
|
|
21
21
|
"type": "library",
|
|
22
|
-
"bom-ref": "pkg:npm/@daloyjs/core@0.
|
|
22
|
+
"bom-ref": "pkg:npm/@daloyjs/core@0.44.0",
|
|
23
23
|
"name": "@daloyjs/core",
|
|
24
|
-
"version": "0.
|
|
24
|
+
"version": "0.44.0",
|
|
25
25
|
"description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
|
|
26
|
-
"purl": "pkg:npm/@daloyjs/core@0.
|
|
26
|
+
"purl": "pkg:npm/@daloyjs/core@0.44.0",
|
|
27
27
|
"licenses": [
|
|
28
28
|
{
|
|
29
29
|
"license": {
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
}
|
|
47
47
|
],
|
|
48
48
|
"swid": {
|
|
49
|
-
"tagId": "swidtag--daloyjs-core-0.
|
|
49
|
+
"tagId": "swidtag--daloyjs-core-0.44.0",
|
|
50
50
|
"name": "@daloyjs/core",
|
|
51
|
-
"version": "0.
|
|
51
|
+
"version": "0.44.0",
|
|
52
52
|
"tagVersion": 0,
|
|
53
53
|
"patch": false
|
|
54
54
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"components": [],
|
|
58
58
|
"dependencies": [
|
|
59
59
|
{
|
|
60
|
-
"ref": "pkg:npm/@daloyjs/core@0.
|
|
60
|
+
"ref": "pkg:npm/@daloyjs/core@0.44.0",
|
|
61
61
|
"dependsOn": []
|
|
62
62
|
}
|
|
63
63
|
]
|
package/dist/sbom.spdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"spdxVersion": "SPDX-2.3",
|
|
3
3
|
"dataLicense": "CC0-1.0",
|
|
4
4
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
|
-
"name": "@daloyjs/core-0.
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-0.
|
|
5
|
+
"name": "@daloyjs/core-0.44.0",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-0.44.0-b8d26872-a6bb-52d0-b454-592bdf7f82cc",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-06-
|
|
8
|
+
"created": "2026-06-20T23:19:34.215Z",
|
|
9
9
|
"creators": [
|
|
10
10
|
"Tool: daloy-generate-sbom",
|
|
11
11
|
"Organization: DaloyJS"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"SPDXID": "SPDXRef-Package--daloyjs-core",
|
|
18
18
|
"name": "@daloyjs/core",
|
|
19
|
-
"versionInfo": "0.
|
|
19
|
+
"versionInfo": "0.44.0",
|
|
20
20
|
"downloadLocation": "https://github.com/daloyjs/daloy",
|
|
21
21
|
"filesAnalyzed": false,
|
|
22
22
|
"licenseConcluded": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
{
|
|
28
28
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
29
29
|
"referenceType": "purl",
|
|
30
|
-
"referenceLocator": "pkg:npm/@daloyjs/core@0.
|
|
30
|
+
"referenceLocator": "pkg:npm/@daloyjs/core@0.44.0"
|
|
31
31
|
}
|
|
32
32
|
]
|
|
33
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daloyjs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -241,7 +241,8 @@
|
|
|
241
241
|
"example": "node --import tsx examples/basic.ts",
|
|
242
242
|
"bench": "node --import tsx bench/router.bench.ts",
|
|
243
243
|
"test": "node --import tsx --test tests/**/*.test.ts",
|
|
244
|
-
"test:red-team": "node --import tsx --test tests/red-team-attacks.test.ts tests/red-team-attacks-2.test.ts tests/red-team-attacks-3.test.ts tests/red-team-attacks-4.test.ts tests/red-team-attacks-5.test.ts tests/red-team-attacks-6.test.ts tests/red-team-attacks-7.test.ts",
|
|
244
|
+
"test:red-team": "node --import tsx --test tests/red-team-attacks.test.ts tests/red-team-attacks-2.test.ts tests/red-team-attacks-3.test.ts tests/red-team-attacks-4.test.ts tests/red-team-attacks-5.test.ts tests/red-team-attacks-6.test.ts tests/red-team-attacks-7.test.ts tests/red-team-attacks-8.test.ts tests/red-team-attacks-9.test.ts tests/red-team-attacks-10.test.ts",
|
|
245
|
+
"red-team:live": "node --import tsx red-team-live/run.ts",
|
|
245
246
|
"coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-lines=90 --test-coverage-functions=90 tests/**/*.test.ts",
|
|
246
247
|
"coverage:branches": "tsc -p tsconfig.coverage.json && node --test --experimental-test-coverage --test-coverage-include='dist-coverage/src/**' --test-coverage-branches=92 dist-coverage/tests/**/*.test.js",
|
|
247
248
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.typetest.json && tsc -p tests/tsconfig.json --noEmit",
|