sliccy 2.42.0 → 2.43.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 (108) hide show
  1. package/dist/node-server/_shared/index.d.ts +4 -0
  2. package/dist/node-server/_shared/index.d.ts.map +1 -0
  3. package/dist/node-server/_shared/index.js +5 -0
  4. package/dist/node-server/_shared/index.js.map +1 -0
  5. package/dist/node-server/_shared/oauth-extra-domains-storage.d.ts +29 -0
  6. package/dist/node-server/_shared/oauth-extra-domains-storage.d.ts.map +1 -0
  7. package/dist/node-server/_shared/oauth-extra-domains-storage.js +85 -0
  8. package/dist/node-server/_shared/oauth-extra-domains-storage.js.map +1 -0
  9. package/dist/node-server/_shared/secret-masking.d.ts +43 -0
  10. package/dist/node-server/_shared/secret-masking.d.ts.map +1 -0
  11. package/dist/node-server/_shared/secret-masking.js +125 -0
  12. package/dist/node-server/_shared/secret-masking.js.map +1 -0
  13. package/dist/node-server/_shared/secrets-pipeline.d.ts +103 -0
  14. package/dist/node-server/_shared/secrets-pipeline.d.ts.map +1 -0
  15. package/dist/node-server/_shared/secrets-pipeline.js +249 -0
  16. package/dist/node-server/_shared/secrets-pipeline.js.map +1 -0
  17. package/dist/node-server/index.js +78 -7
  18. package/dist/node-server/secrets/index.d.ts +3 -3
  19. package/dist/node-server/secrets/index.js +1 -1
  20. package/dist/node-server/secrets/oauth-secret-store.d.ts +12 -0
  21. package/dist/node-server/secrets/oauth-secret-store.js +18 -0
  22. package/dist/node-server/secrets/proxy-manager.d.ts +13 -49
  23. package/dist/node-server/secrets/proxy-manager.js +62 -114
  24. package/dist/node-server/secrets/session-id-file.d.ts +5 -0
  25. package/dist/node-server/secrets/session-id-file.js +33 -0
  26. package/dist/node-server/secrets/types.d.ts +5 -0
  27. package/dist/ui/assets/adobe-C2UaIA41.js +2 -0
  28. package/dist/ui/assets/adobe-DCEW-jwu.js +1 -0
  29. package/dist/ui/assets/{agent-bridge-DPiRY9Cp.js → agent-bridge-DpnoOA1m.js} +1 -1
  30. package/dist/ui/assets/{agent-message-to-chat-DPXL2yWd.js → agent-message-to-chat-BhoxpmpE.js} +1 -1
  31. package/dist/ui/assets/{anthropic-DNaAx4PR.js → anthropic-BBCljDKG.js} +1 -1
  32. package/dist/ui/assets/{anthropic-z3OB3Fnl.js → anthropic-DuP0qmVz.js} +1 -1
  33. package/dist/ui/assets/{azure-openai-T9qO12vJ.js → azure-openai-DJep1H6m.js} +1 -1
  34. package/dist/ui/assets/{azure-openai-BittXGOl.js → azure-openai-es2UEaVa.js} +1 -1
  35. package/dist/ui/assets/{azure-openai-responses-DBxhuZoo.js → azure-openai-responses-BasmI9oz.js} +1 -1
  36. package/dist/ui/assets/{azure-openai-responses-DviTxw7q.js → azure-openai-responses-DBAbXSTD.js} +1 -1
  37. package/dist/ui/assets/{bedrock-camp-CHTXSY1O.js → bedrock-camp-CSp2DPSu.js} +3 -3
  38. package/dist/ui/assets/{bedrock-camp-MgGI9Yh2.js → bedrock-camp-KyNM28gW.js} +2 -2
  39. package/dist/ui/assets/{cdp-BV0AqGz6.js → cdp-CP2mm6ER.js} +2 -2
  40. package/dist/ui/assets/cost-command-DnyD68nK.js +1 -0
  41. package/dist/ui/assets/{es-CMpEjX7X.js → es-BUhsge_v.js} +1 -1
  42. package/dist/ui/assets/{fs-CJrk6h4U.js → fs-1U1rT6mb.js} +2 -2
  43. package/dist/ui/assets/{fs-IOuBZ0fU.js → fs-KzDX6lDt.js} +3 -3
  44. package/dist/ui/assets/github-BoI7NK5Y.js +2 -0
  45. package/dist/ui/assets/github-DZWbceUJ.js +1 -0
  46. package/dist/ui/assets/{google-B_wiPl8S.js → google-CcdeRF42.js} +1 -1
  47. package/dist/ui/assets/{google-BSQPUbsD.js → google-w3iuVje2.js} +1 -1
  48. package/dist/ui/assets/{index-DOah61oi.js → index-Dbfn2pUX.js} +17 -17
  49. package/dist/ui/assets/{kernel-worker-Ct6nuQEv.js → kernel-worker-BuJdZy5v.js} +920 -887
  50. package/dist/ui/assets/{local-llm-BKrBXM1b.js → local-llm-D72tLY3Y.js} +1 -1
  51. package/dist/ui/assets/{local-llm-DG_WcsYf.js → local-llm-XVLqdOOf.js} +1 -1
  52. package/dist/ui/assets/{mistral-cWWq2371.js → mistral-DRSbEgc2.js} +1 -1
  53. package/dist/ui/assets/{mistral-oA9eus1c.js → mistral-b5JBKUqy.js} +1 -1
  54. package/dist/ui/assets/{mount-BE4HlHj3.js → mount-6-HP-fzb.js} +1 -1
  55. package/dist/ui/assets/{mount-d5zFj5UF.js → mount-zcpBZ465.js} +2 -2
  56. package/dist/ui/assets/{nuke-command-Do01VR7B.js → nuke-command-DTu62dL0.js} +1 -1
  57. package/dist/ui/assets/oauth-bootstrap-CsJTQqoE.js +1 -0
  58. package/dist/ui/assets/oauth-service-C0rdcoXi.js +1 -0
  59. package/dist/ui/assets/oauth-service-DTHr_ILl.js +1 -0
  60. package/dist/ui/assets/{offscreen-client-D96UbGR3.js → offscreen-client-BQS5SL9t.js} +1 -1
  61. package/dist/ui/assets/{onboarding-orchestrator-D1l-w9_h.js → onboarding-orchestrator-Dld2OQzb.js} +1 -1
  62. package/dist/ui/assets/{openai-codex-responses-ChYFmtB8.js → openai-codex-responses-BIq_NjtP.js} +1 -1
  63. package/dist/ui/assets/{openai-codex-responses-DYtt5_H9.js → openai-codex-responses-Bw3r2Hnu.js} +1 -1
  64. package/dist/ui/assets/{openai-completions-CkeC09FW.js → openai-completions-68Ek_N21.js} +1 -1
  65. package/dist/ui/assets/{openai-completions-pJVLdfB5.js → openai-completions-BXM-gDWt.js} +1 -1
  66. package/dist/ui/assets/{openai-responses-CfoWjQjX.js → openai-responses-BoV1AGA6.js} +1 -1
  67. package/dist/ui/assets/{openai-responses-BtUhfSCM.js → openai-responses-EH-UgPTz.js} +1 -1
  68. package/dist/ui/assets/{openai-responses-shared-BT4PBv9i.js → openai-responses-shared-BnEuqygR.js} +1 -1
  69. package/dist/ui/assets/{openai-responses-shared-BL73-u8o.js → openai-responses-shared-sCOhnzkS.js} +1 -1
  70. package/dist/ui/assets/page-storage-sync-DEhI1xEB.js +1 -0
  71. package/dist/ui/assets/{panel-rpc-BLyR6B42.js → panel-rpc-g85bMVnq.js} +1 -1
  72. package/dist/ui/assets/panel-rpc-handlers-Cd0NeWGr.js +1 -0
  73. package/dist/ui/assets/provider-settings-BJW3Z-UR.js +2 -0
  74. package/dist/ui/assets/provider-settings-BwyVvIHC.js +90 -0
  75. package/dist/ui/assets/{providers-DDa7zuci.js → providers-Bx2_S0Gs.js} +1 -1
  76. package/dist/ui/assets/secret-env-DES2XPkD.js +1 -0
  77. package/dist/ui/assets/{spawn-CIR7ZcER.js → spawn-Bs7QOa4F.js} +1 -1
  78. package/dist/ui/assets/{upgrade-detection-CHed1OmR.js → upgrade-detection-DaIEsDu3.js} +1 -1
  79. package/dist/ui/index.html +6 -6
  80. package/dist/ui/packages/webapp/index.html +6 -6
  81. package/package.json +4 -3
  82. package/dist/node-server/secrets/domain-match.d.ts +0 -20
  83. package/dist/node-server/secrets/domain-match.js +0 -37
  84. package/dist/node-server/secrets/masking.d.ts +0 -21
  85. package/dist/node-server/secrets/masking.js +0 -82
  86. package/dist/ui/assets/adobe-CqL9wyDB.js +0 -1
  87. package/dist/ui/assets/adobe-Dz3AQqOU.js +0 -1
  88. package/dist/ui/assets/cost-command-CmqxOe10.js +0 -1
  89. package/dist/ui/assets/github-B1wRLpqB.js +0 -1
  90. package/dist/ui/assets/github-BtI_MJWq.js +0 -2
  91. package/dist/ui/assets/oauth-service-BOg00ewY.js +0 -1
  92. package/dist/ui/assets/oauth-service-CfTUWB9P.js +0 -1
  93. package/dist/ui/assets/page-storage-sync-BPAEqPof.js +0 -1
  94. package/dist/ui/assets/panel-rpc-handlers-ZHQOP2j9.js +0 -1
  95. package/dist/ui/assets/provider-settings-CvNDi0PS.js +0 -90
  96. package/dist/ui/assets/provider-settings-DUgrWSPY.js +0 -2
  97. package/dist/ui/assets/secret-masking-BMzkw8ZK.js +0 -1
  98. /package/dist/ui/assets/{azure-ai-foundry-CaxHzIfg.js → azure-ai-foundry-BHhe6xrk.js} +0 -0
  99. /package/dist/ui/assets/{azure-ai-foundry-B1O-wE_V.js → azure-ai-foundry-Bcv3AvZm.js} +0 -0
  100. /package/dist/ui/assets/{bsh-watchdog-BPXsh13l.js → bsh-watchdog-BBjbd-XL.js} +0 -0
  101. /package/dist/ui/assets/{chat-fixture-CM-jsDDl.js → chat-fixture-5h_JuUYl.js} +0 -0
  102. /package/dist/ui/assets/{git-config-BoQzX7Xi.js → git-config-BMEHAjvp.js} +0 -0
  103. /package/dist/ui/assets/{magick-wasm-BnlzD3um.js → magick-wasm-E5pMp5Fh.js} +0 -0
  104. /package/dist/ui/assets/{remote-terminal-view-wMCUFwpP.js → remote-terminal-view-B3w1XsI8.js} +0 -0
  105. /package/dist/ui/assets/{rum-DYAJNvRR.js → rum-_zaGz-D7.js} +0 -0
  106. /package/dist/ui/assets/{slicc-editor-ZPVWM2cU.js → slicc-editor-DAgxp34c.js} +0 -0
  107. /package/dist/ui/assets/{sprinkle-bridge-channel-Dd_0GEWx.js → sprinkle-bridge-channel-DJuNr3kv.js} +0 -0
  108. /package/dist/ui/assets/{sprinkle-bridge-channel-DhKQ_srk.js → sprinkle-bridge-channel-wZyMV5ic.js} +0 -0
