@selvajs/server 1.0.0-beta.8 → 1.0.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.
Files changed (91) hide show
  1. package/README.md +18 -20
  2. package/dist/access/project-access-input.d.ts +13 -16
  3. package/dist/access/project-access-input.d.ts.map +1 -1
  4. package/dist/access/project-access-input.js +2 -2
  5. package/dist/access/project-access-input.js.map +1 -1
  6. package/dist/compute/idempotency.d.ts +18 -22
  7. package/dist/compute/idempotency.d.ts.map +1 -1
  8. package/dist/compute/idempotency.js +10 -14
  9. package/dist/compute/idempotency.js.map +1 -1
  10. package/dist/compute/index.d.ts.map +1 -1
  11. package/dist/compute/index.js +3 -5
  12. package/dist/compute/index.js.map +1 -1
  13. package/dist/compute/limits.d.ts +70 -86
  14. package/dist/compute/limits.d.ts.map +1 -1
  15. package/dist/compute/limits.js +27 -56
  16. package/dist/compute/limits.js.map +1 -1
  17. package/dist/compute/rate-limit.d.ts +41 -53
  18. package/dist/compute/rate-limit.d.ts.map +1 -1
  19. package/dist/compute/rate-limit.js +27 -35
  20. package/dist/compute/rate-limit.js.map +1 -1
  21. package/dist/compute/remote-definition.d.ts +8 -22
  22. package/dist/compute/remote-definition.d.ts.map +1 -1
  23. package/dist/compute/remote-definition.js +9 -19
  24. package/dist/compute/remote-definition.js.map +1 -1
  25. package/dist/compute/safe-url.d.ts +7 -5
  26. package/dist/compute/safe-url.d.ts.map +1 -1
  27. package/dist/compute/safe-url.js +26 -28
  28. package/dist/compute/safe-url.js.map +1 -1
  29. package/dist/definitions/definition-service.d.ts +13 -38
  30. package/dist/definitions/definition-service.d.ts.map +1 -1
  31. package/dist/definitions/definition-service.js +25 -53
  32. package/dist/definitions/definition-service.js.map +1 -1
  33. package/dist/definitions/index.d.ts +1 -1
  34. package/dist/definitions/index.d.ts.map +1 -1
  35. package/dist/definitions/index.js +0 -4
  36. package/dist/definitions/index.js.map +1 -1
  37. package/dist/definitions/load-for-render.d.ts +23 -20
  38. package/dist/definitions/load-for-render.d.ts.map +1 -1
  39. package/dist/definitions/load-for-render.js +37 -40
  40. package/dist/definitions/load-for-render.js.map +1 -1
  41. package/dist/definitions/schema-extraction.d.ts +11 -14
  42. package/dist/definitions/schema-extraction.d.ts.map +1 -1
  43. package/dist/definitions/schema-extraction.js +17 -26
  44. package/dist/definitions/schema-extraction.js.map +1 -1
  45. package/dist/errors/SentryErrorReporter.d.ts +2 -3
  46. package/dist/errors/SentryErrorReporter.d.ts.map +1 -1
  47. package/dist/errors/SentryErrorReporter.js +10 -14
  48. package/dist/errors/SentryErrorReporter.js.map +1 -1
  49. package/dist/http/body-size.d.ts +8 -11
  50. package/dist/http/body-size.d.ts.map +1 -1
  51. package/dist/http/body-size.js +8 -11
  52. package/dist/http/body-size.js.map +1 -1
  53. package/dist/http/redirect.d.ts +3 -7
  54. package/dist/http/redirect.d.ts.map +1 -1
  55. package/dist/http/redirect.js +8 -8
  56. package/dist/http/redirect.js.map +1 -1
  57. package/dist/http/route-classifier.d.ts +20 -24
  58. package/dist/http/route-classifier.d.ts.map +1 -1
  59. package/dist/http/route-classifier.js +6 -7
  60. package/dist/http/route-classifier.js.map +1 -1
  61. package/dist/http/security-headers.d.ts +13 -15
  62. package/dist/http/security-headers.d.ts.map +1 -1
  63. package/dist/http/security-headers.js +10 -12
  64. package/dist/http/security-headers.js.map +1 -1
  65. package/dist/logging/PinoLogger.d.ts +8 -14
  66. package/dist/logging/PinoLogger.d.ts.map +1 -1
  67. package/dist/logging/PinoLogger.js +29 -35
  68. package/dist/logging/PinoLogger.js.map +1 -1
  69. package/dist/logging/requestId.d.ts +20 -28
  70. package/dist/logging/requestId.d.ts.map +1 -1
  71. package/dist/logging/requestId.js +29 -37
  72. package/dist/logging/requestId.js.map +1 -1
  73. package/dist/ops/index.d.ts.map +1 -1
  74. package/dist/ops/index.js +2 -1
  75. package/dist/ops/index.js.map +1 -1
  76. package/dist/ops/semver.d.ts +22 -24
  77. package/dist/ops/semver.d.ts.map +1 -1
  78. package/dist/ops/semver.js +22 -25
  79. package/dist/ops/semver.js.map +1 -1
  80. package/dist/providers/create-selva-providers.d.ts +19 -27
  81. package/dist/providers/create-selva-providers.d.ts.map +1 -1
  82. package/dist/providers/create-selva-providers.js +28 -34
  83. package/dist/providers/create-selva-providers.js.map +1 -1
  84. package/dist/providers/index.d.ts.map +1 -1
  85. package/dist/providers/index.js +2 -4
  86. package/dist/providers/index.js.map +1 -1
  87. package/dist/tokens/token-codec.d.ts +24 -27
  88. package/dist/tokens/token-codec.d.ts.map +1 -1
  89. package/dist/tokens/token-codec.js +18 -21
  90. package/dist/tokens/token-codec.js.map +1 -1
  91. package/package.json +6 -6
