Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
4828f6e
fix(env): stage secret writes in a self-ignoring directory; safe key …
roodboi Nov 13, 2026
b1e2d5d
fix(env): tighten loose machine-local layers on write; report them on…
roodboi Nov 13, 2026
a0753fd
fix(mux): expire env payloads staged for sessions that never ran (HAC…
roodboi Nov 13, 2026
0c94108
feat(doctor): secrets hygiene section with safe --fix (HACK-2165)
roodboi Nov 13, 2026
d74187c
refactor(fs): split the atomic writer's mode handling into helpers (H…
roodboi Nov 13, 2026
d8719bd
fix(env): verify the staging ignore file; unique retired keys; privat…
roodboi Nov 13, 2026
9e060a3
fix(env): publish the staging ignore file atomically; git-check in-pr…
roodboi Nov 13, 2026
d3e799d
fix(env): admit staging on filesystems without Unix permissions; hygi…
roodboi Nov 13, 2026
9ff7fb0
fix(doctor): treat '..'-prefixed in-project names as inside the proje…
roodboi Nov 13, 2026
553731f
fix(doctor): fix project-configured store paths only inside the check…
roodboi Nov 13, 2026
f00c17c
fix(doctor): decide in-checkout store paths on resolved paths (HACK-2…
roodboi Nov 13, 2026
40d6f8e
fix(doctor): resolve a configured store file itself, not only its dir…
roodboi Nov 13, 2026
41b0727
fix(doctor): literal, verified ignore entries; non-blocking no-follow…
roodboi Nov 13, 2026
9861948
fix(doctor): no automatic ignore entry for a name ending in a space (…
roodboi Nov 13, 2026
e0cbce3
fix(env): set the staged ignore file's mode explicitly (HACK-2165)
roodboi Nov 13, 2026
1541d20
fix(doctor): never write an ignore entry for a name with a line break…
roodboi Nov 13, 2026
3da5fd4
style(doctor): drop an unused lint suppression (HACK-2165)
roodboi Nov 13, 2026
bc1b493
Merge origin/next into claude/hack-2165-secrets-at-rest
roodboi Nov 13, 2026
1fdf767
fix(doctor): no copyable commands with repository-chosen names; escap…
roodboi Nov 13, 2026
bcc9648
fix(doctor): untrack advice names the path git tracks (HACK-2165)
roodboi Nov 13, 2026
c08593c
fix(doctor): ignore a configured in-checkout store path before first …
roodboi Nov 13, 2026
a32574c
fix(doctor): judge a not-yet-created store path's fix roots by where …
roodboi Nov 13, 2026
6e636a9
Merge origin/next into claude/hack-2165-secrets-at-rest
roodboi Nov 13, 2026
57e248c
docs(doctor): place the gitignore step comment; key staging wording (…
roodboi Nov 13, 2026
7e74a13
Merge origin/next into claude/hack-2165-secrets-at-rest
roodboi Nov 13, 2026
66e28c5
test(env): write the manual overlay fixture private (HACK-2165)
roodboi Nov 13, 2026
9e97246
fix(doctor): never remove a live configured key; safe recovery comman…
roodboi Nov 13, 2026
604134c
fix(env): writers remove no staging leftovers; rotation needs git's "…
roodboi Nov 13, 2026
92689de
fix(env): rotation asks git without discovery overrides; ignore repai…
roodboi Nov 13, 2026
85ee75a
fix(env): rotation clears every repository-selecting git variable bef…
roodboi Nov 13, 2026
f485eb5
fix(env): the printed rotation command reads git's variable list the …
roodboi Nov 13, 2026
800be21
fix(env): rotation looks for a .git marker instead of reading git's e…
roodboi Nov 13, 2026
2c5b171
Merge next (10e1958d) into the HACK-2165 secrets-at-rest branch
roodboi Nov 13, 2026
beb4c6c
fix(env): the store-key tracking git child gets the filtered environm…
roodboi Nov 13, 2026
08275dc
docs(security): state the markerless-work-tree limit and that local l…
roodboi Nov 13, 2026
1d6e26a
fix(doctor): hygiene cleanup keeps every env input resolution reads, …
roodboi Nov 13, 2026
385b638
fix(doctor): every hygiene removal checks the current configuration's…
roodboi Nov 13, 2026
734e799
fix(doctor): a hygiene removal resolves the current configuration str…
roodboi Nov 13, 2026
adcc1a5
fix(env): unknown discovery and git state never shrink the live set; …
roodboi Nov 13, 2026
54610ae
fix(env): the live-input set takes the git family from one lookup, fa…
roodboi Nov 13, 2026
74a2e6f
Merge next (a7ec3c73) into the HACK-2165 secrets-at-rest branch
roodboi Nov 13, 2026
0a5c6b5
fix(doctor): check stores and keys in any containing git work tree; a…
roodboi Nov 13, 2026
4a43194
fix(doctor): git checks keep literal names, fail closed on lookups, a…
roodboi Nov 13, 2026
6af2a48
Merge next (0a0bfdb8) into the HACK-2165 secrets-at-rest branch
roodboi Nov 13, 2026
6172232
fix(mux): name stale payloads by the resolved path staging returns (H…
roodboi Nov 13, 2026
cae44eb
Merge next (6ab1acec) into the HACK-2165 secrets-at-rest branch
roodboi Nov 13, 2026
334b3c1
fix(doctor): hygiene never removes anything in a retired store (HACK-…
roodboi Nov 13, 2026
f81a8ea
fix(doctor): a removal target that cannot be resolved is never remove…
roodboi Nov 13, 2026
cbcaf53
fix(doctor): an input alias that cannot be resolved makes the live se…
roodboi Nov 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions docs/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,42 @@ or `docker compose down` run, so a failed hook, a remote target or a failed Comp
does not keep it. `hack doctor` reports such a file in its "compose env at rest" check
(counts only, never values), and `hack doctor --fix` removes it.

### Secrets at rest: the "secrets hygiene" check

`hack doctor` checks the files that hold env values or keys, for the project and for the
global Hack home, in its "secrets hygiene" section. It names files, never values:

- machine-local layers (`hack.env.local.yaml`, `hack.env.<overlay>.local.yaml`),
`.hack/.env` files, the project key (checkout-local and shared) and the encrypted
store and its key, when other users can read them, when they are links, or when they
belong to another user;
- those files when git tracks them (an error, except for the encrypted store: it holds
only ciphertext, so a tracked store is a warning while its key stays out of git), or
when git does not ignore them. Inside the project `--fix` adds the ignore entry; a
store or key in another git work tree (a home directory kept in git, say) is
checked there and reported for you to ignore by hand, and one in a checkout git
cannot name is an error;
- staging copies an interrupted write left behind, and env values staged for a terminal
session that never ran (older than 10 minutes);
- an encrypted store whose key file is missing: "store present, key missing, cannot be
read".

`hack doctor --fix` sets the files it reports to mode 0600, adds missing ignore
entries, and removes Hack's own leftovers, and prints each change. It never deletes a
store, a key or a file you wrote, and never untracks a file.

Hack writes a file that holds a secret by staging the complete contents first. Those
staging copies live in a private directory next to the file, `.hack-staging/`, which
holds a `.gitignore` of `*`, so git never lists anything in it. `hack doctor --fix`
removes copies older than 10 minutes, and stale terminal-session payloads, but never a
file env resolution reads (a configured store or key, the project or shared key,
`hack.config.json` or a YAML layer, by its own path or where it resolves), and never
anything in a retired store (`<store>.retired-<stamp>/`). Before each
removal it reads the configuration again, so an edit made since the check counts, and
it removes nothing it cannot confirm; writes themselves remove nothing there. A machine-local layer that a write changes, or
leaves unchanged, is set to 0600 if other users could read it. Commands that read env
layers warn on stderr about a machine-local file other users can read.

A legacy adoption (`hack config adopt`) pins the override's exact bytes and file
identity for as long as the adoption exists, and adopted commands never rewrite it. So:

Expand Down Expand Up @@ -728,9 +764,10 @@ hosts, use the `encrypted_file` secrets backend with its key file in place or
`.hack/hack.env.<overlay>.local.yaml`) instead of the shared repo env file — use it for
per-checkout values you don't want to commit. A machine-local layer that `hack env set --local`,
`hack env unset --local` or `hack env hydrate` creates is private (mode 0600) from creation,
whatever your umask, because it can hold plaintext values. An existing layer keeps its mode;
tighten it yourself with `chmod 600` if it was created by an older version. Tracked layers
keep the umask default. These commands create no new directory for values: the layers live
whatever your umask, because it can hold plaintext values. An existing layer that other users
can read (made by an older version or by hand) is set to 0600 by the next of these writes,
even one that changes nothing; `hack doctor --fix` does it at once. Tracked layers keep the
umask default. These commands create no new directory for values: the layers live
in the existing `.hack` directory, whose mode they don't change.

With no env layers yet, `hack env set --local` writes legacy `.hack/.env` (or
Expand Down Expand Up @@ -880,7 +917,8 @@ never sees a partial file. If Hack is killed between those two steps, an empty
`.hack.secret.key` or lock file is left behind. Hack refuses it rather than use or
replace it; remove it and run the command again. ExFAT and FAT do not store Unix
permissions, so on such a drive the key file is not limited to your user the way mode
`0600` limits it elsewhere.
`0600` limits it elsewhere. The private staging directory (`.hack-staging/`) is the same: Hack still refuses one
that is a link or belongs to another user, but accepts the drive's mount-wide mode.

If you omit `key`, `value`, or `--service`, `hack env add` prompts for them interactively.
Pass `--no-interactive` (or set `HACK_NO_INTERACTIVE=1`) for scripted/agent usage: the
Expand Down
24 changes: 24 additions & 0 deletions docs/guides/upgrade-to-v5.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,30 @@ See [Env and secrets](../env.md#legacy-repos-and-migration) and the
[threat model](../security/env-secrets-threat-model.md). These are manual checks,
not evidence that the real-machine upgrade or rollback has passed.

## After upgrading, check secrets at rest

Earlier versions could leave env values and keys more exposed than v5 does:
machine-local env files created by hand or by an older release with mode 0644,
staging copies of `.hack/.env` or the project key under names git does not
ignore, env values staged for a terminal session that never ran, or an
encrypted secret store whose key file is gone. After upgrading, run this once
in each project, and once outside any project for the global Hack home:

```bash
hack doctor # read the "secrets hygiene" section
hack doctor --fix # apply its safe fixes
```

The "secrets hygiene" section names each file and what it holds, never a value.
`hack doctor --fix` sets loose env layers, `.hack/.env` files, keys and stores to
mode 0600, adds a missing ignore entry for a secret-bearing file, and removes
staging copies and terminal-session payloads that Hack left behind. It prints
each change. It never deletes a store, a key or a file you wrote, and it does not
untrack a file git already tracks: the section names the `git rm --cached`
command, and what that file held should be treated as exposed. A store reported
as "store present, key missing, cannot be read" stays where it is: restore its
key file to read it, or remove the store yourself once you no longer need it.

## The daemon after an upgrade

Installing 5.0.0 over a running 4.x does not replace the daemon. On Linux the old
Expand Down
Loading
Loading