@@ -0,0 +1,249 @@
1
+ import { mask as cryptoMask, buildScrubber, matchesDomains, } from './secret-masking.js';
2
+ function indexOfBytes(haystack, needle, from = 0) {
3
+ outer: for (let i = from; i <= haystack.length - needle.length; i++) {
4
+ for (let j = 0; j < needle.length; j++) {
5
+ if (haystack[i + j] !== needle[j])
6
+ continue outer;
7
+ }
8
+ return i;
9
+ }
10
+ return -1;
11
+ }
12
+ function replaceAllBytes(haystack, needle, replacement) {
13
+ if (indexOfBytes(haystack, needle) < 0)
14
+ return haystack;
15
+ const out = [];
16
+ let i = 0;
17
+ while (i < haystack.length) {
18
+ const idx = indexOfBytes(haystack, needle, i);
19
+ if (idx < 0) {
20
+ for (let k = i; k < haystack.length; k++)
21
+ out.push(haystack[k]);
22
+ break;
23
+ }
24
+ for (let k = i; k < idx; k++)
25
+ out.push(haystack[k]);
26
+ for (let k = 0; k < replacement.length; k++)
27
+ out.push(replacement[k]);
28
+ i = idx + needle.length;
29
+ }
30
+ return new Uint8Array(out);
31
+ }
32
+ /**
33
+ * Stateful unmask/scrub pipeline shared between node-server's /api/fetch-proxy
34
+ * and the chrome-extension SW's fetch-proxy.fetch Port handler.
35
+ *
36
+ * Public surface has four method families:
37
+ *
38
+ * ┌────────────┬────────────────────────────────┬─────────────────────────┐
39
+ * │ │ Text-safe (string in / out) │ Byte-safe (Uint8Array) │
40
+ * ├────────────┼────────────────────────────────┼─────────────────────────┤
41
+ * │ Unmask │ unmask, unmaskBody, │ unmaskBodyBytes │
42
+ * │ (mask→real)│ unmaskHeaders, …Basic, …Url │ │
43
+ * ├────────────┼────────────────────────────────┼─────────────────────────┤
44
+ * │ Scrub │ scrubResponse, scrubHeaders │ scrubResponseBytes │
45
+ * │ (real→mask)│ │ │
46
+ * └────────────┴────────────────────────────────┴─────────────────────────┘
47
+ *
48
+ * Use the byte-safe variants for request/response bodies that may be binary
49
+ * (git packfiles, ZIPs, images, application/octet-stream). The text variants
50
+ * UTF-8-decode their input, which corrupts non-UTF-8 byte sequences
51
+ * (`Buffer.toString('utf-8')` replaces invalid bytes with U+FFFD).
52
+ *
53
+ * Note: unmaskHeaders MUTATES its input in place (matching SecretProxyManager's
54
+ * legacy semantics). The other methods return new strings/byte arrays.
55
+ */
56
+ export class SecretsPipeline {
57
+ sessionId;
58
+ source;
59
+ maskedToSecret = new Map();
60
+ scrubber = (t) => t;
61
+ constructor(opts) {
62
+ this.sessionId = opts.sessionId;
63
+ this.source = opts.source;
64
+ }
65
+ async reload() {
66
+ const all = await this.source.listAll();
67
+ const next = new Map();
68
+ for (const s of all) {
69
+ const maskedValue = await cryptoMask(this.sessionId, s.name, s.value);
70
+ next.set(maskedValue, {
71
+ name: s.name,
72
+ realValue: s.value,
73
+ maskedValue,
74
+ domains: s.domains,
75
+ });
76
+ }
77
+ this.maskedToSecret = next;
78
+ const pairs = Array.from(next.values()).map((ms) => ({
79
+ realValue: ms.realValue,
80
+ maskedValue: ms.maskedValue,
81
+ }));
82
+ this.scrubber = buildScrubber(pairs);
83
+ }
84
+ async maskOne(name, value) {
85
+ return cryptoMask(this.sessionId, name, value);
86
+ }
87
+ hasSecrets() {
88
+ return this.maskedToSecret.size > 0;
89
+ }
90
+ getMaskedEntries() {
91
+ return Array.from(this.maskedToSecret.values()).map((ms) => ({
92
+ name: ms.name,
93
+ maskedValue: ms.maskedValue,
94
+ domains: ms.domains,
95
+ }));
96
+ }
97
+ /**
98
+ * Unmask a single string. Domain mismatch on a matched secret → forbidden.
99
+ * Returns { text } on success, { text: original, forbidden } on block.
100
+ */
101
+ unmask(text, hostname) {
102
+ let result = text;
103
+ for (const [maskedValue, ms] of this.maskedToSecret) {
104
+ if (!result.includes(maskedValue))
105
+ continue;
106
+ if (!matchesDomains(hostname, ms.domains)) {
107
+ return { text, forbidden: { secretName: ms.name, hostname } };
108
+ }
109
+ result = result.split(maskedValue).join(ms.realValue);
110
+ }
111
+ return { text: result };
112
+ }
113
+ /**
114
+ * Unmask body text. Domain mismatch on a matched secret leaves it untouched
115
+ * (NO forbidden — masked values in conversation context are harmless).
116
+ */
117
+ unmaskBody(text, hostname) {
118
+ let result = text;
119
+ for (const [maskedValue, ms] of this.maskedToSecret) {
120
+ if (!result.includes(maskedValue))
121
+ continue;
122
+ if (!matchesDomains(hostname, ms.domains))
123
+ continue;
124
+ result = result.split(maskedValue).join(ms.realValue);
125
+ }
126
+ return { text: result };
127
+ }
128
+ unmaskAuthorizationBasic(headerValue, hostname) {
129
+ const pattern = /^Basic\s+(.+)$/;
130
+ const match = pattern.exec(headerValue);
131
+ if (!match)
132
+ return { value: headerValue };
133
+ let decoded;
134
+ try {
135
+ decoded = atob(match[1].trim());
136
+ }
137
+ catch {
138
+ return { value: headerValue };
139
+ }
140
+ const colon = decoded.indexOf(':');
141
+ if (colon < 0)
142
+ return { value: headerValue };
143
+ let user = decoded.slice(0, colon);
144
+ let pass = decoded.slice(colon + 1);
145
+ let touched = false;
146
+ for (const [maskedValue, ms] of this.maskedToSecret) {
147
+ if (user.includes(maskedValue) || pass.includes(maskedValue)) {
148
+ if (!matchesDomains(hostname, ms.domains)) {
149
+ return { value: headerValue, forbidden: { secretName: ms.name, hostname } };
150
+ }
151
+ if (user.includes(maskedValue))
152
+ user = user.split(maskedValue).join(ms.realValue);
153
+ if (pass.includes(maskedValue))
154
+ pass = pass.split(maskedValue).join(ms.realValue);
155
+ touched = true;
156
+ }
157
+ }
158
+ if (!touched)
159
+ return { value: headerValue };
160
+ return { value: `Basic ${btoa(`${user}:${pass}`)}` };
161
+ }
162
+ extractAndUnmaskUrlCredentials(rawUrl) {
163
+ let parsed;
164
+ try {
165
+ parsed = new URL(rawUrl);
166
+ }
167
+ catch {
168
+ return { url: rawUrl };
169
+ }
170
+ if (!parsed.username && !parsed.password)
171
+ return { url: rawUrl };
172
+ let user = decodeURIComponent(parsed.username);
173
+ let pass = decodeURIComponent(parsed.password);
174
+ const host = parsed.host;
175
+ let touched = false;
176
+ for (const [maskedValue, ms] of this.maskedToSecret) {
177
+ if (user.includes(maskedValue) || pass.includes(maskedValue)) {
178
+ if (!matchesDomains(host, ms.domains)) {
179
+ return { url: rawUrl, forbidden: { secretName: ms.name, hostname: host } };
180
+ }
181
+ if (user.includes(maskedValue)) {
182
+ user = user.split(maskedValue).join(ms.realValue);
183
+ touched = true;
184
+ }
185
+ if (pass.includes(maskedValue)) {
186
+ pass = pass.split(maskedValue).join(ms.realValue);
187
+ touched = true;
188
+ }
189
+ }
190
+ }
191
+ const synthetic = touched && (user || pass) ? `Basic ${btoa(`${user}:${pass}`)}` : undefined;
192
+ parsed.username = '';
193
+ parsed.password = '';
194
+ return { url: parsed.toString(), syntheticAuthorization: synthetic };
195
+ }
196
+ /**
197
+ * Unmask headers IN PLACE. Mutates the headers parameter; returns only { forbidden? }.
198
+ * Match SecretProxyManager's existing semantics so call sites compile unchanged.
199
+ */
200
+ unmaskHeaders(headers, hostname) {
201
+ for (const [key, val] of Object.entries(headers)) {
202
+ if (key.toLowerCase() === 'authorization' && /^Basic\s/i.test(val)) {
203
+ const basic = this.unmaskAuthorizationBasic(val, hostname);
204
+ if (basic.forbidden)
205
+ return { forbidden: basic.forbidden };
206
+ headers[key] = basic.value;
207
+ continue;
208
+ }
209
+ const { text, forbidden } = this.unmask(val, hostname);
210
+ if (forbidden)
211
+ return { forbidden };
212
+ headers[key] = text;
213
+ }
214
+ return {};
215
+ }
216
+ unmaskBodyBytes(body, hostname) {
217
+ let out = body;
218
+ const enc = new TextEncoder();
219
+ for (const [maskedValue, ms] of this.maskedToSecret) {
220
+ if (!matchesDomains(hostname, ms.domains))
221
+ continue;
222
+ const needle = enc.encode(maskedValue);
223
+ const replacement = enc.encode(ms.realValue);
224
+ out = replaceAllBytes(out, needle, replacement);
225
+ }
226
+ return { bytes: out };
227
+ }
228
+ scrubResponse(text) {
229
+ return this.scrubber(text);
230
+ }
231
+ scrubResponseBytes(bytes) {
232
+ let out = bytes;
233
+ const enc = new TextEncoder();
234
+ for (const [maskedValue, ms] of this.maskedToSecret) {
235
+ const needle = enc.encode(ms.realValue);
236
+ const replacement = enc.encode(maskedValue);
237
+ out = replaceAllBytes(out, needle, replacement);
238
+ }
239
+ return out;
240
+ }
241
+ scrubHeaders(headers) {
242
+ const out = {};
243
+ headers.forEach((v, k) => {
244
+ out[k] = this.scrubber(v);
245
+ });
246
+ return out;
247
+ }
248
+ }
249
+ //# sourceMappingURL=secrets-pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-pipeline.js","sourceRoot":"","sources":["../src/secrets-pipeline.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,8 @@ import { createServer } from 'http';
3
3
  import { createServer as createNetServer } from 'net';
4
4
  import { spawn } from 'child_process';
5
5
  import { existsSync, readFileSync } from 'fs';
6
- import { join, resolve } from 'path';
6
+ import { join, resolve, dirname } from 'path';
7
+ import { homedir } from 'os';
7
8
  import { Readable, Transform } from 'stream';
8
9
  import { StringDecoder } from 'string_decoder';
9
10
  import { fileURLToPath } from 'url';
@@ -18,7 +19,9 @@ import { FileLogger } from './file-logger.js';
18
19
  import { CliLogDedup } from './cli-log-dedup.js';
19
20
  import { EnvSecretStore } from './secrets/env-secret-store.js';
20
21
  import { SecretProxyManager } from './secrets/proxy-manager.js';
22
+ import { OauthSecretStore } from './secrets/oauth-secret-store.js';
21
23
  import { handleDaSignAndForward, handleS3SignAndForward } from './secrets/sign-and-forward.js';
24
+ import { readOrCreateSessionId } from './secrets/session-id-file.js';
22
25
  import { FETCH_PROXY_SKIP_HEADERS } from './fetch-proxy-headers.js';
23
26
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
24
27
  const PROJECT_ROOT = resolve(__dirname, '..', '..');
@@ -440,7 +443,6 @@ async function main() {
440
443
  console.error(error instanceof Error ? error.message : String(error));
441
444
  process.exit(1);
442
445
  }
