Repository navigation
Security hardening: rotate local-only HF tokens, enable secret scanning, fix credential file permissions #111
Description
Activity
- addedP0-criticalBlocks everything; safety or correctness landmineBlocks everything; safety or correctness landminesecuritySecurity defect or hardeningSecurity defect or hardening
on Aug 17, 2026 - added a parent issue
on Aug 17, 2026 - added a commit that references this issue
on Aug 17, 2026 Status update 2026-08-17. HF tokens confirmed rotated by @jeremyrmanning — thank you. But this issue is not complete: a second, previously-undetected credential turned up, and 6 of the original 7 items remain.
NEW: a live PyPI API token (please revoke)
GitHub push protection blocked the #112 branch push:
—— PyPI API Token —— commit: 6c918ca path: .ccpm_backup/.claude_20250828_140214/settings.local.json:30Question Answer Ever public? No — scanned every commit reachable from origin/master; absent from allWhere .ccpm_backup/, an accidental 245-file / 1.2 MB backup of local.claude/settingsExposure 11 local commits from 6c918ca(2025-09-04) onward, plus on diskStatus Blob stripped from those commits; .ccpm_backup/now gitignored (PR #129)- Revoke the PyPI token at https://pypi.org/manage/account/token/ — it sat in a commit and on disk for ~11 months
False alarm for the record: public commit
d6b4b7bcontainsclustrix-pypi-test-v…/clustrix-pypi-valida…, which are 1Password item names, not tokens.This is a gap in the original audit. The security track scanned the working tree and
origin/masterhistory, but not the unpushed commits, and PyPI tokens were not in its pattern set. Push protection caught what the audit missed — which is precisely the argument for item 2 below.Remaining checklist
- 1. Rotate the two HF tokens — done
- 1b. Purge the local backup ref. Note
refs/original/refs/heads/masternow points at4feda53(the pre-rewrite state from the Reconcile 30 unpushed local commits; quality_gates.yml has never run #112 fix), having overwritten the older filter-branch backup. The pre-rewrite.gitis also archived outside the repo. Purge when you are satisfied with PR Issue #112: Surface the test-coverage epic work for review (do not merge as-is) #128/Issue #109: Stop the test suite from provisioning billable AWS resources #129. - 2. Enable secret scanning + push protection — still
404 Secret scanning is disabled. Interestingly, push protection is already active for some token types (it fired above), but the repo-level feature is off, so you cannot view or manage detected secrets. https://github.com/ContextLab/clustrix/settings/security_analysis - 3. Scanner-bait placeholders still present verbatim:
credential_manager.py:355(AKIAIOSFODNN7EXAMPLE) and:393(hf_abcdefghij…). These caused false-positive [Security Alert] Exposed API key(s) detected: AWS Access Key, HuggingFace Token #107. - 4.
.envstill not gitignored —git check-ignore .envreturns nothing - 5.
config.py:216-225,308-323still serializeClusterConfig.passwordwith nochmod(world-readable at default umask) - 6.
cli_credentials.py:502-506still leaves the GCP service-account JSON in/tmpunlinked - 7. Write-then-chmod TOCTOU at
cli_credentials.py:634-639,credential_manager.py:338-339; silentchmodfailure atsecure_credentials.py:144-147
So: 2 of 9 done. Items 2, 3 and 4 are quick and worth doing together.
- added 8 commits that reference this issue
on Aug 19, 2026
Part of #108 · Phase 0 · label: security
Bundles the concrete, low-effort security fixes surfaced by the 2026-08-17 audit. The deeper architectural security work (pickle trust model, host-key verification) is tracked separately — see the Phase 3 security issue.
1. Rotate two real HuggingFace tokens (local-only exposure)
Two genuine HF tokens (
hf_Fbf...,hf_hSV...) exist in local git objects:notes/huggingface_validation_fix_2025-06-29.mdd30acd29c3cdfcThey never reached GitHub — verified three ways:
git merge-base --is-ancestor d30acd2 origin/master-> false;gh api repos/ContextLab/clustrix/commits/d30acd2->422 No commit found for SHA; the rewritten master atf0278e8showshf_XXXX...redaction. The only ref retaining them isrefs/original/refs/heads/master, a localgit filter-branchbackup.Still rotate — they sat in a working tree for ~8 months.
git update-ref -d refs/original/refs/heads/master && git reflog expire --expire=now --all && git gc --prune=now2. Enable GitHub secret scanning + push protection
Currently disabled on this public repo:
gh api repos/ContextLab/clustrix/secret-scanning/alerts->404 Secret scanning is disabled. This is exactly the control that would have blocked the 2025-06-29 commit at push time.3. Stop generating scanner bait
Issue #107 was a false positive triggered by placeholders in the
.envtemplate:credential_manager.py:355(AKIAIOSFODNN7EXAMPLE, AWS's own doc placeholder),:356,:393(hf_abcdefghij...).AKIA_YOUR_KEY_HERE,hf_YOUR_TOKEN_HERE) so scanners stop firing on this file4.
.gitignoredoes not ignore a bare.envOnly
.env.local(line 61) and.env.validation(line 62) are covered.git check-ignore .envreturns nothing. Mitigated in practice because the manager defaults to~/.clustrix/.env, but a repo-root.envis currently committable..envand.env.*(with!.env.exampleif wanted)5. Credentials written world-readable
clustrix/config.py:216-225(save_to_file) and:308-323(save_config) doasdict(self)->yaml.dump/json.dumpwith nochmod.ClusterConfig.password(config.py:16) is a plain field, so an SSH password lands at default umask (0644).password(and other secret fields) from serialization, or write to a 0600 temp file andos.replaceinto position6. GCP service-account JSON leaked to /tmp
clustrix/cli_credentials.py:502-506writes the key toNamedTemporaryFile(delete=False), exports the path asGOOGLE_APPLICATION_CREDENTIALS, and never unlinks it.try/finallyoratexitcleanup; create the file 06007. Write-then-chmod TOCTOU
cli_credentials.py:634-639andcredential_manager.py:338-339write secrets at default umask before callingchmod(0o600).secure_credentials.py:144-147wrapscred_dir.chmod(0o700)inexcept Exception: pass, so a failed permission change is silent.os.open(..., 0o600); never chmod after the factExplicitly out of scope / verified clean
No credential is ever logged or printed (
auth_fallbacks.py:137,157,auth_manager.py:70,76log variable names only). Nosshpass, noop, no--passwordon argv, noecho <pw> |— nothing reaches a command line orps. Noshell=Trueinclustrix/. Noverify=Falseanywhere.clustrix.yml, which contains a real host and NetID, is correctly gitignored and untracked.