From 253250580e835e47833084429dbd897423ced72b Mon Sep 17 00:00:00 2001 From: 0xdhx Date: Mon, 3 Aug 2026 03:05:29 -0500 Subject: [PATCH] fix(#2665): wire the live-config guard to strict mode on Linux/macOS CI lanes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- .github/workflows/test.yml | 9 +++++++++ CONTEXT.md | 4 ++-- scripts/live-config-guard.cjs | 10 ++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24d8e8912..7af090cce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/CONTEXT.md b/CONTEXT.md index f4941a753..34d4f7ae8 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -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 /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` --- diff --git a/scripts/live-config-guard.cjs b/scripts/live-config-guard.cjs index 8b645d099..cdfe4d65f 100644 --- a/scripts/live-config-guard.cjs +++ b/scripts/live-config-guard.cjs @@ -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');