@@ -1,34 +1,22 @@
1
1
  /**
2
2
  * Compute/solve server limits, resolved from an injected env map.
3
3
  *
4
- * This is the transport- and framework-agnostic core: it takes a plain
5
- * `Record<string, string | undefined>` (the caller passes SvelteKit's
6
- * `$env/dynamic/private`, `process.env`, or any config source) and returns a
7
- * fully-resolved `ComputeLimits` object. No ambient `$env` / `process.env`
8
- * read happens here, so the module is importable from any runtime and testable
9
- * without env plumbing.
10
- *
11
- * Every knob is env-overridable so deployments can tune without code changes;
12
- * the defaults are sized for typical interactive use (sliders + small uploads).
13
- * Centralizing them surfaces the tradeoffs (raise the rate cap → also consider
14
- * the body cap) at one glance.
4
+ * Env comes in as a parameter — nothing ambient is read — so this is importable
5
+ * from any runtime and testable without env plumbing. Defaults are sized for
6
+ * typical interactive use (sliders + small uploads).
15
7
  *
16
8
  * Client-side counterparts (the in-flight compute throttle and slider debounce)
17
- * live in `@selvajs/ui` and are bundled into the client, so they can't read
18
- * env — they carry their own defaults.
9
+ * are bundled into the client in `@selvajs/ui` and can't read env — they carry
10
+ * their own defaults.
19
11
  */
20
12
  import { NoopLogger } from '@selvajs/platform';
21
13
  const MB = 1024 * 1024;
22
14
  /**
23
- * Optional structured logger for the env-parsing diagnostics below. Defaults to
24
- * `NoopLogger`: this is library code, so an embedder that wires nothing gets
25
- * silence rather than unsolicited stdout writes. The app passes its real logger.
15
+ * Default logger for the env-parsing warnings below. This is library code, so an
16
+ * embedder that wires nothing gets silence rather than unsolicited stdout writes.
26
17
  */
27
18
  const noop = new NoopLogger();
28
- /**
29
- * Parse a positive integer env value. Returns `fallback` when absent, and warns
30
- * + falls back when present but not a finite positive number.
31
- */
19
+ /** Parse a positive integer env value; absent, or present but not finite and positive, falls back. */
32
20
  export function readPositiveInt(env, name, fallback, logger = noop) {
33
21
  const raw = env[name];
34
22
  if (!raw)
@@ -45,12 +33,7 @@ export function readPositiveInt(env, name, fallback, logger = noop) {
45
33
  }
46
34
  return Math.floor(parsed);
47
35
  }
