Skip to content

SECURITY: live GitHub PAT committed in hatch-personal hygiene-scratch dumps — revoke + untrack #6097

Closed

Description

@dezren39

URGENT — security remediation (found 2026-11-05, file-state investigation)

A live GitHub personal access token (authenticates as dezren39; scopes: gist, notifications, repo, workflow) is committed in the private hatch-personal repo — in the current HEAD tree, not just history:

  • 68 tracked files match the token pattern, all under workspace/goals/keep-the-four-repos-healthy/hidden_files/: git-cleanup-2026-10-27/inventory.md + the hygiene-scratch/ laptop-inventory dumps (lap-part*.json, laptop-r*.json).
  • Origin (from the inventory itself): the token was embedded in a git remote URL on the laptop (https://<token>@github.com/developing-today-forks/home-manager) and the 2026-10-27 hygiene sweep captured remote URLs verbatim into its dumps. The dumps were then autocommitted.
  • Verified live on 2026-11-05 by an identity-only API call (GET /user → 200). It is NOT the current sandbox gh token. Token value intentionally not reproduced here.

Exposure assessment

  • The repo is private (dezren39/hatch-personal) and the token does not appear anywhere in the public dezren39/hatch repo (HEAD + full history of its current main scanned — 0 matches). This is a private-repo hygiene incident, not a public leak, on current evidence.
  • Risk remains: full repo scope on a long-lived PAT, sitting in 68 files + full git history of a repo many agents read.

Remediation steps

  1. Revoke the PAT in GitHub settings (identify by scopes gist/notifications/repo/workflow) and confirm whatever used it (laptop home-manager remote) now uses the credential helper instead of a URL-embedded token.
  2. Remove the token-bearing dumps from the working tree; add workspace/goals/*/hidden_files/hygiene-scratch/ to hatch-personal .gitignore alongside the 2026-10-31 regenerable-scratch rules (monitor-/probe-/clone-*) and git rm --cached the tree. Rotation makes the historical value inert; a history rewrite of the private repo is optional and Drew's call.
  3. Sweep note for future inventory/hygiene tooling: redact credential-shaped substrings (esp. https://<token>@github.com remote URLs) at capture time.

Full investigation: ~/workspace/scratch/file-state-investigation-2026-11-05/report.md (sandbox-local path; ask in main chat if a copy is needed here).

Activity

  1. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Remediation complete — 2026-11-05

    All steps executed and verified. The token value is not reproduced anywhere in this record (referenced by location/scope/hash-prefix only).

    1. Dependents identified (before touching anything)

    • Scanned every git config under the laptop's ~/git, ~/code, ~/work plus the home checkout, and both sandbox repos. Exactly one active dependent of this PAT: the laptop ~/home-manager checkout's origin remote (embedded-credential URL for developing-today-forks/home-manager). Hash-match (sha256 prefix) confirmed the embedded credential is the same token as the one in the dumps.
    • Confirmed non-dependents: sandbox gh token (different token, OAuth), laptop gh token (different token, OAuth), no ~/.git-credentials store on the laptop, no copies in sandbox ~/tools, laptop ~/.config, or shell rc files.

    2. Dependent migrated off the PAT

    • ~/home-manager origin (fetch + push) rewritten to the plain URL https://github.com/developing-today-forks/home-manager.git.
    • Proof it works token-free: git ls-remote origin returns HEAD 66774f3d…; git credential fill for github.com resolves via the laptop's configured gh auth git-credential helper and returns the gh OAuth credential (hash-verified distinct from the leaked PAT).

    3. PAT revoked

    • Identified in GitHub settings (classic tokens list, both pages enumerated) by metadata: the token named aa — the only one of 13 classic PATs with the exact scope set gist, notifications, repo, workflow, no expiration. A pre-revoke header check matched: GET /user → 200 with X-OAuth-Scopes: gist, notifications, repo, workflow.
    • Deleted via the settings UI ("I understand, delete this token"); the token no longer appears in the list.
    • Post-revoke verification: GET /user with the old token now returns HTTP 401 (was 200).

    4. Repo cleaned (hatch-personal)

    • The 67 token-bearing hygiene-scratch/ dump files were moved to recoverable trash individually by name (Muse trash, 30-day recovery window) — no wildcard/recursive deletes.
    • git-cleanup-2026-10-27/inventory.md: the single credential occurrence was redacted in place (remote URL is now the plain https://github.com/developing-today-forks/home-manager, matching the migration in step 2).
    • .gitignore: added workspace/goals/*/hidden_files/hygiene-scratch/ alongside the 2026-10-31 regenerable-scratch rules.
    • Whole hygiene-scratch/ tree untracked (git rm -r --cached, 1,756 files; the non-token files remain on disk, now ignored).
    • Autocommit carried it: commit 3939c188d ("auto: snapshot 2026-11-05 07:25 UTC — 1806 file(s)"), pushed; local HEAD == origin main (5e15053ce).
    • Verified at HEAD: 0 hygiene-scratch/ entries in the tree; 0 ghp_-shaped strings anywhere in the HEAD tree.
    • Plain statement on history: historical hatch-personal commits still contain the token in those dumps. It is revoked and therefore inert (401-verified). A history rewrite of the private repo remains Drew's separate call, per the issue body — not done here.
    • Source fix: the standing hygiene-sweep instruction (git-hygiene-sweep-instruction.md, the maintained artifact behind the sweep; the per-run scan scripts were one-offs inside the scratch dir) now carries a "Credential redaction at capture time" section: remote URLs and token-shaped strings must be redacted before any inventory output is written, because autocommit makes anything on disk part of git history within minutes.

    Adjacent observation (not part of this incident)

    Two fleet worktrees (~/git/wt-fleet-refs/fleet-org2, ~/git/wt-fleet-refs/fleet-up) have x-access-token:-style credentials embedded in remote URLs — a different credential class (GitHub App installation tokens, one per remote). Spot-check: the fleet-org2 one returns 401 (expired/inert), consistent with the ~1h installation-token TTL. No live exposure found; flagging the URL-embedding pattern itself for the fleet-org tooling owners rather than acting on it here.

  3. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Follow-up precision note (same day): a broader-pattern recheck of the HEAD tree (ghp_/gho_/github_pat_ shapes, >=20 chars) finds 5 files still containing a token-shaped string — all the same 24-char synthetic fixture (sha256 prefix 45df8dac…), appearing in redaction test data (babysitter-scratch-2026-10-30-1637/remoteC.txt, alongside alphabet-sequence placeholders and example.com URLs) and in the codex-review research captures quoting prose about redaction. It is not a credential (real classic PATs are 40 chars; the revoked one was) and its hash differs from the revoked PAT. So the accurate statement is: 0 copies of the revoked PAT and 0 credential-length ghp_ strings at HEAD; the only remaining token-shaped string is that synthetic test fixture, which needs no action.

  4. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Addendum (same day, from late-completing scans): the laptop config-spot grep finished after the main record was posted. It found ghp_-shaped strings inside Electron app caches (T3 Code IndexedDB, OpenChamber Dev cache). By hash, they are exactly two distinct strings: (1) this revoked PAT itself — cached copies in app data, inert since revocation (401-verified), not tracked files; they age out with the caches; (2) one 90-char ghp_-prefixed string (sha256 prefix 6f397930…) — not a possible GitHub credential (every ghp_/gho_ token GitHub issues is exactly 40 chars; fine-grained PATs use a different prefix/format), characterized by format only after context-extraction attempts timed out under laptop load. Also completed: the sandbox ~/git review clones (5 remotes) — all plain URLs, no embedded credentials, so the dependent enumeration in step 1 now covers them too and the "exactly one dependent" conclusion stands.

  5. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    History rewrite completed locally (2026-11-06, per Drew's approval in the pending-questions batch).

    • The revoked PAT (classic PAT, sha256 fingerprint first8 d5a971d6, formerly embedded in home-manager remote URLs recorded in repo-health inventory/scan files) was replaced with REVOKED-PAT-REMOVED in every blob of the hatch-personal history via git filter-repo. It was the only credential-shaped secret in history: a full-history scan found no other real tokens (one ghp_-prefixed string is a redaction-probe test fixture in probe_redact_test.go; several BEGIN OPENSSH PRIVATE KEY strings are prose fragments in transcripts/docs — a census found zero END PRIVATE KEY lines anywhere in history, i.e. no complete private key exists in the repo).
    • Verification: full object-store scan of the rewritten repo — 0 occurrences of the token (positive control: 67 occurrences of the replacement placeholder). Pre-rewrite HEAD cdf82046927239867b99ffaa49887679d0c2f8ac → post-rewrite HEAD 802f2bd7c80c3b2320761d9b1348200c573d4a40; HEAD tree hash identical before and after (6b646e5e1275e60479fe93b74424856abb9d1181), so the current tree is byte-for-byte unchanged. git fsck --connectivity-only clean. A complete pre-rewrite bundle backup is retained at ~/workspace/scratch/hatch-personal-pre-rewrite-2026-11-06.bundle.
    • NOT yet done (needs Drew's go): origin (dezren39/hatch-personal on GitHub) still holds the old history. No force-push was performed (standing rule: never force-push without Drew's explicit say-so for this). The hatch-autocommit timer is currently STOPPED: its every-tick merge-pull of origin/main would merge the old history straight back into the local repo and resurrect the token, so it stays paused until the force-push lands. Once Drew approves: force-push the rewritten refs to origin, then restart hatch-autocommit.timer.
  6. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    History-rewrite force-push — COMPLETE (2026-11-06)

    Follow-on to the local-rewrite record above (comment 6678902382). Drew approved the endgame on 2026-11-06 01:42 CST. The rewritten history is now what origin serves; the revoked PAT (sha256 fingerprint first8 d5a971d6) exists in 0 objects of the served history.

    Freeze. Both autocommits were stopped first (both were enabled+active): sandbox hatch-autocommit.timer (system) and laptop laptop-autocommit-hatch-personal.timer (user) — the laptop's was still pushing old-history commits (tip da1a0787d, 2026-11-06 01:19 CST).

    Divergence inventory. Fork point: old e6cf6a298 = rewritten 4f8f21d81 (via the filter-repo commit-map). Laptop/origin side had exactly 5 commits past it: 8148e9fc (autocommit 00:19), fb83b967 (PR #27 branch), 3fb8d0bd (merge PR #27), af30540e (autocommit 01:14), da1a0787d (autocommit 01:19). Sandbox side had 9 post-rewrite autocommit snapshots on top of 802f2bd7.

    Safety gate (passed). The 5 laptop commits touch only 8 files (charges-candidates.json, charges-producer-runs.jsonl, and the webhook-gateway tree incl. the new ledger-svc). Zero overlap with the 94 paths that ever carried a ghp_-shaped string in the old history, and the diffs themselves contain zero ghp_ strings and zero credential-userinfo URLs. The patch series is retained as the content rollback artifact.

    Replay. The 5 commits were replayed onto the sandbox tip as 4 content commits (the PR #27 merge replayed as a single content commit with original authorship; each carries a Replayed-from: trailer): f7f924959, 13a1c8d72, 411ebf910, 14502a5e4.

    Pre-push verification. All 8 laptop files byte-identical to their old-tip versions; sandbox-side content untouched by the replay; full reachable-history scan of the new tip (85,851 blobs): 6 unique ghp_-shaped strings, sha256 first8 {45df8dac, 4c281411, e5d5690c, 19d835c9, 72c63c9d, 90c545c1} — none is d5a971d6, so token occurrences = 0 (positive control: 76 occurrences of the REVOKED-PAT-REMOVED placeholder); git fsck --connectivity-only clean.

    Push. git push --force-with-lease (lease pinned to da1a0787d28ef7e09950674c4b1ab2e9bc78a9cf) → origin main = 14502a5e4db8e39f621cd52991f1ecf239bc023f, verified by ls-remote and by GitHub API reads of both the ref and the tip commit.

    Clones. Laptop /home/user/hatch-personal and /home/user/git/hatch-personal were fetched and reset --hard to the new tip. On the live clone, the untracked laptop/tools/repo-task-v1.schema.json survived, and an uncommitted charges-producer delta was backed up before the reset and restored after (it then committed normally as 16fae3e01; the pre-reset copies remain at ~/hatch-personal-reset-backup-20261106/ on the laptop, now redundant). Laptop reachable-object count (173,177) matches the sandbox exactly, and an independent laptop-side blob scan produced the identical fingerprint set — 0 token occurrences there too. The four fleet submodule checkouts were left alone; their pins advance independently.

    Autocommits restarted. Laptop timer active: first cycle committed 16fae3e01 on the new history and pushed (fast-forward). Sandbox timer active: first cycles produced snapshot 94ba96224 + merge-pull c422c8bf3 and pushed; origin/main stood at c422c8bf3 at the time of this comment and advances normally from here.

    Old-history backups disposed (recoverable trash, restorable until 2026-12-06): hatch-personal-pre-rewrite-2026-11-06.bundle → trash 20261106-080652-e1c5c20f9cf849119fbd956be8f7cf4e; hatch-personal-pre-rewrite-2026-10-26.bundle → trash 20261106-080657-fd6d3ccb0f424a43917c8014ae9b728a; hatch-personal-mirror-2026-10-26.git → trash 20261106-080659-c2791cdcacef4034ac37d57c67657559.

    This issue was already CLOSED (revocation + untracking, the remediation of record); this comment completes the optional history-purge follow-on. No state change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions