From a3c47d4cfe9ffb6572712fa7524a124dd503e3d6 Mon Sep 17 00:00:00 2001 From: Jaeyoung Yun Date: Tue, 19 May 2026 22:11:08 +0900 Subject: [PATCH] fix(cli/configure): write .env at mode 0o600 to protect LLM API keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `configure` subcommand prompts the user for their LLM provider API keys (OPENAI_API_KEY, ANTHROPIC_API_KEY, AZURE_OPENAI_API_KEY, etc.) and patches them into the `.env` file via `writeFile(filePath, content, "utf-8")` with no `mode` option. On macOS and most Linux defaults (umask 022) that lands the file at 0o644 — world-readable. Any local account or process that can traverse the home / working directory can recover the keys and use them under the user's account quota. This commit: - Adds `chmod` to the `fs/promises` import. - Passes `{ encoding: "utf-8", mode: 0o600 }` to `writeFile` so a freshly- created `.env` is atomically restricted to the user. - Adds a follow-up `await chmod(filePath, 0o600)` (wrapped in try/catch for Windows) so overwrites of a pre-existing `.env` at 0o644 converge to 0o600 on the next save. Mirrors industry-baseline credential-file handling (GitHub CLI's ~/.config/gh/hosts.yml, AWS CLI's ~/.aws/credentials at 0o600). Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/cli/src/configure.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/configure.ts b/packages/cli/src/configure.ts index ed53ef6b7d..0c9f00d8c7 100644 --- a/packages/cli/src/configure.ts +++ b/packages/cli/src/configure.ts @@ -2,7 +2,7 @@ import { select, input, confirm, password } from "@inquirer/prompts" import { MODEL_PROVIDERS } from "../../core/src/constants" import { resolveLanguageModelConfigurations } from "../../core/src/config" import { parse } from "dotenv" -import { writeFile } from "fs/promises" +import { chmod, writeFile } from "fs/promises" import { runtimeHost } from "../../core/src/host" import { deleteUndefinedValues } from "../../core/src/cleaners" import { logInfo, logVerbose, logWarn } from "../../core/src/util" @@ -183,5 +183,17 @@ async function patchEnvFile(filePath: string, key: string, value: string) { updatedLines.push(`${key}=${value}`) } - await writeFile(filePath, updatedLines.join("\n"), "utf-8") + // .env files written via `configure` embed LLM provider API keys + // (OPENAI_API_KEY, ANTHROPIC_API_KEY, AZURE_OPENAI_API_KEY, etc.) that + // the user just entered at the prompt. Restrict the file to the owning + // user on POSIX so a co-tenant on the same host can't recover them. + // `mode` applies on initial create; chmod afterward handles the overwrite + // case (where `writeFile`'s mode option doesn't apply). Try/catch wraps + // chmod for Windows fallback (no POSIX modes). + await writeFile(filePath, updatedLines.join("\n"), { encoding: "utf-8", mode: 0o600 }) + try { + await chmod(filePath, 0o600) + } catch { + // Windows fallback. + } }