@aexol/spectral 0.9.244 → 0.9.246

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.
@@ -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"}
@@ -0,0 +1,415 @@
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
+ import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
32
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
33
+ import { basename, dirname, join } from "node:path";
34
+ import { getConfigDir } from "../config.js";
35
+ /** Secret names: start with a letter, then letters/digits/underscores, max 64 chars. */
36
+ export const SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
37
+ /** Maximum UTF-8 byte size of a secret value (64 KiB). */
38
+ export const MAX_SECRET_VALUE_BYTES = 64 * 1024;
39
+ /** 8-byte format tag at the head of the encrypted store. */
40
+ const MAGIC = Buffer.from("SPSECRT1", "utf8");
41
+ const IV_LENGTH = 12;
42
+ const TAG_LENGTH = 16;
43
+ const KEY_LENGTH = 32;
44
+ /** Thrown when a secret name is missing/invalid after normalization. */
45
+ export class SecretNameError extends Error {
46
+ constructor(message) {
47
+ super(message);
48
+ this.name = "SecretNameError";
49
+ }
50
+ }
51
+ /** Thrown when a secret value exceeds `MAX_SECRET_VALUE_BYTES`. */
52
+ export class SecretValueError extends Error {
53
+ constructor(message) {
54
+ super(message);
55
+ this.name = "SecretValueError";
56
+ }
57
+ }
58
+ /** Thrown when the encrypted store is unreadable, corrupt, or key-mismatched. */
59
+ export class SecretStoreCorruptError extends Error {
60
+ constructor(message) {
61
+ super(message);
62
+ this.name = "SecretStoreCorruptError";
63
+ }
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Paths
67
+ // ---------------------------------------------------------------------------
68
+ /** Path to the AES-256 key file (32 random bytes). Lazily computed — see `getConfigDir`. */
69
+ export function getSecretKeyPath() {
70
+ return join(getConfigDir(), "secrets.key");
71
+ }
72
+ /** Path to the AES-256-GCM encrypted store. Lazily computed — see `getConfigDir`. */
73
+ export function getSecretDataPath() {
74
+ return join(getConfigDir(), "secrets.json.enc");
75
+ }
76
+ // ---------------------------------------------------------------------------
77
+ // Validation / normalization
78
+ // ---------------------------------------------------------------------------
79
+ /**
80
+ * Normalize a secret name per the shared contract: trim surrounding
81
+ * whitespace, uppercase, then validate against `SECRET_NAME_PATTERN`.
82
+ * Throws `SecretNameError` with a generic message — the offending raw input
83
+ * is never echoed back, so it can't leak into logs.
84
+ */
85
+ export function normalizeSecretName(raw) {
86
+ if (typeof raw !== "string" || raw.trim().length === 0) {
87
+ throw new SecretNameError("Secret name is required");
88
+ }
89
+ const name = raw.trim().toUpperCase();
90
+ if (!SECRET_NAME_PATTERN.test(name)) {
91
+ throw new SecretNameError("Invalid secret name: must start with a letter, contain only letters, digits and underscores, max 64 characters (pattern ^[A-Z][A-Z0-9_]{0,63}$)");
92
+ }
93
+ return name;
94
+ }
95
+ function assertValueSize(value) {
96
+ if (typeof value !== "string") {
97
+ throw new SecretValueError("Secret value must be a string");
98
+ }
99
+ if (Buffer.byteLength(value, "utf8") > MAX_SECRET_VALUE_BYTES) {
100
+ throw new SecretValueError(`Secret value too large: max ${MAX_SECRET_VALUE_BYTES} bytes (64 KiB)`);
101
+ }
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Crypto + file I/O
105
+ // ---------------------------------------------------------------------------
106
+ function encrypt(data, key) {
107
+ const iv = randomBytes(IV_LENGTH);
108
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
109
+ const ciphertext = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
110
+ const tag = cipher.getAuthTag();
111
+ return Buffer.concat([MAGIC, iv, tag, ciphertext]);
112
+ }
113
+ function decrypt(buf, key) {
114
+ if (buf.length < MAGIC.length + IV_LENGTH + TAG_LENGTH ||
115
+ !buf.subarray(0, MAGIC.length).equals(MAGIC)) {
116
+ throw new SecretStoreCorruptError("Unrecognized secret store format");
117
+ }
118
+ const iv = buf.subarray(MAGIC.length, MAGIC.length + IV_LENGTH);
119
+ const tag = buf.subarray(MAGIC.length + IV_LENGTH, MAGIC.length + IV_LENGTH + TAG_LENGTH);
120
+ const ciphertext = buf.subarray(MAGIC.length + IV_LENGTH + TAG_LENGTH);
121
+ try {
122
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
123
+ decipher.setAuthTag(tag);
124
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
125
+ }
126
+ catch {
127
+ throw new SecretStoreCorruptError("Secret store is corrupt or was encrypted with a different key");
128
+ }
129
+ }
130
+ /**
131
+ * Read the EXISTING key file. Throws `SecretStoreCorruptError` when the key
132
+ * is missing or has the wrong size. Deliberately NEVER generates a key: key
133
+ * generation happens on write paths only (`ensureSecretKey`), so a read
134
+ * against encrypted data without a key surfaces the lost-key state instead of
135
+ * silently minting a fresh key that can never decrypt the store.
136
+ */
137
+ function readSecretKey() {
138
+ const keyPath = getSecretKeyPath();
139
+ let key;
140
+ try {
141
+ key = readFileSync(keyPath);
142
+ }
143
+ catch (err) {
144
+ if (err.code === "ENOENT") {
145
+ throw new SecretStoreCorruptError("Secrets key file is missing but encrypted data exists: restore secrets.key or delete the store and re-enter your secrets");
146
+ }
147
+ throw new SecretStoreCorruptError(`Cannot read secrets key: ${err instanceof Error ? err.message : String(err)}`);
148
+ }
149
+ if (key.length !== KEY_LENGTH) {
150
+ throw new SecretStoreCorruptError(`Secrets key file is corrupt (expected ${KEY_LENGTH} bytes): delete it and re-enter your secrets`);
151
+ }
152
+ return key;
153
+ }
154
+ /**
155
+ * Return the AES-256 key, generating a new one ONLY when the key file does
156
+ * not exist yet (write paths). An existing key with loose permissions is
157
+ * tightened back to 0600 (defense-in-depth).
158
+ */
159
+ function ensureSecretKey() {
160
+ const keyPath = getSecretKeyPath();
161
+ mkdirSync(dirname(keyPath), { recursive: true, mode: 0o700 });
162
+ if (existsSync(keyPath)) {
163
+ try {
164
+ chmodSync(keyPath, 0o600);
165
+ }
166
+ catch {
167
+ // Best-effort: some filesystems do not support chmod.
168
+ }
169
+ return readSecretKey();
170
+ }
171
+ const key = randomBytes(KEY_LENGTH);
172
+ writeFileSync(keyPath, key, { mode: 0o600 });
173
+ return key;
174
+ }
175
+ function emptyStoreData() {
176
+ return { version: 1, secrets: {} };
177
+ }
178
+ /** Read + decrypt the store. Returns an empty store when the file is missing. Throws on corruption. */
179
+ function readStoreData() {
180
+ const dataPath = getSecretDataPath();
181
+ if (!existsSync(dataPath))
182
+ return emptyStoreData();
183
+ let raw;
184
+ try {
185
+ raw = readFileSync(dataPath);
186
+ }
187
+ catch (err) {
188
+ throw new SecretStoreCorruptError(`Cannot read secret store: ${err instanceof Error ? err.message : String(err)}`);
189
+ }
190
+ // The read path NEVER generates a key: encrypted data without a key is a
191
+ // lost-key state, and minting a fresh key here would mask it (and touch
192
+ // the filesystem as a side effect of a read). Keys are generated on write
193
+ // paths only (ensureSecretKey via writeStoreData).
194
+ const key = readSecretKey();
195
+ const json = decrypt(raw, key);
196
+ try {
197
+ const parsed = JSON.parse(json);
198
+ if (parsed.version !== 1 || typeof parsed.secrets !== "object" || parsed.secrets === null) {
199
+ throw new SecretStoreCorruptError("Unrecognized secret store payload");
200
+ }
201
+ return { version: 1, secrets: parsed.secrets };
202
+ }
203
+ catch (err) {
204
+ if (err instanceof SecretStoreCorruptError)
205
+ throw err;
206
+ throw new SecretStoreCorruptError("Unrecognized secret store payload");
207
+ }
208
+ }
209
+ /** Orphaned `.tmp-*` files older than this are removed on the next write. */
210
+ const STALE_TMP_MS = 60_000;
211
+ /**
212
+ * Remove orphaned `secrets.json.enc.tmp-*` files left behind by crashed
213
+ * writers. Only files older than `STALE_TMP_MS` are removed so a concurrent
214
+ * process's in-flight write (tmp file created moments ago) is never deleted.
215
+ */
216
+ function cleanupStaleTmpFiles(dataPath) {
217
+ let entries;
218
+ try {
219
+ entries = readdirSync(dirname(dataPath));
220
+ }
221
+ catch {
222
+ return;
223
+ }
224
+ const prefix = `${basename(dataPath)}.tmp-`;
225
+ const cutoff = Date.now() - STALE_TMP_MS;
226
+ for (const entry of entries) {
227
+ if (!entry.startsWith(prefix))
228
+ continue;
229
+ const full = join(dirname(dataPath), entry);
230
+ try {
231
+ if (statSync(full).mtimeMs < cutoff)
232
+ unlinkSync(full);
233
+ }
234
+ catch {
235
+ // Ignore: the file vanished (another writer renamed it away).
236
+ }
237
+ }
238
+ }
239
+ /** Encrypt + atomically write the store (tmp file + rename), mode 0600. */
240
+ function writeStoreData(data) {
241
+ const dataPath = getSecretDataPath();
242
+ const key = ensureSecretKey();
243
+ const payload = encrypt(JSON.stringify(data), key);
244
+ // Unique per-request tmp name: pid alone is shared by every write this
245
+ // process makes, so a crashed write would make the next write silently
246
+ // reuse (and clobber) the orphan. A random hex suffix makes collisions
247
+ // between concurrent writers practically impossible.
248
+ const tmpPath = `${dataPath}.tmp-${process.pid}-${randomBytes(6).toString("hex")}`;
249
+ writeFileSync(tmpPath, payload, { mode: 0o600 });
250
+ try {
251
+ renameSync(tmpPath, dataPath);
252
+ }
253
+ catch (err) {
254
+ // Best-effort: remove this request's tmp file so failed writes cannot
255
+ // accumulate orphans.
256
+ try {
257
+ unlinkSync(tmpPath);
258
+ }
259
+ catch {
260
+ // Already gone.
261
+ }
262
+ throw err;
263
+ }
264
+ // Defense-in-depth: a pre-existing file with loose permissions gets tightened.
265
+ chmodSync(dataPath, 0o600);
266
+ cleanupStaleTmpFiles(dataPath);
267
+ }
268
+ // ---------------------------------------------------------------------------
269
+ // mtime-cached env injection
270
+ // ---------------------------------------------------------------------------
271
+ /**
272
+ * Cache keyed on the data file's `mtimeMs:size` so external writes (other CLI
273
+ * processes, the relay API) invalidate it without a restart. Our own write
274
+ * path (`setSecret`/`deleteSecret`) refreshes the cache explicitly, covering
275
+ * same-millisecond writes where the mtime would not change.
276
+ */
277
+ let envCache = null;
278
+ function dataFileCacheKey() {
279
+ try {
280
+ const data = statSync(getSecretDataPath());
281
+ const key = statSync(getSecretKeyPath());
282
+ return `${data.mtimeMs}:${data.size}:${key.mtimeMs}:${key.size}`;
283
+ }
284
+ catch {
285
+ return null;
286
+ }
287
+ }
288
+ function refreshEnvCache() {
289
+ const cacheKey = dataFileCacheKey();
290
+ const data = readStoreData();
291
+ // PATH is reserved: a secret named PATH would override the shell PATH that
292
+ // getShellEnv() deliberately rebuilds (bin dir injection) and break the
293
+ // bash tool. Stored under that name, it is ignored for env injection only.
294
+ const env = {};
295
+ for (const [name, record] of Object.entries(data.secrets)) {
296
+ if (name.toLowerCase() === "path")
297
+ continue;
298
+ env[name] = record.value;
299
+ }
300
+ envCache = { key: cacheKey ?? "empty", env };
301
+ }
302
+ /**
303
+ * Return the secret env vars (name → value) for injection into the agent
304
+ * shell environment. Synchronous, mtime-cached: new/updated/deleted secrets
305
+ * are picked up on the next call without a CLI restart.
306
+ *
307
+ * Failure modes degrade gracefully — a corrupt store, missing key, or unreadable
308
+ * file returns `{}` instead of throwing, because `getShellEnv()` is sync and
309
+ * must never break bash spawning. No values are ever logged.
310
+ */
311
+ export function getSecretEnvVars() {
312
+ try {
313
+ const cacheKey = dataFileCacheKey();
314
+ if (cacheKey === null) {
315
+ return {};
316
+ }
317
+ if (envCache && envCache.key === cacheKey) {
318
+ return envCache.env;
319
+ }
320
+ refreshEnvCache();
321
+ return envCache?.env ?? {};
322
+ }
323
+ catch {
324
+ return {};
325
+ }
326
+ }
327
+ // ---------------------------------------------------------------------------
328
+ // Public API
329
+ // ---------------------------------------------------------------------------
330
+ /**
331
+ * Create or update a secret. Returns the metadata-only view (never the value).
332
+ * Throws `SecretNameError` for invalid names, `SecretValueError` for oversized
333
+ * values, and `SecretStoreCorruptError` when the store cannot be read/written.
334
+ *
335
+ * Fully synchronous (no awaits): the read-modify-write below is atomic per
336
+ * event-loop turn, so concurrent relay POSTs in one process can never
337
+ * interleave — no mutex is needed. Cross-process writers are isolated by the
338
+ * unique per-request tmp name + rename in `writeStoreData`; the last rename
339
+ * wins and every writer re-reads the store on its next call.
340
+ */
341
+ export function setSecret(rawName, value) {
342
+ const name = normalizeSecretName(rawName);
343
+ assertValueSize(value);
344
+ const data = readStoreData();
345
+ const now = new Date().toISOString();
346
+ const existing = data.secrets[name];
347
+ data.secrets[name] = {
348
+ value,
349
+ createdAt: existing?.createdAt ?? now,
350
+ updatedAt: now,
351
+ };
352
+ writeStoreData(data);
353
+ refreshEnvCache();
354
+ return { name, createdAt: data.secrets[name].createdAt, updatedAt: now };
355
+ }
356
+ /**
357
+ * Delete a secret. Returns true when a record was removed, false when the
358
+ * name was not stored. Throws `SecretNameError` for invalid names.
359
+ */
360
+ export function deleteSecret(rawName) {
361
+ const name = normalizeSecretName(rawName);
362
+ const data = readStoreData();
363
+ if (!Object.prototype.hasOwnProperty.call(data.secrets, name)) {
364
+ return false;
365
+ }
366
+ delete data.secrets[name];
367
+ writeStoreData(data);
368
+ refreshEnvCache();
369
+ return true;
370
+ }
371
+ /**
372
+ * List secrets as metadata only — `{ name, createdAt, updatedAt }`. Values are
373
+ * never part of the returned shape (this is the source for the relay GET
374
+ * response and the `list_secrets` agent tool).
375
+ */
376
+ export function listSecrets() {
377
+ const data = readStoreData();
378
+ return Object.entries(data.secrets)
379
+ .map(([name, record]) => ({
380
+ name,
381
+ createdAt: record.createdAt,
382
+ updatedAt: record.updatedAt,
383
+ }))
384
+ .sort((a, b) => a.name.localeCompare(b.name));
385
+ }
386
+ /**
387
+ * INTERNAL — read a single secret value. Used ONLY for env-var injection via
388
+ * `getSecretEnvVars()`. Deliberately not re-exported from `./index.js` so it
389
+ * cannot be reached from the relay handlers or the LLM-facing tool surface.
390
+ * Never log the return value.
391
+ */
392
+ export function getSecretValue(rawName) {
393
+ const name = normalizeSecretName(rawName);
394
+ const data = readStoreData();
395
+ return data.secrets[name]?.value;
396
+ }
397
+ /**
398
+ * Remove the store + key (test helper and future "reset secrets" support).
399
+ * Returns true when either file was deleted.
400
+ */
401
+ export function resetSecretStore() {
402
+ let removed = false;
403
+ for (const path of [getSecretDataPath(), getSecretKeyPath()]) {
404
+ try {
405
+ unlinkSync(path);
406
+ removed = true;
407
+ }
408
+ catch (err) {
409
+ if (err.code !== "ENOENT")
410
+ throw err;
411
+ }
412
+ }
413
+ envCache = null;
414
+ return removed;
415
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAaH,OAAO,EAQL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AAatC,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAatC,OAAO,EACL,kBAAkB,IAAI,yBAAyB,EAEhD,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AA6BpE,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,aAAa,GACd,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,2BAA2B,EAC3B,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,UAAU,4BAA4B;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CACpC;AAED,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,OAAO,yBAAyB,CAAC;AAmCpE,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,0BAA0B,EAAE,CAAC,GAAG,EAAE;QAChC,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,6EAA6E;QAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,yBAAyB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC9C;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;;;;;;;;;OAWG;IACH,yBAAyB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAmKD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAC,CAAe;IAC/B,OAAO,CAAC,iCAAiC,CAAqB;IAC9D,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,OAAO,CAAC,CAA0B;IAC1C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAqB;IACjC;;;;;OAKG;IACH,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC;;;;OAIG;IACH,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB,CAAC,CAAS;IAEpC,OAAO,CAAC,mBAAmB,CAAC,CAAgC;IAC5D,OAAO,CAAC,YAAY,CAAC,CAAuB;IAC5C,8EAA8E;IAC9E,OAAO,CAAC,kBAAkB,CAAC,CAAgC;IAC3D;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe,CAAK;IAC5B,sFAAsF;IACtF,OAAO,CAAC,iBAAiB,CAMT;IAChB,OAAO,CAAC,WAAW,CACuD;IAC1E,OAAO,CAAC,mBAAmB,CAA+B;IAC1D;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB,CAAK;IAClC;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,cAAc,CAAC,CAA2B;IAClD,OAAO,CAAC,mBAAmB,CAAC,CAAgC;IAC5D;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB,CAO5B;IACJ;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB,CAAqC;IAChE;;;;;OAKG;IACH,OAAO,CAAC,aAAa,CAA6B;gBAEtC,IAAI,EAAE,kBAAkB;IAIpC;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAQlC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+X5B,OAAO,CAAC,4BAA4B;IAQpC;;;;;OAKG;YACW,qBAAqB;IAcnC;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAG3C;;;;;;;;;;;;OAYG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvC;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAmC3B,OAAO,CAAC,sBAAsB;YAahB,oBAAoB;IA8BlC;;;;;;;;;;;;OAYG;IACH;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,0BAA0B;IAoFlC;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;OAQG;IACH,wBAAwB,IAAI,MAAM,GAAG,SAAS;IAI9C;;;;OAIG;IACH,eAAe,IACX;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxE,SAAS;IAIb,mDAAmD;IACnD,WAAW,IAAI,OAAO;IAItB,mEAAmE;IACnE,UAAU,IAAI,OAAO;IAIrB,2EAA2E;IAC3E,MAAM,IAAI,OAAO;IAIjB;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUzE,gBAAgB,IAAI,KAAK,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IAgBF,iBAAiB,IAAI;QACnB,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;QACtE,QAAQ,EAAE;YACR,QAAQ,EAAE,OAAO,CAAC;YAClB,UAAU,EAAE,OAAO,CAAC;YACpB,UAAU,EAAE,OAAO,CAAC;YACpB,MAAM,EAAE,OAAO,CAAC;SACjB,CAAC;KACH;IAYD,OAAO,CAAC,iBAAiB;IAmCnB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IA2DpE;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,iCAAiC;IAkBzC;;;;;;OAMG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAoBpD;;;;;;;;;OASG;IACH,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAI7D;;;;;;;;;;;;OAYG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8CrE;;;;;;;;OAQG;YACW,iBAAiB;IAqB/B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,0BAA0B;IAoBlC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAM1B,2EAA2E;IAC3E,OAAO,CAAC,6BAA6B;IAOrC;;;;;;;;;OASG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;;;;;;OAOG;IACH,OAAO,CAAC,2BAA2B;IAYnC;;;;OAIG;IACH,OAAO,CAAC,8BAA8B;IAQtC,yEAAyE;IACzE,OAAO,CAAC,iCAAiC;IAOzC,OAAO,CAAC,iBAAiB;IAazB;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAc/B,OAAO,CAAC,uBAAuB;IAMzB,OAAO,CACX,OAAO,CAAC,EAAE,MAAM,GAAG,4BAA4B,GAC9C,OAAO,CAAC,gBAAgB,CAAC;IAY5B,OAAO,IAAI,IAAI;IAiDf;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAoCrB;;;;;;;;OAQG;IACH,OAAO,CAAC,6BAA6B;IAiCrC;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAqB7B,uEAAuE;IACvE,OAAO,CAAC,wBAAwB;IAMhC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,sBAAsB;IAqE9B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI;CAsvBzC"}
1
+ {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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"}
@@ -63,6 +63,7 @@ import { BrowserService } from "../extensions/browser/browser-service.js";
63
63
  import subagentExt from "../agent/index.js";
64
64
  import designerExtension from "../designer/index.js";
65
65
  import logicTraceExtension from "../extensions/logic-trace.js";
66
+ import listSecretsExtension from "../extensions/list-secrets.js";
66
67
  import observationalMemory from "../memory/index.js";
67
68
  import { OBSERVATIONAL_MEMORY_CONTEXT_CUSTOM_TYPE, OBSERVATIONAL_MEMORY_SNAPSHOT_CUSTOM_TYPE, } from "../memory/types.js";
68
69
  import { fetchAllowedModels as defaultFetchAllowedModels, } from "../relay/models-fetch.js";
@@ -368,6 +369,12 @@ export class AgentBridge {
368
369
  async (ext) => {
369
370
  observationalMemory(ext);
370
371
  },
372
+ // list_secrets: metadata-only view of the local encrypted secret store
373
+ // (names + timestamps). Values are never returned to the LLM — they are
374
+ // injected as env vars into the bash tool via getShellEnv().
375
+ async (ext) => {
376
+ listSecretsExtension(ext);
377
+ },
371
378
  ];
372
379
  // Always register subagent tool — execute() checks settings from disk
373
380
  // on every invocation, so toggling takes effect immediately (no restart).
@@ -0,0 +1,40 @@
1
+ /**
2
+ * REST handlers for the local secret store.
3
+ *
4
+ * Contract (shared with the landing frontend):
5
+ * - `GET /api/secrets` → `{ secrets: SecretMeta[] }` — metadata only,
6
+ * values are NEVER returned by anything.
7
+ * - `POST /api/secrets` → `{ secret: SecretMeta }` — body
8
+ * `{ name, value }`.
9
+ * - `DELETE /api/secrets/:name` → `{ ok: true }`.
10
+ *
11
+ * Name normalization/validation and the 64 KiB value cap live in
12
+ * `src/secrets/store.ts`. Errors follow the existing handler error shape
13
+ * (`BadRequestError`/`NotFoundError` → 400/404 in the relay rest loop).
14
+ *
15
+ * SECURITY: no handler here (or anywhere in the relay loop) logs a request or
16
+ * response value. Only names appear in error messages, and only after they
17
+ * have passed `normalizeSecretName` (letters/digits/underscores).
18
+ */
19
+ import { type SecretMeta } from "../../secrets/index.js";
20
+ /**
21
+ * `GET /api/secrets` — list secrets as metadata only.
22
+ */
23
+ export declare function handleListSecrets(): {
24
+ secrets: SecretMeta[];
25
+ };
26
+ /**
27
+ * `POST /api/secrets` — create or update a secret. Body: `{ name, value }`.
28
+ * Returns the metadata-only view; the stored value is never echoed back.
29
+ */
30
+ export declare function handleSetSecret(body: Record<string, unknown>): {
31
+ secret: SecretMeta;
32
+ };
33
+ /**
34
+ * `DELETE /api/secrets/:name` — delete a secret. Throws `NotFoundError` (404)
35
+ * when the name is not stored.
36
+ */
37
+ export declare function handleDeleteSecret(rawName: string): {
38
+ ok: true;
39
+ };
40
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../../src/server/handlers/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAON,KAAK,UAAU,EACf,MAAM,wBAAwB,CAAC;AAUhC;;GAEG;AACH,wBAAgB,iBAAiB,IAAI;IAAE,OAAO,EAAE,UAAU,EAAE,CAAA;CAAE,CAE7D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAerF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAiBhE"}