Skip to content

fix(config): treat empty KOSLI_* env vars as unset, not as set - #1089

Merged
JonJagger merged 1 commit into
mainfrom
fix-empty-kosli-env-vars-treated-as-set
Aug 12, 2026
Merged

JonJagger merged 1 commit into
mainfrom
fix-empty-kosli-env-vars-treated-as-set

Conversation

@JonJagger

Copy link
Copy Markdown
Contributor

An environment variable set to the empty string reports as present via
os.LookupEnv, and two hand-rolled reads took that as evidence the user had
supplied a value.

KOSLI_CONFIG_FILE="" replaced the default config path with "", so no config
file was loaded at all and org, api-token and every other configured default
vanished silently.

KOSLI_API_TOKEN="" was read as "the token came from the environment, so it is
plaintext", which skipped decryption of the config-file token. Anyone who
stored their token with kosli config --api-token holds ciphertext, so the
ciphertext was sent as the API token and authentication failed with nothing
pointing at the skipped decryption step.

Both now require a non-empty value, matching how viper already treats every
other bound KOSLI_* variable. Making an empty variable an error is left to the
wider AllowEmptyEnv work, so they all change together rather than two of them
diverging again.

Checklist

  An environment variable set to the empty string reports as present via
  os.LookupEnv, and two hand-rolled reads took that as evidence the user had
  supplied a value.

  KOSLI_CONFIG_FILE="" replaced the default config path with "", so no config
  file was loaded at all and org, api-token and every other configured default
  vanished silently.

  KOSLI_API_TOKEN="" was read as "the token came from the environment, so it is
  plaintext", which skipped decryption of the config-file token. Anyone who
  stored their token with `kosli config --api-token` holds ciphertext, so the
  ciphertext was sent as the API token and authentication failed with nothing
  pointing at the skipped decryption step.

  Both now require a non-empty value, matching how viper already treats every
  other bound KOSLI_* variable. Making an empty variable an error is left to the
  wider AllowEmptyEnv work, so they all change together rather than two of them
  diverging again.
@claude

claude Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @JonJagger's task in 1m 51s —— View job


Code Review: treat empty KOSLI_* env vars as unset

Verdict: LGTM. Correct, well-scoped fix with clear tests and excellent explanatory comments.

What was checked

  • Read both changed files (root.go, root_test.go)
  • go build ./... — passes
  • go vet ./cmd/... — passes
  • Swept for other direct KOSLI_* reads with the same bug
  • Code review

Correctness

Both hand-rolled reads bypassed viper, so they never benefited from viper's default AllowEmptyEnv=false (which treats an empty env var as unset). The fix restores that semantics for the two outliers:

  • root.go:497 — exists && path != "" stops an empty KOSLI_CONFIG_FILE from blanking the config path and silently dropping every configured default. ✅
  • root.go:570-571 — apiTokenPresentInEnv && apiTokenFromEnv != "" stops an empty KOSLI_API_TOKEN from being read as "token came from env, so it's plaintext," which had skipped decryption and sent ciphertext. ✅

I confirmed the alignment claim: with KOSLI_API_TOKEN="", viper's v.IsSet("api-token") returns false for the empty env and true from the config file, so !apiTokenSetInEnv is now true and the decrypt branch is correctly entered. Consistent with every other bound flag.

No other instances of the bug

I swept for direct env reads of KOSLI_* vars in production code. The only other one is main.go:154 (KOSLI_DEBUG), which is parsed via strconv.ParseBool and safely ignores an empty value (err path), so it's unaffected. Nothing else regresses.

Testing

TestEmptyApiTokenEnvVarStillDecryptsConfigToken and TestEmptyConfigFileEnvVarFallsBackToDefault are targeted regression pins with t.Setenv for isolation. The comment explaining why the "as plain text" warning is a stable marker for "decryption branch was entered" is a genuinely good bit of documentation — it makes the test robust against whether an encryption key exists in the credentials store.

Minor (optional, non-blocking)

The idiom exists && val != "" now appears in two places. If more direct KOSLI_* reads are added later, a tiny helper (e.g. lookupNonEmptyEnv(name) (string, bool)) would keep them from diverging again — matching the spirit of the PR description ("so they all change together"). Not worth doing for just two call sites.

Docs / help text

No user-facing flag help or output changed, so the autogenerated docs checklist item is a no-op here. Correct to leave it.

Nice work — the commit message and PR description read as a model of explaining root cause and blast radius.

@JonJagger
JonJagger merged commit cbda3a3 into main Aug 12, 2026
20 checks passed
@JonJagger
JonJagger deleted the fix-empty-kosli-env-vars-treated-as-set branch August 12, 2026 13:16
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
kosli-cli 2.36.6

Created-by: HarmonybrewBot
Commit-by: HarmonybrewBot
Merged-by: HarmonybrewBot
Description: Created by `brew bump`

---

Created with `brew bump-formula-pr`.<details>
  <summary>release notes</summary>
  <pre># Bug fixes
- Empty `--attachments` or `--template` elements (e.g. from unset shell variables) are now rejected with a clear error instead of being silently dropped.
- An empty `KOSLI_CONFIG_FILE` environment variable no longer suppresses config file loading; it is treated as unset and falls back to the default.
- An empty `KOSLI_API_TOKEN` environment variable no longer skips decryption of a config-file token.
- A boolean flag given an empty value (e.g. `--compliant ""` or `--new-compliance-status ""`) is now rejected with a clear error naming the flag, preventing the opposite compliance verdict from being recorded silently.
- Config file or environment values that cannot be applied to a flag now produce an error naming the flag and its source, instead of silently failing.

## What's Changed
* fix(attest): always serialise commits in pull request attestations by @dangrondahl in kosli-dev/cli#1083
* fix(docs): correct invalid regex in snapshot ecs help examples by @dangrondahl in kosli-dev/cli#1080
* fix(config): treat empty KOSLI_* env vars as unset, not as set by @JonJagger in kosli-dev/cli#1089
* fix(config): report config values that cannot be applied to their flag by @JonJagger in kosli-dev/cli#1090
* fix(flags): reject an empty element in a multi-value flag by @JonJagger in kosli-dev/cli#1092
* fix(flags): reject an empty value written after a boolean flag by @JonJagger in kosli-dev/cli#1091
* chore(deps): bump the github-actions-dependencies group with 3 updates by @dependabot[bot] in kosli-dev/cli#1084
* chore(deps): bump the go-dependencies group with 9 updates by @dependabot[bot] in kosli-dev/cli#1085
* chore(deps): bump anthropics/claude-code-action from 1.0.189 to 1.0.191 in the github-actions-dependencies group by @dependabot[bot] in kosli-dev/cli#1094
* chore(deps): bump the go-dependencies group with 7 updates by @dependabot[bot] in kosli-dev/cli#1095
* chore(deps): bump google.golang.org/protobuf from 1.36.12-0.20260120151049-f2248ac996af to 1.36.12 by @dependabot[bot] in kosli-dev/cli#1096


**Full Changelog**: https://github.com/kosli-dev/cli/compare/v2.36.5...v2.36.6</pre>
  <p>View the full release notes at <a href="https://github.com/kosli-dev/cli/releases/tag/v2.36.6">https://github.com/kosli-dev/cli/releases/tag/v2.36.6</a>.</p>
</details>
<hr>

See merge request: Harmonybrew/homebrew-core!16566
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.

2 participants