@daloyjs/core 0.35.2 → 0.36.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/README.md +1 -0
- package/dist/app.d.ts +79 -0
- package/dist/app.js +150 -7
- package/dist/index.d.ts +1 -1
- package/dist/safe-redirect.d.ts +2 -2
- package/dist/safe-redirect.js +2 -2
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -466,6 +466,7 @@ The framework refuses to start (or to construct) when configuration is unsafe:
|
|
|
466
466
|
|
|
467
467
|
- Weak session secrets, `cors({ origin: "*" })` with credentials, `session()` + state-changing route without `csrf()`, and unconfigured `X-Forwarded-*` in production.
|
|
468
468
|
- `secureDefaults: false` in production unless `acknowledgeInsecureDefaults: true` is set, plus a once-per-process `error` log naming every disabled default.
|
|
469
|
+
- `preset: "internal-service"` topology preset for service-to-service deployments behind a mesh / sidecar / private network: turns OFF the browser-only guards (auto `secureHeaders`, `corsCrossOriginGuard`, `csrf` boot guard, unconfigured `X-Forwarded-*` guard) while keeping every input, parser, credential, SSRF, weak-secret, and refuse-to-boot guard ON. Per-knob options still win, the choice is logged at boot under `event: "security.preset.applied"`, and the live posture is auditable via `app.getSecurityPosture()`.
|
|
469
470
|
- `createJwtSigner()` / `createJwtVerifier()` refuse `alg: "none"`, accept only an explicit allowlist, refuse HS + JWK combinations, refuse to sign without `exp`, and refuse HS-shaped secrets under 32 bytes (RFC 7518 §3.2).
|
|
470
471
|
- `secureHeaders()` refuses to construct with `frameOptions: false` AND no CSP `frame-ancestors` directive (no clickjacking defense).
|
|
471
472
|
- `cors()` refuses `methods: ['*']` at construction; default `allowMethods` narrowed to `[GET, HEAD, POST]` so `PUT` / `PATCH` / `DELETE` become explicit opt-ins.
|
package/dist/app.d.ts
CHANGED
|
@@ -10,6 +10,33 @@ import { type BehindProxyConfig } from "./conn-info.js";
|
|
|
10
10
|
export declare function _resetCrashHandlersForTests(): void;
|
|
11
11
|
/** @internal Test-only helper to reset the latch between tests. */
|
|
12
12
|
export declare function _resetInsecureDefaultsLogForTests(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Named security posture preset. Currently only one value is supported:
|
|
15
|
+
*
|
|
16
|
+
* - `"internal-service"` — relaxes the *topology-dependent* defaults that
|
|
17
|
+
* only make sense when an HTTP boundary faces a browser or the public
|
|
18
|
+
* internet (auto `secureHeaders`, cross-origin write guard, the
|
|
19
|
+
* session+state-changing-route CSRF boot guard, and the unconfigured
|
|
20
|
+
* `X-Forwarded-*` guard). Everything that protects the service from
|
|
21
|
+
* malformed input, confused dependencies, or compromised callers —
|
|
22
|
+
* body limits, request timeouts, JWT algorithm allowlists, weak-secret
|
|
23
|
+
* refuse-to-boot, `cors({ origin: '*' })` refuse-to-boot, anonymous
|
|
24
|
+
* stateful plugin refuse-to-boot, `crashOnUnhandledRejection`, schema
|
|
25
|
+
* strictness, prototype-pollution-safe parsers, SSRF-safe `fetchGuard`
|
|
26
|
+
* defaults, RFC 9457 problem+json redaction — stays on. Per-knob
|
|
27
|
+
* options still win (`secureHeaders: { ... }` re-enables it on top of
|
|
28
|
+
* the preset). The preset choice is logged once at boot under the
|
|
29
|
+
* `security.preset.applied` event so operators can audit the posture
|
|
30
|
+
* without reading code.
|
|
31
|
+
*
|
|
32
|
+
* Topology presets are intentionally a small, curated set — they are NOT
|
|
33
|
+
* a master "disable everything" knob. If you really need to disable the
|
|
34
|
+
* entire secure-by-default surface, use the explicit
|
|
35
|
+
* {@link AppOptions.secureDefaults} `false` escape hatch.
|
|
36
|
+
*
|
|
37
|
+
* @since 0.34.0
|
|
38
|
+
*/
|
|
39
|
+
export type SecurityPreset = "internal-service";
|
|
13
40
|
/**
|
|
14
41
|
* Configuration accepted by {@link App}'s constructor. Every field is
|
|
15
42
|
* optional; sensible production defaults are applied.
|
|
@@ -21,6 +48,23 @@ export interface AppOptions {
|
|
|
21
48
|
title?: string;
|
|
22
49
|
version?: string;
|
|
23
50
|
description?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Topology-aware security posture preset. See {@link SecurityPreset}.
|
|
53
|
+
*
|
|
54
|
+
* - `"internal-service"` — for service-to-service deployments behind a
|
|
55
|
+
* service mesh, sidecar, or private network. Turns off the
|
|
56
|
+
* browser-/edge-only guards (auto `secureHeaders`, cross-origin write
|
|
57
|
+
* guard, session+state-changing CSRF boot guard, unconfigured
|
|
58
|
+
* `X-Forwarded-*` guard) while keeping every input-, parser-,
|
|
59
|
+
* credential-, and SSRF-level guard on. The choice is logged once at
|
|
60
|
+
* boot under the `security.preset.applied` event. Per-knob options
|
|
61
|
+
* you pass alongside the preset still win.
|
|
62
|
+
*
|
|
63
|
+
* Omit (default) for browser-facing / public APIs.
|
|
64
|
+
*
|
|
65
|
+
* @since 0.34.0
|
|
66
|
+
*/
|
|
67
|
+
preset?: SecurityPreset;
|
|
24
68
|
/** Validate handler responses against declared response schemas. Default: true. */
|
|
25
69
|
validateResponses?: boolean;
|
|
26
70
|
/** Hard cap on request body size in bytes. Default: 1 MiB. */
|
|
@@ -642,6 +686,41 @@ export declare class App {
|
|
|
642
686
|
* set deep in shared configuration.
|
|
643
687
|
*/
|
|
644
688
|
private assertInsecureDefaultsAcknowledged;
|
|
689
|
+
/**
|
|
690
|
+
* Emit the one-time boot audit entry for an applied security preset.
|
|
691
|
+
* Called from the constructor with the *original* (pre-preset) options
|
|
692
|
+
* so the log captures which fields the preset filled in vs. which the
|
|
693
|
+
* caller set explicitly. Logged at `info` so the line shows up in
|
|
694
|
+
* standard production log shipping without being noisy.
|
|
695
|
+
*
|
|
696
|
+
* Operators can audit the live posture at any time through
|
|
697
|
+
* {@link App.getSecurityPosture}.
|
|
698
|
+
*
|
|
699
|
+
* @since 0.34.0
|
|
700
|
+
*/
|
|
701
|
+
private logSecurityPresetIfApplied;
|
|
702
|
+
/**
|
|
703
|
+
* Structured snapshot of the live security posture. Returns the same
|
|
704
|
+
* data the constructor logs under the `security.preset.applied` audit
|
|
705
|
+
* event plus the resolved values of every secure-by-default knob, so
|
|
706
|
+
* operators can build a `/__security` introspection route or a CI
|
|
707
|
+
* audit without parsing the framework source.
|
|
708
|
+
*
|
|
709
|
+
* @since 0.34.0
|
|
710
|
+
*/
|
|
711
|
+
getSecurityPosture(): {
|
|
712
|
+
preset: SecurityPreset | undefined;
|
|
713
|
+
secureDefaults: boolean;
|
|
714
|
+
secureHeaders: boolean;
|
|
715
|
+
corsCrossOriginGuard: boolean;
|
|
716
|
+
csrf: "off" | "on";
|
|
717
|
+
crashOnUnhandledRejection: boolean | "default";
|
|
718
|
+
trustProxy: true | false | "unconfigured";
|
|
719
|
+
bodyLimitBytes: number;
|
|
720
|
+
requestTimeoutMs: number;
|
|
721
|
+
stripServerHeaders: boolean;
|
|
722
|
+
production: boolean;
|
|
723
|
+
};
|
|
645
724
|
/**
|
|
646
725
|
* Install the secure-by-default global hooks. Currently:
|
|
647
726
|
* - {@link secureHeaders} as a group-level hook so every response carries
|
package/dist/app.js
CHANGED
|
@@ -40,6 +40,36 @@ let insecureDefaultsLoggedThisProcess = false;
|
|
|
40
40
|
export function _resetInsecureDefaultsLogForTests() {
|
|
41
41
|
insecureDefaultsLoggedThisProcess = false;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The exact set of fields the `"internal-service"` preset flips off when
|
|
45
|
+
* the caller has not set them explicitly. Surfaced through the boot
|
|
46
|
+
* audit log entry so operators can see which guards the preset turned
|
|
47
|
+
* off without re-reading the framework source.
|
|
48
|
+
*/
|
|
49
|
+
const INTERNAL_SERVICE_PRESET_DISABLED = Object.freeze([
|
|
50
|
+
"secureHeaders auto-install",
|
|
51
|
+
"corsCrossOriginGuard (state-changing cross-origin write rejection)",
|
|
52
|
+
"csrf boot guard (session() + state-changing route)",
|
|
53
|
+
"unconfigured X-Forwarded-* / trustProxy guard",
|
|
54
|
+
]);
|
|
55
|
+
/**
|
|
56
|
+
* Defaults that the `"internal-service"` preset keeps on. Logged at boot
|
|
57
|
+
* alongside the disabled list so the audit entry shows the full posture.
|
|
58
|
+
*/
|
|
59
|
+
const INTERNAL_SERVICE_PRESET_KEPT = Object.freeze([
|
|
60
|
+
"bodyLimitBytes (1 MiB default)",
|
|
61
|
+
"requestTimeoutMs (30 s default)",
|
|
62
|
+
"crashOnUnhandledRejection (production)",
|
|
63
|
+
"weak session secret refuse-to-boot",
|
|
64
|
+
"cors({ origin: '*' }) refuse-to-boot",
|
|
65
|
+
"anonymous stateful plugin refuse-to-boot",
|
|
66
|
+
"stripServerHeaders",
|
|
67
|
+
"RFC 9457 problem+json prod redaction",
|
|
68
|
+
"JWT algorithm allowlist + timingSafeEqual credential comparison",
|
|
69
|
+
"prototype-pollution-safe parsers + isForbiddenObjectKey",
|
|
70
|
+
"fetchGuard() SSRF defaults",
|
|
71
|
+
"schema .strict() + response validation when enabled",
|
|
72
|
+
]);
|
|
43
73
|
/**
|
|
44
74
|
* List of secure-by-default surfaces disabled when `secureDefaults: false`
|
|
45
75
|
* is set. Surfaced through the once-per-process `error` log so the operator
|
|
@@ -74,6 +104,46 @@ const CANONICAL_HTTP_METHODS = new Set([
|
|
|
74
104
|
"HEAD",
|
|
75
105
|
"OPTIONS",
|
|
76
106
|
]);
|
|
107
|
+
/**
|
|
108
|
+
* Apply a topology-aware security preset on top of caller-supplied
|
|
109
|
+
* options. Returns a new options object where preset defaults fill in
|
|
110
|
+
* any field the caller left `undefined`; explicit caller values always
|
|
111
|
+
* win. Pure / no side effects — the boot audit log is emitted
|
|
112
|
+
* separately by {@link App.logSecurityPresetIfApplied} so this helper is
|
|
113
|
+
* safe to call from `new App({ preset: ... })` in test setups.
|
|
114
|
+
*
|
|
115
|
+
* The `"internal-service"` preset turns off:
|
|
116
|
+
* - `secureHeaders` auto-install (browser-only headers)
|
|
117
|
+
* - `corsCrossOriginGuard` (no browser Origin to guard against)
|
|
118
|
+
* - `csrf` (set to `"off"` — service-to-service callers aren't browsers)
|
|
119
|
+
* - `trustProxy` (set to `false` — explicitly ignore `X-Forwarded-*`
|
|
120
|
+
* and silence the unconfigured-proxy 500 guard; the immediate peer
|
|
121
|
+
* inside the mesh *is* the caller)
|
|
122
|
+
*
|
|
123
|
+
* Everything else (body limits, request timeouts, JWT allowlist,
|
|
124
|
+
* `crashOnUnhandledRejection`, weak-secret refuse-to-boot, cors-wildcard
|
|
125
|
+
* refuse-to-boot, anonymous stateful plugin refuse-to-boot,
|
|
126
|
+
* `stripServerHeaders`, RFC 9457 prod redaction, schema strictness,
|
|
127
|
+
* `fetchGuard`, parser safety) stays at its standard secure-by-default
|
|
128
|
+
* value.
|
|
129
|
+
*
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
function applySecurityPreset(options) {
|
|
133
|
+
if (options.preset !== "internal-service")
|
|
134
|
+
return options;
|
|
135
|
+
const out = { ...options };
|
|
136
|
+
if (out.secureHeaders === undefined)
|
|
137
|
+
out.secureHeaders = false;
|
|
138
|
+
if (out.corsCrossOriginGuard === undefined)
|
|
139
|
+
out.corsCrossOriginGuard = false;
|
|
140
|
+
if (out.csrf === undefined)
|
|
141
|
+
out.csrf = "off";
|
|
142
|
+
if (out.trustProxy === undefined && out.behindProxy === undefined) {
|
|
143
|
+
out.trustProxy = false;
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
77
147
|
const DEFAULTS = {
|
|
78
148
|
bodyLimitBytes: 1024 * 1024,
|
|
79
149
|
requestTimeoutMs: 30_000,
|
|
@@ -245,11 +315,12 @@ export class App {
|
|
|
245
315
|
return this._globalCorsAllowsCache;
|
|
246
316
|
}
|
|
247
317
|
constructor(options = {}) {
|
|
318
|
+
const resolved = applySecurityPreset(options);
|
|
248
319
|
this.options = {
|
|
249
|
-
validateResponses:
|
|
250
|
-
bodyLimitBytes:
|
|
251
|
-
requestTimeoutMs:
|
|
252
|
-
...
|
|
320
|
+
validateResponses: resolved.validateResponses ?? DEFAULTS.validateResponses,
|
|
321
|
+
bodyLimitBytes: resolved.bodyLimitBytes ?? DEFAULTS.bodyLimitBytes,
|
|
322
|
+
requestTimeoutMs: resolved.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs,
|
|
323
|
+
...resolved,
|
|
253
324
|
};
|
|
254
325
|
this.log =
|
|
255
326
|
options.logger === false
|
|
@@ -264,6 +335,7 @@ export class App {
|
|
|
264
335
|
if (this.options.hooks)
|
|
265
336
|
this.assertSecureHookConfig(this.options.hooks);
|
|
266
337
|
this.assertInsecureDefaultsAcknowledged();
|
|
338
|
+
this.logSecurityPresetIfApplied(options);
|
|
267
339
|
this.installSecureDefaults();
|
|
268
340
|
this.maybeInstallCrashHandlers();
|
|
269
341
|
this.maybeMountDocs();
|
|
@@ -318,6 +390,68 @@ export class App {
|
|
|
318
390
|
}, `app({ secureDefaults: false }) disables: ${DISABLED_BY_INSECURE_DEFAULTS.join(", ")}.`);
|
|
319
391
|
}
|
|
320
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Emit the one-time boot audit entry for an applied security preset.
|
|
395
|
+
* Called from the constructor with the *original* (pre-preset) options
|
|
396
|
+
* so the log captures which fields the preset filled in vs. which the
|
|
397
|
+
* caller set explicitly. Logged at `info` so the line shows up in
|
|
398
|
+
* standard production log shipping without being noisy.
|
|
399
|
+
*
|
|
400
|
+
* Operators can audit the live posture at any time through
|
|
401
|
+
* {@link App.getSecurityPosture}.
|
|
402
|
+
*
|
|
403
|
+
* @since 0.34.0
|
|
404
|
+
*/
|
|
405
|
+
logSecurityPresetIfApplied(originalOptions) {
|
|
406
|
+
if (originalOptions.preset !== "internal-service")
|
|
407
|
+
return;
|
|
408
|
+
const userOverrode = [];
|
|
409
|
+
if (originalOptions.secureHeaders !== undefined)
|
|
410
|
+
userOverrode.push("secureHeaders");
|
|
411
|
+
if (originalOptions.corsCrossOriginGuard !== undefined) {
|
|
412
|
+
userOverrode.push("corsCrossOriginGuard");
|
|
413
|
+
}
|
|
414
|
+
if (originalOptions.csrf !== undefined)
|
|
415
|
+
userOverrode.push("csrf");
|
|
416
|
+
if (originalOptions.trustProxy !== undefined)
|
|
417
|
+
userOverrode.push("trustProxy");
|
|
418
|
+
if (originalOptions.behindProxy !== undefined)
|
|
419
|
+
userOverrode.push("behindProxy");
|
|
420
|
+
this.log.info({
|
|
421
|
+
event: "security.preset.applied",
|
|
422
|
+
preset: "internal-service",
|
|
423
|
+
disabled: INTERNAL_SERVICE_PRESET_DISABLED,
|
|
424
|
+
kept: INTERNAL_SERVICE_PRESET_KEPT,
|
|
425
|
+
userOverrode,
|
|
426
|
+
}, `Applied security preset "internal-service": disabled ${INTERNAL_SERVICE_PRESET_DISABLED.length} topology-dependent guards; kept ${INTERNAL_SERVICE_PRESET_KEPT.length} input/credential/SSRF guards on. See app.getSecurityPosture() for the live snapshot.`);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Structured snapshot of the live security posture. Returns the same
|
|
430
|
+
* data the constructor logs under the `security.preset.applied` audit
|
|
431
|
+
* event plus the resolved values of every secure-by-default knob, so
|
|
432
|
+
* operators can build a `/__security` introspection route or a CI
|
|
433
|
+
* audit without parsing the framework source.
|
|
434
|
+
*
|
|
435
|
+
* @since 0.34.0
|
|
436
|
+
*/
|
|
437
|
+
getSecurityPosture() {
|
|
438
|
+
const o = this.options;
|
|
439
|
+
return Object.freeze({
|
|
440
|
+
preset: o.preset,
|
|
441
|
+
secureDefaults: o.secureDefaults !== false,
|
|
442
|
+
secureHeaders: o.secureDefaults !== false && o.secureHeaders !== false,
|
|
443
|
+
corsCrossOriginGuard: o.secureDefaults !== false && o.corsCrossOriginGuard !== false,
|
|
444
|
+
csrf: o.csrf === "off" ? "off" : "on",
|
|
445
|
+
crashOnUnhandledRejection: o.crashOnUnhandledRejection === undefined
|
|
446
|
+
? "default"
|
|
447
|
+
: o.crashOnUnhandledRejection,
|
|
448
|
+
trustProxy: o.trustProxy === undefined ? "unconfigured" : o.trustProxy,
|
|
449
|
+
bodyLimitBytes: this.options.bodyLimitBytes,
|
|
450
|
+
requestTimeoutMs: this.options.requestTimeoutMs,
|
|
451
|
+
stripServerHeaders: o.stripServerHeaders !== false,
|
|
452
|
+
production: this.isProduction(),
|
|
453
|
+
});
|
|
454
|
+
}
|
|
321
455
|
/**
|
|
322
456
|
* Install the secure-by-default global hooks. Currently:
|
|
323
457
|
* - {@link secureHeaders} as a group-level hook so every response carries
|
|
@@ -1499,7 +1633,7 @@ export class App {
|
|
|
1499
1633
|
});
|
|
1500
1634
|
}
|
|
1501
1635
|
this.inflight++;
|
|
1502
|
-
|
|
1636
|
+
let requestId = randomId();
|
|
1503
1637
|
// Skip the per-request child-logger allocation when the app was
|
|
1504
1638
|
// constructed with `{ logger: false }`. noopLogger.child() returns
|
|
1505
1639
|
// itself, so the binding is wasted work on every request.
|
|
@@ -1662,6 +1796,13 @@ export class App {
|
|
|
1662
1796
|
if (allHooks.beforeHandle !== undefined) {
|
|
1663
1797
|
const beforeResult = allHooks.beforeHandle(ctx);
|
|
1664
1798
|
const before = isPromiseLike(beforeResult) ? await beforeResult : beforeResult;
|
|
1799
|
+
// Honor any request id override applied by middleware (e.g. the
|
|
1800
|
+
// `requestId()` Hooks bundle replaces the framework-generated value
|
|
1801
|
+
// with a trusted incoming header or a user-supplied generator).
|
|
1802
|
+
const overriddenId = state.requestId;
|
|
1803
|
+
if (typeof overriddenId === "string" && overriddenId.length > 0) {
|
|
1804
|
+
requestId = overriddenId;
|
|
1805
|
+
}
|
|
1665
1806
|
if (before instanceof Response) {
|
|
1666
1807
|
copyContextHeaders(ctx, before);
|
|
1667
1808
|
if (!before.headers.has("x-request-id"))
|
|
@@ -1686,8 +1827,10 @@ export class App {
|
|
|
1686
1827
|
const serializeResultRes = serializeResult(result, def, this.options.validateResponses ?? true);
|
|
1687
1828
|
let response = isPromiseLike(serializeResultRes) ? await serializeResultRes : serializeResultRes;
|
|
1688
1829
|
copyContextHeaders(ctx, response);
|
|
1689
|
-
|
|
1690
|
-
|
|
1830
|
+
// `serializeResult` always builds a fresh Response with no request id —
|
|
1831
|
+
// skip the `has()` probe and set directly. Saves one undici contains()
|
|
1832
|
+
// call per request on the hot path.
|
|
1833
|
+
response.headers.set("x-request-id", requestId);
|
|
1691
1834
|
let finalized;
|
|
1692
1835
|
if (hasFinalizeHook) {
|
|
1693
1836
|
const fin = finalizeResponse(response, ctx, allHooks, stripFingerprint);
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { createApp } from "./app.js";
|
|
|
3
3
|
export { _resetPackageJsonCacheForTests } from "./app.js";
|
|
4
4
|
export { _resetCrashHandlersForTests } from "./app.js";
|
|
5
5
|
export { _resetInsecureDefaultsLogForTests } from "./app.js";
|
|
6
|
-
export type { AppOptions, AppOpenAPIOptions, DocsRouteOptions, HealthRouteOptions, CspReportRouteOptions, IntrospectedRoute, PluginInstalledEvent, PluginExtension, ShutdownEvent, } from "./app.js";
|
|
6
|
+
export type { AppOptions, AppOpenAPIOptions, DocsRouteOptions, HealthRouteOptions, CspReportRouteOptions, IntrospectedRoute, PluginInstalledEvent, PluginExtension, ShutdownEvent, SecurityPreset, } from "./app.js";
|
|
7
7
|
export { getConnInfo, setConnInfo, assertBehindProxy, resolveClientIp, readRemoteAddress, readRemotePort, pickForwardedForByHops, } from "./conn-info.js";
|
|
8
8
|
export type { BehindProxyConfig, ConnInfo } from "./conn-info.js";
|
|
9
9
|
export { subdomains, PSL_SNAPSHOT_DATE, PSL_PUBLIC_SUFFIXES, MAX_SNAPSHOT_AGE_DAYS, } from "./subdomains.js";
|
package/dist/safe-redirect.d.ts
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* });
|
|
39
39
|
* ```
|
|
40
40
|
*
|
|
41
|
-
* @since 0.
|
|
41
|
+
* @since 0.36.0
|
|
42
42
|
*/
|
|
43
43
|
/** Reason an open-redirect candidate was refused. */
|
|
44
44
|
export type SafeRedirectBlockReason = "empty-target" | "invalid-control-characters" | "protocol-relative" | "backslash-path" | "path-not-allowed" | "origin-not-allowed" | "scheme-not-allowed" | "parse-failed";
|
|
@@ -86,6 +86,6 @@ export interface SafeRedirectOptions {
|
|
|
86
86
|
* @param target - User-supplied URL candidate (path or absolute URL).
|
|
87
87
|
* @param options - Allowlist + response configuration.
|
|
88
88
|
*
|
|
89
|
-
* @since 0.
|
|
89
|
+
* @since 0.36.0
|
|
90
90
|
*/
|
|
91
91
|
export declare function safeRedirect(target: string, options?: SafeRedirectOptions): Response;
|
package/dist/safe-redirect.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* });
|
|
39
39
|
* ```
|
|
40
40
|
*
|
|
41
|
-
* @since 0.
|
|
41
|
+
* @since 0.36.0
|
|
42
42
|
*/
|
|
43
43
|
/** Thrown when {@link safeRedirect} refuses a candidate URL and no `fallback` is configured. */
|
|
44
44
|
export class OpenRedirectBlockedError extends Error {
|
|
@@ -126,7 +126,7 @@ function classify(target, allowedPaths, allowedOrigins) {
|
|
|
126
126
|
* @param target - User-supplied URL candidate (path or absolute URL).
|
|
127
127
|
* @param options - Allowlist + response configuration.
|
|
128
128
|
*
|
|
129
|
-
* @since 0.
|
|
129
|
+
* @since 0.36.0
|
|
130
130
|
*/
|
|
131
131
|
export function safeRedirect(target, options = {}) {
|
|
132
132
|
const allowedPaths = options.allowedPaths ?? [];
|
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:7079f846-98da-5195-89ad-4f54c8938289",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-05-
|
|
7
|
+
"timestamp": "2026-05-28T20:50:52.884Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.36.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.36.0",
|
|
23
23
|
"name": "@daloyjs/core",
|
|
24
|
-
"version": "0.
|
|
24
|
+
"version": "0.36.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.36.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.36.0",
|
|
50
50
|
"name": "@daloyjs/core",
|
|
51
|
-
"version": "0.
|
|
51
|
+
"version": "0.36.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.36.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.36.0",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-0.36.0-7079f846-98da-5195-89ad-4f54c8938289",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-05-
|
|
8
|
+
"created": "2026-05-28T20:50:52.884Z",
|
|
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.36.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.36.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.36.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": {
|