Files
stack/docs/scratchpads/fcm-m2-001-generated-env-boundary.md
jason.woltje 191efaefeb
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
feat(fleet): enforce generated environment boundary (#772)
2026-07-15 08:40:32 +00:00

12 KiB
Raw Blame History

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.