@sriinnu/kosha-discovery 0.5.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 (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +286 -0
  3. package/dist/aliases.d.ts +51 -0
  4. package/dist/aliases.d.ts.map +1 -0
  5. package/dist/aliases.js +115 -0
  6. package/dist/aliases.js.map +1 -0
  7. package/dist/cache.d.ts +57 -0
  8. package/dist/cache.d.ts.map +1 -0
  9. package/dist/cache.js +123 -0
  10. package/dist/cache.js.map +1 -0
  11. package/dist/cli-commands.d.ts +142 -0
  12. package/dist/cli-commands.d.ts.map +1 -0
  13. package/dist/cli-commands.js +746 -0
  14. package/dist/cli-commands.js.map +1 -0
  15. package/dist/cli-format.d.ts +119 -0
  16. package/dist/cli-format.d.ts.map +1 -0
  17. package/dist/cli-format.js +171 -0
  18. package/dist/cli-format.js.map +1 -0
  19. package/dist/cli.d.ts +15 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +143 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/credentials/index.d.ts +2 -0
  24. package/dist/credentials/index.d.ts.map +1 -0
  25. package/dist/credentials/index.js +2 -0
  26. package/dist/credentials/index.js.map +1 -0
  27. package/dist/credentials/resolver.d.ts +170 -0
  28. package/dist/credentials/resolver.d.ts.map +1 -0
  29. package/dist/credentials/resolver.js +597 -0
  30. package/dist/credentials/resolver.js.map +1 -0
  31. package/dist/discovery/anthropic.d.ts +49 -0
  32. package/dist/discovery/anthropic.d.ts.map +1 -0
  33. package/dist/discovery/anthropic.js +101 -0
  34. package/dist/discovery/anthropic.js.map +1 -0
  35. package/dist/discovery/base.d.ts +59 -0
  36. package/dist/discovery/base.d.ts.map +1 -0
  37. package/dist/discovery/base.js +123 -0
  38. package/dist/discovery/base.js.map +1 -0
  39. package/dist/discovery/bedrock.d.ts +122 -0
  40. package/dist/discovery/bedrock.d.ts.map +1 -0
  41. package/dist/discovery/bedrock.js +356 -0
  42. package/dist/discovery/bedrock.js.map +1 -0
  43. package/dist/discovery/cerebras.d.ts +43 -0
  44. package/dist/discovery/cerebras.d.ts.map +1 -0
  45. package/dist/discovery/cerebras.js +65 -0
  46. package/dist/discovery/cerebras.js.map +1 -0
  47. package/dist/discovery/cohere.d.ts +57 -0
  48. package/dist/discovery/cohere.d.ts.map +1 -0
  49. package/dist/discovery/cohere.js +82 -0
  50. package/dist/discovery/cohere.js.map +1 -0
  51. package/dist/discovery/deepinfra.d.ts +41 -0
  52. package/dist/discovery/deepinfra.d.ts.map +1 -0
  53. package/dist/discovery/deepinfra.js +86 -0
  54. package/dist/discovery/deepinfra.js.map +1 -0
  55. package/dist/discovery/deepseek.d.ts +17 -0
  56. package/dist/discovery/deepseek.d.ts.map +1 -0
  57. package/dist/discovery/deepseek.js +45 -0
  58. package/dist/discovery/deepseek.js.map +1 -0
  59. package/dist/discovery/fireworks.d.ts +48 -0
  60. package/dist/discovery/fireworks.d.ts.map +1 -0
  61. package/dist/discovery/fireworks.js +101 -0
  62. package/dist/discovery/fireworks.js.map +1 -0
  63. package/dist/discovery/glm.d.ts +17 -0
  64. package/dist/discovery/glm.d.ts.map +1 -0
  65. package/dist/discovery/glm.js +52 -0
  66. package/dist/discovery/glm.js.map +1 -0
  67. package/dist/discovery/google.d.ts +53 -0
  68. package/dist/discovery/google.d.ts.map +1 -0
  69. package/dist/discovery/google.js +122 -0
  70. package/dist/discovery/google.js.map +1 -0
  71. package/dist/discovery/groq.d.ts +44 -0
  72. package/dist/discovery/groq.d.ts.map +1 -0
  73. package/dist/discovery/groq.js +89 -0
  74. package/dist/discovery/groq.js.map +1 -0
  75. package/dist/discovery/index.d.ts +48 -0
  76. package/dist/discovery/index.d.ts.map +1 -0
  77. package/dist/discovery/index.js +116 -0
  78. package/dist/discovery/index.js.map +1 -0
  79. package/dist/discovery/llama-cpp.d.ts +45 -0
  80. package/dist/discovery/llama-cpp.d.ts.map +1 -0
  81. package/dist/discovery/llama-cpp.js +137 -0
  82. package/dist/discovery/llama-cpp.js.map +1 -0
  83. package/dist/discovery/minimax.d.ts +16 -0
  84. package/dist/discovery/minimax.d.ts.map +1 -0
  85. package/dist/discovery/minimax.js +56 -0
  86. package/dist/discovery/minimax.js.map +1 -0
  87. package/dist/discovery/mistral.d.ts +45 -0
  88. package/dist/discovery/mistral.d.ts.map +1 -0
  89. package/dist/discovery/mistral.js +82 -0
  90. package/dist/discovery/mistral.js.map +1 -0
  91. package/dist/discovery/moonshot.d.ts +16 -0
  92. package/dist/discovery/moonshot.d.ts.map +1 -0
  93. package/dist/discovery/moonshot.js +46 -0
  94. package/dist/discovery/moonshot.js.map +1 -0
  95. package/dist/discovery/nvidia.d.ts +60 -0
  96. package/dist/discovery/nvidia.d.ts.map +1 -0
  97. package/dist/discovery/nvidia.js +145 -0
  98. package/dist/discovery/nvidia.js.map +1 -0
  99. package/dist/discovery/ollama.d.ts +57 -0
  100. package/dist/discovery/ollama.d.ts.map +1 -0
  101. package/dist/discovery/ollama.js +151 -0
  102. package/dist/discovery/ollama.js.map +1 -0
  103. package/dist/discovery/openai-compatible.d.ts +126 -0
  104. package/dist/discovery/openai-compatible.d.ts.map +1 -0
  105. package/dist/discovery/openai-compatible.js +161 -0
  106. package/dist/discovery/openai-compatible.js.map +1 -0
  107. package/dist/discovery/openai.d.ts +62 -0
  108. package/dist/discovery/openai.d.ts.map +1 -0
  109. package/dist/discovery/openai.js +157 -0
  110. package/dist/discovery/openai.js.map +1 -0
  111. package/dist/discovery/openrouter.d.ts +78 -0
  112. package/dist/discovery/openrouter.d.ts.map +1 -0
  113. package/dist/discovery/openrouter.js +184 -0
  114. package/dist/discovery/openrouter.js.map +1 -0
  115. package/dist/discovery/perplexity.d.ts +66 -0
  116. package/dist/discovery/perplexity.d.ts.map +1 -0
  117. package/dist/discovery/perplexity.js +86 -0
  118. package/dist/discovery/perplexity.js.map +1 -0
  119. package/dist/discovery/together.d.ts +41 -0
  120. package/dist/discovery/together.d.ts.map +1 -0
  121. package/dist/discovery/together.js +88 -0
  122. package/dist/discovery/together.js.map +1 -0
  123. package/dist/discovery/vertex.d.ts +146 -0
  124. package/dist/discovery/vertex.d.ts.map +1 -0
  125. package/dist/discovery/vertex.js +371 -0
  126. package/dist/discovery/vertex.js.map +1 -0
  127. package/dist/discovery/zai.d.ts +17 -0
  128. package/dist/discovery/zai.d.ts.map +1 -0
  129. package/dist/discovery/zai.js +51 -0
  130. package/dist/discovery/zai.js.map +1 -0
  131. package/dist/discovery-contract.d.ts +186 -0
  132. package/dist/discovery-contract.d.ts.map +1 -0
  133. package/dist/discovery-contract.js +115 -0
  134. package/dist/discovery-contract.js.map +1 -0
  135. package/dist/discovery-routes.d.ts +14 -0
  136. package/dist/discovery-routes.d.ts.map +1 -0
  137. package/dist/discovery-routes.js +98 -0
  138. package/dist/discovery-routes.js.map +1 -0
  139. package/dist/enrichment/index.d.ts +2 -0
  140. package/dist/enrichment/index.d.ts.map +1 -0
  141. package/dist/enrichment/index.js +2 -0
  142. package/dist/enrichment/index.js.map +1 -0
  143. package/dist/enrichment/litellm.d.ts +52 -0
  144. package/dist/enrichment/litellm.d.ts.map +1 -0
  145. package/dist/enrichment/litellm.js +205 -0
  146. package/dist/enrichment/litellm.js.map +1 -0
  147. package/dist/index.d.ts +30 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +33 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/normalize.d.ts +64 -0
  152. package/dist/normalize.d.ts.map +1 -0
  153. package/dist/normalize.js +166 -0
  154. package/dist/normalize.js.map +1 -0
  155. package/dist/provider-catalog.d.ts +62 -0
  156. package/dist/provider-catalog.d.ts.map +1 -0
  157. package/dist/provider-catalog.js +331 -0
  158. package/dist/provider-catalog.js.map +1 -0
  159. package/dist/registry-delta.d.ts +17 -0
  160. package/dist/registry-delta.d.ts.map +1 -0
  161. package/dist/registry-delta.js +74 -0
  162. package/dist/registry-delta.js.map +1 -0
  163. package/dist/registry-discovery.d.ts +66 -0
  164. package/dist/registry-discovery.d.ts.map +1 -0
  165. package/dist/registry-discovery.js +339 -0
  166. package/dist/registry-discovery.js.map +1 -0
  167. package/dist/registry-query.d.ts +74 -0
  168. package/dist/registry-query.d.ts.map +1 -0
  169. package/dist/registry-query.js +310 -0
  170. package/dist/registry-query.js.map +1 -0
  171. package/dist/registry-runtime.d.ts +48 -0
  172. package/dist/registry-runtime.d.ts.map +1 -0
  173. package/dist/registry-runtime.js +255 -0
  174. package/dist/registry-runtime.js.map +1 -0
  175. package/dist/registry-selection.d.ts +31 -0
  176. package/dist/registry-selection.d.ts.map +1 -0
  177. package/dist/registry-selection.js +167 -0
  178. package/dist/registry-selection.js.map +1 -0
  179. package/dist/registry-state.d.ts +92 -0
  180. package/dist/registry-state.d.ts.map +1 -0
  181. package/dist/registry-state.js +35 -0
  182. package/dist/registry-state.js.map +1 -0
  183. package/dist/registry.d.ts +127 -0
  184. package/dist/registry.d.ts.map +1 -0
  185. package/dist/registry.js +248 -0
  186. package/dist/registry.js.map +1 -0
  187. package/dist/resilience.d.ts +212 -0
  188. package/dist/resilience.d.ts.map +1 -0
  189. package/dist/resilience.js +292 -0
  190. package/dist/resilience.js.map +1 -0
  191. package/dist/security.d.ts +44 -0
  192. package/dist/security.d.ts.map +1 -0
  193. package/dist/security.js +184 -0
  194. package/dist/security.js.map +1 -0
  195. package/dist/server.d.ts +41 -0
  196. package/dist/server.d.ts.map +1 -0
  197. package/dist/server.js +320 -0
  198. package/dist/server.js.map +1 -0
  199. package/dist/shell-safe.d.ts +15 -0
  200. package/dist/shell-safe.d.ts.map +1 -0
  201. package/dist/shell-safe.js +20 -0
  202. package/dist/shell-safe.js.map +1 -0
  203. package/dist/types.d.ts +365 -0
  204. package/dist/types.d.ts.map +1 -0
  205. package/dist/types.js +10 -0
  206. package/dist/types.js.map +1 -0
  207. package/logo.svg +81 -0
  208. package/package.json +85 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ * kosha-discovery — Resilience primitives.
3
+ *
4
+ * Provides a {@link CircuitBreaker} for per-provider fault isolation,
5
+ * a {@link HealthTracker} that manages breakers for all known providers,
6
+ * and a {@link StaleCachePolicy} that implements stale-while-revalidate
7
+ * semantics on top of {@link KoshaCache}.
8
+ *
9
+ * The module is self-contained with no side-effects on import.
10
+ * @module
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // CircuitBreaker
14
+ // ---------------------------------------------------------------------------
15
+ /**
16
+ * Per-provider circuit breaker with three states.
17
+ *
18
+ * - **closed** — Normal operation. All requests are allowed. Consecutive
19
+ * failures are counted; once the {@link CircuitBreakerOptions.failureThreshold}
20
+ * is reached the circuit transitions to `open`.
21
+ * - **open** — Requests are rejected immediately (canExecute returns false).
22
+ * After {@link CircuitBreakerOptions.resetTimeoutMs} has elapsed the circuit
23
+ * transitions to `half-open` so a single probe request can be attempted.
24
+ * - **half-open** — Exactly one request is let through. A success closes the
25
+ * circuit; a failure re-opens it and resets the timeout.
26
+ *
27
+ * @example
28
+ * const cb = new CircuitBreaker("anthropic", { failureThreshold: 5 });
29
+ * if (cb.canExecute()) {
30
+ * try {
31
+ * const result = await callApi();
32
+ * cb.onSuccess();
33
+ * } catch (err) {
34
+ * cb.onFailure(err.message);
35
+ * }
36
+ * }
37
+ */
38
+ export class CircuitBreaker {
39
+ providerId;
40
+ options;
41
+ state = "closed";
42
+ failureCount = 0;
43
+ successCount = 0;
44
+ lastFailureTime = 0;
45
+ lastSuccessTime = 0;
46
+ lastError;
47
+ /** Resolved threshold: consecutive failures before opening. */
48
+ failureThreshold;
49
+ /** Resolved open-state duration before allowing a probe. */
50
+ resetTimeoutMs;
51
+ /** Resolved success count in half-open needed to close. */
52
+ halfOpenSuccessThreshold;
53
+ constructor(providerId, options = {}) {
54
+ this.providerId = providerId;
55
+ this.options = options;
56
+ this.failureThreshold = options.failureThreshold ?? 3;
57
+ this.resetTimeoutMs = options.resetTimeoutMs ?? 60_000;
58
+ this.halfOpenSuccessThreshold = options.halfOpenSuccessThreshold ?? 1;
59
+ }
60
+ /**
61
+ * Check whether a request should be allowed through.
62
+ *
63
+ * - `closed` → always true.
64
+ * - `open` → false, unless the reset timeout has elapsed, in which case
65
+ * the circuit transitions to `half-open` and returns true for the probe.
66
+ * - `half-open` → true (the probe request is already in flight).
67
+ */
68
+ canExecute() {
69
+ if (this.state === "closed") {
70
+ return true;
71
+ }
72
+ if (this.state === "open") {
73
+ const elapsed = Date.now() - this.lastFailureTime;
74
+ if (elapsed >= this.resetTimeoutMs) {
75
+ // Transition to half-open: allow a single probe
76
+ this.state = "half-open";
77
+ this.successCount = 0;
78
+ return true;
79
+ }
80
+ return false;
81
+ }
82
+ // half-open: allow the probe through
83
+ return true;
84
+ }
85
+ /**
86
+ * Record a successful API call.
87
+ *
88
+ * In `half-open` state, once enough successes accumulate (per
89
+ * {@link CircuitBreakerOptions.halfOpenSuccessThreshold}) the circuit closes.
90
+ * In `closed` state the failure counter is reset.
91
+ */
92
+ onSuccess() {
93
+ this.lastSuccessTime = Date.now();
94
+ if (this.state === "half-open") {
95
+ this.successCount++;
96
+ if (this.successCount >= this.halfOpenSuccessThreshold) {
97
+ this.transitionToClosed();
98
+ }
99
+ return;
100
+ }
101
+ // In closed state reset the failure counter on any success
102
+ if (this.state === "closed") {
103
+ this.failureCount = 0;
104
+ }
105
+ }
106
+ /**
107
+ * Record a failed API call.
108
+ *
109
+ * In `closed` state, increments the failure counter and opens the circuit
110
+ * when the threshold is reached. In `half-open` state, immediately
111
+ * re-opens the circuit.
112
+ *
113
+ * @param error - Optional error message to store for diagnostics.
114
+ */
115
+ onFailure(error) {
116
+ this.lastFailureTime = Date.now();
117
+ this.lastError = error;
118
+ if (this.state === "half-open") {
119
+ // Probe failed — reopen immediately
120
+ this.transitionToOpen();
121
+ return;
122
+ }
123
+ if (this.state === "closed") {
124
+ this.failureCount++;
125
+ if (this.failureCount >= this.failureThreshold) {
126
+ this.transitionToOpen();
127
+ }
128
+ }
129
+ }
130
+ /**
131
+ * Return a point-in-time health snapshot for this provider's circuit.
132
+ */
133
+ health() {
134
+ return {
135
+ providerId: this.providerId,
136
+ state: this.state,
137
+ failureCount: this.failureCount,
138
+ lastFailureTime: this.lastFailureTime,
139
+ lastError: this.lastError,
140
+ lastSuccessTime: this.lastSuccessTime,
141
+ };
142
+ }
143
+ /**
144
+ * Force the circuit back to `closed` state, resetting all counters.
145
+ * Useful for manual recovery or test teardown.
146
+ */
147
+ reset() {
148
+ this.transitionToClosed();
149
+ this.lastFailureTime = 0;
150
+ this.lastSuccessTime = 0;
151
+ this.lastError = undefined;
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ // Private helpers
155
+ // ---------------------------------------------------------------------------
156
+ transitionToOpen() {
157
+ this.state = "open";
158
+ this.successCount = 0;
159
+ }
160
+ transitionToClosed() {
161
+ this.state = "closed";
162
+ this.failureCount = 0;
163
+ this.successCount = 0;
164
+ }
165
+ }
166
+ // ---------------------------------------------------------------------------
167
+ // HealthTracker
168
+ // ---------------------------------------------------------------------------
169
+ /**
170
+ * Manages {@link CircuitBreaker} instances for all tracked providers.
171
+ *
172
+ * Breakers are created lazily on first access via {@link breaker}.
173
+ *
174
+ * @example
175
+ * const tracker = new HealthTracker();
176
+ * const cb = tracker.breaker("anthropic");
177
+ * if (cb.canExecute()) { ... }
178
+ */
179
+ export class HealthTracker {
180
+ breakers = new Map();
181
+ /**
182
+ * Retrieve the {@link CircuitBreaker} for the given provider, creating
183
+ * one with default options if it does not yet exist.
184
+ *
185
+ * @param providerId - Provider slug (e.g. `"anthropic"`).
186
+ * @param options - Options forwarded to a newly created breaker only.
187
+ */
188
+ breaker(providerId, options) {
189
+ let cb = this.breakers.get(providerId);
190
+ if (!cb) {
191
+ cb = new CircuitBreaker(providerId, options);
192
+ this.breakers.set(providerId, cb);
193
+ }
194
+ return cb;
195
+ }
196
+ /**
197
+ * Return health snapshots for every tracked provider, sorted by provider ID.
198
+ */
199
+ healthReport() {
200
+ return Array.from(this.breakers.values())
201
+ .map((cb) => cb.health())
202
+ .sort((a, b) => a.providerId.localeCompare(b.providerId));
203
+ }
204
+ /**
205
+ * Return provider IDs whose circuit is `closed` or `half-open`
206
+ * (i.e. requests are currently being allowed through).
207
+ */
208
+ availableProviders() {
209
+ return Array.from(this.breakers.values())
210
+ .filter((cb) => cb.canExecute())
211
+ .map((cb) => cb.providerId)
212
+ .sort();
213
+ }
214
+ /**
215
+ * Return provider IDs whose circuit is `open`
216
+ * (i.e. requests are currently being rejected).
217
+ */
218
+ downProviders() {
219
+ return Array.from(this.breakers.values())
220
+ .filter((cb) => !cb.canExecute())
221
+ .map((cb) => cb.providerId)
222
+ .sort();
223
+ }
224
+ /**
225
+ * Reset all tracked circuit breakers to `closed` state.
226
+ */
227
+ resetAll() {
228
+ for (const cb of this.breakers.values()) {
229
+ cb.reset();
230
+ }
231
+ }
232
+ }
233
+ // ---------------------------------------------------------------------------
234
+ // StaleCachePolicy
235
+ // ---------------------------------------------------------------------------
236
+ /**
237
+ * Utility that wraps {@link KoshaCache} reads to implement
238
+ * stale-while-revalidate semantics.
239
+ *
240
+ * Unlike the registry's normal cache path (which returns `null` for expired
241
+ * entries), this policy always returns whatever was cached along with a
242
+ * `stale` flag. This lets callers serve the old data immediately while
243
+ * triggering a background refresh.
244
+ *
245
+ * Returns `null` only when the cache has never held a value for the key.
246
+ *
247
+ * @example
248
+ * const result = await StaleCachePolicy.getWithStale<ModelCard[]>(cache, "provider_anthropic");
249
+ * if (result) {
250
+ * serveToClient(result.data); // always fast
251
+ * if (result.stale) triggerBackgroundRefresh();
252
+ * }
253
+ */
254
+ export class StaleCachePolicy {
255
+ /**
256
+ * Fetch a cached value regardless of its expiry, annotating the result
257
+ * with staleness metadata.
258
+ *
259
+ * @param cache - The {@link KoshaCache} instance to read from.
260
+ * @param key - Cache key to look up.
261
+ * @returns A {@link StaleResult} when any cached value exists, or `null`
262
+ * when the key has never been written.
263
+ */
264
+ static async getWithStale(cache, key) {
265
+ // Use the raw get() which returns the entry regardless of TTL.
266
+ // TTL checking is done separately so we can set the stale flag.
267
+ const entry = await cache.get(key);
268
+ if (entry === null) {
269
+ return null;
270
+ }
271
+ const now = Date.now();
272
+ const age = now - entry.timestamp;
273
+ // We need to determine staleness. KoshaCache.isExpired requires a TTL,
274
+ // but StaleCachePolicy is TTL-agnostic — we expose the raw age and let
275
+ // the caller decide. We set stale=true when age > 0 to signal that the
276
+ // entry is not brand-new; callers that want TTL-based staleness should
277
+ // compare age against their own threshold.
278
+ //
279
+ // A more useful signal: mark stale when the entry is older than the
280
+ // default 24 h cache TTL used by the registry. Callers with custom TTLs
281
+ // can compare age directly.
282
+ const DEFAULT_TTL_MS = 86_400_000;
283
+ const stale = age > DEFAULT_TTL_MS;
284
+ return {
285
+ data: entry.data,
286
+ stale,
287
+ age,
288
+ cachedAt: entry.timestamp,
289
+ };
290
+ }
291
+ }
292
+ //# sourceMappingURL=resilience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilience.js","sourceRoot":"","sources":["../src/resilience.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * kosha-discovery — Security guardrails for external data.
3
+ *
4
+ * Centralised defences against supply-chain data injection.
5
+ * Every external data ingestion point (HTTP responses, CLI output,
6
+ * cache reads) MUST pass through {@link assertCleanPayload} before
7
+ * the parsed payload is used.
8
+ *
9
+ * Threat catalogue:
10
+ * - **base64** Encoded credential exfiltration (LiteLLM incident)
11
+ * - **credential_leak** Leaked API keys / tokens (OpenAI, AWS, GitHub, Slack, Google, etc.)
12
+ * - **script_injection** XSS / HTML injection via `<script>`, `javascript:`, event handlers
13
+ * - **shell_injection** Command injection via `$(…)`, backtick blocks, pipe/chain to shells
14
+ * - **data_uri** Executable data URIs (`data:text/html`, `data:application/…`)
15
+ * - **null_byte** Null-byte injection to bypass string validators
16
+ * - **proto_pollution** Prototype pollution via `__proto__` keys
17
+ * - **hex_payload** Long hex-encoded binary blobs (potential obfuscated payloads)
18
+ * - **oversized_string** Unreasonably long strings for model metadata (>2048 chars)
19
+ *
20
+ * @module
21
+ */
22
+ export interface ThreatHit {
23
+ threat: string;
24
+ path: string;
25
+ value: string;
26
+ }
27
+ /**
28
+ * Deep-scan a parsed JSON payload for security threats in keys and string
29
+ * values at any nesting depth.
30
+ *
31
+ * @returns The first {@link ThreatHit} found, or `undefined` if clean.
32
+ */
33
+ export declare function scanPayload(obj: unknown, path?: string): ThreatHit | undefined;
34
+ /**
35
+ * Throw if the given payload contains **any** security threat — base64,
36
+ * leaked credentials, script injection, shell injection, data URIs,
37
+ * null bytes, prototype pollution keys, hex blobs, or oversized strings.
38
+ *
39
+ * @param data - Parsed JSON payload to scan.
40
+ * @param source - Human-readable label for error messages (e.g. "litellm", "OpenAI API").
41
+ * @throws {Error} with the threat name and offending path.
42
+ */
43
+ export declare function assertCleanPayload(data: unknown, source: string): void;
44
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAiIH,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACd;AAMD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,SAAK,GAAG,SAAS,GAAG,SAAS,CA8B1E;AAMD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOtE"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * kosha-discovery — Security guardrails for external data.
3
+ *
4
+ * Centralised defences against supply-chain data injection.
5
+ * Every external data ingestion point (HTTP responses, CLI output,
6
+ * cache reads) MUST pass through {@link assertCleanPayload} before
7
+ * the parsed payload is used.
8
+ *
9
+ * Threat catalogue:
10
+ * - **base64** Encoded credential exfiltration (LiteLLM incident)
11
+ * - **credential_leak** Leaked API keys / tokens (OpenAI, AWS, GitHub, Slack, Google, etc.)
12
+ * - **script_injection** XSS / HTML injection via `<script>`, `javascript:`, event handlers
13
+ * - **shell_injection** Command injection via `$(…)`, backtick blocks, pipe/chain to shells
14
+ * - **data_uri** Executable data URIs (`data:text/html`, `data:application/…`)
15
+ * - **null_byte** Null-byte injection to bypass string validators
16
+ * - **proto_pollution** Prototype pollution via `__proto__` keys
17
+ * - **hex_payload** Long hex-encoded binary blobs (potential obfuscated payloads)
18
+ * - **oversized_string** Unreasonably long strings for model metadata (>2048 chars)
19
+ *
20
+ * @module
21
+ */
22
+ /** 32+ chars of pure base64 alphabet with optional `=` padding. */
23
+ const BASE64_PATTERN = /^[A-Za-z0-9+/]{32,}={0,2}$/;
24
+ /** Known credential prefixes — each must be followed by enough chars to be a real key. */
25
+ const CREDENTIAL_PATTERNS = [
26
+ /\bsk-[A-Za-z0-9]{20,}/, // OpenAI API keys
27
+ /\bsk-proj-[A-Za-z0-9]{20,}/, // OpenAI project keys
28
+ /\bAKIA[A-Z0-9]{16}/, // AWS access key IDs
29
+ /\bghp_[A-Za-z0-9]{36,}/, // GitHub personal access tokens
30
+ /\bgho_[A-Za-z0-9]{36,}/, // GitHub OAuth tokens
31
+ /\bghs_[A-Za-z0-9]{36,}/, // GitHub app installation tokens
32
+ /\bxoxb-[A-Za-z0-9\-]{24,}/, // Slack bot tokens
33
+ /\bxoxp-[A-Za-z0-9\-]{24,}/, // Slack user tokens
34
+ /\bAIza[A-Za-z0-9_\-]{30,}/, // Google API keys
35
+ /\bya29\.[A-Za-z0-9_\-]{50,}/, // Google OAuth access tokens
36
+ /\bglpat-[A-Za-z0-9\-]{20,}/, // GitLab personal access tokens
37
+ /\bnpm_[A-Za-z0-9]{36,}/, // npm tokens
38
+ /\bpypi-[A-Za-z0-9]{50,}/, // PyPI tokens
39
+ /\bhf_[A-Za-z0-9]{30,}/, // Hugging Face tokens
40
+ /\bBearer\s+[A-Za-z0-9._\-]{40,}/, // Generic bearer tokens in data
41
+ ];
42
+ /** Script / HTML injection patterns (case-insensitive). */
43
+ const SCRIPT_PATTERN = /<script[\s>]/i;
44
+ const EVENT_HANDLER_PATTERN = /\bon\w+\s*=\s*["'`]/i;
45
+ const JAVASCRIPT_URI_PATTERN = /javascript\s*:/i;
46
+ /** Shell injection — command substitution and chaining to exfiltration tools. */
47
+ const SHELL_INJECTION_PATTERNS = [
48
+ /\$\([^)]+\)/, // $(command)
49
+ /`[^`]{2,}`/, // `command` (backtick execution, 2+ chars to avoid markdown)
50
+ /[;|&]\s*(curl|wget|nc|bash|sh|python|node|ruby|perl|php)\b/i, // chain to dangerous commands
51
+ ];
52
+ /** Data URIs that can carry executable content. */
53
+ const DATA_URI_PATTERN = /^data:(text\/html|application\/)/i;
54
+ /** Null byte in any form. */
55
+ const NULL_BYTE_PATTERN = /\x00|\\x00|\\u0000|%00/;
56
+ /** Long hex-only blobs (64+ hex chars) — potential obfuscated payloads. */
57
+ const HEX_BLOB_PATTERN = /^(0x)?[0-9a-fA-F]{64,}$/;
58
+ /** Prototype pollution key — `__proto__` is the only key that directly
59
+ * triggers pollution on `JSON.parse` output when merged naively.
60
+ * `constructor` and `prototype` are common legitimate JSON keys and
61
+ * are intentionally not flagged to avoid false positives. */
62
+ const PROTO_POLLUTION_KEY = "__proto__";
63
+ /** Maximum reasonable string length for model metadata values. */
64
+ const MAX_STRING_LENGTH = 2048;
65
+ // ---------------------------------------------------------------------------
66
+ // Threat registry — evaluated once per string value during scan.
67
+ // ---------------------------------------------------------------------------
68
+ // Ordered most-specific first → broadest last. This ensures that a leaked
69
+ // API key (which is also valid base64) reports "credential_leak" rather than
70
+ // the generic "base64" catch-all.
71
+ const VALUE_THREATS = [
72
+ {
73
+ name: "null_byte",
74
+ test: (v) => NULL_BYTE_PATTERN.test(v),
75
+ },
76
+ {
77
+ name: "credential_leak",
78
+ test: (v) => CREDENTIAL_PATTERNS.some((p) => p.test(v)),
79
+ },
80
+ {
81
+ name: "script_injection",
82
+ test: (v) => SCRIPT_PATTERN.test(v) || EVENT_HANDLER_PATTERN.test(v) || JAVASCRIPT_URI_PATTERN.test(v),
83
+ },
84
+ {
85
+ name: "shell_injection",
86
+ test: (v) => SHELL_INJECTION_PATTERNS.some((p) => p.test(v)),
87
+ },
88
+ {
89
+ name: "data_uri",
90
+ test: (v) => DATA_URI_PATTERN.test(v),
91
+ },
92
+ {
93
+ name: "oversized_string",
94
+ test: (v) => v.length > MAX_STRING_LENGTH,
95
+ },
96
+ {
97
+ name: "hex_payload",
98
+ test: (v) => HEX_BLOB_PATTERN.test(v),
99
+ },
100
+ {
101
+ name: "base64",
102
+ test: (v) => BASE64_PATTERN.test(v),
103
+ },
104
+ ];
105
+ /** Threats checked against object keys specifically. */
106
+ const KEY_THREATS = [
107
+ {
108
+ name: "null_byte",
109
+ test: (v) => NULL_BYTE_PATTERN.test(v),
110
+ },
111
+ {
112
+ name: "proto_pollution",
113
+ test: (v) => v === PROTO_POLLUTION_KEY,
114
+ },
115
+ {
116
+ name: "credential_leak",
117
+ test: (v) => CREDENTIAL_PATTERNS.some((p) => p.test(v)),
118
+ },
119
+ {
120
+ name: "base64",
121
+ test: (v) => BASE64_PATTERN.test(v),
122
+ },
123
+ ];
124
+ // ---------------------------------------------------------------------------
125
+ // Deep scanner
126
+ // ---------------------------------------------------------------------------
127
+ /**
128
+ * Deep-scan a parsed JSON payload for security threats in keys and string
129
+ * values at any nesting depth.
130
+ *
131
+ * @returns The first {@link ThreatHit} found, or `undefined` if clean.
132
+ */
133
+ export function scanPayload(obj, path = "") {
134
+ if (typeof obj === "string") {
135
+ for (const t of VALUE_THREATS) {
136
+ if (t.test(obj)) {
137
+ return { threat: t.name, path, value: obj.length > 80 ? `${obj.slice(0, 80)}…` : obj };
138
+ }
139
+ }
140
+ return undefined;
141
+ }
142
+ if (Array.isArray(obj)) {
143
+ for (let i = 0; i < obj.length; i++) {
144
+ const hit = scanPayload(obj[i], `${path}[${i}]`);
145
+ if (hit)
146
+ return hit;
147
+ }
148
+ return undefined;
149
+ }
150
+ if (obj !== null && typeof obj === "object") {
151
+ for (const key of Object.keys(obj)) {
152
+ // Check the key itself
153
+ for (const t of KEY_THREATS) {
154
+ if (t.test(key)) {
155
+ return { threat: t.name, path: `${path}.${key}`, value: key };
156
+ }
157
+ }
158
+ // Recurse into the value
159
+ const hit = scanPayload(obj[key], `${path}.${key}`);
160
+ if (hit)
161
+ return hit;
162
+ }
163
+ }
164
+ return undefined;
165
+ }
166
+ // ---------------------------------------------------------------------------
167
+ // Public assertion API
168
+ // ---------------------------------------------------------------------------
169
+ /**
170
+ * Throw if the given payload contains **any** security threat — base64,
171
+ * leaked credentials, script injection, shell injection, data URIs,
172
+ * null bytes, prototype pollution keys, hex blobs, or oversized strings.
173
+ *
174
+ * @param data - Parsed JSON payload to scan.
175
+ * @param source - Human-readable label for error messages (e.g. "litellm", "OpenAI API").
176
+ * @throws {Error} with the threat name and offending path.
177
+ */
178
+ export function assertCleanPayload(data, source) {
179
+ const hit = scanPayload(data);
180
+ if (hit) {
181
+ throw new Error(`Rejected ${source} data: ${hit.threat} detected at "${hit.path}" — refusing to load potentially compromised payload`);
182
+ }
183
+ }
184
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * kosha-discovery — HTTP API server (Hono-based).
3
+ *
4
+ * Exposes the model registry over a lightweight REST API so that
5
+ * editor extensions, scripts, and other tools can query discovered
6
+ * models without importing the library directly.
7
+ *
8
+ * Routes:
9
+ * GET /api/models — List models (query: ?provider, ?originProvider, ?mode, ?capability)
10
+ * GET /api/models/cheapest — Cheapest eligible models for a role/capability
11
+ * GET /api/models/:idOrAlias/routes — All provider routes with preferred/direct metadata
12
+ * GET /api/models/:idOrAlias — Get a single model by ID or alias (+ baseUrl/version)
13
+ * GET /api/roles — Provider->model->roles matrix
14
+ * GET /api/providers — List all providers (summary)
15
+ * GET /api/providers/:id — Get a single provider with its models
16
+ * POST /api/refresh — Trigger re-discovery (body: { provider?: string })
17
+ * GET /api/resolve/:alias — Resolve an alias to its canonical model ID
18
+ * GET /api/discovery-errors — Errors from last discovery pass
19
+ * GET /health — Health check
20
+ * @module
21
+ */
22
+ import { Hono } from "hono";
23
+ import { ModelRegistry } from "./registry.js";
24
+ /**
25
+ * Create a Hono application wired to the given {@link ModelRegistry}.
26
+ *
27
+ * The returned app is not yet listening — call `serve()` or mount it
28
+ * inside another Hono app to start accepting requests.
29
+ *
30
+ * @param registry - A pre-populated (or lazy) ModelRegistry instance.
31
+ * @returns A configured Hono app with all kosha REST routes.
32
+ */
33
+ export declare function createServer(registry: ModelRegistry): Hono;
34
+ /**
35
+ * Boot a standalone kosha API server.
36
+ *
37
+ * Runs full discovery, then starts an HTTP listener on the given port.
38
+ * @param port - TCP port to bind (default `3000`, overridable via `PORT` env var).
39
+ */
40
+ export declare function startServer(port?: number): Promise<void>;
41
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAiC9C;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAwP1D;AAMD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB5D"}