48
- /**
49
- * Parse a non-negative integer env value — like {@link readPositiveInt} but `0`
50
- * is a valid value (used by knobs where `0` means "disable", e.g. a cache
51
- * budget). Returns `fallback` when absent, and warns + falls back when present
52
- * but not a finite non-negative number.
53
- */
36
+ /** Like {@link readPositiveInt}, but `0` is valid — knobs where it means "disable". */
54
37
  export function readNonNegativeInt(env, name, fallback, logger = noop) {
55
38
  const raw = env[name];
56
39
  if (!raw)
@@ -68,16 +51,12 @@ export function readNonNegativeInt(env, name, fallback, logger = noop) {
68
51
  return Math.floor(parsed);
69
52
  }
70
53
  /**
71
- * Read a renamed variable, honouring its previous name for one minor version.
72
- *
73
- * Returns the env map's value under `name`, or — when that is unset and the old
74
- * name is present — the old value plus a warning naming both. Setting both is not
75
- * an error: the new name wins silently, which is what an operator mid-migration
76
- * expects.
54
+ * Fall back to a variable's previous name, warning when the old one is used.
55
+ * Setting both is not an error: the new name wins silently, which is what an
56
+ * operator mid-migration expects.
77
57
  *
78
- * This exists so a rename is never a silent behaviour change. A var that quietly
79
- * stops being read looks exactly like a var that is working, and the failure only
80
- * surfaces as a memory number nobody is watching.
58
+ * A var that quietly stops being read looks exactly like one that is working —
59
+ * the failure only surfaces as a memory number nobody is watching.
81
60
  */
82
61
  function readRenamed(env, name, oldName, logger) {
83
62
  if (env[name] !== undefined || env[oldName] === undefined)
@@ -90,8 +69,8 @@ function readRenamed(env, name, oldName, logger) {
90
69
  return { ...env, [name]: env[oldName] };
91
70
  }
92
71
  /**
93
- * Parse a boolean env flag. Accepts `true/1/yes/on` (case-insensitive) as true
94
- * and `false/0/no/off` as false; any other / absent value falls back.
72
+ * Parse a boolean env flag. Accepts `true/1/yes/on` and `false/0/no/off`
73
+ * (case-insensitive); any other spelling warns and falls back.
95
74
  */
96
75
  export function readBool(env, name, fallback, logger = noop) {
97
76
  const raw = env[name];
@@ -111,20 +90,17 @@ export function readBool(env, name, fallback, logger = noop) {
111
90
  return fallback;
112
91
  }
113
92
  /**
114
- * Resolve every server-side compute limit from an env map. Pure: the same env
115
- * always yields the same limits, and nothing is read ambiently. The app calls
116
- * this once at its composition root with `$env/dynamic/private`.
93
+ * Resolve every server-side compute limit from an env map. The app calls this
94
+ * once at its composition root with `$env/dynamic/private`.
117
95
  */
118
96
  export function resolveComputeLimits(env, logger = noop) {
119
- // Renamed 2026-07 so each cache var says what it HOLDS. Old names still read,
120
- // with a boot warning; drop this shim one minor version on.
97
+ // Deprecation shims, so each var says what it HOLDS and what it bounds — one
98
+ // solve — rather than a vague "duration". Drop one minor version on.
121
99
  env = readRenamed(env, 'COMPUTE_DEFINITION_CACHE_MB', 'COMPUTE_DEFINITION_BYTE_CACHE_MB', logger);
122
100
  env = readRenamed(env, 'COMPUTE_SOLVE_CACHE_MB', 'COMPUTE_RESPONSE_CACHE_MB', logger);
123
101
  env = readRenamed(env, 'REMOTE_DEFINITION_CACHE_TTL_MS', 'DEFINITION_CACHE_TTL_MS', logger);
124
- // Renamed 2026-08 into the COMPUTE_* namespace it belongs to, and to say what it
125
- // bounds — one solve — rather than a vague "duration".
126
102
  env = readRenamed(env, 'COMPUTE_SOLVE_DEADLINE_MS', 'MAX_SOLVE_DURATION_MS', logger);
127
- // Intentional clean-cut rename (no old-name dual-read): pre-1.0 breaking change.
103
+ // Clean-cut rename with no old-name dual-read: deliberate pre-1.0 breaking change.
128
104
  const maxDefinitionFileSize = readPositiveInt(env, 'MAX_DEFINITION_FILE_SIZE_BYTES', 50 * MB, logger);
129
105
  return {
130
106
  solveDeadlineMs: readPositiveInt(env, 'COMPUTE_SOLVE_DEADLINE_MS', 100_000, logger),
@@ -134,26 +110,21 @@ export function resolveComputeLimits(env, logger = noop) {
134
110
  maxImageFileSize: readPositiveInt(env, 'MAX_IMAGE_FILE_SIZE_BYTES', 10 * MB, logger),
135
111
  computeRequestMaxBytes: readPositiveInt(env, 'COMPUTE_REQUEST_MAX_BYTES', 210 * MB, logger),
136
112
  computeResponseMaxBytes: readPositiveInt(env, 'COMPUTE_RESPONSE_MAX_BYTES', 300 * MB, logger),
137
- // Tracks the upload cap so a remote URL can't smuggle a larger file.
138
113
  remoteDefinitionMaxBytes: maxDefinitionFileSize,
139
114
  remoteDefinitionFetchTimeoutMs: readPositiveInt(env, 'REMOTE_DEFINITION_FETCH_TIMEOUT_MS', 30_000, logger),
140
115
  remoteDefinitionCacheTtlMs: readPositiveInt(env, 'REMOTE_DEFINITION_CACHE_TTL_MS', 5 * 60 * 1000, logger),
141
116
  computeReuseDefinitionCache: readBool(env, 'COMPUTE_REUSE_DEFINITION_CACHE', true, logger),
142
117
  computeServerCachesolve: readBool(env, 'COMPUTE_SERVER_CACHESOLVE', true, logger),
143
118
  computeCacheErroredSolves: readBool(env, 'COMPUTE_CACHE_ERRORED_SOLVES', false, logger),
144
- // Both 0 (unbounded / no deadline) by default — nothing is rejected until an
145
- // operator who's measured their pool opts in. readNonNegativeInt so `0` is a
146
- // valid "disabled" value, not treated as invalid.
119
+ // Both default to off — nothing is rejected until an operator who has measured
120
+ // their pool opts in. readNonNegativeInt throughout, so `0` reads as "disabled"
121
+ // rather than as an invalid value that falls back.
147
122
  computeMaxQueueDepth: readNonNegativeInt(env, 'COMPUTE_MAX_QUEUE_DEPTH', 0, logger),
148
123
  computeQueueWaitMs: readNonNegativeInt(env, 'COMPUTE_QUEUE_WAIT_MS', 0, logger),
149
- // Env is MB; 0 disables. Default 256 MB holds a handful of typical
150
- // definitions warm without pinning gigabytes. readNonNegativeInt so `0`
151
- // (disable) is honored rather than treated as invalid.
124
+ // 256 MB holds a handful of typical definitions warm without pinning gigabytes.
152
125
  computeDefinitionCacheBytes: readNonNegativeInt(env, 'COMPUTE_DEFINITION_CACHE_MB', 256, logger) * MB,
153
- // Per-warm-client solve cache budget. Env is MB; 0 disables it. Default
154
- // 256 MB: enough to keep a scrub session's recent solves warm without
155
- // letting worst-case entries pin gigabytes (audit C2). PER warm client —
156
- // see the field doc for the ×16 worst case.
126
+ // 256 MB keeps a scrub session's recent solves warm; PER warm client, so see
127
+ // the field doc for the ×16 worst case.
157
128
  computeSolveCacheBytes: readNonNegativeInt(env, 'COMPUTE_SOLVE_CACHE_MB', 256, logger) * MB
158
129
  };
159
130
  }
@@ -1 +1 @@
1
- {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/compute/limits.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/compute/limits.ts"],"names":[],"mappings":"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"}
@@ -1,41 +1,37 @@
1
1
  /**
2
- * Per-key fixed-window rate limiter for the compute-solve endpoint. Compute
3
- * solves are the one expensive endpoint in a Selva-engine app — without a cap a
4
- * single authenticated user (or share-link consumer) can burn through compute
5
- * budget far faster than any other surface allows.
2
+ * Per-key fixed-window rate limiter for the compute-solve endpoint. Solves are
3
+ * the one expensive endpoint in a Selva-engine app — without a cap a single user
4
+ * or share-link consumer burns compute budget far faster than any other surface
5
+ * allows.
6
6
  *
7
7
  * Design:
8
8
  * - Process-local `Map`. Multi-instance deployments see N× the per-key rate —
9
- * acceptable as a first line; a distributed limiter is a follow-up. The state
10
- * lives behind this factory so a shared-store implementation can slot in
11
- * later without changing the call sites.
12
- * - Bounded. A key is only interesting until its window expires, but nothing
13
- * revisits a bucket that is never checked again — so an unswept `Map` grows
14
- * with the number of distinct keys ever seen (every user, share link and
15
- * login IP, forever). Two inline mechanisms keep it bounded, both driven by
9
+ * acceptable as a first line. State lives behind this factory so a shared-store
10
+ * implementation can slot in later without touching call sites.
11
+ * - Bounded. Nothing revisits a bucket that is never checked again, so an
12
+ * unswept `Map` grows with every distinct key ever seen — every user, share
13
+ * link and login IP, forever. Two mechanisms keep it bounded, both driven by
16
14
  * `check`/`peek` rather than a timer: a timer would keep the event loop alive
17
15
  * and give this module a lifecycle its callers don't have.
18
16
  * 1. Amortized sweep — every `sweepIntervalMs` (default: one window), drop
19
- * every expired bucket. Expired buckets are semantically dead: `check`
20
- * already treats them as absent, so dropping them changes no verdict.
21
- * 2. Hard cap — if live buckets alone exceed `maxKeys`, evict the entries
22
- * closest to resetting. Those are the ones with the least budget left to
23
- * protect, so a distinct-key flood can't pin unbounded memory. Eviction
17
+ * expired buckets. `check` already treats them as absent, so dropping
18
+ * them changes no verdict.
19
+ * 2. Hard cap — if live buckets alone exceed `maxKeys`, evict those closest
20
+ * to resetting; they have the least budget left to protect. Eviction
24
21
  * forgives a key's counter, so `maxKeys` is sized to be unreachable in
25
22
  * normal operation and only bites under a key-space attack.
26
- * - Fixed window (vs sliding) — simpler, no per-request bookkeeping; the
27
- * "burst at window edges" mode is fine here (share-link `maxSolves` and
28
- * per-user auth already prevent unbounded abuse — this flattens short spikes).
29
- * - Keyed by caller — the app passes `user:{userId}` for authenticated solves,
30
- * `share:{linkId}` for share-token solves, so anonymous consumers of one link
31
- * don't share a bucket with the link's owner.
23
+ * - Fixed window rather than sliding — simpler, no per-request bookkeeping. The
24
+ * burst-at-window-edges behaviour is fine here: share-link `maxSolves` and
25
+ * per-user auth already prevent unbounded abuse, and this flattens spikes.
26
+ * - Keyed by caller — the app passes `user:{userId}` for authenticated solves
27
+ * and `share:{linkId}` for share-token solves, so anonymous consumers of one
28
+ * link don't share a bucket with the link's owner.
32
29
  *
33
- * The window + cap are injected (they live in `ComputeLimits`), so this module
34
- * reads no env of its own.
30
+ * Window and cap are injected from `ComputeLimits`; this module reads no env.
35
31
  *
36
- * Not compute-only despite the name: `peek` + `clear` make it usable for
37
- * failure-counting flows too (login limiting keys by IP, records only failed
38
- * attempts via `check`, and forgives on success via `clear`).
32
+ * Not compute-only despite the name: `peek` + `clear` also serve failure-counting
33
+ * flows — login limiting keys by IP, records only failures via `check`, and
34
+ * forgives on success via `clear`.
39
35
  */
40
36
  export interface RateLimitResult {
41
37
  allowed: boolean;
@@ -48,54 +44,46 @@ export interface ComputeRateLimiterConfig {
48
44
  /** Max requests admitted per key per window. */
49
45
  maxPerWindow: number;
50
46
  /**
51
- * Max retained buckets. Once live (unexpired) buckets exceed this, the ones
52
- * nearest their reset are evicted. Evicting forgives that key's counter, so
53
- * this is a memory backstop against a distinct-key flood, not a tuning knob:
54
- * size it well above the plausible number of concurrently-active keys.
55
- * Defaults to {@link DEFAULT_MAX_KEYS}.
47
+ * Max retained buckets; defaults to {@link DEFAULT_MAX_KEYS}. Evicting forgives
48
+ * that key's counter, so this is a memory backstop against a distinct-key
49
+ * flood, not a tuning knob — size it well above the plausible number of
50
+ * concurrently-active keys.
56
51
  */
57
52
  maxKeys?: number;
58
53
  /**
59
- * How often (ms) to sweep expired buckets, amortized onto `check`/`peek`.
60
- * Defaults to `windowMs` — every bucket swept is already past its reset, so
61
- * sweeping more often than a window buys nothing. `0` disables the sweep,
62
- * leaving only the `maxKeys` cap (test seam).
54
+ * How often (ms) to sweep expired buckets. Defaults to `windowMs` — every
55
+ * bucket swept is already past its reset, so sweeping more often buys nothing.
56
+ * `0` disables the sweep, leaving only the `maxKeys` cap (test seam).
63
57
  */
64
58
  sweepIntervalMs?: number;
65
59
  }
66
60
  /** Default {@link ComputeRateLimiterConfig.maxKeys}. */
67
61
  export declare const DEFAULT_MAX_KEYS = 100000;
68
62
  export interface ComputeRateLimiter {
69
- /**
70
- * Check + record one request against `key`. Increments the counter on allow.
71
- * Returns `{ allowed: false, retryAfter }` once the bucket is full.
72
- */
63
+ /** Check and record one request against `key`, incrementing the counter on allow. */
73
64
  check(key: string): RateLimitResult;
74
65
  /**
75
- * Check WITHOUT recording. For flows that only count selected outcomes —
76
- * e.g. login limiting, where `peek` gates the attempt and `check` records
77
- * only failures (a success calls `clear` instead).
66
+ * Check WITHOUT recording — for flows that count only selected outcomes. Login
67
+ * limiting gates the attempt with `peek`, records failures with `check`, and
68
+ * calls `clear` on success.
78
69
  */
79
70
  peek(key: string): RateLimitResult;
80
- /** Drop `key`'s bucket — e.g. a successful login forgives prior failures. */
71
+ /** Drop `key`'s bucket — a successful login forgives prior failures. */
81
72
  clear(key: string): void;
82
73
  /**
83
- * Requests recorded against `key` in the current window; `0` when the bucket
84
- * is absent or expired. Observability/test seam — which key a caller charges
85
- * is a security property (an anonymous share solve must not spend the link
86
- * owner's budget), and a bare allow/deny verdict can't express it.
74
+ * Requests recorded against `key` this window; `0` when the bucket is absent or
75
+ * expired. Which key a caller charges is a security property — an anonymous
76
+ * share solve must not spend the link owner's budget — and an allow/deny
77
+ * verdict alone can't express it.
87
78
  */
88
79
  count(key: string): number;
89
80
  /** Test seam — wipes in-memory state. Production code never calls this. */
90
81
  reset(): void;
91
- /** Retained bucket count, including any not yet swept. For tests/observability. */
82
+ /** Retained bucket count, including any not yet swept. Tests/observability. */
92
83
  size(): number;
93
84
  /** The resolved config, exposed for time-based test assertions. */
94
85
  readonly config: Readonly<ComputeRateLimiterConfig>;
95
86
  }
96
- /**
97
- * Build a process-local fixed-window rate limiter. Each limiter owns its own
98
- * bucket `Map`, so distinct endpoints/keys-spaces don't collide.
99
- */
87
+ /** Each limiter owns its own bucket `Map`, so distinct key-spaces don't collide. */
100
88
  export declare function createComputeRateLimiter(config: ComputeRateLimiterConfig): ComputeRateLimiter;
101
89
  //# sourceMappingURL=rate-limit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/compute/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACxC,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC,MAAM,WAAW,kBAAkB;IAClC;;;OAGG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACnC,6EAA6E;IAC7E,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,KAAK,IAAI,IAAI,CAAC;IACd,mFAAmF;IACnF,IAAI,IAAI,MAAM,CAAC;IACf,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,wBAAwB,CAAC,CAAC;CACpD;AAOD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,wBAAwB,GAAG,kBAAkB,CA8F7F"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/compute/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACxC,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAExC,MAAM,WAAW,kBAAkB;IAClC,qFAAqF;IACrF,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACnC,wEAAwE;IACxE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,KAAK,IAAI,IAAI,CAAC;IACd,+EAA+E;IAC/E,IAAI,IAAI,MAAM,CAAC;IACf,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,wBAAwB,CAAC,CAAC;CACpD;AAOD,oFAAoF;AACpF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,wBAAwB,GAAG,kBAAkB,CA6F7F"}
@@ -1,48 +1,41 @@
1
1
  /**
2
- * Per-key fixed-window rate limiter for the compute-solve endpoint. Compute
3
- * solves are the one expensive endpoint in a Selva-engine app — without a cap a
4
- * single authenticated user (or share-link consumer) can burn through compute
5
- * budget far faster than any other surface allows.
2
+ * Per-key fixed-window rate limiter for the compute-solve endpoint. Solves are
3
+ * the one expensive endpoint in a Selva-engine app — without a cap a single user
4
+ * or share-link consumer burns compute budget far faster than any other surface
5
+ * allows.
6
6
  *
7
7
  * Design:
8
8
  * - Process-local `Map`. Multi-instance deployments see N× the per-key rate —
9
- * acceptable as a first line; a distributed limiter is a follow-up. The state
10
- * lives behind this factory so a shared-store implementation can slot in
11
- * later without changing the call sites.
12
- * - Bounded. A key is only interesting until its window expires, but nothing
13
- * revisits a bucket that is never checked again — so an unswept `Map` grows
14
- * with the number of distinct keys ever seen (every user, share link and
15
- * login IP, forever). Two inline mechanisms keep it bounded, both driven by
9
+ * acceptable as a first line. State lives behind this factory so a shared-store
10
+ * implementation can slot in later without touching call sites.
11
+ * - Bounded. Nothing revisits a bucket that is never checked again, so an
12
+ * unswept `Map` grows with every distinct key ever seen — every user, share
13
+ * link and login IP, forever. Two mechanisms keep it bounded, both driven by
16
14
  * `check`/`peek` rather than a timer: a timer would keep the event loop alive
17
15
  * and give this module a lifecycle its callers don't have.
18
16
  * 1. Amortized sweep — every `sweepIntervalMs` (default: one window), drop
19
- * every expired bucket. Expired buckets are semantically dead: `check`
20
- * already treats them as absent, so dropping them changes no verdict.
21
- * 2. Hard cap — if live buckets alone exceed `maxKeys`, evict the entries
22
- * closest to resetting. Those are the ones with the least budget left to
23
- * protect, so a distinct-key flood can't pin unbounded memory. Eviction
17
+ * expired buckets. `check` already treats them as absent, so dropping
18
+ * them changes no verdict.
19
+ * 2. Hard cap — if live buckets alone exceed `maxKeys`, evict those closest
20
+ * to resetting; they have the least budget left to protect. Eviction
24
21
  * forgives a key's counter, so `maxKeys` is sized to be unreachable in
25
22
  * normal operation and only bites under a key-space attack.
26
- * - Fixed window (vs sliding) — simpler, no per-request bookkeeping; the
27
- * "burst at window edges" mode is fine here (share-link `maxSolves` and
28
- * per-user auth already prevent unbounded abuse — this flattens short spikes).
29
- * - Keyed by caller — the app passes `user:{userId}` for authenticated solves,
30
- * `share:{linkId}` for share-token solves, so anonymous consumers of one link
31
- * don't share a bucket with the link's owner.
23
+ * - Fixed window rather than sliding — simpler, no per-request bookkeeping. The
24
+ * burst-at-window-edges behaviour is fine here: share-link `maxSolves` and
25
+ * per-user auth already prevent unbounded abuse, and this flattens spikes.
26
+ * - Keyed by caller — the app passes `user:{userId}` for authenticated solves
27
+ * and `share:{linkId}` for share-token solves, so anonymous consumers of one
28
+ * link don't share a bucket with the link's owner.
32
29
  *
33
- * The window + cap are injected (they live in `ComputeLimits`), so this module
34
- * reads no env of its own.
30
+ * Window and cap are injected from `ComputeLimits`; this module reads no env.
35
31
  *
36
- * Not compute-only despite the name: `peek` + `clear` make it usable for
37
- * failure-counting flows too (login limiting keys by IP, records only failed
38
- * attempts via `check`, and forgives on success via `clear`).
32
+ * Not compute-only despite the name: `peek` + `clear` also serve failure-counting
33
+ * flows — login limiting keys by IP, records only failures via `check`, and
34
+ * forgives on success via `clear`.
39
35
  */
40
36
  /** Default {@link ComputeRateLimiterConfig.maxKeys}. */
41
37
  export const DEFAULT_MAX_KEYS = 100_000;
42
- /**
43
- * Build a process-local fixed-window rate limiter. Each limiter owns its own
44
- * bucket `Map`, so distinct endpoints/keys-spaces don't collide.
45
- */
38
+ /** Each limiter owns its own bucket `Map`, so distinct key-spaces don't collide. */
46
39
  export function createComputeRateLimiter(config) {
47
40
  const { windowMs, maxPerWindow } = config;
48
41
  const maxKeys = Math.max(1, Math.floor(config.maxKeys ?? DEFAULT_MAX_KEYS));
@@ -55,13 +48,12 @@ export function createComputeRateLimiter(config) {
55
48
  const buckets = new Map();
56
49
  let nextSweepAt = Number.POSITIVE_INFINITY;
57
50
  /**
58
- * Drop dead buckets, then enforce the hard cap. Amortized onto `check`/`peek`
59
- * so the limiter needs no timer of its own. Runs at most once per
51
+ * Drop dead buckets, then enforce the hard cap. Runs at most once per
60
52
  * `sweepIntervalMs` — except when the cap is already breached, which must be
61
53
  * enforced on the spot regardless of the sweep clock.
62
54
  *
63
- * `headroom` is how many buckets the caller is about to insert; the cap is
64
- * enforced against the post-insert size so `maxKeys` is a true ceiling rather
55
+ * `headroom` is how many buckets the caller is about to insert. The cap is
56
+ * enforced against the post-insert size, so `maxKeys` is a true ceiling rather
65
57
  * than one the very next write steps over.
66
58
  */
67
59
  function maybeSweep(now, headroom = 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/compute/rate-limit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/compute/rate-limit.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,10 @@
1
1
  /**
2
2
  * Remote-definition fetch: SSRF-guarded, size-capped, timed, and TTL-cached.
3
3
  *
4
- * A solve can reference its `.gh` by remote URL instead of a stored blob. That
5
- * URL is attacker-controllable (a share-token caller supplies `definitionUrl`),
6
- * so every fetch runs through {@link assertSafeRemoteDefinitionUrl} (literal +
7
- * DNS host validation, blocking private/loopback/link-local targets and
8
- * literal-encoding bypasses), streams the body with an early size cap, and
9
- * bounds the wall time with a deadline.
10
- *
11
- * The TTL cache is a per-fetcher `Map<url, {data, fetchedAt}>` so repeated
12
- * solves of the same remote definition skip the network. It's created by
13
- * {@link createRemoteDefinitionFetcher} rather than being module-global, so a
14
- * consuming app can hold one fetcher per process (the app binding does) or
15
- * several isolated ones (tests) without shared state leaking across them.
4
+ * A solve can reference its `.gh` by remote URL instead of a stored blob, and
5
+ * that URL is attacker-controllable — a share-token caller supplies
6
+ * `definitionUrl`. Hence {@link assertSafeRemoteDefinitionUrl}, the early size
7
+ * cap, and the deadline.
16
8
  */
17
9
  import { type ILogger } from '@selvajs/platform';
18
10
  /** Injected limits — the app derives these from its env-resolved `ComputeLimits`. */
@@ -23,16 +15,11 @@ export interface RemoteDefinitionConfig {
23
15
  fetchTimeoutMs: number;
24
16
  /** How long a fetched definition stays warm in the in-process cache. */
25
17
  cacheTtlMs: number;
26
- /**
27
- * Current epoch millis. Injected (not read via `Date.now()` ambiently) so the
28
- * TTL is testable and the module stays side-effect-free. The app binding
29
- * passes `() => Date.now()`.
30
- */
18
+ /** Current epoch millis. Injected so the TTL is testable; the app passes `() => Date.now()`. */
31
19
  now: () => number;
32
20
  /**
33
- * Structured logger for the SSRF guard's block diagnostics. Optional; defaults
34
- * to `NoopLogger` so an embedder that wires nothing gets silence rather than
35
- * unsolicited stdout writes.
21
+ * Receives the SSRF guard's block diagnostics. Defaults to `NoopLogger` so an
22
+ * embedder that wires nothing gets silence rather than unsolicited stdout writes.
36
23
  */
37
24
  logger?: ILogger;
38
25
  }
@@ -47,8 +34,7 @@ export interface RemoteDefinitionFetcher {
47
34
  /**
48
35
  * Read a response body into memory, aborting (and throwing) as soon as the
49
36
  * running byte total exceeds `maxBytes`. Falls back to `arrayBuffer()` only when
50
- * the body isn't a readable stream (older fetch impls). Exported for direct use
51
- * and reuse; the fetcher below wraps it with SSRF + TTL.
37
+ * the body isn't a readable stream (older fetch impls).
52
38
  */
53
39
  export declare function readBodyWithCap(response: Response, maxBytes: number, controller: AbortController): Promise<Uint8Array>;
54
40
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"remote-definition.d.ts","sourceRoot":"","sources":["../../src/compute/remote-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE7D,qFAAqF;AACrF,MAAM,WAAW,sBAAsB;IACtC,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACvC;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACvC;AAMD;;;;;GAKG;AACH,wBAAsB,eAAe,CACpC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,eAAe,GACzB,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,sBAAsB,GAC5B,uBAAuB,CAsDzB"}
1
+ {"version":3,"file":"remote-definition.d.ts","sourceRoot":"","sources":["../../src/compute/remote-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE7D,qFAAqF;AACrF,MAAM,WAAW,sBAAsB;IACtC,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,gGAAgG;IAChG,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACvC;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACvC;AAMD;;;;GAIG;AACH,wBAAsB,eAAe,CACpC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,eAAe,GACzB,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,sBAAsB,GAC5B,uBAAuB,CAqDzB"}
@@ -1,18 +1,10 @@
1
1
  /**
2
2
  * Remote-definition fetch: SSRF-guarded, size-capped, timed, and TTL-cached.
3
3
  *
4
- * A solve can reference its `.gh` by remote URL instead of a stored blob. That
5
- * URL is attacker-controllable (a share-token caller supplies `definitionUrl`),
6
- * so every fetch runs through {@link assertSafeRemoteDefinitionUrl} (literal +
7
- * DNS host validation, blocking private/loopback/link-local targets and
8
- * literal-encoding bypasses), streams the body with an early size cap, and
9
- * bounds the wall time with a deadline.
10
- *
11
- * The TTL cache is a per-fetcher `Map<url, {data, fetchedAt}>` so repeated
12
- * solves of the same remote definition skip the network. It's created by
13
- * {@link createRemoteDefinitionFetcher} rather than being module-global, so a
14
- * consuming app can hold one fetcher per process (the app binding does) or
15
- * several isolated ones (tests) without shared state leaking across them.
4
+ * A solve can reference its `.gh` by remote URL instead of a stored blob, and
5
+ * that URL is attacker-controllable — a share-token caller supplies
6
+ * `definitionUrl`. Hence {@link assertSafeRemoteDefinitionUrl}, the early size
7
+ * cap, and the deadline.
16
8
  */
17
9
  import { assertSafeRemoteDefinitionUrl } from './safe-url.js';
18
10
  import { NoopLogger } from '@selvajs/platform';
@@ -22,8 +14,7 @@ const CACHE_EVICT_BATCH = 10;
22
14
  /**
23
15
  * Read a response body into memory, aborting (and throwing) as soon as the
24
16
  * running byte total exceeds `maxBytes`. Falls back to `arrayBuffer()` only when
25
- * the body isn't a readable stream (older fetch impls). Exported for direct use
26
- * and reuse; the fetcher below wraps it with SSRF + TTL.
17
+ * the body isn't a readable stream (older fetch impls).
27
18
  */
28
19
  export async function readBodyWithCap(response, maxBytes, controller) {
29
20
  if (!response.body) {
@@ -64,9 +55,8 @@ export function createRemoteDefinitionFetcher(config) {
64
55
  const logger = config.logger ?? new NoopLogger();
65
56
  return {
66
57
  async load(url) {
67
- // Resolves the host and rejects when any resolved IP is private/loopback/
68
- // link-local — covers literal-encoding bypasses (integer/octal/hex/short-form,
69
- // IPv4-mapped IPv6) and public names that point inward. Throws on rejection.
58
+ // Before the cache read, not after: a URL must never serve warm bytes
59
+ // without passing the guard.
70
60
  await assertSafeRemoteDefinitionUrl(url, logger);
71
61
  const now = config.now();
72
62
  const cached = cache.get(url);
@@ -89,9 +79,9 @@ export function createRemoteDefinitionFetcher(config) {
89
79
  if (Number.isFinite(declared) && declared > config.maxBytes) {
90
80
  throw new Error('Remote definition exceeds size limit');
91
81
  }
92
- // Stream and count rather than `arrayBuffer()` — a missing/lying
82
+ // Stream and count rather than `arrayBuffer()` — a missing or lying
93
83
  // content-length must not let an unbounded body buffer into memory
94
- // before the cap is checked. Abort the moment we cross the limit.
84
+ // before the cap is checked. Aborts the moment we cross the limit.
95
85
  data = await readBodyWithCap(response, config.maxBytes, controller);
96
86
  }
97
87
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"remote-definition.js","sourceRoot":"","sources":["../../src/compute/remote-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAgB,MAAM,mBAAmB,CAAC;AAiC7D,oFAAoF;AACpF,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,QAAkB,EAClB,QAAgB,EAChB,UAA2B;IAE3B,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC5C,IAAI,MAAM,CAAC,UAAU,GAAG,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC1F,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACzC,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,IAAI,EAAE,CAAC;QACb,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,IAAI;YAAE,MAAM;QAChB,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC;QAC1B,IAAI,KAAK,GAAG,QAAQ,EAAE,CAAC;YACtB,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC;IAC5B,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAC5C,MAA8B;IAE9B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmD,CAAC;IACzE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;IAEjD,OAAO;QACN,KAAK,CAAC,IAAI,CAAC,GAAW;YACrB,0EAA0E;YAC1E,+EAA+E;YAC/E,6EAA6E;YAC7E,MAAM,6BAA6B,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAEjD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC1D,OAAO,MAAM,CAAC,IAAI,CAAC;YACpB,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;YAC5E,IAAI,IAAgB,CAAC;YACrB,IAAI,CAAC;gBACJ,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;oBACjC,MAAM,EAAE,UAAU,CAAC,MAAM;oBACzB,QAAQ,EAAE,OAAO;iBACjB,CAAC,CAAC;gBACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBAClB,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;gBACpE,CAAC;gBACD,2DAA2D;gBAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;gBAChE,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;oBAC7D,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;gBACzD,CAAC;gBACD,iEAAiE;gBACjE,mEAAmE;gBACnE,kEAAkE;gBAClE,IAAI,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YACrE,CAAC;oBAAS,CAAC;gBACV,YAAY,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;YAED,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YAEzC,IAAI,KAAK,CAAC,IAAI,GAAG,iBAAiB,EAAE,CAAC;gBACpC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gBACxD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,iBAAiB,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC5C,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC7B,CAAC;YACF,CAAC;YAED,OAAO,IAAI,CAAC;QACb,CAAC;KACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"remote-definition.js","sourceRoot":"","sources":["../../src/compute/remote-definition.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,18 @@
1
1
  import { type ILogger } from '@selvajs/platform';
2
2
  /**
3
- * Synchronous literal pre-filter. Rejects bad schemes, loopback hostnames, and
4
- * any IP literal (in any encoding) that resolves to a blocked address. Does NOT
3
+ * Synchronous literal pre-filter: rejects bad schemes, loopback hostnames, and
4
+ * any IP literal (in any encoding) that maps to a blocked address. Does NOT
5
5
  * perform DNS — a public *name* that points at a private IP passes here and is
6
- * caught by `assertSafeRemoteDefinitionUrl`.
6
+ * caught only by `assertSafeRemoteDefinitionUrl`.
7
7
  */
8
8
  export declare function isSafeRemoteDefinitionUrl(raw: string): boolean;
9
9
  /**
10
10
  * Full SSRF check: literal pre-filter, then resolve every A/AAAA record and
11
11
  * reject if *any* resolved IP is blocked. Returns the original URL on success
12
- * so callers can `await assertSafeRemoteDefinitionUrl(url)` inline. Throws on
13
- * any rejection.
12
+ * so callers can `await assertSafeRemoteDefinitionUrl(url)` inline.
13
+ *
14
+ * Every rejection throws the same generic message so the caller gets no oracle;
15
+ * which host was blocked and by which layer is logged server-side instead.
14
16
  */
15
17
  export declare function assertSafeRemoteDefinitionUrl(raw: string, logger?: ILogger): Promise<string>;
16
18
  //# sourceMappingURL=safe-url.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"safe-url.d.ts","sourceRoot":"","sources":["../../src/compute/safe-url.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AA2H7D;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CA2B9D;AAED;;;;;GAKG;AACH,wBAAsB,6BAA6B,CAClD,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,OAA0B,GAChC,OAAO,CAAC,MAAM,CAAC,CAwCjB"}
1
+ {"version":3,"file":"safe-url.d.ts","sourceRoot":"","sources":["../../src/compute/safe-url.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AA0H7D;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CA2B9D;AAED;;;;;;;GAOG;AACH,wBAAsB,6BAA6B,CAClD,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,OAA0B,GAChC,OAAO,CAAC,MAAM,CAAC,CAqCjB"}