443
- throw new Error('unreachable');
444
446
  })();
445
447
  const chromePath = findChromeExecutable({
446
448
  executablePreference: !DEV_MODE && !chromeProfile.id ? 'installed' : 'chrome-for-testing',
@@ -489,7 +491,16 @@ async function main() {
489
491
  });
490
492
  }
491
493
  // 3. Set up express app with request logging
492
- const secretProxy = new SecretProxyManager();
494
+ const sessionDir = RUNTIME_FLAGS.envFile
495
+ ? dirname(RUNTIME_FLAGS.envFile)
496
+ : join(homedir(), '.slicc');
497
+ const sessionId = readOrCreateSessionId(sessionDir);
498
+ const oauthStore = new OauthSecretStore();
499
+ // Env-file secrets (~/.slicc/secrets.env) feed the fetch-proxy mask
500
+ // pipeline alongside OAuth tokens. The same instance is reused below
501
+ // for /api/secrets and handleS3SignAndForward.
502
+ const secretStore = new EnvSecretStore(RUNTIME_FLAGS.envFile ?? undefined);
503
+ const secretProxy = new SecretProxyManager(secretStore, sessionId, oauthStore);
493
504
  try {
494
505
  await secretProxy.reload();
495
506
  if (secretProxy.hasSecrets()) {
@@ -792,8 +803,9 @@ async function main() {
792
803
  });
793
804
  res.json({ ok: true });
794
805
  });
