Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions src/backends/shared/envFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*/

import {
FRICTION_SIDECAR_ENV_VAR,
PM_WRITE_SIDECAR_ENV_VAR,
PR_SIDECAR_ENV_VAR,
PUSHED_CHANGES_SIDECAR_ENV_VAR,
REVIEW_SIDECAR_ENV_VAR,
Expand Down Expand Up @@ -57,9 +59,19 @@ export const SHARED_ALLOWED_ENV_EXACT = new Set([

// GitHub ack comment ID for subprocess deletion after PR review
GITHUB_ACK_COMMENT_ID_ENV_VAR,
// Sidecar paths — written by cascade-tools CLI subprocesses, read by the
// adapter to satisfy `requiresPR / requiresReview / requiresPushedChanges /
// requiresPMWrite` completion gates. Every constant in
// `src/gadgets/sessionState.ts` must be listed here; the regression test
// `tests/unit/backends/shared-envFilter.test.ts` enforces it. Prod
// incidents MNG-741 / MNG-736 / MNG-739 (2026-05-12) failed every planning
// run because PM_WRITE_SIDECAR_ENV_VAR was silently filtered out before
// reaching the agent subprocess.
PR_SIDECAR_ENV_VAR,
PUSHED_CHANGES_SIDECAR_ENV_VAR,
REVIEW_SIDECAR_ENV_VAR,
PM_WRITE_SIDECAR_ENV_VAR,
FRICTION_SIDECAR_ENV_VAR,

// Node
'NODE_PATH',
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/backends/shared-envFilter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import {
SHARED_ALLOWED_ENV_PREFIXES,
SHARED_BLOCKED_ENV_EXACT,
} from '../../../src/backends/shared/envFilter.js';
import {
FRICTION_SIDECAR_ENV_VAR,
PM_WRITE_SIDECAR_ENV_VAR,
PR_SIDECAR_ENV_VAR,
PUSHED_CHANGES_SIDECAR_ENV_VAR,
REVIEW_SIDECAR_ENV_VAR,
} from '../../../src/gadgets/sessionState.js';

describe('filterProcessEnv (shared)', () => {
it('passes through exact-match shared allowed vars', () => {
Expand Down Expand Up @@ -170,6 +177,36 @@ describe('SHARED_ALLOWED_ENV_EXACT', () => {
const result = filterProcessEnv({ [GITHUB_ACK_COMMENT_ID_ENV_VAR]: '12345' });
expect(result[GITHUB_ACK_COMMENT_ID_ENV_VAR]).toBe('12345');
});

// Regression net for prod incidents MNG-741 / MNG-736 / MNG-739 (2026-05-12):
// `sidecarManager` injected `CASCADE_PM_WRITE_SIDECAR_PATH` into projectSecrets
// but the allowlist here dropped it on the way to the subprocess, so the
// agent's `cascade-tools pm add-checklist` call never wrote the sidecar and
// every planning run failed the `requiresPMWrite` gate. Same drift hazard
// for any future sidecar env var. This block iterates every sidecar constant
// defined in `src/gadgets/sessionState.ts` and asserts each is allowlisted
// AND survives `filterProcessEnv` round-trip — so a new sidecar can't ship
// without its env var being plumbed end-to-end.
describe('sidecar env-var allowlist invariant (MNG-741 regression)', () => {
const sidecarEnvVars = [
{ name: 'PM_WRITE_SIDECAR_ENV_VAR', value: PM_WRITE_SIDECAR_ENV_VAR },
{ name: 'PR_SIDECAR_ENV_VAR', value: PR_SIDECAR_ENV_VAR },
{ name: 'PUSHED_CHANGES_SIDECAR_ENV_VAR', value: PUSHED_CHANGES_SIDECAR_ENV_VAR },
{ name: 'REVIEW_SIDECAR_ENV_VAR', value: REVIEW_SIDECAR_ENV_VAR },
{ name: 'FRICTION_SIDECAR_ENV_VAR', value: FRICTION_SIDECAR_ENV_VAR },
] as const;

for (const { name, value } of sidecarEnvVars) {
it(`${name} (${value}) is in SHARED_ALLOWED_ENV_EXACT`, () => {
expect(SHARED_ALLOWED_ENV_EXACT.has(value)).toBe(true);
});

it(`${name} survives filterProcessEnv round-trip`, () => {
const result = filterProcessEnv({ [value]: '/tmp/sidecar-xyz.json' });
expect(result[value]).toBe('/tmp/sidecar-xyz.json');
});
}
});
});

describe('SHARED_ALLOWED_ENV_PREFIXES', () => {
Expand Down
Loading