From a7ac6fea752bea29f9d902d67a79b8148d43ced0 Mon Sep 17 00:00:00 2001 From: Muhammad Haseeb Date: Fri, 7 Aug 2026 18:49:40 +0500 Subject: [PATCH] fix(security): ignore .env so a generated portal skill's API token can't be committed (#303) /add-portal can generate a skill for a portal that only returns usable content through a paid fetching service, and such a skill reads its API token from the environment. Nothing stopped the `.env` holding that token from being committed: `.gitignore` had no `.env` rule, and `REQUIRED_IGNORE_RULES` in tools/security_guards.py did not pin one. No shipped portal needs a credential - all six are free and unauthenticated - so upstream has never hit this. A fork whose generated portals do need one hits it on the first `git add -A`. Add `.env` and `.env.*` to `.gitignore`, and pin both in `REQUIRED_IGNORE_RULES` so the guard fails if the rule is later dropped. No negation rule is added, so `ALLOWED_IGNORE_NEGATIONS` is untouched. Verified: - `printf 'X=y' > .env && git check-ignore -v .env` -> matched - dropping the `.env` line makes `python3 tools/security_guards.py` report the missing rule and fail; restoring it returns OK - `lint_skills`, `check_framework_version`, `security_guards` all OK; `python3 -m unittest discover -s tests` 196 passed Co-authored-by: Claude Opus 5 --- .gitignore | 5 +++++ CHANGELOG.md | 8 ++++++++ tools/security_guards.py | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/.gitignore b/.gitignore index f3d0b0b..95d6792 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,11 @@ salary_data.json *_BehavioralReport.pdf linkedin_Profile.pdf +# Secrets. A portal skill generated by /add-portal may need an API token for a +# fetching service; the .env holding it must never be committed. +.env +.env.* + # Personal photos and signatures *.jpg *.jpeg diff --git a/CHANGELOG.md b/CHANGELOG.md index 80c6f41..e38668b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,14 @@ per-file diff commands. ### Fixed +- **A `.env` was committable** (`.gitignore`, `tools/security_guards.py`). `/add-portal` + can generate a skill for a portal that only returns usable content through a paid + fetching service, and such a skill reads an API token from the environment - but + nothing stopped the `.env` holding that token from being committed. No shipped portal + needs a credential, so upstream never hit this; a fork whose generated portals do hit + it immediately. `.env` and `.env.*` are now ignored and pinned in + `REQUIRED_IGNORE_RULES`, so the guard fails if the rule is ever dropped. + - **The robots gate did not fail closed** (`tools/robots_check.py`, #277). Found by an adversarial review run over the merged file, not by inspection. Both cases are pinned in `tests/test_robots_check.py` as FAIL-OPEN REGRESSIONs: diff --git a/tools/security_guards.py b/tools/security_guards.py index 69c9d09..03fcf6d 100644 --- a/tools/security_guards.py +++ b/tools/security_guards.py @@ -70,6 +70,11 @@ REQUIRED_IGNORE_RULES = [ "gmail_sync/", "reports/", "upskill/*.md", + # Not personal data but the same failure mode: /add-portal can generate a + # skill for a portal that only returns usable content through a paid + # fetching service, and that skill reads an API token from the environment. + ".env", + ".env.*", ] # Negation (re-include) rules the template legitimately ships. .gitignore is