summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2026-01-17 20:59:42 -0600
committerAiden Cline <[email protected]>2026-01-17 20:59:50 -0600
commit052f887a9a7aaf79d9f1a560f9b686d59faa8348 (patch)
tree2d0d3745607b04695c1b8f80c5a943cc32daf219 /packages
parent759e68616e53f4c4d3a647606203bf46a9193733 (diff)
downloadopencode-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.ts5
-rw-r--r--packages/opencode/test/config/config.test.ts38
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) => {