Files
stack/docs/scratchpads/fcm-m2-001-generated-env-boundary.md
Jarvis 18b7ab96e7
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
feat(fleet): enforce generated environment boundary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 03:30:59 -05:00

111 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FCM-M2-001 — Generated Environment Boundary
- **Issue/card:** #758 / FCM-M2-001
- **Branch/base:** `feat/758-generated-env-boundary` from `origin/main` `e9c4aa3e8b3780719cd5a43c0ef3f37fc70de666`
- **Budget assumption:** 30K-card budget; implement only the deterministic generated/local environment boundary and its launch-chain/docs/tests.
## Objective
Replace the generic fleet agent `.env` authority/merge path with a deterministic roster-derived `<agent>.env.generated` projection and strict, data-only `<agent>.env.local`. The roster remains the desired-state authority. Reject bad input before the launcher creates a tmux session; never print sensitive or privileged-command values.
## Scope and non-goals
- In scope: deterministic render/write, strict generated/local parse rules, legacy `.env` disposition/quarantine, systemd/launcher boundary, permission/path checks, focused fail-closed tests, operator/reference documentation, USC interface evidence.
- Excluded: roster CRUD/mutation, v2 roster schema changes, lifecycle/reconcile/apply behavior, migration/canary rollout, connectors, remote surfaces, live-fleet actions, and M2-002.
## Plan
1. Add red tests for generated-key shadowing, malformed/duplicate/unknown/command/sensitive input, no-value diagnostics, deterministic/idempotent projection, secure file modes, and legacy disposition.
2. Implement a pure strict environment contract plus atomic projection/quarantine helper.
3. Replace the generic `.env` writer/merge path and systemd reference with `.env.generated` + `.env.local` ownership.
4. Make the shell launcher parse the files without `source`/`eval`, reject unsafe input before tmux creation, and construct only the roster-derived runtime command.
5. Add operator/reference documentation with the requested USC M1 interface evidence and M2M4 gate statement.
6. Run focused/package/root gates and audit the USC interface packet. Per continuation scope, stop before review, commit, push, PR, or live mutation.
## Initial evidence
- No existing owner: target worktree path absent; no target local/remote branch; `pr-list.sh -s open` returned no open PRs.
- M1 compiler/API/docs and executable disposition evidence are present at the assigned base.
- Existing launch chain writes `fleet/agents/<agent>.env`, preserves arbitrary legacy lines via `mergeAgentEnv`, sources `MOSAIC_AGENT_COMMAND`, and executes it through `bash -c`; all are M2 remediation targets.
- `~/.config/mosaic/guides/SECURITY.md` is absent. Read the available security-review role contract and the vault/secrets guide instead.
## Verification log
### Continuation (2026-07-14)
- Preserved the inherited 14-file delta; no reset, stash, rebase, roster mutation, lifecycle action,
live-fleet action, commit, push, or PR action was performed.
- Focused gates passed:
- `pnpm --dir packages/mosaic test -- src/fleet/generated-env-boundary.spec.ts` — 1 file, 10 tests passed.
- `bash packages/mosaic/framework/tools/fleet/test-start-agent-session.sh` — passed.
- `bash packages/mosaic/framework/systemd/user/test-fleet-units.sh` — passed.
- `pnpm --dir packages/mosaic test -- src/commands/fleet.spec.ts` — 1 file, 192 tests passed.
- Package gates passed before final documentation/format follow-up:
- `pnpm --dir packages/mosaic typecheck` — passed.
- `pnpm --dir packages/mosaic lint` — passed.
- `pnpm --dir packages/mosaic test` — 52 files, 752 tests passed.
- `pnpm format:check` initially failed only for the new boundary reference and generated-boundary
TypeScript files; targeted Prettier normalization was applied. A final `pnpm format:check` passed.
- USC packet audit: the M1 structural compiler is `parseRosterV2` with roster `version: 2`; the
semantic resolver is `validateRosterV2Semantics`; disposition artifacts retain `version: 1` fixture
evidence. `docs/TASKS.md` records M1-001 done, M1-002 in-progress, and M1-003 not-started; no
product release version is claimed. The packet now distinguishes these statuses from checkout
artifact presence and states the M2 → M3 → M4 downstream gates.
## Review remediation (2026-07-14)
- **Blocker 1 red-first:** Added a launcher reproducer with a `0777` `fleet/agents` parent and a private generated file. Before implementation, `bash packages/mosaic/framework/tools/fleet/test-start-agent-session.sh` failed: `FAIL: generated file under a world-writable parent was accepted`. The failure occurred after the launch path reached fake tmux, proving the parent was not validated.
- **Blocker 2 red-first:** Added a `symlink()` projection-directory reproducer that preloads generated/local/quarantine/legacy target files and asserts no target mutation. Before implementation, `pnpm --dir packages/mosaic test -- src/fleet/generated-env-boundary.spec.ts` failed the new test because the existing writer followed the `agentEnvDir` symlink and parsed its target legacy input (`expected /unsafe-directory/i`, received `code=malformed-line`). The initial test-only missing `mkdir` import was corrected before recording this behavior failure.
- **Blocker 3 red-first:** Added fresh/stale/absent native-heartbeat regression coverage. Before implementation, an isolated fake-tmux launcher reproducer with a fresh `<agent>.hb.native` marker failed `FAIL: fresh native heartbeat was overwritten`; the existing sidecar immediately replaced native `status=busy`/`model` content.
- **Remediation result:** The launcher now rejects a group/world-accessible or symlinked `fleet/agents` parent before an environment read or tmux call. The projection writer uses `lstat` before chmod/write processing and rejects a symlinked directory without creating generated/local/quarantine files or deleting legacy input. The heartbeat sidecar defers to a fresh non-symlink native marker and falls back when stale/absent. Focused green evidence before independent review: `bash packages/mosaic/framework/tools/fleet/test-start-agent-session.sh` and `pnpm --dir packages/mosaic test -- src/fleet/generated-env-boundary.spec.ts` (11 tests) passed.
- **Independent-review follow-up red-first:** Codex code review returned one blocker and security review one medium CWE-732 finding: the writer repaired an already `0777` directory with `chmod` before trusting its contents. Added a reproducer with a safe local file beneath an existing `0777` directory. Before the follow-up fix, `pnpm --dir packages/mosaic test -- src/fleet/generated-env-boundary.spec.ts` failed because the promise resolved and wrote `coder0.env.generated` instead of rejecting.
- **Independent-review remediation:** Existing directories now pass non-following private-directory validation before any read or chmod; only a directory created in this call is normalized to `0700`. The fleet-add test fixture now creates its simulated trusted `fleet/agents` boundary at `0700`; this corrects fixture setup to match the new required contract rather than weakening the rejection assertion. Focused reruns passed: generated-boundary 12 tests, launcher boundary suite, and fleet suite 192 tests.
- **Final verification before re-review:** Launcher + systemd suites passed; package suite passed (52 files, 754 tests); package lint/typecheck, root typecheck (42 tasks), format check, and diff check passed. The rerun code review still reports a tmux command-arity blocker, and the security rerun reports systemd `EnvironmentFile` pre-validation injection findings for both agent units. These were discovered after the specified three-remediation scope; no additional source changes were made. Independent review therefore remains `REQUEST CHANGES` despite the requested three fixes passing their behavioral suites.
## Systemd pre-validation remediation (2026-07-14)
- **Red-first:** Updated the fleet unit contract to reject any `EnvironmentFile=` projection preload, require a cleared bootstrap environment, and require a validated exact-stop path. Before implementation, `bash packages/mosaic/framework/systemd/user/test-fleet-units.sh` failed: `FAIL: agent units must not preload projections before strict parsing`.
- **Red-first parser/stop coverage:** Added interaction-wrapper and exact-stop cases to the launcher boundary suite. Before implementation, `bash packages/mosaic/framework/tools/fleet/test-start-agent-session.sh` failed: `FAIL: interaction did not use shared strict parser first`, because the interaction wrapper consumed inherited environment before projection validation.
- **Focused green:** Both unit templates now use `env -i` with fixed `HOME`, agent instance, and PATH; neither has `Environment=`/`EnvironmentFile=`. The interaction wrapper delegates to `start-agent-session.sh --interaction`, so strict generated/local parsing precedes pinned Pi profile checks. `--stop` reuses the strict generated parser before exact `=<agent>` socket/session termination. Passed: systemd unit suite, launcher boundary suite (including malformed interaction, pinned profile, and ambient-socket stop cases), and 210 focused TypeScript tests.
- **Final verification:** `pnpm --dir packages/mosaic test` passed (52 files, 754 tests); package lint/typecheck, root typecheck (42 tasks), format/diff, and shell syntax checks passed. Security review passed with no findings. Code review repeated the previously refuted tmux argv concern and a pre-existing Claude trust-lock suggestion; per the assigned narrow follow-up, no tmux or unrelated trust-path change was made.
## Risks and next review
- This card is uncommitted and unreleased. The canonical tracker still records its dependencies as
M1-002 in progress and M1-003 not started; this continuation does not reinterpret those task states.
- Final post-documentation checks passed: `pnpm --dir packages/mosaic typecheck`,
`pnpm --dir packages/mosaic lint`, `pnpm --dir packages/mosaic test` (52 files, 752 tests),
`pnpm typecheck` (42 Turbo tasks), and `pnpm format:check`.
- Obtain independent code and security review of the complete delta next. Do not run commit, push,
PR, or live-fleet commands in this continuation.
## Fresh-install directory remediation (2026-07-14)
- **Objective:** Remediate only the fresh-install path where `installFleet` created `fleet/agents`
with host-umask permissions before the boundary writer correctly rejected it.
- **Plan:** Add a real `fleet install --no-enable` integration reproducer; prove red; let the
existing boundary writer own directory creation; run focused and full gates. No commit, push,
PR, review disposition, or live-fleet action.
- **Red evidence:** Before the one-line remediation,
`pnpm --dir packages/mosaic test -- src/commands/fleet.spec.ts` failed the new test with
`AgentEnvBoundaryError: code=unsafe-permissions` at `ensurePrivateProjectionDirectory`, after
`installFleet` pre-created the directory.
- **Change:** Removed only the recursive `mkdir(activePaths.agentEnvDir)` in `installFleet`.
`writeAgentEnvironmentProjection` remains the sole creator and retains its existing `lstat`,
private-directory, symlink, and existing-unsafe-directory fail-closed checks.
- **Focused green:** `pnpm --dir packages/mosaic test -- src/commands/fleet.spec.ts` — 193 tests
passed. The new integration executes a fresh `fleet install --no-enable`, asserts a real
non-symlink `0700` directory and a `0600` generated projection. Existing unsafe-directory
coverage remains in `generated-env-boundary.spec.ts` and asserts no chmod repair/no generated
file write.
- **Full gates green:** generated-boundary 12 tests; launcher and systemd suites; package
typecheck/lint and 52 files / 755 tests; root typecheck (42 tasks), lint, format, diff check,
and root test (42 tasks) all passed.
- **Independent review:** The complete inherited uncommitted delta still has Codex `REQUEST CHANGES`
findings outside this narrow fix (tmux command arity and Claude trust-lock regression), plus a
security-review medium finding on unvalidated writable ancestor directories. No out-of-scope
source changes were made.
- **Risk:** The writer's existing create-then-validate sequence is relied on for the creation
boundary; a concurrent substitution causes fail-closed validation rather than repair. The
review findings above remain residual risks for the complete card delta.