795
- // Secret management API — direct .env file access (no browser needed)
796
- const secretStore = new EnvSecretStore(RUNTIME_FLAGS.envFile ?? undefined);
806
+ // Secret management API — direct .env file access (no browser needed).
807
+ // `secretStore` was created above and wired into `secretProxy` so the
808
+ // fetch-proxy and the management API share one source of truth.
797
809
  app.get('/api/secrets', (_req, res) => {
798
810
  try {
799
811
  const entries = secretStore.list();
@@ -873,6 +885,31 @@ async function main() {
873
885
  .json({ error: err instanceof Error ? err.message : 'Failed to get masked secrets' });
874
886
  }
875
887
  });
888
+ // OAuth secret update — stores access token from OAuth login flow
889
+ app.post('/api/secrets/oauth-update', express.json(), async (req, res) => {
890
+ const { providerId, accessToken, domains } = req.body ?? {};
891
+ if (typeof providerId !== 'string' ||
892
+ typeof accessToken !== 'string' ||
893
+ !Array.isArray(domains) ||
894
+ domains.length === 0) {
895
+ return res.status(400).json({ error: 'bad-request' });
896
+ }
897
+ const name = `oauth.${providerId}.token`;
898
+ oauthStore.set(name, accessToken, domains);
899
+ await secretProxy.reload();
900
+ const masked = secretProxy.getMaskedEntries().find((e) => e.name === name)?.maskedValue;
901
+ res.json({ providerId, name, maskedValue: masked, domains });
902
+ });
903
+ // OAuth secret deletion — removes access token on logout
904
+ app.delete('/api/secrets/oauth/:providerId', async (req, res) => {
905
+ const name = `oauth.${req.params.providerId}.token`;
906
+ if (!oauthStore.list().some((e) => e.name === name)) {
907
+ return res.status(404).json({ error: 'not-found' });
908
+ }
909
+ oauthStore.delete(name);
910
+ await secretProxy.reload();
911
+ res.status(204).end();
912
+ });
876
913
  // Fetch proxy — forwards cross-origin requests from the browser to bypass CORS.
877
914
  // Used by just-bash's curl which calls the browser's fetch() API.
878
915
  // Note: express.json() may have already parsed the body, so we check req.body first.
@@ -986,6 +1023,23 @@ async function main() {
986
1023
  return;
987
1024
  }
988
1025
  }
