@daloyjs/core 1.0.0 → 1.1.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.
@@ -8,7 +8,11 @@
8
8
  * @since 0.19.0
9
9
  */
10
10
  import { ForbiddenError } from "./errors.js";
11
- import { resolveForwardedClientIp, resolveForwardedTrust } from "./conn-info.js";
11
+ import { resolveForwardedClientIp, resolveForwardedTrust, resolveTrustedProxyMatchers, } from "./conn-info.js";
12
+ // Matcher primitives live in a leaf module so `conn-info` can peer-verify
13
+ // without importing this file (which would form a cycle through trust helpers).
14
+ import { compileCidrMatcher, matchesMatcher, parseIp } from "./ip-match.js";
15
+ export { compileCidrMatcher, matchesMatcher, parseIp } from "./ip-match.js";
12
16
  /**
13
17
  * Block or allow requests by source IP / CIDR range. In direct Web-standard
14
18
  * runtimes, pass `resolveIp` from the adapter-specific connection metadata.
@@ -42,7 +46,9 @@ export function ipRestriction(opts) {
42
46
  const allow = (opts.allow ?? []).map(compileCidrMatcher);
43
47
  const deny = (opts.deny ?? []).map(compileCidrMatcher);
44
48
  const hops = resolveForwardedTrust("ipRestriction()", opts);
45
- const resolveIp = opts.resolveIp ?? (hops !== undefined ? forwardedIpResolver(hops) : noIpResolver);
49
+ const proxyMatchers = resolveTrustedProxyMatchers("ipRestriction()", opts);
50
+ const resolveIp = opts.resolveIp ??
51
+ (hops !== undefined ? forwardedIpResolver(hops, proxyMatchers) : noIpResolver);
46
52
  const message = opts.message ?? "IP address not permitted";
47
53
  return {
48
54
  // `preBody`, not `beforeHandle`: a gate that returns a Response from
@@ -69,163 +75,6 @@ export function ipRestriction(opts) {
69
75
  function noIpResolver(_ctx) {
70
76
  return undefined;
71
77
  }
72
- function forwardedIpResolver(hops) {
73
- return (ctx) => resolveForwardedClientIp(ctx.request, hops);
74
- }
75
- /**
76
- * Test whether a parsed IP falls inside a compiled CIDR matcher, comparing
77
- * only the matcher's prefix bits. IPv4-mapped IPv6 addresses
78
- * (`::ffff:a.b.c.d`) are normalized so they match IPv4 matchers.
79
- *
80
- * @param ip Parsed client address from {@link parseIp}.
81
- * @param m Compiled matcher from {@link compileCidrMatcher}.
82
- * @returns `true` when the address is within the matcher's range.
83
- * @internal
84
- */
85
- export function matchesMatcher(ip, m) {
86
- const candidate = normalizeFamily(ip, m.family);
87
- if (!candidate)
88
- return false;
89
- const expected = m.bytes;
90
- const totalBits = candidate.length * 8;
91
- const prefix = Math.min(m.prefix, totalBits);
92
- const fullBytes = prefix >> 3;
93
- for (let i = 0; i < fullBytes; i++) {
94
- if (candidate[i] !== expected[i])
95
- return false;
96
- }
97
- const remaining = prefix - fullBytes * 8;
98
- if (remaining === 0)
99
- return true;
100
- const mask = 0xff << (8 - remaining);
101
- return ((candidate[fullBytes] ^ expected[fullBytes]) & mask) === 0;
102
- }
103
- /**
104
- * Compile an IP or CIDR pattern (e.g. `"10.0.0.0/8"`, `"::1"`) into an
105
- * {@link IpMatcher}. A bare address gets a full-length prefix (/32 or /128);
106
- * host bits beyond the prefix are masked to zero.
107
- *
108
- * @param input IPv4/IPv6 address, optionally with a `/prefix` suffix.
109
- * @returns The compiled matcher used by {@link matchesMatcher}.
110
- * @throws Error when the address or CIDR prefix is invalid.
111
- * @internal
112
- */
113
- export function compileCidrMatcher(input) {
114
- let addr = input;
115
- let prefixStr;
116
- if (input.includes("/")) {
117
- const slash = input.indexOf("/");
118
- addr = input.slice(0, slash);
119
- prefixStr = input.slice(slash + 1);
120
- }
121
- const parsed = parseIp(addr);
122
- if (!parsed) {
123
- throw new Error(`ipRestriction(): invalid IP address ${JSON.stringify(input)}.`);
124
- }
125
- const totalBits = parsed.family === 4 ? 32 : 128;
126
- let prefix = totalBits;
127
- if (prefixStr !== undefined) {
128
- if (!/^\d+$/.test(prefixStr)) {
129
- throw new Error(`ipRestriction(): invalid CIDR prefix in ${JSON.stringify(input)}.`);
130
- }
131
- prefix = Number.parseInt(prefixStr, 10);
132
- if (!Number.isInteger(prefix) || prefix < 0 || prefix > totalBits) {
133
- throw new Error(`ipRestriction(): invalid CIDR prefix in ${JSON.stringify(input)}.`);
134
- }
135
- }
136
- return { family: parsed.family, prefix, bytes: applyPrefixMask(parsed.bytes, prefix) };
137
- }
138
- function normalizeFamily(ip, family) {
139
- if (ip.family === family)
140
- return ip.bytes;
141
- if (ip.family === 6 && family === 4) {
142
- // IPv4-mapped IPv6 (::ffff:a.b.c.d) — accept as IPv4.
143
- const b = ip.bytes;
144
- const isMapped = b.slice(0, 10).every((x) => x === 0) && b[10] === 0xff && b[11] === 0xff;
145
- if (isMapped)
146
- return b.slice(12);
147
- return undefined;
148
- }
149
- return undefined;
150
- }
151
- function applyPrefixMask(bytes, prefix) {
152
- const out = new Uint8Array(bytes);
153
- const fullBytes = prefix >> 3;
154
- const remaining = prefix - fullBytes * 8;
155
- if (remaining > 0 && fullBytes < out.length) {
156
- const mask = 0xff << (8 - remaining);
157
- out[fullBytes] = out[fullBytes] & mask;
158
- }
159
- for (let i = fullBytes + (remaining > 0 ? 1 : 0); i < out.length; i++) {
160
- out[i] = 0;
161
- }
162
- return out;
163
- }
164
- /**
165
- * Parse an IPv4 or IPv6 address string into raw bytes. Supports IPv6 `::`
166
- * compression and IPv4-mapped tails (`::ffff:1.2.3.4`).
167
- *
168
- * @param input Address string; surrounding whitespace is trimmed.
169
- * @returns The parsed address, or `undefined` when the input is not a valid
170
- * IP (callers treat unparseable addresses as a rejection, failing closed).
171
- * @internal
172
- */
173
- export function parseIp(input) {
174
- const trimmed = input.trim();
175
- if (trimmed.includes(":"))
176
- return parseIPv6(trimmed);
177
- return parseIPv4(trimmed);
178
- }
179
- function parseIPv4(input) {
180
- const parts = input.split(".");
181
- if (parts.length !== 4)
182
- return undefined;
183
- const bytes = new Uint8Array(4);
184
- for (let i = 0; i < 4; i++) {
185
- const part = parts[i];
186
- if (!/^\d{1,3}$/.test(part))
187
- return undefined;
188
- const n = Number.parseInt(part, 10);
189
- if (n < 0 || n > 255)
190
- return undefined;
191
- bytes[i] = n;
192
- }
193
- return { bytes, family: 4 };
194
- }
195
- function parseIPv6(input) {
196
- // Support IPv4-mapped tail (::ffff:1.2.3.4).
197
- let working = input;
198
- const lastColon = working.lastIndexOf(":");
199
- if (lastColon !== -1 && working.slice(lastColon + 1).includes(".")) {
200
- const v4 = parseIPv4(working.slice(lastColon + 1));
201
- if (!v4)
202
- return undefined;
203
- const hi = (v4.bytes[0] << 8) | v4.bytes[1];
204
- const lo = (v4.bytes[2] << 8) | v4.bytes[3];
205
- working = working.slice(0, lastColon + 1) + hi.toString(16) + ":" + lo.toString(16);
206
- }
207
- const parts = working.split("::");
208
- if (parts.length > 2)
209
- return undefined;
210
- const headGroups = parts[0] === "" ? [] : parts[0].split(":");
211
- const tailGroups = parts.length === 2 && parts[1] !== "" ? parts[1].split(":") : [];
212
- const explicit = headGroups.length + tailGroups.length;
213
- if (explicit > 8)
214
- return undefined;
215
- if (parts.length === 1 && explicit !== 8)
216
- return undefined;
217
- const missing = parts.length === 2 ? 8 - explicit : 0;
218
- const groups = [...headGroups, ...Array.from({ length: missing }, () => "0"), ...tailGroups];
219
- if (groups.length !== 8)
220
- return undefined;
221
- const bytes = new Uint8Array(16);
222
- for (let index = 0; index < 8; index++) {
223
- const group = groups[index];
224
- if (!/^[0-9a-fA-F]{1,4}$/.test(group))
225
- return undefined;
226
- const n = Number.parseInt(group, 16);
227
- bytes[index * 2] = (n >> 8) & 0xff;
228
- bytes[index * 2 + 1] = n & 0xff;
229
- }
230
- return { bytes, family: 6 };
78
+ function forwardedIpResolver(hops, trustedPeers) {
79
+ return (ctx) => resolveForwardedClientIp(ctx.request, hops, trustedPeers);
231
80
  }
package/dist/jwt.d.ts CHANGED
@@ -57,7 +57,7 @@ export interface JwtSignerOptions {
57
57
  env?: "production" | "development" | "test";
58
58
  /** Set `secureDefaults: false` to skip the production opt-out gate. */
59
59
  secureDefaults?: boolean;
60
- /** Optional extra header fields (`kid`, `typ`, ...). `alg` is always derived. */
60
+ /** Optional extra header fields (`kid`, `typ`, ...). `alg` is always derived. `crit` is refused: no JWS extensions are supported, so a `crit` token could never verify. */
61
61
  header?: Record<string, unknown>;
62
62
  }
63
63
  /** Options for {@link createJwtVerifier}. */
@@ -94,6 +94,20 @@ export interface JwtVerifierOptions {
94
94
  * never `token_revoked` (which would leak the existence of the `jti`).
95
95
  */
96
96
  isRevoked?: (verified: JwtVerified) => boolean | Promise<boolean>;
97
+ /**
98
+ * Maximum accepted token lifetime in seconds, enforced at verification
99
+ * time as `exp - (iat ?? now) <= maxLifetimeSeconds`. Mirrors the signer-
100
+ * side {@link JwtSignerOptions.maxLifetimeSeconds} requirement: without it,
101
+ * a verifier accepts any validly-signed token no matter how long it lives
102
+ * (a 100-year token issued by a misconfigured or compromised signer of the
103
+ * same key would verify). Tokens with no `exp` are also rejected when this
104
+ * is set. Optional — left unset for callers that deliberately accept
105
+ * externally-issued long-lived tokens (e.g. refresh tokens verified by a
106
+ * dedicated endpoint).
107
+ *
108
+ * @since 1.1.0
109
+ */
110
+ maxLifetimeSeconds?: number;
97
111
  /** Optional injectable clock for tests. */
98
112
  now?: () => number;
99
113
  }
package/dist/jwt.js CHANGED
@@ -260,6 +260,14 @@ export function createJwtSigner(opts) {
260
260
  opts.secureDefaults !== false) {
261
261
  throw new JwtError("ack_no_exp_refused_in_production", "jwt(): acknowledgeNoExp: true is refused in production under secureDefaults — every issued JWT must carry an exp claim.");
262
262
  }
263
+ // The verifier refuses any `crit` header per RFC 7515 §4.1.11 (no JWS
264
+ // extensions are supported), so the signer must never emit one either —
265
+ // otherwise it would mint tokens its own verifier rejects. Checked
266
+ // synchronously at construction like every other signer option.
267
+ const extraHeader = opts.header && isJsonObject(opts.header) ? { ...opts.header } : {};
268
+ if ("crit" in extraHeader) {
269
+ throw new JwtError("unsupported_crit", "jwt(): header.crit is refused — this implementation supports no JWS extensions, so a crit token could never verify.");
270
+ }
263
271
  const resolved = (async () => {
264
272
  const key = await importKey(alg, opts.key, "sign");
265
273
  return {
@@ -267,7 +275,7 @@ export function createJwtSigner(opts) {
267
275
  key,
268
276
  maxLifetimeSeconds: opts.maxLifetimeSeconds,
269
277
  allowNoExp: opts.acknowledgeNoExp === true,
270
- header: opts.header && isJsonObject(opts.header) ? { ...opts.header } : {},
278
+ header: extraHeader,
271
279
  };
272
280
  })();
273
281
  return {
@@ -375,6 +383,12 @@ export function createJwtVerifier(opts) {
375
383
  if (opts.isRevoked !== undefined && typeof opts.isRevoked !== "function") {
376
384
  throw new JwtError("invalid_is_revoked", "jwt(): isRevoked must be a function (verified) => boolean | Promise<boolean>.");
377
385
  }
386
+ if (opts.maxLifetimeSeconds !== undefined &&
387
+ (!Number.isFinite(opts.maxLifetimeSeconds) ||
388
+ opts.maxLifetimeSeconds <= 0 ||
389
+ !Number.isInteger(opts.maxLifetimeSeconds))) {
390
+ throw new JwtError("invalid_max_lifetime", "jwt(): verifier maxLifetimeSeconds must be a positive integer.");
391
+ }
378
392
  const issuers = normalizeStringSet(opts.issuer);
379
393
  const audiences = normalizeStringSet(opts.audience);
380
394
  const keyCache = new Map();
@@ -403,6 +417,7 @@ export function createJwtVerifier(opts) {
403
417
  audiences,
404
418
  clockSkewSeconds: opts.clockSkewSeconds ?? 0,
405
419
  isRevoked: opts.isRevoked ?? null,
420
+ maxLifetimeSeconds: opts.maxLifetimeSeconds ?? null,
406
421
  now: opts.now ?? (() => Math.floor(Date.now() / 1000)),
407
422
  };
408
423
  return {
@@ -449,6 +464,15 @@ async function verifyInternal(token, r) {
449
464
  throw new JwtError("alg_not_allowed", `jwt(): token alg "${String(algRaw)}" is not in the allowlist.`);
450
465
  }
451
466
  const alg = algRaw;
467
+ // RFC 7515 §4.1.11: a JWS whose header carries `crit` MUST be rejected when
468
+ // any listed extension is not understood. This verifier supports no JWS
469
+ // extensions at all, so any `crit` header is refused outright — otherwise a
470
+ // token smuggling an extension-marked-critical parameter (e.g.
471
+ // `crit:["exp"]` with `exp` mirrored into the header) would be accepted
472
+ // while its critical semantics were silently ignored.
473
+ if (header.crit !== undefined) {
474
+ throw new JwtError("unsupported_crit", "jwt(): token header carries 'crit' but this verifier supports no JWS extensions; refusing per RFC 7515 §4.1.11.");
475
+ }
452
476
  const key = await r.resolveKey(header);
453
477
  const sig = b64urlDecode(sigB64);
454
478
  const signingInput = ENC.encode(`${headerB64}.${payloadB64}`);
@@ -475,6 +499,16 @@ async function verifyInternal(token, r) {
475
499
  }
476
500
  throw err;
477
501
  }
502
+ if (r.maxLifetimeSeconds !== null) {
503
+ const exp = payload.exp;
504
+ if (typeof exp !== "number" || !Number.isFinite(exp)) {
505
+ throw new JwtError("missing_exp", "jwt(): verifier maxLifetimeSeconds is set but the token has no exp claim.");
506
+ }
507
+ const iat = typeof payload.iat === "number" && Number.isFinite(payload.iat) ? payload.iat : now;
508
+ if (exp - iat > r.maxLifetimeSeconds) {
509
+ throw new JwtError("lifetime_exceeded", `jwt(): token lifetime ${exp - iat}s exceeds verifier maxLifetimeSeconds ${r.maxLifetimeSeconds}s.`);
510
+ }
511
+ }
478
512
  if (r.issuers) {
479
513
  const iss = payload.iss;
480
514
  if (typeof iss !== "string" || !r.issuers.has(iss)) {
@@ -453,7 +453,7 @@ export interface RateLimitStore {
453
453
  export type RateLimitContext = PreBodyContext<any> | BaseContext<any, any>;
454
454
  /** Options for {@link rateLimit}. */
455
455
  export interface RateLimitOptions {
456
- /** Rolling-window width in milliseconds (e.g. `60_000` for one minute). */
456
+ /** Fixed-window width in milliseconds (e.g. `60_000` for one minute). */
457
457
  windowMs: number;
458
458
  /** Maximum allowed requests per `windowMs` per key. */
459
459
  max: number;
@@ -474,7 +474,9 @@ export interface RateLimitOptions {
474
474
  * When enabled, the key is the **rightmost** `X-Forwarded-For` entry — the
475
475
  * one your immediate proxy appended — never the attacker-influenceable
476
476
  * leftmost one, so rotating spoofed left entries cannot evade the limit.
477
- * Behind more than one proxy hop, set {@link trustedHops} instead.
477
+ * Behind more than one proxy hop, set {@link trustedHops} instead; to also
478
+ * verify the peer is one of YOUR proxies (required when the origin itself
479
+ * can be reached), set {@link trustedProxies}.
478
480
  */
479
481
  trustProxyHeaders?: boolean;
480
482
  /**
@@ -486,6 +488,18 @@ export interface RateLimitOptions {
486
488
  * is supplied.
487
489
  */
488
490
  trustedHops?: number;
491
+ /**
492
+ * Declare WHICH proxies are yours: an IP/CIDR allowlist for the immediate
493
+ * peer's address. Forwarded headers feed the bucket key only when the TCP
494
+ * socket actually talking to the adapter matches the list — the one
495
+ * property a remote client cannot spoof — so a direct-to-origin attacker
496
+ * can neither evade the limit with rotating spoofed XFF nor burn another
497
+ * identity's bucket. Implies proxy-header trust at one hop unless
498
+ * {@link trustedHops} says otherwise; validated and compiled at
499
+ * construction. On peer-less edge platforms verification fails closed.
500
+ * Ignored when a custom `keyGenerator` is supplied.
501
+ */
502
+ trustedProxies?: readonly string[];
489
503
  /** When true, set Retry-After header on 429. Default: true. */
490
504
  retryAfter?: boolean;
491
505
  /**
@@ -578,6 +592,17 @@ export interface LoginThrottleOptions {
578
592
  * supplied.
579
593
  */
580
594
  trustedHops?: number;
595
+ /**
596
+ * Declare WHICH proxies are yours: an IP/CIDR allowlist for the immediate
597
+ * peer's address. Forwarded headers feed the throttle key only when the
598
+ * TCP socket actually talking to the adapter matches the list, so a
599
+ * direct-to-origin attacker cannot dodge the slowdown with spoofed XFF.
600
+ * Implies proxy-header trust at one hop unless {@link trustedHops} says
601
+ * otherwise; validated and compiled at construction. On peer-less edge
602
+ * platforms verification fails closed. Ignored when a custom
603
+ * `keyGenerator` is supplied.
604
+ */
605
+ trustedProxies?: readonly string[];
581
606
  /** When true, set Retry-After header on 429. Default: true. */
582
607
  retryAfter?: boolean;
583
608
  /** Start slowing responses after this many attempts in the same window. Default: 2. */
@@ -7,7 +7,7 @@
7
7
  import { assertCookieAttributes, readRequestCookie, serializeCookie } from "./cookie.js";
8
8
  import { TooManyRequestsError, ForbiddenError } from "./errors.js";
9
9
  import { randomId, sanitizeHeaderName, timingSafeEqual } from "./security.js";
10
- import { resolveForwardedClientIp, resolveForwardedTrust } from "./conn-info.js";
10
+ import { getConnInfo, resolveForwardedClientIp, resolveForwardedTrust, resolveTrustedProxyMatchers, } from "./conn-info.js";
11
11
  /**
12
12
  * Generate or accept a stable `X-Request-ID` for every request. The id is
13
13
  * stamped on `ctx.state.requestId`, mirrored on outgoing response headers,
@@ -777,7 +777,8 @@ export function rateLimit(opts) {
777
777
  }
778
778
  const groupPrefix = opts.groupId ? `${opts.groupId}:` : "";
779
779
  const hops = resolveForwardedTrust("rateLimit()", opts);
780
- const keyOf = opts.keyGenerator ?? defaultForwardedRateLimitKey(hops);
780
+ const proxyMatchers = resolveTrustedProxyMatchers("rateLimit()", opts);
781
+ const keyOf = opts.keyGenerator ?? defaultForwardedRateLimitKey(hops, proxyMatchers);
781
782
  const enforce = async (ctx) => {
782
783
  const key = `${groupPrefix}${keyOf(ctx)}`;
783
784
  const { count, resetMs } = await store.hit(key, opts.windowMs);
@@ -807,20 +808,37 @@ function assertPositiveInteger(name, value) {
807
808
  }
808
809
  /**
809
810
  * Default rate-limit / login-throttle key: the spoof-resistant forwarded client
810
- * IP, or the shared `"global"` bucket when proxy-header trust is off or the
811
- * request carries no trustworthy forwarded identity.
811
+ * IP, the unspoofable TCP peer when no trustworthy forwarded identity exists,
812
+ * or the shared `"global"` bucket only when proxy-header trust is off or the
813
+ * adapter attached no peer metadata.
812
814
  *
813
815
  * @param hops - Trusted proxy hop count from
814
816
  * {@link "./conn-info.js".resolveForwardedTrust}, or `undefined` when
815
817
  * forwarded-header trust is disabled.
818
+ * @param trustedPeers - Compiled `trustedProxies` allowlist from
819
+ * {@link "./conn-info.js".resolveTrustedProxyMatchers}; when supplied, the
820
+ * forwarded identity is honoured only for a verified proxy peer.
816
821
  * @returns A key generator suitable for {@link rateLimit} and
817
822
  * {@link loginThrottle}.
818
823
  * @internal
819
824
  */
820
- function defaultForwardedRateLimitKey(hops) {
825
+ function defaultForwardedRateLimitKey(hops, trustedPeers) {
821
826
  if (hops === undefined)
822
827
  return () => "global";
823
- return (ctx) => resolveForwardedClientIp(ctx.request, hops) ?? "global";
828
+ return (ctx) => {
829
+ const forwarded = resolveForwardedClientIp(ctx.request, hops, trustedPeers);
830
+ if (forwarded !== undefined)
831
+ return forwarded;
832
+ // Fail safe, not silent: missing/untrusted XFF must not collapse every
833
+ // caller into one shared bucket (attacker-induced global lockout). Key on
834
+ // the unspoofable TCP peer when the adapter exposed one — including the
835
+ // plain `trustProxyHeaders: true` path, not only `trustedProxies`. Only a
836
+ // truly peer-less request shares "global".
837
+ const peer = getConnInfo(ctx.request)?.remoteAddress;
838
+ if (peer !== undefined)
839
+ return `peer:${peer}`;
840
+ return "global";
841
+ };
824
842
  }
825
843
  function wait(ms) {
826
844
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -854,7 +872,8 @@ export function loginThrottle(opts = {}) {
854
872
  assertNonNegativeInteger("maxDelayMs", maxDelayMs);
855
873
  const groupId = opts.groupId ?? "login";
856
874
  const hops = resolveForwardedTrust("loginThrottle()", opts);
857
- const keyGenerator = opts.keyGenerator ?? defaultForwardedRateLimitKey(hops);
875
+ const proxyMatchers = resolveTrustedProxyMatchers("loginThrottle()", opts);
876
+ const keyGenerator = opts.keyGenerator ?? defaultForwardedRateLimitKey(hops, proxyMatchers);
858
877
  const limiter = rateLimit({
859
878
  windowMs,
860
879
  max,
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:74d895c1-b279-5248-ae56-b1fc6706445d",
4
+ "serialNumber": "urn:uuid:6f1d01b1-95cd-5475-994d-9d54a26deac5",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-08-03T08:30:50.442Z",
7
+ "timestamp": "2026-08-07T14:15:51.163Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "1.0.0"
12
+ "version": "1.1.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@1.0.0",
22
+ "bom-ref": "pkg:npm/@daloyjs/core@1.1.0",
23
23
  "name": "@daloyjs/core",
24
- "version": "1.0.0",
24
+ "version": "1.1.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@1.0.0",
26
+ "purl": "pkg:npm/@daloyjs/core@1.1.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-1.0.0",
49
+ "tagId": "swidtag--daloyjs-core-1.1.0",
50
50
  "name": "@daloyjs/core",
51
- "version": "1.0.0",
51
+ "version": "1.1.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@1.0.0",
60
+ "ref": "pkg:npm/@daloyjs/core@1.1.0",
61
61
  "dependsOn": []
62
62
  }
63
63
  ]
@@ -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-1.0.0",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-74d895c1-b279-5248-ae56-b1fc6706445d",
5
+ "name": "@daloyjs/core-1.1.0",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.1.0-6f1d01b1-95cd-5475-994d-9d54a26deac5",
7
7
  "creationInfo": {
8
- "created": "2026-08-03T08:30:50.442Z",
8
+ "created": "2026-08-07T14:15:51.163Z",
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": "1.0.0",
19
+ "versionInfo": "1.1.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@1.0.0"
30
+ "referenceLocator": "pkg:npm/@daloyjs/core@1.1.0"
31
31
  }
32
32
  ]
33
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daloyjs/core",
3
- "version": "1.0.0",
3
+ "version": "1.1.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 \u2014 distributed via pnpm.",
5
5
  "type": "module",
6
6
  "publishConfig": {