Skip to content

security: API keys via /.env + typed env.py, scrub leaked key from config#163

Merged
KailasMahavarkar merged 1 commit intomainfrom
fix/api-key-env-loader
Apr 20, 2026
Merged

security: API keys via /.env + typed env.py, scrub leaked key from config#163
KailasMahavarkar merged 1 commit intomainfrom
fix/api-key-env-loader

Conversation

@KailasMahavarkar
Copy link
Copy Markdown
Contributor

CRITICAL context

tools/finetune/training_config.json had an OpenRouter API key committed inline in commit 5c54c9c (PR #93). The key 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 has been publicly visible on GitHub since that merge.

Action required from maintainer: revoke the key at https://openrouter.ai/settings/keys. Revoking is the only real mitigation - git history rewrites are cosmetic after the key has been indexed by scanners.

New secret layout

File Role
/.env Raw KEY=value lines. Gitignored.
/.env.example Tracked template. Copy to .env and fill in.
/env.py Loads .env at import. Exposes typed ENV frozen dataclass. Callers do ENV["openrouter_key"] or ENV.openrouter_key. Typos raise KeyError.

Providers resolve keys via a pid → ENV-field map in providers.py:

_PROVIDER_KEY_MAP = {
    "openrouter": "openrouter_key",
    "local_ollama": "ollama_key",
}

Files scrubbed

  • tools/finetune/training_config.json - key removed from HEAD (still in history)
  • tools/autoresearch/config.json - local, inline keys removed
  • tools/autoresearch/config.example.json - template, inline key placeholders removed

Test plan

  • 1802 tests pass, 101 skipped
  • End-to-end verify: from env import ENV; resolve_providers(load_config()) returns providers with keys populated from /.env
  • Out-of-band: revoke leaked OpenRouter key
  • Optional: git filter-repo --replace-text to purge the key from all branches + force-push main (cosmetic)

🤖 Generated with Claude Code

Root cause: tools/finetune/training_config.json had an OpenRouter API key
committed inline (commit 5c54c9c in PR #93, visible on GitHub). The key
`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`
is public in git history. **Must be revoked out-of-band at openrouter.ai**
- revoking the key is the only real mitigation; history rewrites are
cosmetic after the key is indexed.

New layout (all secrets via .env):
  /.env            - raw KEY=value, gitignored
  /.env.example    - tracked template, committed
  /env.py          - loads .env at import, exposes typed ENV singleton.
                     Callers do ENV["openrouter_key"] or ENV.openrouter_key.
                     Frozen dataclass + __getitem__ so typos raise KeyError.

Providers resolve keys via a pid -> ENV-field map in providers.py:
  _PROVIDER_KEY_MAP = {"openrouter": "openrouter_key",
                       "local_ollama": "ollama_key"}

Config files now carry zero secrets:
  tools/autoresearch/config.json          (gitignored, key fields removed)
  tools/autoresearch/config.example.json  (template, key fields removed)
  tools/finetune/training_config.json     (tracked, leaked key replaced)

.gitignore now excludes /.env at repo root.

Full suite: 1802 passed, 101 skipped.

FOLLOW-UP NEEDED FROM USER:
  1. Revoke the exposed key at https://openrouter.ai/settings/keys
     (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)
  2. Optional: git filter-repo to purge the key from all history +
     force-push main (cosmetic - scanners have already seen it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@KailasMahavarkar KailasMahavarkar merged commit 13db725 into main Apr 20, 2026
5 checks passed
@KailasMahavarkar KailasMahavarkar deleted the fix/api-key-env-loader branch April 20, 2026 10:42
KailasMahavarkar added a commit that referenced this pull request Apr 20, 2026
Follow-up to #163. The behaviour audit of the config subsystem turned
up four issues; fixing them all here.

1. Truth fork (HIGH)
   tools/finetune/training.py was reading its own tools/finetune/
   training_config.json with a bespoke loader (`load_openrouter_config`)
   that ignored the shared /.env and tools/autoresearch/config.json.
   Replace with a 10-line delegation to `tools.autoresearch.providers.
   resolve_providers`. Training now uses the same single source of truth
   as autoresearch and every bench. The preferred training model is
   hardcoded as `_DEFAULT_TRAINING_MODEL = "deepseek/deepseek-v3.2:nitro"`
   (overridable via OPENROUTER_TRAINING_MODEL env var).

   Delete tools/finetune/training_config.json entirely.

2. Silent fallback on missing env_key (HIGH)
   providers.resolve_providers used to fall back to api_key="ollama" when
   a provider's env_key field was missing. That hid misconfiguration in
   bogus auth failures far from the root cause. Now: non-local providers
   without env_key raise ValueError at resolve time with a pointer at
   what to add. Local providers still tolerate missing env_key (Ollama
   accepts any non-empty key string).

3. ENV KeyError lost field list (MEDIUM)
   ENV["typo"] previously raised bare `KeyError('typo')`. Restore the
   earlier format: `KeyError("ENV: unknown key 'typo'. Known: [...]")`.

4. Silent .env load (MEDIUM)
   env.py now emits a DEBUG-level log line with the count of variables
   loaded from /.env (or "no .env; shell env only" when absent).

Verified end-to-end:
  - ENV typed access + KeyError show field list
  - Missing env_key on non-local provider raises ValueError
  - resolve_providers returns 5 candidates for current /.env + config
  - 1802 tests pass, 101 skipped

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KailasMahavarkar added a commit that referenced this pull request Apr 20, 2026
…164)

Follow-up to #163. The behaviour audit of the config subsystem turned
up four issues; fixing them all here.

1. Truth fork (HIGH)
   tools/finetune/training.py was reading its own tools/finetune/
   training_config.json with a bespoke loader (`load_openrouter_config`)
   that ignored the shared /.env and tools/autoresearch/config.json.
   Replace with a 10-line delegation to `tools.autoresearch.providers.
   resolve_providers`. Training now uses the same single source of truth
   as autoresearch and every bench. The preferred training model is
   hardcoded as `_DEFAULT_TRAINING_MODEL = "deepseek/deepseek-v3.2:nitro"`
   (overridable via OPENROUTER_TRAINING_MODEL env var).

   Delete tools/finetune/training_config.json entirely.

2. Silent fallback on missing env_key (HIGH)
   providers.resolve_providers used to fall back to api_key="ollama" when
   a provider's env_key field was missing. That hid misconfiguration in
   bogus auth failures far from the root cause. Now: non-local providers
   without env_key raise ValueError at resolve time with a pointer at
   what to add. Local providers still tolerate missing env_key (Ollama
   accepts any non-empty key string).

3. ENV KeyError lost field list (MEDIUM)
   ENV["typo"] previously raised bare `KeyError('typo')`. Restore the
   earlier format: `KeyError("ENV: unknown key 'typo'. Known: [...]")`.

4. Silent .env load (MEDIUM)
   env.py now emits a DEBUG-level log line with the count of variables
   loaded from /.env (or "no .env; shell env only" when absent).

Verified end-to-end:
  - ENV typed access + KeyError show field list
  - Missing env_key on non-local provider raises ValueError
  - resolve_providers returns 5 candidates for current /.env + config
  - 1802 tests pass, 101 skipped

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KailasMahavarkar added a commit that referenced this pull request Apr 20, 2026
After #163 (.env + typed env.py) and #164 (env_key pointer in
provider config), the autoresearch README still documented the old
"inline api_key in config.json" shape. Updated:

* Config example now shows env_key pointers (ollama_key, openrouter_key)
  instead of api_key strings
* Added a short preamble explaining that secrets live in /.env and
  config.json is shape-only
* Setup section now tells the user to copy both config.example.json
  AND .env.example, and edit /.env for real keys
* "Add a new model" section points at env.py + .env.example for
  provider-level onboarding

Other README.md files in the repo were scanned; only this one had
stale config refs. Nothing else needs updating.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KailasMahavarkar added a commit that referenced this pull request Apr 20, 2026
After #163 (.env + typed env.py) and #164 (env_key pointer in
provider config), the autoresearch README still documented the old
"inline api_key in config.json" shape. Updated:

* Config example now shows env_key pointers (ollama_key, openrouter_key)
  instead of api_key strings
* Added a short preamble explaining that secrets live in /.env and
  config.json is shape-only
* Setup section now tells the user to copy both config.example.json
  AND .env.example, and edit /.env for real keys
* "Add a new model" section points at env.py + .env.example for
  provider-level onboarding

Other README.md files in the repo were scanned; only this one had
stale config refs. Nothing else needs updating.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KailasMahavarkar added a commit that referenced this pull request Apr 20, 2026
After #163 (.env + typed env.py) and #164 (env_key pointer in
provider config), the autoresearch README still documented the old
"inline api_key in config.json" shape. Updated:

* Config example now shows env_key pointers (ollama_key, openrouter_key)
  instead of api_key strings
* Added a short preamble explaining that secrets live in /.env and
  config.json is shape-only
* Setup section now tells the user to copy both config.example.json
  AND .env.example, and edit /.env for real keys
* "Add a new model" section points at env.py + .env.example for
  provider-level onboarding

Other README.md files in the repo were scanned; only this one had
stale config refs. Nothing else needs updating.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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