SECURITY: canonical .gitignore misses common secret shapes (*.env, .envrc, *.p12/*.pfx, extensionless SSH keys) #38

Open
opened 2026-08-09 18:23:03 +02:00 by clawbot · 0 comments
Collaborator

Found while implementing #29, which fixed the equivalent gaps in .dockerignore. Filed separately because the two files have different semantics and must be written to their own — the whole lesson of #29 is that deriving one from the other produces a file that is wrong in a way that looks careful.

Problem

Canonical .gitignore's secrets section is:

.env
.env.*
*.pem
*.key

Measured as reaching a build context in the #29 probes, and equally uncommitted-only-by-luck here:

  • prod.env / local.env — the <name&gt;.env convention is common and .env.* does not match it (.env.* matches .env.production, not prod.env).
  • .envrc — direnv's config, a secrets file by convention, matched by nothing above.
  • *.p12 / *.pfx — certificate bundles that contain private keys.
  • Extensionless SSH private keysid_rsa, id_dsa, id_ecdsa, id_ed25519. deploy/secrets/id_rsa.key was caught in the probes only because someone had given it a .key suffix; the default ssh-keygen output has none.

REPO_POLICIES.md states "Never commit secrets. .env files, credentials, API keys, and private keys must be in .gitignore. No exceptions." The current file does not deliver that for any of the shapes above.

Why this is filed rather than fixed alongside #29

.dockerignore needed **/ prefixes because it uses Go filepath.Match. .gitignore must NOT get those prefixes — an unanchored .gitignore pattern already matches at any depth, so prefixing would be both redundant and, for anchored patterns, actively wrong (**/binary would newly match sub/binary where /binary deliberately did not). Verified with git check-ignore during the #29 review.

That asymmetry is exactly why the two changes must not ride together: a single commit touching both invites the next reader to conclude the files are kept in sync.

Definition of done

  • Canonical .gitignore covers the shapes above, written to gitignore semantics — no **/ prefixes.
  • Verify with git check-ignore -v against planted files at the repo root and nested at least two directories deep, plus a positive control (an ordinary source file that must remain trackable). Reading the patterns is not verification.
  • Confirm nothing legitimate becomes untrackable — *.env in particular should be checked against any file a repo might genuinely want committed, e.g. a checked-in example.env or sample.env. If that conflict is real, prefer a negation (!example.env) over dropping the pattern, and record the decision.
  • Follow-up: consuming repos vendor .gitignore, so this needs the same propagation treatment. It can ride along with the #35 sweep as a separate commit, not merged into the .dockerignore one.

Note

This is parity with the pre-existing state rather than a regression — nothing got worse when #29 landed. But #29 tightened .dockerignore past what .gitignore covers, so the two now disagree about what counts as a secret, and .gitignore is the weaker of the two. A developer's prod.env is currently one git add away from the repository.

Found while implementing #29, which fixed the equivalent gaps in `.dockerignore`. Filed separately because the two files have **different semantics** and must be written to their own — the whole lesson of #29 is that deriving one from the other produces a file that is wrong in a way that looks careful. ## Problem Canonical `.gitignore`'s secrets section is: ``` .env .env.* *.pem *.key ``` Measured as reaching a build context in the #29 probes, and equally uncommitted-only-by-luck here: - **`prod.env` / `local.env`** — the `<name&gt;.env` convention is common and `.env.*` does not match it (`.env.*` matches `.env.production`, not `prod.env`). - **`.envrc`** — direnv's config, a secrets file by convention, matched by nothing above. - **`*.p12` / `*.pfx`** — certificate bundles that contain private keys. - **Extensionless SSH private keys** — `id_rsa`, `id_dsa`, `id_ecdsa`, `id_ed25519`. `deploy/secrets/id_rsa.key` was caught in the probes only because someone had given it a `.key` suffix; the default `ssh-keygen` output has none. `REPO_POLICIES.md` states "Never commit secrets. `.env` files, credentials, API keys, and private keys must be in `.gitignore`. No exceptions." The current file does not deliver that for any of the shapes above. ## Why this is filed rather than fixed alongside #29 `.dockerignore` needed `**/` prefixes because it uses Go `filepath.Match`. **`.gitignore` must NOT get those prefixes** — an unanchored `.gitignore` pattern already matches at any depth, so prefixing would be both redundant and, for anchored patterns, actively wrong (`**/binary` would newly match `sub/binary` where `/binary` deliberately did not). Verified with `git check-ignore` during the #29 review. That asymmetry is exactly why the two changes must not ride together: a single commit touching both invites the next reader to conclude the files are kept in sync. ## Definition of done - Canonical `.gitignore` covers the shapes above, written to **gitignore** semantics — no `**/` prefixes. - Verify with `git check-ignore -v` against planted files at the repo root **and** nested at least two directories deep, plus a positive control (an ordinary source file that must remain trackable). Reading the patterns is not verification. - Confirm nothing legitimate becomes untrackable — `*.env` in particular should be checked against any file a repo might genuinely want committed, e.g. a checked-in `example.env` or `sample.env`. If that conflict is real, prefer a negation (`!example.env`) over dropping the pattern, and record the decision. - Follow-up: consuming repos vendor `.gitignore`, so this needs the same propagation treatment. It can ride along with the #35 sweep **as a separate commit**, not merged into the `.dockerignore` one. ## Note This is parity with the pre-existing state rather than a regression — nothing got worse when #29 landed. But #29 tightened `.dockerignore` past what `.gitignore` covers, so the two now disagree about what counts as a secret, and `.gitignore` is the weaker of the two. A developer's `prod.env` is currently one `git add` away from the repository.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/prompts#38