onboarding: write ~/.aider/oauth-keys.env at 0o600 instead of default umask (0o644)#5154
Open
JAE0Y2N wants to merge 1 commit into
Open
onboarding: write ~/.aider/oauth-keys.env at 0o600 instead of default umask (0o644)#5154JAE0Y2N wants to merge 1 commit into
JAE0Y2N wants to merge 1 commit into
Conversation
… umask (0o644) The OpenRouter OAuth flow saves the user's API key to ~/.aider/oauth-keys.env via plain `open(path, "a")`. Python's open() honours the process umask, which on standard Linux and macOS installs (umask 022) creates the file at mode 0o644 — world-readable. Any local UID on the same machine (other user accounts, cron jobs running as another user, container sharing the home volume, sandboxed CI runner mounting the home dir, co-tenant on a multi-user dev box) can read the OPENROUTER_API_KEY value from the file by walking through the also-default-0o755 ~/.aider/ directory. This brings the credential file in line with the cross-CLI baseline that gh, aws, gcloud, stripe, docker, and most other vendor CLIs follow for OAuth-bearing files: 0o600 file + 0o700 directory. Three pieces of hardening land together: 1. The directory is explicitly chmoded to 0o700 even if it already exists, since `os.makedirs(exist_ok=True)` does not tighten an already-existing directory's mode. 2. When the credential file does not yet exist, it is created via `os.open(... O_WRONLY|O_CREAT|O_TRUNC, 0o600)` so the file is never visible at a wider mode at any point in its lifetime (avoids TOCTOU between create-at-default-mode and a follow-up chmod). 3. When the credential file already exists (re-running the OAuth flow against the same install), the existing-file path keeps the append semantics but follows up with an explicit os.chmod(key_file, 0o600) so the mode is corrected on every save. Empirically verified — the new code produces -rw------- on macOS and Linux umask 022 hosts; the prior code produced -rw-r--r-- on the same hosts. The fix is conservative — Windows / non-Unix platforms swallow the chmod errors via the (OSError, NotImplementedError) except, so the behaviour matches the prior code on those platforms while gaining hardening on Unix-like systems.
|
|
Author
|
I have read the CLA Document and I hereby sign the CLA |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The OpenRouter OAuth flow in
aider/onboarding.py(around line 366) saves the user's API key to~/.aider/oauth-keys.envvia plainopen(path, "a"). Python'sopen()honours the process umask, which on standard Linux/macOS installs (umask 022) creates the file at mode0o644— world-readable. Any local UID on the same host — another user account, a cron job running as another user, a container sharing the home volume, a sandboxed CI runner mounting the home dir, a co-tenant on a multi-user dev box — can read theOPENROUTER_API_KEYvalue by walking through the also-default-0o755~/.aider/directory.The OAuth token granted by OpenRouter is the user's bearer credential against the OpenRouter API. A local reader can quietly use it to make OpenRouter calls under the victim's account (consuming their credit, running prompts under their auditable identity, etc.). This is the same class of issue that
gh,aws,gcloud,stripe,docker, and most other vendor CLIs explicitly defend against by writing OAuth/credential files at0o600inside a0o700directory.Empirically reproduced before the fix (umask 022, macOS 14):
After the fix the same flow produces
drwx------on the directory and-rw-------on the credential file.Three pieces of hardening land together in this PR:
The directory is explicitly
chmoded to0o700even if it already exists.os.makedirs(exist_ok=True)does not tighten the mode of an already-existing directory, so a~/.aider/previously laid down at0o755by another tool (or by a prior aider release) keeps the wider mode without an explicit tightening step.When the credential file does not yet exist, it is created via
os.open(... O_WRONLY|O_CREAT|O_TRUNC, 0o600)and then wrapped inos.fdopenfor the actual write. This avoids the TOCTOU window where the file would otherwise be briefly visible at the default0o644mode between the create-at-default and a follow-up chmod.When the credential file already exists (a re-run of the OAuth flow against the same install), the append semantics are preserved but an explicit
os.chmod(key_file, 0o600)follows the write so the mode is corrected even on the re-run path.Windows / non-Unix platforms swallow the
chmoderrors viaexcept (OSError, NotImplementedError), so the behaviour matches the prior code on those platforms while gaining hardening on Unix-like hosts.This is the same class of finding I've reported privately against several other vendor CLIs that store credentials with the default Python
open()mode — most recently againsthuggingface/huggingface_hub(PR #4234, approved by@Wauplinwith a refactor request that landed the_write_secrethelper usingos.open(O_CREAT, 0o600)),eosphoros-ai/DB-GPT(#3077, same pattern), and several Node-ecosystem CLIs (clineGHSA-58hj-8g5g-q689, replicateGHSA-9g42-hx86-3ppm, prismaGHSA-cgq2-43wx-qgpq, cloudflare workers-sdkGHSA-hfj5-88mp-26jqtoday). The aider instance is structurally identical, just in Python with the OpenRouter token specifically.I filed this as a PR rather than a private vulnerability report because the repo doesn't have Private Vulnerability Reporting enabled and no SECURITY.md / direct security contact. If you'd prefer a private channel for future reports, enabling
Settings → Security → Private vulnerability reportingadds a private intake on this repo without any other process change. Happy to coordinate timeline / hold a public advisory until after the fix releases if that's preferable to a PR-as-disclosure flow.— Jaeyoung Yun (
JAE0Y2N)