Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
f87ee54
fix(env): refuse --service and unusable layers in legacy env set/unset
roodboi Oct 23, 2026
9819a8f
Merge remote-tracking branch 'origin/next' into g60-local-wf-g60-lega…
roodboi Oct 24, 2026
456bdad
fix(env): make the legacy --service refusal remedy migrate .env-only …
roodboi Oct 24, 2026
6515e21
Merge remote-tracking branch 'origin/next' into g104-local-wf-g60-leg…
roodboi Oct 24, 2026
78b7276
fix(env): keep gitignored values out of tracked layers in the .env-on…
roodboi Oct 24, 2026
e42bcf0
Merge remote-tracking branch 'origin/next' into wf/g60-legacy-env-ser…
roodboi Oct 31, 2026
23c8e7e
fix(env): refuse a non-file or unreadable env layer on reads instead …
roodboi Oct 31, 2026
69fb8ea
fix(env): refuse unusable layers in the contract migration and doctor…
roodboi Oct 31, 2026
0e73292
fix(env): keep migrated plaintext private, ignored and complete in th…
roodboi Oct 31, 2026
69def4f
fix(env): migrate symlinked legacy overlays instead of dropping them
roodboi Oct 31, 2026
ede4cb9
fix(env): name the legacy overlay files a migration refuses, quoted o…
roodboi Oct 31, 2026
1b1806d
fix(env): refuse a migration that would lose Compose-referenced templ…
roodboi Oct 31, 2026
36fab6c
fix(env): refuse a migration that would drop a Compose-selected overlay
roodboi Nov 1, 2026
95ffaa4
fix(env): refuse a .env-only migration that would override an explici…
roodboi Nov 1, 2026
ff06193
fix(env): keep legacy symlinks and other Compose env files from chang…
roodboi Nov 1, 2026
2ab94f5
Merge remote-tracking branch 'origin/next' into HEAD
roodboi Nov 1, 2026
de517d4
fix(env): refuse a migration that would widen a legacy file to new se…
roodboi Nov 1, 2026
b34821d
fix(env): refuse legacy lines Compose reads differently from the migr…
roodboi Nov 1, 2026
296eb23
fix(env): drop the self-reference exception and close migration expos…
roodboi Nov 1, 2026
b41965c
fix(env): refuse more Compose dotenv forms and default-overlay exposure
roodboi Nov 1, 2026
49cf488
fix(env): refuse text after a closing quote and backslashes in quoted…
roodboi Nov 1, 2026
581da64
fix(env): refuse an env_file order that changes a value on migration
roodboi Nov 1, 2026
93c5702
fix(env): refuse any symlinked legacy source in the contract migration
roodboi Nov 1, 2026
bb7f722
fix(env): refuse repeated env_file entries and env_file format on mig…
roodboi Nov 1, 2026
ed6ebe9
fix(env): refuse home-relative env_file paths and Compose merge tags …
roodboi Nov 1, 2026
ae03d03
fix(env): refuse any Compose use of a key the default overlay changes
roodboi Nov 1, 2026
1e1db24
fix(env): refuse multi-document Compose files and tool settings on mi…
roodboi Nov 1, 2026
ef9147f
fix(env): keep a teammate's .env values after a committed migration
roodboi Nov 1, 2026
190c66b
fix(env): decide unmigrated legacy env by what the layers deliver
roodboi Nov 1, 2026
6361682
fix(env): refuse unreadable local layers and overlay disagreements on…
roodboi Nov 1, 2026
1f23d03
fix(env): refuse unreadable local layers and merge without following …
roodboi Nov 1, 2026
b3f8f00
fix(env): recheck a local layer's content just before the private rename
roodboi Nov 1, 2026
9b41047
fix(env): compare overlay values with the layers as they will be afte…
roodboi Nov 1, 2026
d88ff02
fix(env): refuse a migration that would widen values to services that…
roodboi Nov 1, 2026
ca5ef35
fix(env): correct migration refusal guidance and pin the overlay regr…
roodboi Nov 1, 2026
dadc9d2
fix(env): don't migrate generated .env output as legacy base values
roodboi Nov 1, 2026
e78c870
fix(env): treat .env as generated only when its text proves it
roodboi Nov 1, 2026
2cafd2a
test(env): pin the stale state file through a real git branch switch
roodboi Nov 1, 2026
ce5c770
fix(env): let materialize run with overlay keys waiting, and refuse a…
roodboi Nov 1, 2026
64dc985
test: deny implicit daemon starts in private CLI fixtures
roodboi Nov 1, 2026
af73f1d
fix(env): refuse a state path that exists but can't be read
roodboi Nov 1, 2026
122747d
test: merge fixture daemon-autostart prevention into the PR 436 candi…
roodboi Nov 1, 2026
ddfa557
fix(env): refuse a migration when a layer or Compose file can't be read
roodboi Nov 1, 2026
17d946f
fix(env): keep a local layer's own refusal ahead of the delivered-val…
roodboi Nov 1, 2026
c7a3f31
fix(env): name the overlay in the layer refusal and read quoted env_f…
roodboi Nov 1, 2026
d254ee5
merge: bring next at b4b9d696 into the PR 436 candidate
roodboi Nov 1, 2026
be1b652
fix(env): pass the project root to the migration's layer writes
roodboi Nov 1, 2026
55a1136
fix(env): keep the project boundary check when a layer is a symlink
roodboi Nov 1, 2026
d037617
test(env): point the doctor symlink fixture inside the project
roodboi Nov 1, 2026
f980705
docs(env): correct what doctor and env add do with linked layers
roodboi Nov 1, 2026
c07cc4a
merge: bring next at 75c62b97 into PR 436
roodboi Nov 1, 2026
11d5e8f
docs(env): say when doctor migrates nothing, and move values with --l…
roodboi Nov 1, 2026
91db178
merge: bring next at b8947ead into PR 436
roodboi Nov 1, 2026
b9ec877
merge: bring next into PR 436, keeping both legacy env set refusals
roodboi Nov 1, 2026
5fafb7e
fix(env): refuse a migrated value that replaces a scoped layer value
roodboi Nov 2, 2026
2155631
fix(env): check overlays a linked worktree inherits before migrating
roodboi Nov 2, 2026
4602b49
fix(env): skip reserved names in the inherited overlay check
roodboi Nov 2, 2026
a62921a
test(env): cover host, secret and unknown scopes in the scoped-value …
roodboi Nov 2, 2026
eaa083d
test(env): pin what the host-named and invalid-scope tests actually show
roodboi Nov 2, 2026
8d804f7
test(env): run the unlistable primary test on Linux in CI
roodboi Nov 2, 2026
85068fa
Merge next ae032e79be4b3941b9c04c6591cc4ddbef5820f6 into PR 436
roodboi Nov 2, 2026
ebb7300
fix(env): compute retention on the no-contract migration route
roodboi Nov 2, 2026
2bb4271
fix(env): refuse a migrated value over a scoped deletion with no glob…
roodboi Nov 2, 2026
0ba68ef
Merge next 41f00169f7f9cd92d7fe1c064f47470c6b469bc4 into PR 436
roodboi Nov 2, 2026
e2e36fe
fix(env): refuse a no-contract migration into a local overlay slot
roodboi Nov 2, 2026
1d7112b
Merge next ddc73a0dfefa8d15bcd8fd6a825b782add2670ce into PR 436
roodboi Nov 2, 2026
0c14155
fix(env): apply the local overlay refusal outside git only
roodboi Nov 2, 2026
3c1bf86
Merge next ea0b0961e9b559ab7efb9c599b83ec55a17a41c2 into PR 436
roodboi Nov 2, 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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ jobs:
run: bun test tests/native-compose-file-sources.test.ts tests/native-compose-file-owner.test.ts
- name: Test explicit imported jobs and readiness with the matching compiler
run: HACK_TEST_NATIVE_COMPILER_BINARY="$PWD/dist/hack-config-compiler" bun test tests/native-config-import-jobs.test.ts tests/native-config-import-readiness.test.ts
# The test shards run on macOS, where this test skips. Fail if it skips here too.
- name: Test the native Linux primary-checkout listing refusal
if: runner.os == 'Linux'
shell: bash
run: |
bun test tests/project-env-local-inheritance.test.ts --test-name-pattern 'on Linux, a primary checkout' 2>&1 | tee "$RUNNER_TEMP/listing-refusal.log"
grep -Eq '^ *1 pass$' "$RUNNER_TEMP/listing-refusal.log"
- name: Independently validate generated schema shape corpus
run: |
python3 -m venv "$RUNNER_TEMP/config-schema"
Expand Down
206 changes: 201 additions & 5 deletions docs/env.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion docs/security/env-secrets-threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ These gaps are known and not closed on `next`. Each one needs its own change.
path-based, not descriptor-relative `openat`/`renameat`: an ancestor directory
swapped after the last check can still redirect an operation. This residual race
remains open; ordinary outside-link redirection is refused. In-project dangling
links retain set/unset's prior replacement behavior; strict reads refuse them.
links are refused by `hack env set`, `hack env unset` and strict reads; `hack env add`
replaces them.
6. **`encrypted_file` key file reads.** Key writes now use the
`src/lib/private-file.ts` helpers: explicit provisioning replaces the key
atomically, and first-use generation publishes it exclusively. `readEncryptedFileKey`
Expand Down
21 changes: 21 additions & 0 deletions src/cli/env-arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@ export function parseEnvScopeOption(input: {
}
}

/**
* Refusal of a non-global `--service` for `hack env set`/`unset` in a project
* that still uses the legacy `.hack/.env` file. That file holds one value per
* key, so honoring the scope is impossible and ignoring it would change the
* global value instead.
*/
export const LEGACY_ENV_SERVICE_SCOPE_MESSAGE =
'--service needs env layers, but this project uses the legacy .hack/.env file, which has no per-service values. Migrate to env layers with `hack doctor --migrate-env-config` (see "Legacy repos and migration" in docs/env.md), or omit --service to change the global value.';

/**
* `hack env set`/`unset --service` for a legacy `.hack/.env` project: only the
* global scope (omitted, empty or `global`) is accepted. Pass the scope after
* an invalid one has been refused, and call it before stdin is read or any
* file or secret store is touched.
*/
export function assertLegacyEnvScope(input: { readonly scope: string }): void {
if (input.scope !== "global") {
throw new CliUsageError(LEGACY_ENV_SERVICE_SCOPE_MESSAGE);
}
}

/**
* Resolve the `--scope`/`--service` pair of host execution commands.
* Returns `null` when neither names a scope (the global scope).
Expand Down
Loading
Loading