Skip to content

loadConfig: let OCGO_API_KEY override the config file, as documented - #15

Open
quadseven wants to merge 1 commit into
emanuelcasco:mainfrom
quadseven:fix/env-key-precedence
Open

loadConfig: let OCGO_API_KEY override the config file, as documented#15
quadseven wants to merge 1 commit into
emanuelcasco:mainfrom
quadseven:fix/env-key-precedence

Conversation

@quadseven

Copy link
Copy Markdown

Why

OCGO_API_KEY is seeded as the struct default before json.Unmarshal reads config.json over it:

cfg := Config{Host: defaultHost, Port: defaultPort, APIKey: os.Getenv("OCGO_API_KEY")}
b, err := os.ReadFile(configFile())
if err == nil {
    _ = json.Unmarshal(b, &cfg)   // <- overwrites the env value
}

So a stale api_key in the file silently wins, and the env var documented in the README —

"You can also provide the key at runtime with an environment variable: export OCGO_API_KEY=sk-opencode-your-key"

— does nothing whenever a config file exists.

Hit this for real: a stale key in config.json, the correct key exported in OCGO_API_KEY, and every request returning 401 Invalid API key through both ocgo serve and direct calls. Nothing indicates which credential was used, ocgo setup looks healthy, and the documented override appears to be ignored for no reason.

What

Read the file first, then let a non-empty env var win — explicit env > config file > defaults, matching the README and the usual precedence.

cfg := Config{Host: defaultHost, Port: defaultPort}
b, err := os.ReadFile(configFile())
if err == nil { _ = json.Unmarshal(b, &cfg) }
if k := strings.TrimSpace(os.Getenv("OCGO_API_KEY")); k != "" {
    cfg.APIKey = k
}

TrimSpace means a whitespace-only value counts as unset, so an export OCGO_API_KEY= left in a shell profile cannot blank out a working config file.

Side benefit: it makes it possible to source the key from a secret store at run time without ever writing it to disk, which is why I went looking.

Test plan

Three new tests. configFile() resolves through os.UserHomeDir(), so they point HOME at a temp dir with t.Setenv rather than needing production code changed for testability:

  • TestEnvKeyOverridesConfigFile — env wins over a differing file value
  • TestConfigFileUsedWhenEnvUnset — file still used when no env var
  • TestBlankEnvDoesNotClobberConfigFile — whitespace-only env ignored

Verified the first one actually catches the bug by restoring the original ordering:

--- FAIL: TestEnvKeyOverridesConfigFile
    main_test.go:1172: APIKey = "sk-stale-from-file", want the env var to win over the config file

Restored → all pass. go build ./..., go vet ./..., go test ./... and gofmt -l . all clean.

OCGO_API_KEY was seeded as the struct default BEFORE json.Unmarshal read
config.json over it, so a stale `api_key` in the file silently won. The
README says "You can also provide the key at runtime with an environment
variable", and it does not work whenever a config file exists.

The failure is quiet and expensive to chase: every request comes back
`401 Invalid API key` with no indication which credential was used, while
`ocgo setup` looks fine and the documented override appears to be ignored
for no reason.

Reading the file first and letting a non-empty env var win afterwards
matches the documentation and the usual precedence (explicit env > file >
defaults). It also makes it possible to source the key from a secret store
at run time without ever writing it to disk.

Whitespace-only values are treated as unset, so an `export OCGO_API_KEY=`
left in a shell profile cannot blank out a working config file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant