hate_crack maintainer's git hook mishap pushes private notes to public repo; project splits config and hardens its release process
TrustedSec's Justin Bollinger describes how a prek pre-push hook corrupted his git index on July 30. The resulting commit deleted 85,280 lines across about 165 files and staged gitignored, local-only paths (CLAUDE.md, .claude/, docs/plans/, docs/superpowers/). The commit reached the public trustedsec/hate_crack remote before it was force-pushed away. The maintainer says the exposed content turned out to be clean. He also found that an earlier history purge had not unpublished the files, because GitHub's immutable refs/pull/N/head refs kept them fetchable from PRs #78 through #137. The post is a hardening retrospective covering new commit-time guards, a breaking config split into config.json and .env, an offsite-LLM egress control, and a rebuilt release-tagging scheme.
Detalhes técnicos
- Root cause: the pre-push hook's stash-and-restore cycle returned a corrupted index. The only secret-scanning gate,
detect-private-key, did not cover these paths, CI had no checks, andgit add -fbypasses.gitignore. - New guards (pre-commit):
check-publication-boundary.shreads the staged changeset viagit diff --cachedand refuses forbidden paths with no override.check-mass-deletion.shaborts commits that delete more than 50 tracked files;HATE_CRACK_ALLOW_MASS_DELETE=1is the escape hatch. Tests run both scripts as real hooks against throwaway repos. - Publication policy change (2.33.0):
CLAUDE.mdand.claude/are now published deliberately..claude/plans/,.claude/specs/,docs/plans/,docs/superpowers/and.claude/settings.local.jsonremain refused.tests/test_agent_instruction_integrity.pypins hooks, scripts, subagents and skills under.claude/to an approved set. It refuses apermissionskey in shared settings and any network verb in.claude/scripts, because a PR could otherwise alter files a maintainer's coding agent reads as instructions or executes as code. - Config split (breaking):
config.jsonkeeps operational settings (41 keys). A new.envholds 16 integration keys (Hashview, Hashmob, Pushover, Ollama/LLM). Each key has one home file; the process environment is an ephemeral override. A one-shot migration creates.envwith 0600 permissions and backs up the original toconfig.json.pre-split.bak. Dangling config symlinks are now fatal.LLM_API_KEYis not marked as a secret and is not redacted from error messages. - LLM egress control:
OLLAMA_NO_CLOUD=1refuses-cloudmodels and, throughllm.is_offsite_url(), any offsite destination for the Ollama, vLLM and OpenAI-compatible backends. It fails closed on unresolvable hosts, treats mixed private/global DNS results as offsite, treats CGNAT (100.64.0.0/10) as local, and treats 6to4 as offsite. Prompts carry recovered plaintexts and client details.OLLAMA_HOSTpreviously never worked from.envbecausedotenv_values()does not export to the environment, so runs could silently talk to localhost. - CLI and env hygiene: eight flags became schema-backed defaults using
BooleanOptionalAction.DEBUGandUPDATE_CHANNELwere renamedHATE_CRACK_DEBUGandHATE_CRACK_UPDATE_CHANNEL, since an ambientDEBUG=1would write cracked plaintexts to disk. - Release engineering: automatic Conventional-Commit tagging produced eight releases in one day (July 28). It was replaced with PEP 440 release candidates aimed one version forward (e.g.
v2.33.1rc1) onnightly-dev, promoted to final on merge tomain. The policy lives intools/next_version.py. Majors are never bumped automatically.workflow_runmust pinhead_sha, and the nightly workflow must be byte-identical on both branches. CI runs ruff, ty, pytest, bandit (baseline) and pip-audit, with 3,246 tests across 136 files.
Remediação
- Enforce publication boundaries at commit time with pre-commit hooks rather than
.gitignoreor pre-push checks, and never commit sensitive files to a repository that accepts PRs, since PR refs retain content after a history rewrite. - Gate release tagging on CI passing and pin
workflow_runcheckouts tohead_sha. - Set
OLLAMA_NO_CLOUD=1when engagement terms forbid data leaving the host. - Namespace environment variables (e.g.
HATE_CRACK_*). - Move integration keys from
config.jsonto.envafter upgrading. - Recover from the index corruption with
git config core.bare false,git reset HEAD, thengit status --short.
Linha do tempo
-
hate_crack maintainer's git hook mishap pushes private notes to public repo; project splits config and hardens its release process
ler a análise desta fonte ocultar
TrustedSec's Justin Bollinger describes how a prek pre-push hook corrupted his git index on July 30. The resulting commit deleted 85,280 lines across about 165 files and staged gitignored, local-only paths (
CLAUDE.md,.claude/,docs/plans/,docs/superpowers/). The commit reached the public trustedsec/hate_crack remote before it was force-pushed away. The maintainer says the exposed content turned out to be clean. He also found that an earlier history purge had not unpublished the files, because GitHub's immutablerefs/pull/N/headrefs kept them fetchable from PRs #78 through #137. The post is a hardening retrospective covering new commit-time guards, a breaking config split intoconfig.jsonand.env, an offsite-LLM egress control, and a rebuilt release-tagging scheme.Technical Details
- Root cause: the pre-push hook's stash-and-restore cycle returned a corrupted index. The only secret-scanning gate,
detect-private-key, did not cover these paths, CI had no checks, andgit add -fbypasses.gitignore. - New guards (pre-commit):
check-publication-boundary.shreads the staged changeset viagit diff --cachedand refuses forbidden paths with no override.check-mass-deletion.shaborts commits that delete more than 50 tracked files;HATE_CRACK_ALLOW_MASS_DELETE=1is the escape hatch. Tests run both scripts as real hooks against throwaway repos. - Publication policy change (2.33.0):
CLAUDE.mdand.claude/are now published deliberately..claude/plans/,.claude/specs/,docs/plans/,docs/superpowers/and.claude/settings.local.jsonremain refused.tests/test_agent_instruction_integrity.pypins hooks, scripts, subagents and skills under.claude/to an approved set. It refuses apermissionskey in shared settings and any network verb in.claude/scripts, because a PR could otherwise alter files a maintainer's coding agent reads as instructions or executes as code. - Config split (breaking):
config.jsonkeeps operational settings (41 keys). A new.envholds 16 integration keys (Hashview, Hashmob, Pushover, Ollama/LLM). Each key has one home file; the process environment is an ephemeral override. A one-shot migration creates.envwith 0600 permissions and backs up the original toconfig.json.pre-split.bak. Dangling config symlinks are now fatal.LLM_API_KEYis not marked as a secret and is not redacted from error messages. - LLM egress control:
OLLAMA_NO_CLOUD=1refuses-cloudmodels and, throughllm.is_offsite_url(), any offsite destination for the Ollama, vLLM and OpenAI-compatible backends. It fails closed on unresolvable hosts, treats mixed private/global DNS results as offsite, treats CGNAT (100.64.0.0/10) as local, and treats 6to4 as offsite. Prompts carry recovered plaintexts and client details.OLLAMA_HOSTpreviously never worked from.envbecausedotenv_values()does not export to the environment, so runs could silently talk to localhost. - CLI and env hygiene: eight flags became schema-backed defaults using
BooleanOptionalAction.DEBUGandUPDATE_CHANNELwere renamedHATE_CRACK_DEBUGandHATE_CRACK_UPDATE_CHANNEL, since an ambientDEBUG=1would write cracked plaintexts to disk. - Release engineering: automatic Conventional-Commit tagging produced eight releases in one day (July 28). It was replaced with PEP 440 release candidates aimed one version forward (e.g.
v2.33.1rc1) onnightly-dev, promoted to final on merge tomain. The policy lives intools/next_version.py. Majors are never bumped automatically.workflow_runmust pinhead_sha, and the nightly workflow must be byte-identical on both branches. CI runs ruff, ty, pytest, bandit (baseline) and pip-audit, with 3,246 tests across 136 files.
Remediation
- Enforce publication boundaries at commit time with pre-commit hooks rather than
.gitignoreor pre-push checks, and never commit sensitive files to a repository that accepts PRs, since PR refs retain content after a history rewrite. - Gate release tagging on CI passing and pin
workflow_runcheckouts tohead_sha. - Set
OLLAMA_NO_CLOUD=1when engagement terms forbid data leaving the host. - Namespace environment variables (e.g.
HATE_CRACK_*). - Move integration keys from
config.jsonto.envafter upgrading. - Recover from the index corruption with
git config core.bare false,git reset HEAD, thengit status --short.
- Root cause: the pre-push hook's stash-and-restore cycle returned a corrupted index. The only secret-scanning gate,