Repository navigation
fix(env): refuse machine-local writes git could collect; no-follow legacy dotenv reads (HACK-2165) - #762
Conversation
Two audit findings on machine-local env layers: - F4: a local layer that is a symlink (for example to the tracked default layer) passed the tracking check, which tests the link's name, while the writer replaced the file it resolves to. Every machine-local write now refuses a symlinked destination, checked again immediately before the commit rename. - F5: local writes only warned when the ignore rule could not be established, and `env set --env <name> --local` could write into an already tracked local overlay. Before every machine-local write (set, add, unset, hydrate) Hack now refuses a tracked layer, an unknown tracking status inside a checkout, and a layer no effective rule ignores after one repair attempt, with the migration's fail-closed semantics. The HACK-1363 tests now cover a read-only .hack/.gitignore while another rule ignores the layer (warns and writes) and while nothing does (refuses). docs/env.md describes the checks and the legacy read change in the next commit. Co-Authored-By: Claude <noreply@anthropic.com>
Audit F3: legacy env resolution, including `hack env list`, read `.hack/.env` and the selected overlay with a reader that follows symlinks, so a link to a dotenv file outside the project was parsed and a plain_env value printed. Every legacy dotenv read in hack-env.ts now uses the private no-follow reader the mutations already used, and refuses a redirected file before reading it. Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…r and migration checks (HACK-2165) Own-reviewer round on bee7837: - On a case-insensitive filesystem a write to hack.env.qa.local.yaml replaces a tracked Hack.env.qa.local.yaml, which the exact ls-files check missed. The machine-local guard also asks git with an icase,literal pathspec and refuses a tracked case variant. - findUnmigratedLegacyDotenv and doctor's legacy DEV_HOST read still followed a .hack/.env link. Both now use the private no-follow reader; a linked file is reported unreadable and never read. - The guard's ignore repair is silent, so a read-only .hack/.gitignore warns once, not twice. Co-Authored-By: Claude <noreply@anthropic.com>
|
@codex review |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. 🚀 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
…itive disks (HACK-2165) Hosted shard 1 runs on macOS, where hack.env.qa.local.yaml and the tracked Hack.env.qa.local.yaml are one file, so the "new file does not exist" assertion cannot hold there; the refusal itself passed. The test now checks that only the tracked spelling is listed, which holds on both kinds of filesystem. Co-Authored-By: Claude <noreply@anthropic.com>
…acy and alias gaps (HACK-2165) Second-family review of a19c030 (1 blocker, 4 should-fix), under the stated contract: protect against accidental collection by git and other OS users; a same-user racer is out of scope. - Blocker: a machine-local write resolved its destination with realpath, so a link swapped in after admission could redirect the write to the tracked default layer. Machine-local writes now use noFollowTarget: a link at the destination is refused and the commit renames onto the admitted path itself, which a later link cannot redirect. - Legacy `env set`/`unset` (a project without env layers) wrote `.hack/.env` without the git gate. After the info/exclude repair, assertMachineLocalFileGitPrivate now refuses a tracked, unknown or unignored file before any value is written. - Checkout detection walked only the lexical parents of the project path, so an alias outside a repository to a project inside one was "outside every checkout" when git refused. It now walks the resolved path's parents too and fails closed on an unresolvable path or an uninspectable `.git` entry. - A private read could be satisfied from a parent directory swapped to another directory and restored. After reading, the pinned directories are re-checked and the path must still name the file opened; otherwise the read retries, then refuses. Staging-file ignore protection (the fifth item) is closed by #764, which stages machine-local writes in the self-ignoring .hack-staging/. docs/env.md states the contract and the new checks. Co-Authored-By: Claude <noreply@anthropic.com>
Keeps writeTextFileAtomicIfChanged under the complexity limit after noFollowTarget; no behavior change. Co-Authored-By: Claude <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
…ixture env (HACK-2165) Hosted shard 7: tests/fixture-hermeticity.test.ts requires every fixture git spawn to use fixtureGitEnv. The new legacy git-privacy test's helper built its own environment. Co-Authored-By: Claude <noreply@anthropic.com>
… environment (HACK-2165) Second-family review of the merge with next (723ee60): #756 filters Hack key material out of the git probes' environment, but this branch's extra `ls-files :(icase,literal)` probe was added after that base and auto-merged without it, so git inherited HACK_ENV_SECRET_KEY and the other credentials. It now passes childEnvironment(). Test: a local `env set` with a key sentinel in Hack's environment records the probe's child environment through the spawn recorder; it is explicit and holds no sentinel. Co-Authored-By: Claude <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
|
Ready for lead: HACK-2165 first PR (F3, F4, F5 and the second-family review's five findings), merged with next c96c444.
|
next edf23ae (the squash of #762, plus #767 and #769) conflicts in src/lib/fs.ts and src/lib/project-env-config.ts. Both sides are kept: - writeTextFileAtomicIfChanged has #762's noFollowTarget (destination never resolved through a link) and this branch's tightenExisting and private staging; machine-local writes pass all of createMode 0600, noFollowTarget and tightenExisting, with #762's link recheck before the rename. The in-place tighten for an unchanged write honours noFollowTarget too. - #769's live-input guard and #767's value-free listing are untouched (auto-merged). docs/env.md no longer says an existing layer keeps its mode. Co-Authored-By: Claude <noreply@anthropic.com>
…ent; close threat-model gaps 17 and 18 isKeyFileTrackedByGit (from #766) spawned git with the full environment, so HACK_ENV_SECRET_KEY and other Hack key material reached the child. It now passes childEnvironment(), as #756 requires for tool children. Regression: a fresh process with a recording git first on PATH checks a key inside a work tree with a sentinel project key set; git sees no Hack key variable. It fails without the change. The threat model's gaps 17 (HACK-2169, closed by #766) and 18 (HACK-2165, closed by #762 and this branch) now describe the implemented guards and their remaining limits, and the rows that pointed at them as open agree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in docs/security/env-secrets-threat-model.md gaps 16-19 only: next's wording for gaps 17 (#766) and 19 (#768, HACK-2171) is kept; gap 18 takes next's #762 text plus this branch's mode tightening, read warning, ACL/umask limits and markerless-work-tree note; gap 16 keeps the doctor reference, and the two table rows that pointed at gaps 17/18 say "narrowed". #768's cleanup receipts merge cleanly with this branch's strictGit live-input collector (its new caller keeps the non-strict path). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… mux payloads, doctor secrets hygiene (HACK-2165) (#764) Second of two PRs for HACK-2165 (umbrella HACK-2163): audit findings F1 and S1, lead items 1, 2, 3 and 5, and the `hack doctor` "secrets hygiene" section as the migration path. The first PR is #762 (F3, F4, F5); please merge this one after it. Item 4 (encrypted-file backend links and key cache) moved to HACK-2169 and is not here. ## F1 and item 2: no committable copies of keys or env files - **Staging leftovers.** Private writes (`.hack/.env` files, `.hack.secret.key`, the encrypted store and its key) staged their contents in a `.<name>.<uuid>.tmp` sibling that no ignore entry matched, so a crash could leave a copy for `git add -A`. They now stage in `.hack-staging/` next to the target. That directory is private (0700), and it holds a `.gitignore` of `*` that exists before any staging file, so git never lists anything in it, without changing any project ignore file. The rename stays on the same filesystem. The next write there removes leftovers older than ten minutes, plus older versions' siblings for the same target. A linked or shared staging directory is refused. Writers whose directories must hold only their own files (background commands, shared-host claims, the `.internal` compose override) keep sibling staging. `.hack-staging/` stays in the directory afterwards, holding only its `.gitignore`. - **Weak-key remedy.** It no longer says `mv .hack.secret.key .hack.secret.key.old` (an unignored name). It now says to keep a 0600 copy outside the worktree (`~/.hack/retired-keys/`) until every secret is re-added, and to re-add with `--value-stdin` or the hidden prompt, never as argv. One other doctor hint that suggested `--secret KEY VALUE` is fixed too. ## S1 and item 1: loose machine-local layers - A machine-local write drops group and other bits from an existing layer. The staging copy gets the tightened mode before any content is written, and a write that changes nothing tightens the file in place. Shared layers keep their mode. HACK-1680 had kept an existing layer's mode; its test now asserts the tightening. - Commands that read env layers (`env`, the up and run paths, `session`) warn once per file on stderr about a loose machine-local file. The warning names the file and its mode, and is printed after a successful read, so refusals stay one line. ## Item 3: terminal-session env payloads Payloads in `$TMPDIR/hack-mux-env-*/env.json` now expire after ten minutes. The pane refuses and removes an expired one. The staging process removes its payload when the time limit passes, if it is still running, and the next staging sweeps this user's older payloads. ## Item 5 and the migration path: `hack doctor` "secrets hygiene" This section covers the project and the global Hack home, and names files, never values. It reports: - secret-bearing files that are loose, links, owned by another user, tracked, or unignored; - staging leftovers; - stale terminal payloads; - a store that is present while its key file is missing: "store present, key missing, cannot be read". `hack doctor --fix` tightens modes (through a no-follow handle, only while the file is still the one found), adds missing ignore entries, and removes Hack's own leftovers. It prints each change. It never deletes a store, a key or a user's file, and it never untracks a file. `docs/guides/upgrade-to-v5.md` gains an "after upgrading, check secrets at rest" step; `docs/env.md` and the threat model describe the behaviour. ## Merge ordering and overlap - **#762:** both PRs touch `writeProjectEnvConfigFile` in `src/lib/project-env-config.ts`. #762 adds the destination guard and this PR adds `tightenExisting`. The conflict is a small merge in one object literal; after #762 lands I will merge `next` into this branch. - **The HACK-2163 session:** none of its functions are touched here (`src/lib/shell.ts`, the env parse errors, `src/templates.ts`, `src/daemon/routes/env.ts`). - **HACK-2169:** `src/lib/secret-store.ts` only gains a config-only export at the end of the file (`resolveFileSecretStorePaths`). ## Validation - **New tests, all with temp directories and sentinel values:** - the staging directory, including a git control showing an interrupted key publication leaves nothing to stage; - the sweep, and refusal of linked or shared staging directories; - tightening on write and on unchanged writes; - the read-time warning; - payload expiry and sweep; - each hygiene finding and fix, including that `--fix` never deletes a store. - **Local, pinned Bun 1.4.4:** typecheck, lint, `privacy:check`, `docs:check` and gitleaks pass. 162 test files that import the changed modules ran. - The failures are all in native and adoption files. They fail the same way without these changes on this host: they need the compiled config compiler, or hit native-state refusals here. - Hosted CI runs the full suite. - **Not run:** a real macOS keychain, a real store or key, or a reboot. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- codesmith:footer --> --- <a href="https://backend.blacksmith.sh/track/run-proof?expires=1797144836&installation_model_id=17053&pr_number=764&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F764&variant=prove_it_works&signature=d73e9462a4d99d7d8864b40dd7ee3d13bb27ccfd101c77774057f7ba2cbf3ce0"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-prove-it-works-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-prove-it-works-light.svg"><img alt="Prove it works" src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-prove-it-works-dark-3x.png" width="129" height="28"></picture></a> <sup><a href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/764?autoLogin=true&ref=codesmith_pr_footer">Open in [code]smith</a> · Need help on this PR? Tag <code>@codesmith-bot</code> with what you need.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> --------- Co-authored-by: Kit <344842+roodboi@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
The next.17 section omitted the tmux breaking-change warning that already existed at the page tail. Move it into the release section and put its v4 stop procedure before the v5 trial. The env breaking entry was already correct; keep its scoped single-key/terminal/stdin migration guidance. Audit all **71 commits** from `v5.0.0-next.16` (`b21c3b1898`) through candidate **`5002cf87bb50686b17ceae8a4c835f524ee1b3a5`** against merged code and PR text. Add missing upgrade-assistant, activity, observe/plan/version, interactive exec, recovery, background-command and host/secret-custody notes. Preserve the complete native env-file behavior table and mark the four new JSON contracts **shape may change before 5.0.0**, with the planned HACK-2238 envelope migration. Correct stale native interactive-exec/network/metadata/Ctrl-C claims and the short upgrade assistant wording. Credit the publisher-shutdown fix to #719, already in next.16; keep next.17 lifecycle-command retirement separate. Retake a private stopped-state backup after v4 stops zellij, keeping the first copy too. Keep next.16 warnings scoped to that bundle. Keep original-key backups before older Doctor migrations and distinguish file rollback from runtime/data recovery. Only eight Markdown files change. Runtime, tests, workflows, generated CLI reference, diagrams, immutable snapshots and the site pin are unchanged. No diagram changed, so a new visual would add nothing. The site re-pin remains a separate release follow-up. Merge timing: respect the lead's candidate freeze; intended for the batch immediately after next.17 publication and the site re-pin. The moved tmux breaking-change notice is called out for the lead's pre-cut decision. This PR does not merge or publish anything. Validation at **`d95cfc554fabc4a0d70e3ba6caeb6746f63a198c`**: - Bun 1.4.4, frozen dependencies. Docs prepare/check/typecheck/build and site tests (2 passed, 25 assertions) pass. Production-mode HTTP smoke: 97 docs, 82 SVGs, 217 local links/anchors; task server stopped. - All 50 Hack commands named anywhere in the notes exist in the current generated CLI reference; 19 option contracts checked. CLI-reference regeneration has no diff. - All 71 commits have one row below, including the test commit and merge for #786. All 70 source evidence paths exist at the fixed candidate. Source release-note local links/anchors pass. - Privacy and complete reachable-history Gitleaks pass (1,330 commits, no findings). Persistent Claude Opus second-family review is CLEAN at this exact head. Hosted run 43585059152 passed docs site, secret scan and every planned Linux unit shard (4–8), plus the required test fan-in. Typecheck/privacy/quality ran on shard 8. Focus had no selected tests. Compiler, native core, runtime images/models, process-lifetime and Docker execution were out of scope; a green Docker fan-in is not Docker execution evidence. All bot comments were read; CLEAR at d95cfc5, zero unresolved threads/change requests. Codex code/security reviews found no issues. - No real install, migration, daemon/DNS/trust change, Docker or native VM run was performed for this documentation audit. Native acceptance and the candidate's hosted release qualification remain separate. ## Candidate commit coverage `entry present` means the behavior already had an entry within the next.17 section (some now have fuller detail). `added` means missing behavior or migration guidance was added there; #824 moves the existing warning into that section. `not user-visible` means no shipped CLI/runtime change: documentation, test/CI/repository housekeeping or the internal desktop preview. Source links pin the candidate, rather than a moving branch. | Commit / PR | Coverage | Entry or reason | Audited source | | --- | --- | --- | --- | | [70d328e](70d328e) [#743](#743) | added | Host setup: privileged/NSS safeguards | [src/lib/linux-trust-setup.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/linux-trust-setup.ts) | | [51f15c8](51f15c8) [#746](#746) | not user-visible | Docs-only upgrade checklist and site generation; no installed behavior | [scripts/docs/prepare.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/docs/prepare.ts) | | [c14f3f5](c14f3f5) [#744](#744) | added | Native setup JSON accounting | [packages/runtime-core/src/provider/setup.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/provider/setup.rs) | | [b0c6bcd](b0c6bcd) [#705](#705) | added | Retained background exec/run and cursor tools | [src/commands/background-command-start.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/background-command-start.ts) | | [c89f4fd](c89f4fd) [#745](#745) | added | Linux daemon query/home refusals | [src/commands/daemon-systemd.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/daemon-systemd.ts) | | [cce4b59](cce4b59) [#752](#752) | not user-visible | Site pin/snapshots and editorial pages only | [scripts/docs/versions.json](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/docs/versions.json) | | [56676fc](56676fc) [#754](#754) | added | Setup reuse/offline accounting; pin-watch changes are internal | [packages/runtime-core/src/provider/setup.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/provider/setup.rs) | | [7a496f1](7a496f1) [#755](#755) | added | MCP registered selectors and background tool guidance | [src/mcp/server.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/mcp/server.ts) | | [e7a7039](e7a7039) [#747](#747) | entry present | Native follow text shape; added session ends/restart recovery | [src/backends/native-log-follow.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-log-follow.ts) | | [87a6df5](87a6df5) [#758](#758) | added | Compose short one-shot startup | [src/lib/compose-startup-state.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/compose-startup-state.ts) | | [2afb5c4](2afb5c4) [#761](#761) | added | Native Hack networks and inert generated labels | [src/lib/native-hack-compose-conventions.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/native-hack-compose-conventions.ts) | | [342a585](342a585) [#771](#771) | not user-visible | Docs-only next.16 trial record | [docs/versions/v5/guides/upgrade-from-v4.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/versions/v5/guides/upgrade-from-v4.md) | | [6f0acf1](6f0acf1) [#769](#769) | entry present | Migration live-key/alias preservation | [src/lib/project-env-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-env-config.ts) | | [c96c444](c96c444) [#756](#756) | added | Child key filtering, fixed diagnostics, legacy ignore/YAML API refusals | [src/lib/child-environment.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/child-environment.ts) | | [e2c73c6](e2c73c6) [#748](#748) | not user-visible | Contributor CI selection/coalescing, outside product release notes | [scripts/ci-change-scope.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/ci-change-scope.ts) | | [ebf794e](ebf794e) [#760](#760) | added | Agent/output custody warning; runtime unchanged | [src/agents/instruction-source.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/agents/instruction-source.ts) | | [021b16b](021b16b) [#763](#763) | added | One-pass background redaction scan; no public benchmark claim | [src/lib/background-command/output-scanner.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/background-command/output-scanner.ts) | | [8564ec4](8564ec4) [#765](#765) | added | Shared preflight/start admission and detached failure reporting | [src/backends/native-project-review.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-project-review.ts) | | [0c0fba2](0c0fba2) [#767](#767) | entry present | Breaking env exposure/protect/stdin/scope/API guidance | [src/commands/env.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/env.ts) | | [9435da9](9435da9) [#762](#762) | added | Local Git/link refusals and no-follow dotenv | [src/lib/project-env-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-env-config.ts) | | [edf23ae](edf23ae) [#753](#753) | added | Daemon stop proves the live Hack home | [src/commands/daemon-systemd.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/daemon-systemd.ts) | | [573ceec](573ceec) [#766](#766) | entry present | Doctor layer/integrity error; added store/key write refusals | [src/lib/secret-store.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/secret-store.ts) | | [a0e9760](a0e9760) [#757](#757) | added | Orphan output truncation and final-sync evidence | [src/lib/background-command/recovery.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/background-command/recovery.ts) | | [10e1958](10e1958) [#759](#759) | added | Private lifecycle logs/FIFO and expiring mux payloads | [src/lib/private-file.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/private-file.ts) | | [bd899ee](bd899ee) [#775](#775) | not user-visible | Portable test fixtures only | [tests/single-file-tar.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/single-file-tar.test.ts) | | [80dae3d](80dae3d) [#776](#776) | added | Candidate missing-daemon diagnosis is optional/healthy | [src/commands/doctor.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/doctor.ts) | | [5b2a2d0](5b2a2d0) [#772](#772) | added | Bounded attaching-network retry; strict saved reads remain | [src/lib/native-compose-ownership.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/native-compose-ownership.ts) | | [ab29d49](ab29d49) [#777](#777) | not user-visible | CI scheduling only | [docs/reference/ci-test-selection.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/reference/ci-test-selection.md) | | [31339c7](31339c7) [#770](#770) | entry present | Native inferred readiness, 240 s, HTTP/service_healthy limits | [packages/runtime-core/src/project/compose/service.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/project/compose/service.rs) | | [156ee7f](156ee7f) [#773](#773) | entry present | Loopback proxy port/Doctor/open checks | [src/lib/proxy-loopback.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/proxy-loopback.ts) | | [a7ec3c7](a7ec3c7) [#768](#768) | entry present | Cleanup receipts/partial outcomes and tracked-file wording | [src/lib/project-env-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-env-config.ts) | | [ceb3e23](ceb3e23) [#778](#778) | entry present | Native bridge socket readiness | [packages/runtime-core/src/provider/bridge.rs](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/packages/runtime-core/src/provider/bridge.rs) | | [af91049](af91049) [#783](#783) | entry present | Pointer reconciliation after partial cleanup | [src/commands/doctor.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/doctor.ts) | | [0a0bfdb](0a0bfdb) [#780](#780) | not user-visible | Site-only Linux guide publication; no CLI behavior | [scripts/docs/versions.json](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/docs/versions.json) | | [d0cf767](d0cf767) [#786](#786) | not user-visible | Consent test wording only (both commits) | [tests/env-migration-tracked-consent.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/env-migration-tracked-consent.test.ts) | | [7e2e630](7e2e630) [#786](#786) | not user-visible | Consent test wording only (both commits) | [tests/env-migration-tracked-consent.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/env-migration-tracked-consent.test.ts) | | [91c311e](91c311e) [#779](#779) | entry present | Restore or reversible retire; missing store keys scoped to readers | [src/lib/secret-store-retire.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/secret-store-retire.ts) | | [6ab1ace](6ab1ace) [#785](#785) | not user-visible | Docs-only release/upgrade/env notes | [docs/guides/v5-release-notes.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/guides/v5-release-notes.md) | | [25bd63c](25bd63c) [#764](#764) | entry present | Doctor hygiene, local modes, ignored staging and live-input protection | [src/lib/secrets-hygiene.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/secrets-hygiene.ts) | | [d9df57c](d9df57c) [#784](#784) | not user-visible | Resolver/proxy and consent test corrections only | [tests/project-open-loopback.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/project-open-loopback.test.ts) | | [2c1f665](2c1f665) [#791](#791) | added | Detached failure names, open DNS hint, init placeholder; prune guidance | [src/commands/project.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/project.ts) | | [f6fe55f](f6fe55f) [#787](#787) | not user-visible | Docs-only retirement/hygiene follow-up | [docs/guides/v5-release-notes.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/guides/v5-release-notes.md) | | [7f9548c](7f9548c) [#792](#792) | not user-visible | Start-here docs, navigation and contributor rule only | [docs/versions/v5/install.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/versions/v5/install.md) | | [a0cfc73](a0cfc73) [#793](#793) | added | Verified updater publication, backups and reversible candidate removal | [src/lib/self-update.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/self-update.ts) | | [292d312](292d312) [#782](#782) | added | Lock-free native ps and proved failed-start down | [src/backends/native-project-observe.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-project-observe.ts) | | [b1e123b](b1e123b) [#796](#796) | added | Doctor reader count/worktree grouping in store recovery | [src/lib/doctor-secret-integrity.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/doctor-secret-integrity.ts) | | [286d00f](286d00f) [#789](#789) | added | Foreground command-before-wrapper retirement | [src/lib/project-lifecycle-processes.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-lifecycle-processes.ts) | | [bb6a7d1](bb6a7d1) [#788](#788) | not user-visible | Linux qualification runbook only | [docs/guides/linux-host-qualification.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/docs/guides/linux-host-qualification.md) | | [a262d42](a262d42) [#800](#800) | not user-visible | Internal desktop design; outside supported CLI release scope | [apps/desktop/DESIGN.md](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/apps/desktop/DESIGN.md) | | [6b161ba](6b161ba) [#774](#774) | added | Native interactive exec, finite fallback and receipt recovery | [src/backends/native-project-exec.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-project-exec.ts) | | [1249272](1249272) [#799](#799) | not user-visible | Portable output-contract CI selection only | [scripts/test-shard-plan.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/test-shard-plan.ts) | | [fa3f03d](fa3f03d) [#790](#790) | added | Read-only upgrade check, snapshot and rollback | [src/commands/upgrade.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/upgrade.ts) | | [c3b79bc](c3b79bc) [#807](#807) | added | Observe-only global status and unknown evidence | [src/commands/global-status-observe.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/global-status-observe.ts) | | [29be043](29be043) [#812](#812) | added | Read-only local CA trust diagnosis | [src/lib/ca-trust-status.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/ca-trust-status.ts) | | [f0102fd](f0102fd) [#814](#814) | not user-visible | Prepared-base verifier test fixture only | [tests/qualify-provider-pin.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/qualify-provider-pin.test.ts) | | [b4ebfec](b4ebfec) [#815](#815) | added | Compact upgrade plan, snapshot-backed apply/resume | [src/lib/upgrade-apply.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/upgrade-apply.ts) | | [c44b266](c44b266) [#801](#801) | not user-visible | Internal optional desktop preview; not shipped CLI behavior | [apps/desktop/Cargo.toml](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/apps/desktop/Cargo.toml) | | [56d1d0f](56d1d0f) [#804](#804) | added | Automatic mux sweep preserves live/retired config inputs | [src/lib/live-inputs.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/live-inputs.ts) | | [1958363](1958363) [#805](#805) | added | MCP timedOut result and Codex installer timeout | [src/mcp/server.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/mcp/server.ts) | | [e6207e8](e6207e8) [#810](#810) | added | Lifecycle activity writes including handler refusals; activity JSON | [src/commands/lifecycle-activity.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/lifecycle-activity.ts) | | [23fe08f](23fe08f) [#825](#825) | added | Read-only batch review and permission-only key repair | [src/lib/upgrade-review.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/upgrade-review.ts) | | [cab596c](cab596c) [#781](#781) | entry present | Full native env-file behavior table; added support/precedence summary | [src/backends/native-compose-environment.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/backends/native-compose-environment.ts) | | [1e84684](1e84684) [#798](#798) | not user-visible | Rust test process isolation/nextest; internal qualification only | [scripts/test-runtime-core.sh](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/scripts/test-runtime-core.sh) | | [518af29](518af29) [#817](#817) | not user-visible | Remove accidental empty checkout files; no installed CLI change | [.gitignore](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/.gitignore) | | [07192f7](07192f7) [#824](#824) | added | BREAKING warning moved from page tail into next.17; v4 stop procedure retained | [src/mux/mux-config.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/mux/mux-config.ts) | | [fe09a5b](fe09a5b) [#816](#816) | added | Read-only install/trust admin plans and exits | [src/commands/global.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/global.ts) | | [8977676](8977676) [#827](#827) | added | Registry-preserving project observe, runtime opt-in | [src/commands/projects.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/commands/projects.ts) | | [5965b23](5965b23) [#828](#828) | added | Running CLI version identity JSON | [src/lib/version-report.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/version-report.ts) | | [2922210](2922210) [#830](#830) | added | Upgrade safe/admin/manual action kinds | [src/lib/upgrade-action-kind.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/upgrade-action-kind.ts) | | [51b55c0](51b55c0) [#820](#820) | not user-visible | Archive/plan/shared-helper verification fixtures only | [tests/python-free-download.test.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/tests/python-free-download.test.ts) | | [5002cf8](5002cf8) [#809](#809) | added | Retirement read retry, second Ctrl-C and native cancellation cleanup | [src/lib/project-lifecycle-processes.ts](https://github.com/hack-dance/hack/blob/5002cf87bb50686b17ceae8a4c835f524ee1b3a5/src/lib/project-lifecycle-processes.ts) | --------- Co-authored-by: Kit <344842+roodboi@users.noreply.github.com>
First of two PRs for HACK-2165 (umbrella HACK-2163): audit findings F4, F5 and F3. F1, S1 and the
doctor"secrets hygiene" section follow in a second PR.F4: a symlinked machine-local layer could write into a tracked file
hack env hydratechecked whether git tracks the local layer by the link's own name, which is untracked. The writer then resolved the link and replaced its target, which could be the trackedhack.env.default.yaml. Every machine-local write (set,addandunsetwith--local, andhydrate) now refuses a symlinked destination. The check runs before the write and again immediately before the commit rename. Shared layers can still be links.F5: local writes continued when git privacy was not established
Plain local writes only warned when
.hack/.gitignorecould not be updated, andenv set --env qa --localhad no tracked-file check. Before every machine-local write,writeProjectEnvConfigFilenow refuses, with the same fail-closed semantics as the migration's checks:.hack/.gitignoreblock.Outside a git checkout nothing can commit the layer, so these checks pass.
flowchart TD W[machine-local write] --> L{destination is a symlink?} L -- yes --> R1[refuse: linked] L -- no --> T{git tracks it?} T -- yes --> R2[refuse: tracked] T -- unknown, in checkout --> R3[refuse: tracking unknown] T -- no / no checkout --> I{effectively ignored?} I -- no --> F[repair .hack/.gitignore once] --> I2{ignored now?} I2 -- no --> R4[refuse: not ignored] I -- yes --> OK[write; link re-checked before rename] I2 -- yes --> OKThe HACK-1363 tests encoded the old "warn and continue" behaviour. They now cover two cases: a read-only
.hack/.gitignorewhile another rule ignores the layer (warns and writes), and while nothing ignores it (refuses). The symlink-boundary test that expected hydrate and unset to follow an in-project local-layer link now expects a refusal.F3: legacy
env listfollowed a dotenv symlink out of the projectLegacy resolution read
.hack/.envand the selected overlay with a reader that follows symlinks, so aplain_envvalue from a file outside the project was printed. All eight legacy dotenv reads insrc/lib/hack-env.tsnow use the private no-follow reader the mutations already used, and refuse a redirected file before reading it.Merge ordering
The other session on HACK-2163 (
codex/hack-2163-env-secret-leaks) edits the env parse errors insrc/lib/project-env-config.ts. This PR touches that file only inwriteProjectEnvConfigFile, plus three new functions placed right after it:assertMachineLocalLayerWriteDestination,machineLocalLayerGitProblemandassertMachineLocalLayerNotLinked. It does not touch any parse or error-reporting function,src/lib/shell.ts,src/templates.tsorsrc/daemon/routes/env.ts.Validation
hack.env.qa.local.yamlwith--env qa --local; a read-only.hack/.gitignorewith nothing else ignoring the layer; legacy.hack/.envand overlay links outside the project. All six fail on the base commit.privacy:check,docs:check, gitleaks. 45 env-related test files: 918 pass. One suite failed because it needs the compiled config compiler, which this host cannot build; hosted CI runs it.🤖 Generated with Claude Code
Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.