diff options
| author | Aiden Cline <[email protected]> | 2026-01-17 20:59:42 -0600 |
|---|---|---|
| committer | Aiden Cline <[email protected]> | 2026-01-17 20:59:50 -0600 |
| commit | 052f887a9a7aaf79d9f1a560f9b686d59faa8348 (patch) | |
| tree | 2d0d3745607b04695c1b8f80c5a943cc32daf219 /packages | |
| parent | 759e68616e53f4c4d3a647606203bf46a9193733 (diff) | |
| download | opencode-052f887a9a7aaf79d9f1a560f9b686d59faa8348.tar.gz opencode-052f887a9a7aaf79d9f1a560f9b686d59faa8348.zip | |
core: prevent env variables in config from being replaced with actual values
When opencode.json was missing a $schema, the config loader would add it
and write the file back - but with env variables like {env:API_KEY} replaced
with their actual secret values. This made it impossible to safely commit
opencode.json to version control.
Now the original config text is preserved when adding $schema, keeping
variable placeholders intact.
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/opencode/src/config/config.ts | 5 | ||||
| -rw-r--r-- | packages/opencode/test/config/config.test.ts | 38 |
2 files changed, 42 insertions, 1 deletions
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 1574c644d..5a2e086bf 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1115,6 +1115,7 @@ export namespace Config { } async function load(text: string, configFilepath: string) { + const original = text text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => { return process.env[varName] || "" }) @@ -1184,7 +1185,9 @@ export namespace Config { if (parsed.success) { if (!parsed.data.$schema) { parsed.data.$schema = "https://opencode.ai/config.json" - await Bun.write(configFilepath, JSON.stringify(parsed.data, null, 2)).catch(() => {}) + // Write the $schema to the original text to preserve variables like {env:VAR} + const updated = original.replace(/^\s*\{/, '{\n "$schema": "https://opencode.ai/config.json",') + await Bun.write(configFilepath, updated).catch(() => {}) } const data = parsed.data if (data.plugin) { diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 86cadca5d..0463d29d7 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -127,6 +127,44 @@ test("handles environment variable substitution", async () => { } }) +test("preserves env variables when adding $schema to config", async () => { + const originalEnv = process.env["PRESERVE_VAR"] + process.env["PRESERVE_VAR"] = "secret_value" + + try { + await using tmp = await tmpdir({ + init: async (dir) => { + // Config without $schema - should trigger auto-add + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + theme: "{env:PRESERVE_VAR}", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.theme).toBe("secret_value") + + // Read the file to verify the env variable was preserved + const content = await Bun.file(path.join(tmp.path, "opencode.json")).text() + expect(content).toContain("{env:PRESERVE_VAR}") + expect(content).not.toContain("secret_value") + expect(content).toContain("$schema") + }, + }) + } finally { + if (originalEnv !== undefined) { + process.env["PRESERVE_VAR"] = originalEnv + } else { + delete process.env["PRESERVE_VAR"] + } + } +}) + test("handles file inclusion substitution", async () => { await using tmp = await tmpdir({ init: async (dir) => { |
