# task-2721+2 — PR #167 P0-b bounded medium fix: path-traversal 방어 + fail-safe 예외 보강 (MEDIUM 5건)

## 회장 인가 (2026-06-02, bounded medium auto-fix)
PR #167(head `98cb8a2a`) fresh HIGH 0 / MEDIUM 5, 전부 expected_files 내부·forbidden 0·ANU key net-new 0·active=false → **bounded medium auto-fix 대상**(chair 승인 없이 자동 수렴 구간). task_id path traversal = security-medium, dismiss 금지·실제 결함으로 수정.

## worktree
- `/home/jay/workspace/.worktrees/task-2721-dev1` (branch `task/task-2721-dev1`, head `98cb8a2a`)에서 **이어서**. base origin/main 29175f80(P0-a 포함).

## expected_files (PR #167 동일 6개 — 그 외 수정 금지)
1. `deploy/systemd/anu-pickup.path`
2. `deploy/systemd/anu-pickup.service`
3. `scripts/anu_pickup_entrypoint.sh`
4. `dispatch/anu_pickup_driver.py`  ← 주 수정(medium 4건)
5. `tests/regression/test_anu_pickup_driver_2721.py`
6. `docs/p0b_driver_runbook_260601.md`
★ `scripts/finish-task.sh` 수정 금지. P0-a main(`anu_result_pickup_runner.py`/`anu_owned_callback_enforcement.py`) 무수정.

## 수정 1 — task_id path traversal 방어 (MEDIUM driver:274, 보안 실제 결함)
- `task_id = result.get("task_id")` 가 `done_path = os.path.join(result_dir, f"{task_id}.pickup.done")` 에 직접 사용됨.
- ★ **schema 단계(조건 3, owner proof 전)에서** task_id 에 경로 탐색 문자(`/`, `\`, `..`, 또는 절대경로/path separator) 포함 시 **`schema_fail` quarantine**.
  - 권장: `task_id` 가 `os.path.basename(task_id) == task_id` 이고 `..` 미포함이며 `/`·`\` 미포함인지 검증. 위반 → `_quarantine("schema_fail")`.
- 검증을 owner proof 전에 둬서 신뢰경계 통과 전 차단(defense-in-depth).

## 수정 2 — driver fail-safe 예외 범위 보강 (MEDIUM driver:110/152/192)
- ★ driver 는 어떤 입력에도 **크래시 없이 no-op/quarantine** 해야 함.
- :110 activation flag 읽기: `except OSError` → **`except (OSError, ValueError)`** (UnicodeDecodeError 는 ValueError 계열). flag 읽기 실패 = fail-closed no-op.
- :152 quarantine `shutil.move`: `except OSError` → **`except (OSError, shutil.Error)`** (shutil.Error 는 OSError 미상속).
- :192 ledger 읽기: `except OSError` → **`except (OSError, ValueError)`** (UnicodeDecodeError 대응). 읽기 실패 = dedupe 미확인 보수 처리(기존 동작 유지).
- 의미/동작 변경 없이 **예외 포착 범위만** 확대(fail-safe). 정상 경로 회귀 0.

## 수정 3 — ExecStart 견고성 (MEDIUM service:8)
- `deploy/systemd/anu-pickup.service` `ExecStart=%h/workspace/scripts/anu_pickup_entrypoint.sh`
  → **`ExecStart=/bin/bash %h/workspace/scripts/anu_pickup_entrypoint.sh`** (executable bit 없는 환경에서도 동작).
- ★ 설치/enable 0 (파일만 수정). active=false 유지.

## 수정 4 — active=false 유지
- installed=false / wired=false / active=false. capability matrix 과장 금지.

## regression (추가/갱신)
- 기존 13 PASS 유지/증가.
- ★ **path traversal**: task_id 에 `..`/`/`/`\` 포함 result.json → `schema_fail` quarantine + pickup_once 미호출(mock) + done_path 가 result_dir 밖 미생성.
- ★ **fail-safe 예외**: flag/ledger invalid UTF-8 → 크래시 0(no-op/보수처리). quarantine 중 shutil.Error → 크래시 0.
- default disabled no-op / scan 한정 / 출력물 watched 밖 / 기존 quarantine 6조건 PASS 유지.
- pickup_once 전부 mock/stub. 실 pickup/wake/ANU key wake 실행 0. key literal 0.

## 검증 (finalize 전)
- `python3 -m pytest tests/regression/test_anu_pickup_driver_2721.py -q` 전 PASS(13→증가).
- `git diff --name-only origin/main` = 6파일. finish-task.sh 0. ANU key net-new 0.
- task_id 검증 로직 존재(`..`/separator). `except (OSError, ValueError)`/`shutil.Error` 반영. ExecStart `/bin/bash` 반영.

## 금지 (회장 verbatim)
- finish-task.sh 수정 금지 · systemctl enable/start/daemon-reload 금지 · unit 설치 금지 · activation flag 생성 금지.
- 실 pickup/wake/ANU key wake 실행 금지 · merge 금지 · force push/rebase/admin override 금지.
- ANU key literal 노출 0. pickup_once lock 재도입 금지. P0-a main 무수정. expected_files 밖 수정 금지.
- ★ 새 HIGH 발생 / expected_files 밖 수정 필요 / 권한확장 / forbidden path / 동일 blocker 반복 시 → 추가 fix 금지, **CHAIR_REQUIRED 보고**.

## finalize (로컬 commit 까지만)
1. fix → regression PASS → `git add` 6파일 → 로컬 commit. **push/PR/merge 금지(ANU 수행)**.
2. `memory/reports/task-2721.md` 에 bounded medium fix 내역(path-traversal/예외 보강 3/ExecStart) 추가.
3. `memory/events/task-2721+2.done` 생성. ANU key(c119085, sealed) callback. ★ ANU key literal 노출 0.

## ANU 후속 (봇 아님 — governance)
독립 재검증(6파일/path-traversal quarantine/예외 보강/ExecStart/regression/key 0/active=false) → FF push → OWNER `/gemini review` 1회 → CI GREEN + fresh unresolved 0 → **MERGE_READY_CANDIDATE** 보고 / 새 HIGH·동일 blocker 반복 시 CHAIR_REQUIRED. merge 회장 승인 전 금지.

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "deploy/systemd/anu-pickup.path"
    - "deploy/systemd/anu-pickup.service"
    - "scripts/anu_pickup_entrypoint.sh"
    - "dispatch/anu_pickup_driver.py"
    - "tests/regression/test_anu_pickup_driver_2721.py"
    - "docs/p0b_driver_runbook_260601.md"
    - "memory/events/task-2721+2.done"
    - "memory/reports/task-2721.md"
  forbidden_paths:
    - "scripts/finish-task.sh"
    - "dispatch/anu_result_pickup_runner.py"
    - "dispatch/anu_owned_callback_enforcement.py"
    - ".github/**"
  commands:
    - "pytest"
    - "python3 -m pytest"
    - "python3 -m py_compile"
  merge_policy: "none"
  ttl_hours: 48
```

## ★ dispatch 템플릿 G3(PR/머지) 무시. finalize 로컬 commit only. finish-task --action pr/systemctl/설치 일체 금지.

## goal_assertions (auto-generated)
- `python3 -m pytest tests/regression/test_anu_pickup_driver_2721.py -q`
- `python3 -c "import sys; s=open('dispatch/anu_pickup_driver.py').read(); sys.exit(0 if ('shutil.Error' in s and 'ValueError' in s) else 1)"`
- `python3 -c "import sys; s=open('deploy/systemd/anu-pickup.service').read(); sys.exit(0 if '/bin/bash' in s else 1)"`