1026
+ // --- Secret injection: unmask URL-embedded credentials ---
1027
+ let cleanedUrl = targetUrl;
1028
+ if (secretProxy.hasSecrets()) {
1029
+ const credsResult = secretProxy.extractAndUnmaskUrlCredentials(targetUrl);
1030
+ if (credsResult.forbidden) {
1031
+ res.setHeader('X-Proxy-Error', '1');
1032
+ res.status(403).json({
1033
+ error: `Secret "${credsResult.forbidden.secretName}" is not allowed for domain "${credsResult.forbidden.hostname}"`,
1034
+ });
1035
+ return;
1036
+ }
1037
+ cleanedUrl = credsResult.url;
1038
+ // Attach synthetic Authorization if the URL had credentials and the header isn't already set
1039
+ if (credsResult.syntheticAuthorization && !('authorization' in headers)) {
1040
+ headers.authorization = credsResult.syntheticAuthorization;
1041
+ }
1042
+ }
989
1043
  if (Object.keys(headers).length > 0)
990
1044
  fetchInit.headers = headers;
991
1045
  if (rawBody.length > 0 && !['GET', 'HEAD'].includes(req.method)) {
@@ -994,7 +1048,24 @@ async function main() {
994
1048
  // (safe/meaningless) rather than rejecting. This avoids false 403s when
995
1049
  // LLM conversation context contains masked secrets sent to non-matching
996
1050
  // domains like Bedrock.
997
- if (secretProxy.hasSecrets()) {
1051
+ //
1052
+ // Skip the unmask for non-text content (git packfiles, octet-stream,
1053
+ // ZIPs, images, …) — `Buffer.toString('utf-8')` on arbitrary bytes
1054
+ // replaces invalid sequences with U+FFFD, silently corrupting the
1055
+ // payload. Masked values are hex strings with known prefixes; they
1056
+ // do not appear in deflated git packfiles or other compressed binary,
1057
+ // so skipping is safe.
1058
+ const reqCt = (headers['content-type'] ?? headers['Content-Type'] ?? '').toLowerCase();
1059
+ const reqIsText = !reqCt ||
1060
+ reqCt.startsWith('text/') ||
1061
+ reqCt.includes('json') ||
1062
+ reqCt.includes('xml') ||
1063
+ reqCt.includes('javascript') ||
1064
+ reqCt.includes('ecmascript') ||
1065
+ reqCt.includes('html') ||
1066
+ reqCt.includes('css') ||
1067
+ reqCt.includes('svg');
1068
+ if (reqIsText && secretProxy.hasSecrets()) {
998
1069
  const bodyStr = rawBody.toString('utf-8');
999
1070
  const bodyResult = secretProxy.unmaskBody(bodyStr, targetHostname);
1000
1071
  rawBody = Buffer.from(bodyResult.text, 'utf-8');
@@ -1020,7 +1091,7 @@ async function main() {
1020
1091
  };
1021
1092
  res.on('close', onClientClose);
1022
1093
  fetchInit.signal = abortController.signal;
1023
- const upstream = await fetch(targetUrl, fetchInit);
1094
+ const upstream = await fetch(cleanedUrl, fetchInit);
1024
1095
  // Forward status, prevent browser caching of proxy responses
1025
1096
  res.status(upstream.status);
1026
1097
  res.setHeader('Cache-Control', 'no-store, no-cache');
@@ -1,5 +1,5 @@
1
- export type { Secret, SecretEntry, SecretStore } from './types.js';
1
+ export type { Secret, SecretEntry, SecretStore, MaskedSecret } from './types.js';
2
2
  export { EnvSecretStore } from './env-secret-store.js';
3
- export { matchDomain, matchesDomains } from './domain-match.js';
3
+ export { domainMatches as matchDomain, matchesDomains } from '../_shared/index.js';
4
4
  export { parseEnvFile, serializeEnvFile } from './env-file.js';
5
- export { SecretProxyManager, type MaskedSecret } from './proxy-manager.js';
5
+ export { SecretProxyManager } from './proxy-manager.js';
@@ -1,4 +1,4 @@
1
1
  export { EnvSecretStore } from './env-secret-store.js';
2
- export { matchDomain, matchesDomains } from './domain-match.js';
2
+ export { domainMatches as matchDomain, matchesDomains } from '../_shared/index.js';
3
3
  export { parseEnvFile, serializeEnvFile } from './env-file.js';
4
4
  export { SecretProxyManager } from './proxy-manager.js';
@@ -0,0 +1,12 @@
1
+ export interface OauthEntry {
2
+ name: string;
3
+ value: string;
4
+ domains: string[];
5
+ }
6
+ export declare class OauthSecretStore {
7
+ private entries;
8
+ set(name: string, value: string, domains: string[]): void;
9
+ delete(name: string): void;
10
+ list(): OauthEntry[];
11
+ get(name: string): string | undefined;
12
+ }
@@ -0,0 +1,18 @@
1
+ export class OauthSecretStore {
2
+ entries = new Map();
3
+ set(name, value, domains) {
4
+ if (!Array.isArray(domains) || domains.length === 0) {
5
+ throw new Error('OauthSecretStore: domains must be non-empty');
6
+ }
7
+ this.entries.set(name, { name, value, domains });
8
+ }
9
+ delete(name) {
10
+ this.entries.delete(name);
11
+ }
12
+ list() {
13
+ return Array.from(this.entries.values());
14
+ }
15
+ get(name) {
16
+ return this.entries.get(name)?.value;
17
+ }
18
+ }
@@ -4,45 +4,27 @@
4
4
  *
5
5
  * On init: loads all secrets, generates session-scoped masked values,
6
6
  * and builds lookup tables for fast replacement.
7
+ *
8
+ * As of Task 1.4, this is a thin wrapper around SecretsPipeline from @slicc/shared-ts.
7
9
  */
8
- import { EnvSecretStore } from './env-secret-store.js';
9
- export interface MaskedSecret {
10
- name: string;
11
- maskedValue: string;
12
- realValue: string;
13
- domains: string[];
14
- }
10
+ import { type EnvSecretStore } from './env-secret-store.js';
11
+ import { type OauthSecretStore } from './oauth-secret-store.js';
15
12
  export declare class SecretProxyManager {
16
- private readonly store;
17
- private readonly sessionId;
18
- /** maskedValue → MaskedSecret */
19
- private maskedToSecret;
20
- /** Scrubber that replaces real→masked in response content */
21
- private scrubber;
22
- constructor(store?: EnvSecretStore, sessionId?: string);
23
- /**
24
- * Load secrets from the store and generate masked values.
25
- * Call once on startup and again whenever secrets change.
26
- */
13
+ private readonly pipeline;
14
+ private readonly _sessionId;
15
+ private _envStore?;
16
+ private _oauthStore?;
17
+ constructor(store?: EnvSecretStore, sessionId?: string, oauthStore?: OauthSecretStore);
18
+ private buildSource;
19
+ setOauthStore(store: OauthSecretStore): void;
20
+ get sessionId(): string;
27
21
  reload(): Promise<void>;
28
- /**
29
- * Check if any secrets are loaded.
30
- */
31
22
  hasSecrets(): boolean;
32
- /**
33
- * Get all masked entries (name + maskedValue + domains) for env population.
34
- */
35
23
  getMaskedEntries(): Array<{
36
24
  name: string;
37
25
  maskedValue: string;
38
26
  domains: string[];
39
27
  }>;
40
- /**
41
- * Unmask a text blob: replace masked values with real values.
42
- * Validates each secret against the target hostname.
43
- *
44
- * @returns { text, forbidden } — forbidden is set if a secret was blocked.
45
- */
46
28
  unmask(text: string, targetHostname: string): {
47
29
  text: string;
48
30
  forbidden?: {
@@ -50,34 +32,16 @@ export declare class SecretProxyManager {
50
32
  hostname: string;
51
33
  };
52
34
  };
53
- /**
54
- * Unmask body text: replace masked values with real values when domain matches.
55
- * When domain does NOT match, the masked value is left as-is (not rejected).
56
- * This is safe because the masked value is meaningless to the remote service —
57
- * it's typically just conversation context sent to an LLM API.
58
- */
59
35
  unmaskBody(text: string, targetHostname: string): {
60
36
  text: string;
61
37
  };
62
- /**
63
- * Unmask headers in-place. Returns forbidden info if blocked.
64
- */
65
38
  unmaskHeaders(headers: Record<string, string>, targetHostname: string): {
66
39
  forbidden?: {
67
40
  secretName: string;
68
41
  hostname: string;
69
42
  };
70
43
  };
71
- /**
72
- * Scrub real secret values from response text → masked values.
73
- */
44
+ extractAndUnmaskUrlCredentials(rawUrl: string): import("../_shared/index.js").ExtractedUrlCreds;
74
45
  scrubResponse(text: string): string;
75
- /**
76
- * Scrub headers: replace real values with masked in header values.
77
- */
78
46
  scrubHeaders(headers: Headers): Record<string, string>;
79
- /**
80
- * Look up a secret by its masked value.
81
- */
82
- getByMaskedValue(maskedValue: string): MaskedSecret | undefined;
83
47
  }