mpfaffenberger / mpfaffenberger/code_puppy

API keys stored in puppy.cfg are world-readable (no chmod 600) and credential lookup logic is triplicated across config.py / model_factory.py / provider_credentials.py

Open
#406 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
814
Forks
278
Avg merge
2d 5h
Merged PRs (30d)
76

Description

Problem

code_puppy/provider_credentials.py writes API keys in plain text to puppy.cfg, and code_puppy/config.py reads them back — this is by design. Two concrete hygiene problems around it:

1. Secrets file permissions are not enforced (config.py lines ~206-212)

ensure_config_exists() creates the directories with 0o700, but puppy.cfg itself is written with open(CONFIG_FILE, \"w\") (default umask, typically 0644) — every set_config_value() rewrite as well. Since save_credential() stores OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. in this file, any local user can read the keys on shared systems. With XDG vars set, the parent dir may be a pre-existing ~/.config (0755), so directory perms don't protect the file either.

2. get_api_key() duplicated and the case-insensitive lookup contract is fragile

model_factory.get_api_key() (model_factory.py lines 86-104) and provider_credentials.get_credential_value() (lines 100-110) implement the same precedence logic twice, and config.get_api_key() (config.py line ~2020) implements a third variant that does NOT lower-case the key. configparser lower-cases keys on write, so config.get_api_key(\"OPENAI_API_KEY\") only works by accident of configparser's optionxform; if anyone ever sets a custom optionxform or reads the raw file, the three implementations diverge. "There should be one obvious way to do it."

Suggested fix

  1. After writing the cfg file, clamp permissions:
with open(CONFIG_FILE, \"w\", encoding=\"utf-8\") as f:
    config.write(f)
os.chmod(CONFIG_FILE, 0o600)

(one helper _write_config(config) used by ensure_config_exists, set_config_value, reset_value, set_model_name, clear_model_settings would also de-duplicate the five copies of the write logic).

  1. Make provider_credentials.get_credential_value() the single canonical resolver and have model_factory.get_api_key and config.get_api_key delegate to it.

Filed by Zen Reviewer A (code-puppy-60635a)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ensure_config_exists(), set_config_value(), reset_value(), set_model_name(), and clear_model_settings() in config.py, then compare get_api_key() with the resolvers in model_factory.py and provider_credentials.py. Done means puppy.cfg is written with 0600 permissions and credential lookup has one consistent, case-insensitive implementation across the three modules.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.