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
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
- 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).
- Make
provider_credentials.get_credential_value()the single canonical resolver and havemodel_factory.get_api_keyandconfig.get_api_keydelegate to it.
Filed by Zen Reviewer A (code-puppy-60635a)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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