Repository navigation
chore: remove local machine paths from the repo and guard against new ones - #1051
Merged
Merged
Conversation
Task documents and their captured evidence carried absolute home paths, Claude scratchpad paths, macOS temp folders, worktree layout, session ids and a device id from one contributor's machine. Each is now a neutral placeholder: <home>, <worktree>, <scratch>, <tmp>, <session>, <device>, <installation>, <user>. Runnable commands use a relative path or a quoted placeholder. Git history still holds the old values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
A home directory, a Claude scratchpad or a macOS temp folder names its owner and means nothing elsewhere. The check matches patterns, never the current account, so it works for every contributor; a short allowlist keeps the placeholder accounts tests already use. Pre-commit checks the staged files; validate.yml runs the same hook with --all-files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
runneradmin never matched the guard on the tree: every occurrence ends without a separator. The allowlist keeps only names the check meets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Session ids under a plain "session" key and in Copilot's session-state folder are now <session>, wherever the same id appears. The tracked cli/.claude/scheduled_tasks.lock, already gitignored, held a live session id and is untracked. A third party's personal address in two kanban specs becomes someone@example.com. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
A home no longer needs a trailing separator, so a bare $HOME value is caught. A home folded into a Claude project slug, and a UUID a session key or Copilot's session-state folder names, are caught too; made-up ids that repeat a few digits pass. The allowlist grows only by the placeholder accounts the tree's tests use. Files are read inside a try, without a racy existence check first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Both were real UUIDv7 session ids in rollout file names, and the file name's local time, read against the id's own clock, gave away the contributor's timezone. Both become rollout-<timestamp>-<session>.jsonl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
A UUID naming a .jsonl transcript, Claude's <project>/<id>.jsonl or Codex's rollout-<time>-<id>.jsonl, is now a leak, with the same made-up-id skip as a session key. Tests pin the made-up-id boundary (seven distinct digits pass, eight fail), a thirteen-digit real-looking id, and a JSON-escaped session key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The threadIds list held the real ids of seven Codex runs. They become <session-1> to <session-7>, so uniqueThreadIds: 7 still reads true. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
thread_id, threadId and threadIds join the session keys, and an array under any of them is read whole, each id reported on its own line. A Windows transcript path is covered by a test, and a .json named by a UUID pins the pattern to .jsonl only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Each array under a session key is found by its opening bracket, and the next closing bracket is looked up once and shared by every key before it, so an unclosed key no longer rescans the text. A body is read up to 64 KiB, and a line number comes from a binary search over newline offsets computed once. Tests pin threadId, where an array ends, the line order of mixed hits, and a key nested in an array read once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The previous commit said the next closing bracket was shared by every key before it. It never was: the search always resumes past that bracket, so the keys inside an array are skipped, not visited. The lookup is now made once per array, and the first array with no closing bracket ends the pass. Tests pin two arrays in one text, an unindented id ending the text, and a single bracket scan for many unclosed keys. The 64 KiB body cap is documented as an assumption. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 What & why
Task documents and their captured evidence held paths and identifiers from one contributor's machine: home directories, Claude scratchpad paths, macOS temp folders, worktree layout, session ids and a device id. Two kanban specs held a third party's personal email address. This replaces them with placeholders in 164 files and adds a check so none comes back.
🛠️ How it works
aidd_docs/tasks/,cli/aidd_docs/tasks/and twokanban/aidd_docs/tasks/specs. Each value is now a placeholder:<home>,<worktree>,<scratch>,<tmp>,<session>,<device>,<installation>,<user>(owner column of anls -lcapture).cd cli,cd ../test-sdlc-891-codex) or a quoted placeholder (node "<worktree>/cli/dist/cli.js",B='<scratch>/bench').session_id,sessionId,session, Codexsession id:, Copilotsession-state/<id>, Codexrollout-<time>-<id>.jsonlfile names, whose timestamps are replaced too because they give away the timezone), along with the other places the same ids appear (thread_id,prompt_cache_key, window ids,copilot --resume=). Thedevice_idfingerprint and Codexinstallation_idthat sit next to them were replaced too. The seven CodexthreadIdsin the sandbox observations are<session-1>to<session-7>, souniqueThreadIds: 7still holds. Random per-run ids stay:context_window_id,turn_id,root_turn_idandmsg_ids. Made-up ids (3333…,0000…000a) are unchanged.cli/.claude/scheduled_tasks.lock, already listed incli/.gitignore, was tracked with a live session id; it is now untracked.someone@example.com./home/user,C:\Users\someone, …) stay./Users/<name>,/home/<name>,C:\Users\<name>, with or without a trailing separator), a home folded into a project slug (-Users-<name>-,C--Users-<name>-),/private/tmp/claude-<uid>/,/var/folders/, and a UUID that a session or thread key (session,session_id,sessionId,thread_id,threadId,threadIds, including every id of an array under one) or Copilot'ssession-state/folder names, and a UUID naming a.jsonltranscript (<project>/<id>.jsonl,D:\work\<id>.jsonl,rollout-<time>-<id>.jsonl). Each hit is reported asfile:line: <pattern>; the matched text is never printed./proj/home/x/and the fake HOME/home/.claude/therefore pass. A session UUID with fewer than 8 distinct hex digits is treated as made up (00000000-…-00000000000a) and passes.user, u, a, me, dev, someone, alice, tester, test, p, aidd, jane, first, runner, runneradmin, compared case-insensitively, ignoring a~N8.3 suffix and a trailing.. Each one is matched in a test or task doc on the tree; removing any one turns--allred.jane/firstare the first word ofJane Doe/First Last;runner/runneradminare the GitHub Actions accounts. No path is excluded; binary files are skipped.tryand skipped onENOENT/EISDIR, with no existence check first (CodeQLjs/file-system-race).pre-commit→no-local-pathson{staged_files}. In CI, validate.yml already runslefthook run pre-commit --all-files, so it checks every tracked file.--allruns the same scan by hand.{staged_files}, not{files}: with no glob,{files}was skipped with "no files for inspection" on a staged leak.🧪 How to verify
origin/nextcheckout before the cleanup:/var/folders/, 743/Users/<name>/, 106/private/tmp/claude-<uid>/, across 110 files.node scripts/check-no-local-paths.js --all→✅ No local paths: 0 leak(s) in 3154 file(s).origin/next(3167d690): 2179 hits (1034/var/folders/, 743/Users/<name>, 188 session id, 106-Users-<name>-, 106/private/tmp/claude-<uid>/, 2 session file). On this branch: 0. On the merge of this branch withorigin/next(git merge-tree, exit 0):0 leak(s) in 3173 file(s).aidd_docs/tasks/2026_10/2026_10_15_ledger-by-day/phase-5.mdfromorigin/next→phase-5.md:54: -Users-<name>-andphase-5.md:54: /private/tmp/claude-<uid>/, exit 1.Cannot find module), then green; red again on each new shape before its pattern landed. Mutants run against the test file, each red: each new pattern (slug, session key, transcript name, itsrollout-and/prefixes), the bare-name lookahead, the trailing-.strip, theENOENTskip, the made-up-id threshold at< 16and at< 5, the escaped-key support removed,threadIdalone removed,thread_id/threadIdsremoved, the array pass removed, an array read to the last]instead of the next, the line sort removed, the skip past an array removed, the line search off by one,< offset - 2andhigh = newlines.length - 1in the line search, a stale]reused for a later array,returnturned intocontinueon an unclosed array (pinned by a count of]scans, not a time bound), the array cap at 0,.jsonlturned into.jsonor.jsonl?, and the backslash branch of the transcript pattern removed. Allowlist names are pinned by--allon the tree, not by the unit tests: removing any one turns it red.findLocalPaths, one run each): 480 KB of unclosed"threadIds": [2127 → 19 ms; 20000 single-id arrays 2588 → 40 ms; 100k ids in 2000 arrays 70312 → 181 ms.--allon the tree: 0.42-0.43 s before, 0.47-0.49 s now. The pass looks up each array's]once, and stops when no]is left in the text.node --test 'scripts/__tests__/**/*.test.js'474 pass, 0 fail (13 of them in the guard's own test file).lefthook run pre-commit --all-files --forceis all green.lefthook run pre-pushis green: cli-knip, and cli-test with 6748 passing.git diff origin/next...HEAD: no added line holds the username,/var/folders/,/tmp/claude-<n>or the worktree layout.rule-contracts/evidence/{index,copilot/index,cursor-native/index}.json,autonomous-rule-generation/rule-presentation/evidence/index.jsonandrealistic-sandbox/native-evidence/index.json. Those hashes still describe the original captures, which only history holds. No test verifies them./private/tmp/aidd-*sandbox paths remain on purpose: they name no account.cli/.claude/scheduled_tasks.lockdeletes it from other checkouts on their next pull. It is a short-lived scheduler lock, recreated on demand.[(the unclosed[takes the next]as its own, and only its first 64 KB is read);ls -lowner column,whoamior$USERoutput);session_id: <uuid>);.jsonltranscript, and a bare<uuid>.jsonlwith no folder before it;--resume <uuid>);"uuid"message ids.🔗 Linked issue
None. Found by the R1 telemetry cleanup audit.
✅ I certify
🤖 Generated with Claude Code
https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp