@aexol/spectral 0.9.245 → 0.9.247
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/cli.js +6 -0
- package/dist/commands/secret.d.ts +16 -0
- package/dist/commands/secret.d.ts.map +1 -0
- package/dist/commands/secret.js +153 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +21 -0
- package/dist/extensions/list-secrets.d.ts +14 -0
- package/dist/extensions/list-secrets.d.ts.map +1 -0
- package/dist/extensions/list-secrets.js +63 -0
- package/dist/generated/zeus/const.d.ts.map +1 -1
- package/dist/generated/zeus/const.js +16 -0
- package/dist/generated/zeus/index.d.ts +73 -0
- package/dist/generated/zeus/index.d.ts.map +1 -1
- package/dist/relay/routing.d.ts +1 -1
- package/dist/relay/routing.d.ts.map +1 -1
- package/dist/relay/routing.js +93 -17
- package/dist/sdk/coding-agent/utils/shell.d.ts.map +1 -1
- package/dist/sdk/coding-agent/utils/shell.js +15 -1
- package/dist/secrets/index.d.ts +11 -0
- package/dist/secrets/index.d.ts.map +1 -0
- package/dist/secrets/index.js +10 -0
- package/dist/secrets/store.d.ts +109 -0
- package/dist/secrets/store.d.ts.map +1 -0
- package/dist/secrets/store.js +415 -0
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +20 -2
- package/dist/server/attachments/cleanup.d.ts +42 -0
- package/dist/server/attachments/cleanup.d.ts.map +1 -0
- package/dist/server/attachments/cleanup.js +100 -0
- package/dist/server/attachments/persist.d.ts +131 -0
- package/dist/server/attachments/persist.d.ts.map +1 -0
- package/dist/server/attachments/persist.js +406 -0
- package/dist/server/handlers/secrets.d.ts +40 -0
- package/dist/server/handlers/secrets.d.ts.map +1 -0
- package/dist/server/handlers/secrets.js +78 -0
- package/dist/server/session-stream.d.ts +5 -0
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +51 -1
- package/dist/server/storage.d.ts +6 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +9 -0
- package/package.json +1 -1
package/dist/relay/routing.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { type WizardLoginResult } from "./wizard-login.js";
|
|
|
20
20
|
* `id` is populated when the route had an `:id` placeholder.
|
|
21
21
|
*/
|
|
22
22
|
interface RouteMatch {
|
|
23
|
-
route: "list_projects" | "create_project" | "update_project" | "delete_project" | "bind_studio" | "unbind_studio" | "get_project_observations" | "list_project_sessions" | "cleanup_project_sessions" | "cleanup_all_sessions" | "create_session" | "get_session" | "get_session_active_agent" | "update_session_active_agent" | "get_session_messages" | "update_session" | "delete_session" | "get_session_memory" | "get_session_memory_details" | "compact_session" | "remember_and_delete_session" | "fork_session" | "list_path_autocomplete" | "list_directory" | "get_home_directory" | "create_directory" | "search_project_files" | "enqueue_prompt" | "get_prompt_queue" | "remove_prompt" | "clear_prompt_queue" | "list_mcp_status" | "reload_mcp_config" | "refresh_native_extensions" | "refresh_cloud_agents" | "get_settings" | "put_settings" | "get_agent_settings" | "put_agent_settings" | "get_prompt_mutation_settings" | "put_prompt_mutation_settings" | "wizard_login" | "list_agents" | "list_dev_processes" | "start_dev_process" | "kill_dev_process" | "restart_dev_process" | "scan_dev_servers" | "kill_dev_server";
|
|
23
|
+
route: "list_projects" | "create_project" | "update_project" | "delete_project" | "bind_studio" | "unbind_studio" | "get_project_observations" | "list_project_sessions" | "cleanup_project_sessions" | "cleanup_all_sessions" | "create_session" | "get_session" | "get_session_active_agent" | "update_session_active_agent" | "get_session_messages" | "update_session" | "delete_session" | "get_session_memory" | "get_session_memory_details" | "compact_session" | "remember_and_delete_session" | "fork_session" | "list_path_autocomplete" | "list_directory" | "get_home_directory" | "create_directory" | "search_project_files" | "enqueue_prompt" | "get_prompt_queue" | "remove_prompt" | "clear_prompt_queue" | "list_mcp_status" | "reload_mcp_config" | "refresh_native_extensions" | "refresh_cloud_agents" | "get_settings" | "put_settings" | "get_agent_settings" | "put_agent_settings" | "get_prompt_mutation_settings" | "put_prompt_mutation_settings" | "wizard_login" | "list_agents" | "list_dev_processes" | "start_dev_process" | "kill_dev_process" | "restart_dev_process" | "scan_dev_servers" | "kill_dev_server" | "list_secrets" | "create_secret" | "delete_secret";
|
|
24
24
|
id?: string;
|
|
25
25
|
/** Parsed query params, if the path carried a `?...` suffix. */
|
|
26
26
|
query?: URLSearchParams;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/relay/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAKvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAmDzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/relay/routing.ts"],"names":[],"mappings":"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"}
|
package/dist/relay/routing.js
CHANGED
|
@@ -25,7 +25,26 @@ import { handleGetAgentSettingsAsync, handleListAgents, handlePutAgentSettingsAs
|
|
|
25
25
|
import { handleGetSettingsAsync, handlePutSettingsAsync, } from "../server/handlers/settings.js";
|
|
26
26
|
import { handleGetPromptMutationSettings, handlePutPromptMutationSettings, } from "../server/handlers/prompt-mutation-settings.js";
|
|
27
27
|
import { handleKillDevProcess, handleKillDetectedDevServer, handleListDevProcesses, handleRestartDevProcess, handleScanDevServers, handleStartDevProcess, } from "../server/handlers/dev-processes.js";
|
|
28
|
+
import { handleDeleteSecret, handleListSecrets, handleSetSecret, } from "../server/handlers/secrets.js";
|
|
28
29
|
import { startWizardLogin } from "./wizard-login.js";
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Path matching
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
/**
|
|
34
|
+
* `decodeURIComponent` that never throws. Malformed percent-encoding (e.g.
|
|
35
|
+
* `%ZZ`) returns `null` instead of a URIError escaping `matchRoute` — the
|
|
36
|
+
* matcher sits OUTSIDE the REST try/catch, so a synchronous throw here would
|
|
37
|
+
* surface as an unhandled rejection and could take down `spectral serve`.
|
|
38
|
+
* Callers treat `null` as no-match → 404.
|
|
39
|
+
*/
|
|
40
|
+
function decodeSegment(raw) {
|
|
41
|
+
try {
|
|
42
|
+
return decodeURIComponent(raw);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
29
48
|
/**
|
|
30
49
|
* Inline path matcher. Returns `null` for any path/method combination we
|
|
31
50
|
* don't recognise; the caller turns that into a `404 Unknown route`.
|
|
@@ -134,7 +153,9 @@ export function matchRoute(method, path) {
|
|
|
134
153
|
// /api/projects/:id/files/search
|
|
135
154
|
const projectFilesMatch = /^\/api\/projects\/([^/]+)\/files\/search$/.exec(cleanPath);
|
|
136
155
|
if (projectFilesMatch) {
|
|
137
|
-
const id =
|
|
156
|
+
const id = decodeSegment(projectFilesMatch[1]);
|
|
157
|
+
if (id === null)
|
|
158
|
+
return null;
|
|
138
159
|
if (method === "GET")
|
|
139
160
|
return { route: "search_project_files", id, query };
|
|
140
161
|
return null;
|
|
@@ -142,14 +163,18 @@ export function matchRoute(method, path) {
|
|
|
142
163
|
// /api/projects/:id/bind-studio and /api/projects/:id/unbind-studio
|
|
143
164
|
const bindStudioMatch = /^\/api\/projects\/([^/]+)\/bind-studio$/.exec(cleanPath);
|
|
144
165
|
if (bindStudioMatch) {
|
|
145
|
-
const id =
|
|
166
|
+
const id = decodeSegment(bindStudioMatch[1]);
|
|
167
|
+
if (id === null)
|
|
168
|
+
return null;
|
|
146
169
|
if (method === "POST")
|
|
147
170
|
return { route: "bind_studio", id };
|
|
148
171
|
return null;
|
|
149
172
|
}
|
|
150
173
|
const unbindStudioMatch = /^\/api\/projects\/([^/]+)\/unbind-studio$/.exec(cleanPath);
|
|
151
174
|
if (unbindStudioMatch) {
|
|
152
|
-
const id =
|
|
175
|
+
const id = decodeSegment(unbindStudioMatch[1]);
|
|
176
|
+
if (id === null)
|
|
177
|
+
return null;
|
|
153
178
|
if (method === "POST")
|
|
154
179
|
return { route: "unbind_studio", id };
|
|
155
180
|
return null;
|
|
@@ -157,7 +182,9 @@ export function matchRoute(method, path) {
|
|
|
157
182
|
// /api/projects/:id/observations
|
|
158
183
|
const projectObsMatch = /^\/api\/projects\/([^/]+)\/observations$/.exec(cleanPath);
|
|
159
184
|
if (projectObsMatch) {
|
|
160
|
-
const id =
|
|
185
|
+
const id = decodeSegment(projectObsMatch[1]);
|
|
186
|
+
if (id === null)
|
|
187
|
+
return null;
|
|
161
188
|
if (method === "GET")
|
|
162
189
|
return { route: "get_project_observations", id };
|
|
163
190
|
return null;
|
|
@@ -165,7 +192,9 @@ export function matchRoute(method, path) {
|
|
|
165
192
|
// /api/projects/:id and /api/projects/:id/sessions
|
|
166
193
|
const projectMatch = /^\/api\/projects\/([^/]+)(\/sessions)?$/.exec(cleanPath);
|
|
167
194
|
if (projectMatch) {
|
|
168
|
-
const id =
|
|
195
|
+
const id = decodeSegment(projectMatch[1]);
|
|
196
|
+
if (id === null)
|
|
197
|
+
return null;
|
|
169
198
|
const isSessions = projectMatch[2] === "/sessions";
|
|
170
199
|
if (isSessions) {
|
|
171
200
|
if (method === "GET")
|
|
@@ -181,7 +210,9 @@ export function matchRoute(method, path) {
|
|
|
181
210
|
// /api/projects/:id/cleanup-sessions
|
|
182
211
|
const projectCleanupMatch = /^\/api\/projects\/([^/]+)\/cleanup-sessions$/.exec(cleanPath);
|
|
183
212
|
if (projectCleanupMatch) {
|
|
184
|
-
const id =
|
|
213
|
+
const id = decodeSegment(projectCleanupMatch[1]);
|
|
214
|
+
if (id === null)
|
|
215
|
+
return null;
|
|
185
216
|
if (method === "POST")
|
|
186
217
|
return { route: "cleanup_project_sessions", id };
|
|
187
218
|
return null;
|
|
@@ -196,14 +227,18 @@ export function matchRoute(method, path) {
|
|
|
196
227
|
// /api/sessions/:id
|
|
197
228
|
const sessionMessagesMatch = /^\/api\/sessions\/([^/]+)\/messages$/.exec(cleanPath);
|
|
198
229
|
if (sessionMessagesMatch) {
|
|
199
|
-
const id =
|
|
230
|
+
const id = decodeSegment(sessionMessagesMatch[1]);
|
|
231
|
+
if (id === null)
|
|
232
|
+
return null;
|
|
200
233
|
if (method === "GET")
|
|
201
234
|
return { route: "get_session_messages", id, query };
|
|
202
235
|
return null;
|
|
203
236
|
}
|
|
204
237
|
const sessionMatch = /^\/api\/sessions\/([^/]+)$/.exec(cleanPath);
|
|
205
238
|
if (sessionMatch) {
|
|
206
|
-
const id =
|
|
239
|
+
const id = decodeSegment(sessionMatch[1]);
|
|
240
|
+
if (id === null)
|
|
241
|
+
return null;
|
|
207
242
|
if (method === "GET")
|
|
208
243
|
return { route: "get_session", id };
|
|
209
244
|
if (method === "PATCH")
|
|
@@ -214,7 +249,9 @@ export function matchRoute(method, path) {
|
|
|
214
249
|
}
|
|
215
250
|
const sessionAgentMatch = /^\/api\/sessions\/([^/]+)\/active-agent$/.exec(cleanPath);
|
|
216
251
|
if (sessionAgentMatch) {
|
|
217
|
-
const id =
|
|
252
|
+
const id = decodeSegment(sessionAgentMatch[1]);
|
|
253
|
+
if (id === null)
|
|
254
|
+
return null;
|
|
218
255
|
if (method === "GET")
|
|
219
256
|
return { route: "get_session_active_agent", id };
|
|
220
257
|
if (method === "PUT")
|
|
@@ -224,7 +261,9 @@ export function matchRoute(method, path) {
|
|
|
224
261
|
// /api/sessions/:id/memory/details and /api/sessions/:id/memory and /api/sessions/:id/compact
|
|
225
262
|
const sessionActionMatch = /^\/api\/sessions\/([^/]+)\/(memory(?:\/details)?|compact)$/.exec(cleanPath);
|
|
226
263
|
if (sessionActionMatch) {
|
|
227
|
-
const id =
|
|
264
|
+
const id = decodeSegment(sessionActionMatch[1]);
|
|
265
|
+
if (id === null)
|
|
266
|
+
return null;
|
|
228
267
|
const action = sessionActionMatch[2];
|
|
229
268
|
if (action === "memory" && method === "GET")
|
|
230
269
|
return { route: "get_session_memory", id };
|
|
@@ -237,7 +276,9 @@ export function matchRoute(method, path) {
|
|
|
237
276
|
// /api/sessions/:id/remember-and-delete
|
|
238
277
|
const rememberDeleteMatch = /^\/api\/sessions\/([^/]+)\/remember-and-delete$/.exec(cleanPath);
|
|
239
278
|
if (rememberDeleteMatch) {
|
|
240
|
-
const id =
|
|
279
|
+
const id = decodeSegment(rememberDeleteMatch[1]);
|
|
280
|
+
if (id === null)
|
|
281
|
+
return null;
|
|
241
282
|
if (method === "POST")
|
|
242
283
|
return { route: "remember_and_delete_session", id };
|
|
243
284
|
return null;
|
|
@@ -245,7 +286,9 @@ export function matchRoute(method, path) {
|
|
|
245
286
|
// /api/sessions/:id/fork
|
|
246
287
|
const forkMatch = /^\/api\/sessions\/([^/]+)\/fork$/.exec(cleanPath);
|
|
247
288
|
if (forkMatch) {
|
|
248
|
-
const id =
|
|
289
|
+
const id = decodeSegment(forkMatch[1]);
|
|
290
|
+
if (id === null)
|
|
291
|
+
return null;
|
|
249
292
|
if (method === "POST")
|
|
250
293
|
return { route: "fork_session", id };
|
|
251
294
|
return null;
|
|
@@ -253,8 +296,12 @@ export function matchRoute(method, path) {
|
|
|
253
296
|
// /api/sessions/:id/queue and /api/sessions/:id/queue/:itemId
|
|
254
297
|
const queueMatch = /^\/api\/sessions\/([^/]+)\/queue(\/([^/]+))?$/.exec(cleanPath);
|
|
255
298
|
if (queueMatch) {
|
|
256
|
-
const id =
|
|
257
|
-
|
|
299
|
+
const id = decodeSegment(queueMatch[1]);
|
|
300
|
+
if (id === null)
|
|
301
|
+
return null;
|
|
302
|
+
const itemId = queueMatch[3] ? decodeSegment(queueMatch[3]) : undefined;
|
|
303
|
+
if (itemId === null)
|
|
304
|
+
return null;
|
|
258
305
|
if (!itemId) {
|
|
259
306
|
if (method === "GET")
|
|
260
307
|
return { route: "get_prompt_queue", id };
|
|
@@ -279,14 +326,18 @@ export function matchRoute(method, path) {
|
|
|
279
326
|
}
|
|
280
327
|
const restartDevProcessMatch = /^\/api\/dev-processes\/([^/]+)\/restart$/.exec(cleanPath);
|
|
281
328
|
if (restartDevProcessMatch) {
|
|
282
|
-
const id =
|
|
329
|
+
const id = decodeSegment(restartDevProcessMatch[1]);
|
|
330
|
+
if (id === null)
|
|
331
|
+
return null;
|
|
283
332
|
if (method === "POST")
|
|
284
333
|
return { route: "restart_dev_process", id };
|
|
285
334
|
return null;
|
|
286
335
|
}
|
|
287
336
|
const devProcessMatch = /^\/api\/dev-processes\/([^/]+)$/.exec(cleanPath);
|
|
288
337
|
if (devProcessMatch) {
|
|
289
|
-
const id =
|
|
338
|
+
const id = decodeSegment(devProcessMatch[1]);
|
|
339
|
+
if (id === null)
|
|
340
|
+
return null;
|
|
290
341
|
if (method === "DELETE")
|
|
291
342
|
return { route: "kill_dev_process", id };
|
|
292
343
|
return null;
|
|
@@ -299,7 +350,9 @@ export function matchRoute(method, path) {
|
|
|
299
350
|
}
|
|
300
351
|
const devServerMatch = /^\/api\/dev-servers\/([^/]+)$/.exec(cleanPath);
|
|
301
352
|
if (devServerMatch) {
|
|
302
|
-
const id =
|
|
353
|
+
const id = decodeSegment(devServerMatch[1]);
|
|
354
|
+
if (id === null)
|
|
355
|
+
return null;
|
|
303
356
|
if (method === "DELETE") {
|
|
304
357
|
const rawPort = query?.get("port");
|
|
305
358
|
const parsedPort = rawPort === null ? Number.NaN : Number(rawPort);
|
|
@@ -310,6 +363,23 @@ export function matchRoute(method, path) {
|
|
|
310
363
|
}
|
|
311
364
|
return null;
|
|
312
365
|
}
|
|
366
|
+
// /api/secrets and /api/secrets/:name
|
|
367
|
+
if (cleanPath === "/api/secrets") {
|
|
368
|
+
if (method === "GET")
|
|
369
|
+
return { route: "list_secrets" };
|
|
370
|
+
if (method === "POST")
|
|
371
|
+
return { route: "create_secret" };
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
const secretMatch = /^\/api\/secrets\/([^/]+)$/.exec(cleanPath);
|
|
375
|
+
if (secretMatch) {
|
|
376
|
+
const id = decodeSegment(secretMatch[1]);
|
|
377
|
+
if (id === null)
|
|
378
|
+
return null;
|
|
379
|
+
if (method === "DELETE")
|
|
380
|
+
return { route: "delete_secret", id };
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
313
383
|
return null;
|
|
314
384
|
}
|
|
315
385
|
/**
|
|
@@ -646,6 +716,12 @@ export async function dispatchRoute(match, body, deps) {
|
|
|
646
716
|
return handleScanDevServers(store, deps.devProcessRegistry);
|
|
647
717
|
case "kill_dev_server":
|
|
648
718
|
return await handleKillDetectedDevServer(store, Number(id), match.port);
|
|
719
|
+
case "list_secrets":
|
|
720
|
+
return handleListSecrets();
|
|
721
|
+
case "create_secret":
|
|
722
|
+
return handleSetSecret(asObject(body));
|
|
723
|
+
case "delete_secret":
|
|
724
|
+
return handleDeleteSecret(id);
|
|
649
725
|
}
|
|
650
726
|
}
|
|
651
727
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../../src/sdk/coding-agent/utils/shell.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../../src/sdk/coding-agent/utils/shell.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CACf;AAyCD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,WAAW,CAqDpE;AAED,wBAAgB,WAAW,IAAI,MAAM,CAAC,UAAU,CAmC/C;AAED;;;;;;;GAOG;AACH,wBAAgB,kCAAkC,IAAI,MAAM,CAU3D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA8BxD;AAQD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAyBjD"}
|
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { delimiter } from "node:path";
|
|
3
3
|
import { spawn, spawnSync } from "child_process";
|
|
4
4
|
import { getBinDir } from "../config.js";
|
|
5
|
+
import { getSecretEnvVars } from "../../../secrets/index.js";
|
|
5
6
|
/**
|
|
6
7
|
* Find bash executable on PATH (cross-platform)
|
|
7
8
|
*/
|
|
@@ -100,7 +101,7 @@ export function getShellEnv() {
|
|
|
100
101
|
const pathEntries = currentPath.split(delimiter).filter(Boolean);
|
|
101
102
|
const hasBinDir = pathEntries.includes(binDir);
|
|
102
103
|
const updatedPath = hasBinDir ? currentPath : [binDir, currentPath].filter(Boolean).join(delimiter);
|
|
103
|
-
|
|
104
|
+
const env = {
|
|
104
105
|
...process.env,
|
|
105
106
|
[pathKey]: updatedPath,
|
|
106
107
|
// Agent shell hygiene (mirrors mini-swe-agent / SWE-agent environments):
|
|
@@ -114,6 +115,19 @@ export function getShellEnv() {
|
|
|
114
115
|
TQDM_DISABLE: "1",
|
|
115
116
|
GIT_TERMINAL_PROMPT: "0",
|
|
116
117
|
};
|
|
118
|
+
// Secret env-var injection (see src/secrets/store.ts). Synchronous,
|
|
119
|
+
// mtime-cached read of the encrypted store, so new/updated/deleted secrets
|
|
120
|
+
// are picked up on the next bash spawn without an agent restart. Secrets
|
|
121
|
+
// are applied on top of the base environment and intentionally win over any
|
|
122
|
+
// same-named variable from process.env — the store is the managed source.
|
|
123
|
+
// PATH is excluded by the store itself (a secret named PATH would override
|
|
124
|
+
// the rebuilt PATH above and break the bin dir injection). Failures degrade
|
|
125
|
+
// to the base env: spawning must never break, and no values are ever logged.
|
|
126
|
+
const secretEnv = getSecretEnvVars();
|
|
127
|
+
const secretKeys = Object.keys(secretEnv);
|
|
128
|
+
if (secretKeys.length === 0)
|
|
129
|
+
return env;
|
|
130
|
+
return { ...env, ...secretEnv };
|
|
117
131
|
}
|
|
118
132
|
/**
|
|
119
133
|
* Platform-specific instructions injected into the system prompt.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the local encrypted secret store.
|
|
3
|
+
*
|
|
4
|
+
* Note what is deliberately NOT exported here: `getSecretValue()` (internal —
|
|
5
|
+
* used only for env-var injection) and `resetSecretStore()` (test/reset
|
|
6
|
+
* helper). Consumers importing from this index can never expose secret values:
|
|
7
|
+
* every export either returns metadata-only views or the raw env map consumed
|
|
8
|
+
* by the bash tool's shell environment.
|
|
9
|
+
*/
|
|
10
|
+
export { MAX_SECRET_VALUE_BYTES, SECRET_NAME_PATTERN, SecretNameError, SecretStoreCorruptError, SecretValueError, deleteSecret, getSecretDataPath, getSecretEnvVars, getSecretKeyPath, listSecrets, normalizeSecretName, setSecret, type SecretMeta, } from "./store.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/secrets/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACN,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,mBAAmB,EACnB,SAAS,EACT,KAAK,UAAU,GACf,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the local encrypted secret store.
|
|
3
|
+
*
|
|
4
|
+
* Note what is deliberately NOT exported here: `getSecretValue()` (internal —
|
|
5
|
+
* used only for env-var injection) and `resetSecretStore()` (test/reset
|
|
6
|
+
* helper). Consumers importing from this index can never expose secret values:
|
|
7
|
+
* every export either returns metadata-only views or the raw env map consumed
|
|
8
|
+
* by the bash tool's shell environment.
|
|
9
|
+
*/
|
|
10
|
+
export { MAX_SECRET_VALUE_BYTES, SECRET_NAME_PATTERN, SecretNameError, SecretStoreCorruptError, SecretValueError, deleteSecret, getSecretDataPath, getSecretEnvVars, getSecretKeyPath, listSecrets, normalizeSecretName, setSecret, } from "./store.js";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local encrypted secret store.
|
|
3
|
+
*
|
|
4
|
+
* Storage layout (both files under `getConfigDir()`, honoring
|
|
5
|
+
* `SPECTRAL_CONFIG_DIR` for tests):
|
|
6
|
+
* - `<configDir>/secrets.key` 32 random bytes, mode 0600 — AES-256 key
|
|
7
|
+
* - `<configDir>/secrets.json.enc` AES-256-GCM encrypted JSON, mode 0600
|
|
8
|
+
*
|
|
9
|
+
* Encrypted file layout:
|
|
10
|
+
* - bytes 0-7: magic `"SPSECRT1"` (format tag)
|
|
11
|
+
* - bytes 8-19: 12-byte random IV
|
|
12
|
+
* - bytes 20-35: 16-byte GCM auth tag
|
|
13
|
+
* - bytes 36+: ciphertext (UTF-8 JSON)
|
|
14
|
+
*
|
|
15
|
+
* Security rules enforced here and by this module's consumers:
|
|
16
|
+
* - `listSecrets()` returns ONLY `{ name, createdAt, updatedAt }` — values are
|
|
17
|
+
* never part of any listing.
|
|
18
|
+
* - `getSecretValue()` is INTERNAL: it is consumed only by
|
|
19
|
+
* `getSecretEnvVars()` (env-var injection into the agent's bash tool). It
|
|
20
|
+
* is deliberately NOT re-exported from `./index.js`, so neither the relay
|
|
21
|
+
* handlers nor the LLM-facing tool surface can reach it.
|
|
22
|
+
* - No caller ever logs a value; error messages and listings carry names only.
|
|
23
|
+
*
|
|
24
|
+
* All I/O is synchronous by design: `getSecretEnvVars()` must be callable from
|
|
25
|
+
* the sync `getShellEnv()` in `src/sdk/coding-agent/utils/shell.ts`. The store
|
|
26
|
+
* is tiny (values are capped at 64 KiB each), so sync reads are negligible.
|
|
27
|
+
*
|
|
28
|
+
* Keep this module zero-side-effect on import: nothing touches the filesystem
|
|
29
|
+
* until one of the exported functions is called.
|
|
30
|
+
*/
|
|
31
|
+
/** Secret names: start with a letter, then letters/digits/underscores, max 64 chars. */
|
|
32
|
+
export declare const SECRET_NAME_PATTERN: RegExp;
|
|
33
|
+
/** Maximum UTF-8 byte size of a secret value (64 KiB). */
|
|
34
|
+
export declare const MAX_SECRET_VALUE_BYTES: number;
|
|
35
|
+
/** Metadata-safe view of a secret: NEVER includes the value. */
|
|
36
|
+
export interface SecretMeta {
|
|
37
|
+
name: string;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
updatedAt: string;
|
|
40
|
+
}
|
|
41
|
+
/** Thrown when a secret name is missing/invalid after normalization. */
|
|
42
|
+
export declare class SecretNameError extends Error {
|
|
43
|
+
constructor(message: string);
|
|
44
|
+
}
|
|
45
|
+
/** Thrown when a secret value exceeds `MAX_SECRET_VALUE_BYTES`. */
|
|
46
|
+
export declare class SecretValueError extends Error {
|
|
47
|
+
constructor(message: string);
|
|
48
|
+
}
|
|
49
|
+
/** Thrown when the encrypted store is unreadable, corrupt, or key-mismatched. */
|
|
50
|
+
export declare class SecretStoreCorruptError extends Error {
|
|
51
|
+
constructor(message: string);
|
|
52
|
+
}
|
|
53
|
+
/** Path to the AES-256 key file (32 random bytes). Lazily computed — see `getConfigDir`. */
|
|
54
|
+
export declare function getSecretKeyPath(): string;
|
|
55
|
+
/** Path to the AES-256-GCM encrypted store. Lazily computed — see `getConfigDir`. */
|
|
56
|
+
export declare function getSecretDataPath(): string;
|
|
57
|
+
/**
|
|
58
|
+
* Normalize a secret name per the shared contract: trim surrounding
|
|
59
|
+
* whitespace, uppercase, then validate against `SECRET_NAME_PATTERN`.
|
|
60
|
+
* Throws `SecretNameError` with a generic message — the offending raw input
|
|
61
|
+
* is never echoed back, so it can't leak into logs.
|
|
62
|
+
*/
|
|
63
|
+
export declare function normalizeSecretName(raw: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* Return the secret env vars (name → value) for injection into the agent
|
|
66
|
+
* shell environment. Synchronous, mtime-cached: new/updated/deleted secrets
|
|
67
|
+
* are picked up on the next call without a CLI restart.
|
|
68
|
+
*
|
|
69
|
+
* Failure modes degrade gracefully — a corrupt store, missing key, or unreadable
|
|
70
|
+
* file returns `{}` instead of throwing, because `getShellEnv()` is sync and
|
|
71
|
+
* must never break bash spawning. No values are ever logged.
|
|
72
|
+
*/
|
|
73
|
+
export declare function getSecretEnvVars(): Record<string, string>;
|
|
74
|
+
/**
|
|
75
|
+
* Create or update a secret. Returns the metadata-only view (never the value).
|
|
76
|
+
* Throws `SecretNameError` for invalid names, `SecretValueError` for oversized
|
|
77
|
+
* values, and `SecretStoreCorruptError` when the store cannot be read/written.
|
|
78
|
+
*
|
|
79
|
+
* Fully synchronous (no awaits): the read-modify-write below is atomic per
|
|
80
|
+
* event-loop turn, so concurrent relay POSTs in one process can never
|
|
81
|
+
* interleave — no mutex is needed. Cross-process writers are isolated by the
|
|
82
|
+
* unique per-request tmp name + rename in `writeStoreData`; the last rename
|
|
83
|
+
* wins and every writer re-reads the store on its next call.
|
|
84
|
+
*/
|
|
85
|
+
export declare function setSecret(rawName: string, value: string): SecretMeta;
|
|
86
|
+
/**
|
|
87
|
+
* Delete a secret. Returns true when a record was removed, false when the
|
|
88
|
+
* name was not stored. Throws `SecretNameError` for invalid names.
|
|
89
|
+
*/
|
|
90
|
+
export declare function deleteSecret(rawName: string): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* List secrets as metadata only — `{ name, createdAt, updatedAt }`. Values are
|
|
93
|
+
* never part of the returned shape (this is the source for the relay GET
|
|
94
|
+
* response and the `list_secrets` agent tool).
|
|
95
|
+
*/
|
|
96
|
+
export declare function listSecrets(): SecretMeta[];
|
|
97
|
+
/**
|
|
98
|
+
* INTERNAL — read a single secret value. Used ONLY for env-var injection via
|
|
99
|
+
* `getSecretEnvVars()`. Deliberately not re-exported from `./index.js` so it
|
|
100
|
+
* cannot be reached from the relay handlers or the LLM-facing tool surface.
|
|
101
|
+
* Never log the return value.
|
|
102
|
+
*/
|
|
103
|
+
export declare function getSecretValue(rawName: string): string | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Remove the store + key (test helper and future "reset secrets" support).
|
|
106
|
+
* Returns true when either file was deleted.
|
|
107
|
+
*/
|
|
108
|
+
export declare function resetSecretStore(): boolean;
|
|
109
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/secrets/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAiBH,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,QAA2B,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,QAAY,CAAC;AAQhD,gEAAgE;AAChE,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB;AAaD,wEAAwE;AACxE,qBAAa,eAAgB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI3B;AAED,mEAAmE;AACnE,qBAAa,gBAAiB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI3B;AAED,iFAAiF;AACjF,qBAAa,uBAAwB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI3B;AAMD,4FAA4F;AAC5F,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAMD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAWvD;AA+ND;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAczD;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAcpE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAUrD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,UAAU,EAAE,CAS1C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIlE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAY1C"}
|