# task-2612+2 — _read_json validate→open TOCTOU symlink-swap 봉합 (AUTO_REMEDIATION 자동 수렴)

- **분류**: AUTO_REMEDIATION_HOLD 자동 수렴 (non-Critical HIGH×1 · CRITICAL 0 · critical7 false)
- **판정 정본**: `memory/events/task-2612+1.independent-collector-adjudication.json` verbatim 승계 (H2 CLOSED · H1 원 gap 전부 CLOSED · 잔여 TOCTOU 만)
- **status**: `REMEDIATED_HARDENING_DONE_PENDING_INDEPENDENT_ANU_REAUDIT`
- **spec 무결성**: `sha256(memory/tasks/task-2612+2.md)` = `381bf672…5323e` 일치 확인

## 1. 잔여 HIGH (H1r) — 원인

`anu_v3/auto_remediation_planner.py::_read_json` 이
`_contained_resolved()`(resolve+strict containment) 로 검증한 뒤
별도 `Path.read_text()` 로 재 open → **검증과 open 사이 TOCTOU
symlink-swap 윈도**. CANONICAL_WS_ROOT 내부 쓰기 권한 보유 공격자가
검증 통과 후 경로 구성요소를 ws-밖 symlink 로 교체하면 out-of-scope
read(`/etc/passwd` 등) 가능. (원 `../`·임의 절대·기존 symlink·
prefix-confusion gap 은 task-2612+1 에서 전부 CLOSED — 잔여는 race 만.)

## 2. 교정 (validate→open 원자성 봉합 · defense-in-depth)

단일 진입점 `_read_json` 유지. 1차 정적검증 보존 + 2차 fd 재검증 additive:

1. **1차(정적, 보존)**: `_contained_resolved()` resolve()+strict
   containment — `..`·임의 절대·prefix-confusion·기존 symlink fail-closed.
2. **2차(열린 fd 기준, 신규)**:
   - `os.open(resolved, O_RDONLY|O_NOFOLLOW|O_CLOEXEC)` — 검증 후
     **최종 구성요소** symlink-swap 추종 차단 (ELOOP → fail-closed).
   - `_fd_realpath(fd)` = `os.readlink(/proc/self/fd/<fd>)` — open
     시점 커널이 해소한 **실경로**(중간 구성요소 symlink-swap 까지
     반영, 재 walk 없음 → 추가 TOCTOU 0)가 CANONICAL_WS_ROOT strict
     하위인지 재확인. `" (deleted)"`/NUL → fail-closed.
   - `os.fstat` `S_ISREG` 확인 후 **fd 로만** read.
   - 어떤 이탈도 `PathContainmentError` (fail-closed). fd 누수 방지
     (`os.fdopen(closefd=True)` 소유권 이관 + finally close).

helper 1개(`_fd_realpath`)만 추가. 산출 성격(plan 골격 생성만·실
dispatch 0·실 코드수정 0) 불변 — `os.open/fdopen` 은 plan-only AST
가드 forbidden 집합(`os.system/popen/exec*/spawn*` 등) 비포함, 가드 PASS.

## 3. regression

- baseline: `23 passed` → 교정 후 `31 passed` (기존 23 **전수 무회귀**
  + additive 8).
- additive: (a) final-component symlink-swap-after-check 차단 ·
  intermediate-dir symlink-swap fd-recheck 차단 (b) fd-recheck 경로
  ws-하위 read 무회귀 + legit rel/abs 무회귀 (c) 2604/2605/2609 plan
  변환 byte-0 무회귀×3 (d) plan-only AST 가드 PASS.
- self-check: `--self-check` → all_passed=True · plan_only=True ·
  dispatch_performed=False (2604/2605/2609 실 증거 read-only, mock 0).

## 4. invariant / 무결성

- schema `schemas/auto_remediation_plan.schema.json` byte-0
  (`272a6f41…bc430` 일치).
- H2/`_normalize_expected_rel`/`_expected_files`/skeleton byte-0.
- git HEAD `20456b5f83fc039f2fd6f50f4b94095c29b41bfb` · branch
  `task/task-2553p1-f1-clean-replacement` 전후 EQUAL · 커밋 0
  (ANU Layer-A no-git).
- allowlist 외 write 0 (working tree 의 기타 ??/M 은 사전 존재 ANU
  multitrack 상태 — 본 작업 무변조).

### deliverable sha256

- `anu_v3/auto_remediation_planner.py` =
  `bd266f9180ca055ffe8f2714e704080a17c694ce92b9cdbe653ac0e5601e6f96`
- `tests/regression/test_auto_remediation_planner.py` =
  `7bc4897c6c23b12d49c22d9bba8062fd6106986e88b3495a8c72285e334da204`

## 5. callback / 수렴

normal completion callback 은 **독립 ANU key `c119085addb0f8b7`**
로만 발사. executor self key `7943afbe12c12f7d` 미사용. 본인 collector/
adjudication/write-back/하류 dispatch 수행 0 — 독립 ANU 가 회수·Codex
재audit·adjudication. **+53 durable-success write-back 은 독립 ANU 의
Codex 재audit HIGH/CRITICAL 0 확정 후에만**(본인 수행 금지). 잔여
non-Critical 시 회장 보고 없이 task-2612+3 자동 수렴 계속.
