# task-2617+2 — post-link recheck inode-bound 화 (reopen-by-name substitution race 폐쇄)

- **분류**: AUTO_REMEDIATION_HOLD 자동 수렴 연속 (non-Critical HIGH·CRITICAL 0·회장 확인 대기 0)
- **Executor**: dev1-team 헤르메스 (key c38fb9955616e24d, 1회 한정)
- **spec sha256**: `98fe0a2f61fce6e05fdd0734c9f35872cd20f956198e14b42af6e2496a8fe53c` (일치 확인)
- **ts**: 2026-05-19 16:21:10 KST

## 1. 잔여 HIGH (task-2617+1 Codex 재audit 확정)

`atomic_guarded_write` 의 post-link 재검증이 **inode-bound 가 아니었음**: `os.link()` 후
fname 을 NAME 으로 재오픈해 reopened fd 의 realpath 만 검증 — `os.link()` 가 생성한 실제
inode 에 bound 되지 않음. link 와 재오픈 사이 fname 이 *다른 in-ws inode* 로 치환되면
realpath containment 만으로는 탐지 불가(reopen-by-name substitution race). CRITICAL 0 ·
신규 Critical7 0 → non-Critical AUTO_REMEDIATION 연속.

## 2. 수정 (inode-bound 화 — 검출 1건 한정)

`anu_v3/cli_output_path_guard.py` `atomic_guarded_write` 만 변경:

1. **사전 anchor 고정**: temp 를 containment-검증된 부모 dir fd(`dfd`) 상대로
   `O_CREAT|O_EXCL|O_NOFOLLOW` 생성한 직후, write fd 에서 `os.fstat` 로 그 inode
   `(st_dev, st_ino)` 를 `trusted_dev_ino` anchor 로 캡처. `os.link(tmp_name, fname)`
   은 *이 inode* 로의 hardlink 이므로 정상 경로의 final inode 는 반드시 anchor 와 동일.
2. **inode-bound 재검증**: post-link 재오픈 fd 를 hold 한 채
   - `inode_bound_ok` = 재오픈 fd `os.fstat (st_dev,st_ino) == trusted_dev_ino`
     (다른 inode 로 치환 시 즉시 False)
   - `containment_ok` = 동일 hold fd 의 `/proc/self/fd` realpath 가
     ws_root/allowed_root 하위 (dir-rename TOCTOU 계속 차단)
   - 둘 중 하나라도 불일치 → final `os.unlink(dir_fd=dfd)` + `SystemExit`(fail-closed).
   재오픈은 fd 획득 수단일 뿐, **보안 판정의 reopen-by-name 의존을 제거**.

기존 통과 경로 동작 불변: 정상 write 는 final == temp inode·realpath ws 안 → 통과.

## 3. regression — 33/33 PASS

- 기존 30 전수 무회귀
- task-2617+1 `test_dir_rename_after_check_toctou_blocked` (containment=False 로 계속 차단)
- **신규** `test_reopen_by_name_substitution_race_blocked`: link 직후 fname 을 다른
  in-ws inode(decoy)로 치환 → `containment=True` 인데도 `inode_bound=False` 로 차단,
  decoy unlink·temp 잔존 0 입증

## 4. 불변식 검증

- **guard import-only**: argparse/main/__main__ 미추가 (AST 테스트 PASS)
- **git Layer-A no-git**: HEAD `20456b5f83fc039f2fd6f50f4b94095c29b41bfb`
  before==after, branch `task/task-2553p1-f1-clean-replacement` 불변, commits 0
  (산출물 untracked `??`)
- **3 sink + policy yaml byte-0**: mtime 13:48–13:49 KST 미변경
- **stdout-only 3건 byte-0**: sha256 == `STDOUT_ONLY_BASELINE`
- **타 track byte-0**: task-2611+2/Track B(2618)/C(2619)·task-2553·2604 multitrack·
  2610~2616 미변경

## 5. write-back gate · callback

- **premature write-back 금지 준수**: +53 durable-success write-back 은 독립 ANU
  collector 의 Codex 재audit HIGH/CRITICAL 0 확인 후에만.
- **callback**: 독립 ANU key `c119085addb0f8b7` 로만 normal completion callback 발사.
  executor self key `c38fb9955616e24d` callback/collector/adjudication/dispatch
  0건(+49 코드 정본).
- **HOLD_FOR_CHAIR=false**: 신규 Critical7 0·shared invariant 파손 0. 잔여
  non-Critical HIGH 시 회장 보고 없이 task-2617+3 자동 수렴 계속.
