Skip to content

fix: only load allowlisted API keys from shared .env files - #511

Open
ixion36-svg wants to merge 1 commit into
nextlevelbuilder:mainfrom
ixion36-svg:fix/narrow-load-env-allowlist
Open

ixion36-svg wants to merge 1 commit into
nextlevelbuilder:mainfrom
ixion36-svg:fix/narrow-load-env-allowlist

Conversation

@ixion36-svg

@ixion36-svg ixion36-svg commented Oct 6, 2026 •

Copy link
Copy Markdown

What

load_env() in the three design generate scripts copies every key it finds into os.environ:

key, value = line.split("=", 1)
if key not in os.environ:
    os.environ[key] = value.strip("\"'")

It reads three paths, in order:

<skill>/.env
~/.claude/skills/.env
~/.claude/.env

Why it is worth narrowing

The last two are not the skill's own files. ~/.claude/.env is a shared location that any Claude Code skill or tool may write to, and the same process then makes outbound calls to api.muapi.ai, api.atlascloud.ai and the Gemini API.

So an unrelated secret parked in a shared .env — a database password, a cloud key — is loaded into the environment of a process that talks to third-party services. Nothing in this repo sends it anywhere, and this is not a leak report. The point is that the blast radius is set by a file the script does not own, while the script only ever needs four keys.

I raised this against my own install, found the three scripts share the pattern, and am sending the fix upstream rather than carrying a local patch.

Change

An explicit allowlist of the keys these scripts actually read:

script reads
cip/generate.py GEMINI_API_KEY, GOOGLE_API_KEY
icon/generate.py GEMINI_API_KEY
logo/generate.py GEMINI_API_KEY, ATLASCLOUD_API_KEY, MUAPI_API_KEY

The union is used in all three so load_env() stays identical across the files — happy to switch to per-file lists if you'd prefer them tighter.

Also adds key.strip(): without it KEY = value (spaces around =) yields a key of "KEY ", which would silently fail the allowlist check.

Compatibility

No behaviour change for the keys the scripts use — they load exactly as before. Only unrelated keys stop being imported. No new dependencies, no signature changes.

Verification

Lifted ENV_ALLOWLIST and load_env out of the patched source via AST and ran them against a .env holding two allowlisted keys and three decoys:

loaded: {'MUAPI_API_KEY': 'allowed-456', 'GEMINI_API_KEY': 'allowed-123'}
leaked: NONE
RESULT: PASS

AWS_SECRET_ACCESS_KEY, ION_DB_PASSWORD and GITHUB_TOKEN stayed out. All three files compile; tested with both LF and CRLF .env input.

Note on channel

SECURITY.md asks for private reporting of vulnerabilities. I judged this out of that scope — the in-scope list is CLI code execution, path traversal and npm supply chain, and there is no attacker-controlled path here. It is defence-in-depth, so a public PR seemed right. Happy to move it if you disagree.

load_env() copied every key it found into os.environ. Two of the three
paths it reads -- ~/.claude/skills/.env and ~/.claude/.env -- are shared
with other tools, and these scripts call third-party image APIs, so an
unrelated secret parked in one of them entered the process environment.

Restrict the import to the four keys these scripts actually read. Also
strip the key, without which "KEY = value" yields "KEY " and never
matches.

Co-Authored-By: Claude Opus 5 <[email protected]>
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