|
1 | 1 | // Loads dotenv files while blocking unsafe workspace env keys. |
2 | | -import fs from "node:fs"; |
3 | 2 | import path from "node:path"; |
4 | | -import dotenv from "dotenv"; |
5 | | -import { createSubsystemLogger } from "../logging/subsystem.js"; |
6 | 3 | import { listKnownProviderAuthEnvVarNames } from "../secrets/provider-env-vars.js"; |
7 | | -import { loadGlobalRuntimeDotEnvFiles } from "./dotenv-global.js"; |
| 4 | +import { loadGlobalRuntimeDotEnvFiles, readDotEnvFile } from "./dotenv-global.js"; |
8 | 5 | import { |
9 | 6 | isDangerousHostEnvOverrideVarName, |
10 | 7 | isDangerousHostEnvVarName, |
11 | 8 | normalizeEnvVarKey, |
12 | 9 | } from "./host-env-security.js"; |
13 | 10 | |
14 | | -const logger = createSubsystemLogger("infra:dotenv"); |
15 | | - |
16 | 11 | const BLOCKED_PROVIDER_AUTH_WORKSPACE_DOTENV_KEYS = [ |
17 | 12 | "AI_GATEWAY_API_KEY", |
18 | 13 | "ANTHROPIC_API_KEY", |
@@ -222,55 +217,6 @@ function shouldBlockWorkspaceDotEnvKey(
|
222 | 217 | ); |
223 | 218 | } |
224 | 219 | |
225 | | -type DotEnvEntry = { |
226 | | -key: string; |
227 | | -value: string; |
228 | | -}; |
229 | | - |
230 | | -type LoadedDotEnvFile = { |
231 | | -filePath: string; |
232 | | -entries: DotEnvEntry[]; |
233 | | -}; |
234 | | - |
235 | | -function readDotEnvFile(params: { |
236 | | -filePath: string; |
237 | | -shouldBlockKey: (key: string) => boolean; |
238 | | -quiet?: boolean; |
239 | | -}): LoadedDotEnvFile | null { |
240 | | -let content: string; |
241 | | -try { |
242 | | -content = fs.readFileSync(params.filePath, "utf8"); |
243 | | -} catch (error) { |
244 | | -if (!params.quiet) { |
245 | | -const code = |
246 | | -error && typeof error === "object" && "code" in error ? String(error.code) : undefined; |
247 | | -if (code !== "ENOENT") { |
248 | | -logger.warn(`Failed to read ${params.filePath}: ${String(error)}`, { error }); |
249 | | -} |
250 | | -} |
251 | | -return null; |
252 | | -} |
253 | | - |
254 | | -let parsed: Record<string, string>; |
255 | | -try { |
256 | | -parsed = dotenv.parse(content); |
257 | | -} catch (error) { |
258 | | -if (!params.quiet) { |
259 | | -logger.warn(`Failed to parse ${params.filePath}: ${String(error)}`, { error }); |
260 | | -} |
261 | | -return null; |
262 | | -} |
263 | | -const entries: DotEnvEntry[] = []; |
264 | | -for (const [rawKey, value] of Object.entries(parsed)) { |
265 | | -const key = normalizeEnvVarKey(rawKey, { portable: true }); |
266 | | -if (!key || params.shouldBlockKey(key)) { |
267 | | -continue; |
268 | | -} |
269 | | -entries.push({ key, value }); |
270 | | -} |
271 | | -return { filePath: params.filePath, entries }; |
272 | | -} |
273 | | - |
274 | 220 | export function loadWorkspaceDotEnvFile(filePath: string, opts?: { quiet?: boolean }) { |
275 | 221 | let providerAuthBlockedKeys: ReadonlySet<string> | undefined; |
276 | 222 | const getProviderAuthBlockedKeys = () => { |
@@ -279,7 +225,7 @@ export function loadWorkspaceDotEnvFile(filePath: string, opts?: { quiet?: boole
|
279 | 225 | }; |
280 | 226 | const parsed = readDotEnvFile({ |
281 | 227 | filePath, |
282 | | -shouldBlockKey: (key) => shouldBlockWorkspaceDotEnvKey(key, getProviderAuthBlockedKeys), |
| 228 | +entryFilter: (key) => !shouldBlockWorkspaceDotEnvKey(key, getProviderAuthBlockedKeys), |
283 | 229 | quiet: opts?.quiet ?? true, |
284 | 230 | }); |
285 | 231 | if (!parsed) { |
|