fix(#2665): wire the live-config guard to strict mode on Linux/macOS CI lanes

Review round 4, Major 2, answering the explicit report-only-vs-strict
question: strict now. A future regression of the class this PR closes
should fail CI, not print a warning nobody reads — that is what the PR
title promises.

Scoped deliberately: GSD_STRICT_LIVE_CONFIG_GUARD=1 on the Linux/macOS
lanes of all three test jobs; Windows lanes stay report-only because the
guard's first run found pre-existing USERPROFILE leaks there (~190 test
sites sandbox HOME alone) — flipping them strict today reddens next on a
defect class this PR does not carry. Promote once that sweep lands (the
SEVERITY note in live-config-guard.cjs and the CONTEXT.md seam both now
record that state).
This commit is contained in:
0xdhx
2026-08-03 03:05:29 -05:00
parent 209f2fe983
commit 253250580e
3 changed files with 19 additions and 4 deletions

View File

@@ -140,6 +140,11 @@ jobs:
timeout-minutes: 15
env:
GSD_PLUGIN_ROOT: .ci-gsd-plugin-root-disabled
# #2665: a live-config leak fails the run on Linux/macOS lanes. Windows
# stays report-only: the guard's first run found PRE-EXISTING USERPROFILE
# leaks there (~190 test sites sandbox HOME alone), a documented separate
# class — promote once that sweep lands (see live-config-guard.cjs SEVERITY).
GSD_STRICT_LIVE_CONFIG_GUARD: ${{ matrix.os != 'windows-latest' && '1' || '' }}
# #2854: pin the emitted gate's baseline to the SAME commit the tree was merged
# with. "Rebase check" merges `pull_request.base.sha` (pinned by #2472 so all 12
# matrix jobs agree on one tree), but `resolveBase()` otherwise falls through to
@@ -333,6 +338,8 @@ jobs:
timeout-minutes: 15
env:
GSD_PLUGIN_ROOT: .ci-gsd-plugin-root-disabled
# #2665: ubuntu-only lane — strict unconditionally (see the `test` job note).
GSD_STRICT_LIVE_CONFIG_GUARD: '1'
# #2854: same pin as the other rebase-merged lanes. This lane runs a targeted
# list that can include the emitted gate, and the invariant is easier to keep
# with no exceptions: if a job merges a pinned base, the gate uses that base.
@@ -410,6 +417,8 @@ jobs:
timeout-minutes: 30
env:
GSD_PLUGIN_ROOT: .ci-gsd-plugin-root-disabled
# #2665: strict on Linux/macOS, report-only on Windows (see the `test` job note).
GSD_STRICT_LIVE_CONFIG_GUARD: ${{ matrix.os != 'windows-latest' && '1' || '' }}
# #2854: pin the emitted gate's baseline to the SAME commit the tree was merged
# with. "Rebase check" merges `pull_request.base.sha` (pinned by #2472 so all 12
# matrix jobs agree on one tree), but `resolveBase()` otherwise falls through to

View File

@@ -596,8 +596,8 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr
`LIVE-CONFIG.GUARD.SEAM.scope=ownership-based, never whole-root: GSD_OWNED_ENTRIES top-level footprint + gsd--prefixed children of GSD_PREFIXED_PARENTS (dirs shared with the host agent); watching a shared root wholesale false-positives on the host's own writes and a guard that cries wolf gets disabled`
`LIVE-CONFIG.GUARD.SEAM.non-root-targets=resolveExtraWatchTargets covers the two live write surfaces that are not runtime config ROOTS: $GSD_HOME/.gsd watched WHOLESALE (exclusively GSD-owned, so the shared-root trap does not apply) and <resolveKimiHooksTomlDir()>/config.toml watched as a SINGLE FILE (the root belongs to Kimi CLI); passed to snapshotLiveConfig explicitly so a fixture-root caller cannot pull the real ~/.gsd into its snapshot`
`LIVE-CONFIG.GUARD.SEAM.truncation=MAX_ENTRIES/MAX_DEPTH bound the walk; a bound hit sets truncated and diffLiveConfig emits kind:'unverified' — a truncated scan MUST NOT read as clean; boundary covered at {limit-1,limit,limit+1} via newestMtime's injected budget plus fast-check monotonicity, per RULESET.TESTS.boundary-coverage + RULESET.TESTS.property-based-testing`
`LIVE-CONFIG.GUARD.SEAM.severity=reports by default; fails only under GSD_STRICT_LIVE_CONFIG_GUARD=1, skipped by GSD_SKIP_LIVE_CONFIG_GUARD=1; promote to fatal once the USERPROFILE sweep lands, mirroring local/no-source-grep warn->error (ADR 452)`
`LIVE-CONFIG.GUARD.SEAM.ci-blind=CI cannot catch the ambient-env half of this class at all — CI never has these vars set, so green CI is not evidence; the guard is the only loud signal`
`LIVE-CONFIG.GUARD.SEAM.severity=reports by default locally; CI wires GSD_STRICT_LIVE_CONFIG_GUARD=1 on Linux/macOS lanes (test.yml, all three test jobs) so a suite-produced leak FAILS those runs; Windows lanes stay report-only pending the documented pre-existing USERPROFILE sweep (~190 test sites sandbox HOME alone) — promote once that lands; skipped by GSD_SKIP_LIVE_CONFIG_GUARD=1`
`LIVE-CONFIG.GUARD.SEAM.ci-blind=the AMBIENT-ENV half stays CI-blind — CI never has these vars set, so green CI is not evidence for it; what strict mode catches in CI is the suite's own default-root leaks (HOME/USERPROFILE-derived), the guard remains the only loud signal for ambient-var escapes`
---

View File

@@ -317,9 +317,15 @@ function formatViolations(violations) {
lines.push(` ${label}: ${v.path}`);
}
lines.push('');
// The footer must state the mode the run is ACTUALLY in — a strict-mode
// failure captioned "Reporting only" sends the reader away from the very
// violation that just reddened their run.
lines.push(
'Reporting only. Set GSD_STRICT_LIVE_CONFIG_GUARD=1 to make this fail the run, ' +
'or GSD_SKIP_LIVE_CONFIG_GUARD=1 to skip the check entirely.',
process.env.GSD_STRICT_LIVE_CONFIG_GUARD === '1'
? 'STRICT MODE (GSD_STRICT_LIVE_CONFIG_GUARD=1): these violations fail the run. ' +
'GSD_SKIP_LIVE_CONFIG_GUARD=1 skips the check entirely.'
: 'Reporting only. Set GSD_STRICT_LIVE_CONFIG_GUARD=1 to make this fail the run, ' +
'or GSD_SKIP_LIVE_CONFIG_GUARD=1 to skip the check entirely.',
);
lines.push('');
return lines.join('\n');