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.
This commit is contained in:
Aiden Cline
2026-01-17 20:59:42 -06:00
parent 759e68616e
commit 052f887a9a
2 changed files with 42 additions and 1 deletions

View File

@@ -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) {

View File

@@ -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) => {