Pre-tool-use hook cannot learn the target at a multi-target root, so the write guard stays permissive there [capture] #575

Closed
opened 2026-09-14 14:16:07 +00:00 by jared · 1 comment
Owner

Context

#571 made ScopePolicy.load resolve the project from cwd via Project.load(target: ENV["OS_SDLC_TARGET"]), rescuing only TargetSelectionError to a nil test_path (ADR-0178). At the cc-os root, .sdlc/project.yaml defines five named targets. The hook process inherits the Claude Code session env, and OS_SDLC_TARGET is unset in a normal session (verified 2026-09-14). So at this root the guard still allows a test-writer to write production code.

VERDICT: CREATE
REASON: Reproduced security gap — write guard is provably permissive at multi-target roots without user env export, matching a recorded ADR-0178 consequence, not a hardening speculation; this is the root (design gap, not another symptom of #572-574).

Observed

cd /home/jared/dev/cc-os && printf '%s' '{"hook_event_name":"PreToolUse","agent_type":"test-writer","tool_name":"Write","tool_input":{"file_path":"plugins/os-sdlc/lib/os_sdlc/x.rb"}}' | ruby plugins/os-sdlc/hooks/pre_tool_use.rb

Prints nothing (allow). With OS_SDLC_TARGET=os-sdlc exported the same event prints a deny.

Reproduce

cd /home/jared/dev/cc-os && printf '%s' '{"hook_event_name":"PreToolUse","agent_type":"test-writer","tool_name":"Write","tool_input":{"file_path":"plugins/os-sdlc/lib/os_sdlc/x.rb"}}' | ruby plugins/os-sdlc/hooks/pre_tool_use.rb

Expected

The hook denies the write at a multi-target root during a pipeline run without the user exporting an env var. Candidate sources for the target: the open implementation row in .sdlc/pipeline.db (the runner already knows the target per ADR-0169), or the runner exporting OS_SDLC_TARGET into agent env at dispatch.

Origin

  • Trigger: hand verification after /os-sdlc:implement 571, 2026-09-14
  • Improvised this session: none; the limit is recorded in ADR-0178 Consequences
  • Chain: guard permissive at multi-target root ← hook has no target ← target lives only in CLI flag/env (DESIGN, ADR-0088)
  • Root candidate: this ticket
  • Where: plugins/os-sdlc/lib/os_sdlc/scope_policy.rb, plugins/os-sdlc/hooks/pre_tool_use.rb
  • Session: 23a382aa-f78e-495f-9cbd-0934e163b37d
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/23a382aa-f78e-495f-9cbd-0934e163b37d.jsonl
## Context #571 made `ScopePolicy.load` resolve the project from cwd via `Project.load(target: ENV["OS_SDLC_TARGET"])`, rescuing only `TargetSelectionError` to a nil `test_path` (ADR-0178). At the cc-os root, `.sdlc/project.yaml` defines five named targets. The hook process inherits the Claude Code session env, and `OS_SDLC_TARGET` is unset in a normal session (verified 2026-09-14). So at this root the guard still allows a test-writer to write production code. VERDICT: CREATE REASON: Reproduced security gap — write guard is provably permissive at multi-target roots without user env export, matching a recorded ADR-0178 consequence, not a hardening speculation; this is the root (design gap, not another symptom of #572-574). ## Observed ``` cd /home/jared/dev/cc-os && printf '%s' '{"hook_event_name":"PreToolUse","agent_type":"test-writer","tool_name":"Write","tool_input":{"file_path":"plugins/os-sdlc/lib/os_sdlc/x.rb"}}' | ruby plugins/os-sdlc/hooks/pre_tool_use.rb ``` Prints nothing (allow). With `OS_SDLC_TARGET=os-sdlc` exported the same event prints a deny. ## Reproduce ``` cd /home/jared/dev/cc-os && printf '%s' '{"hook_event_name":"PreToolUse","agent_type":"test-writer","tool_name":"Write","tool_input":{"file_path":"plugins/os-sdlc/lib/os_sdlc/x.rb"}}' | ruby plugins/os-sdlc/hooks/pre_tool_use.rb ``` ## Expected The hook denies the write at a multi-target root during a pipeline run without the user exporting an env var. Candidate sources for the target: the open implementation row in `.sdlc/pipeline.db` (the runner already knows the target per ADR-0169), or the runner exporting `OS_SDLC_TARGET` into agent env at dispatch. ## Origin - Trigger: hand verification after /os-sdlc:implement 571, 2026-09-14 - Improvised this session: none; the limit is recorded in ADR-0178 Consequences - Chain: guard permissive at multi-target root ← hook has no target ← target lives only in CLI flag/env (DESIGN, ADR-0088) - Root candidate: this ticket - Where: plugins/os-sdlc/lib/os_sdlc/scope_policy.rb, plugins/os-sdlc/hooks/pre_tool_use.rb - Session: 23a382aa-f78e-495f-9cbd-0934e163b37d - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/23a382aa-f78e-495f-9cbd-0934e163b37d.jsonl
Author
Owner

Resolution

Done: Won't-fix by deletion: the PreToolUse write guard is removed (hook, WriteGuard, BashGuard, ScopePolicy, six tests, hooks.json entry). The OS_SDLC_TARGET read that #571 added to ScopePolicy.load is gone with the file.

Evidence: Transcript search of every session and subagent file since 2026-08-18: zero live denials, only self-tests. One run failure caused by the guard (#528 impl 8). Merged to main 4a5e0c3, ADR-0182 supersedes ADR-0126/0156, voids ADR-0138 guard clauses and the ADR-0178 ScopePolicy clause. Suite 1159 runs, 0 failures. Plugin cache refreshed.

Follow-ups: none

## Resolution **Done:** Won't-fix by deletion: the PreToolUse write guard is removed (hook, WriteGuard, BashGuard, ScopePolicy, six tests, hooks.json entry). The OS_SDLC_TARGET read that #571 added to ScopePolicy.load is gone with the file. **Evidence:** Transcript search of every session and subagent file since 2026-08-18: zero live denials, only self-tests. One run failure caused by the guard (#528 impl 8). Merged to main 4a5e0c3, ADR-0182 supersedes ADR-0126/0156, voids ADR-0138 guard clauses and the ADR-0178 ScopePolicy clause. Suite 1159 runs, 0 failures. Plugin cache refreshed. **Follow-ups:** none
jared closed this issue 2026-09-14 16:52:55 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#575
No description provided.