Skip to content

fix(core,cli): 원자적 쓰기 공용 경계로 통합해 skill 심볼릭 링크 관통 차단 - #82

Merged
ictechgy merged 1 commit into
mainfrom
fix/atomic-write-hardening
Sep 13, 2026
Merged

ictechgy merged 1 commit into
mainfrom
fix/atomic-write-hardening

Conversation

@ictechgy

Copy link
Copy Markdown
Owner

배경

전체 개선 검토(보안·성능·구조·기능·사용성)에서 확인된 최상위 보안 지적이다.

  • skill --installFile.writeAsString으로 대상에 직접 썼다. writeAsString은 심볼릭 링크를 따라가므로, 신뢰할 수 없는 체크아웃이 <dir>/dartograph/SKILL.md를 git 심볼릭 링크로 심어둔 경우 링크 대상(체크아웃 밖 경로 포함)이 그대로 덮여썼진다. 링크 대상이 없으면 --force 없이도 발생했다.
  • initBaselineStore.write의 임시 파일 이름은 .tmp.$pid로 예측 가능하고, 기존 내용물(심어둔 링크 포함)을 절단·관통했다. init만 PR #80의 rename 교체 정책이 있었고 baseline은 finally 정리, skill은 정책 자체가 없는 3박자였다.

변경

  • lib/src/core/atomic_write.dart 신설: PID+무작위 접미사(16 hex) 임시 이름, createSync(exclusive: true)로 배타적 생성(그 자리에 무엇이 있으면 실패 — 심어둔 링크 절단·관통 불가), rename 교체(대상 자리의 링크는 링크 자체를 교체), 실패 시 임시 정리는 best-effort로 원래 예외를 보존. 보장 경계(POSIX 전용 속성, 적극적 race 범위 밖, 부모 디렉터리 미생성)를 클래스 문서로 명시.
  • init·skill·BaselineStore.write를 공용 경계로 이관해 정책 일원화. 종료 코드·payload 바이트 계약 불변.
  • skill 충돌 가드를 init과 정렬: Link(...).exists()를 추가해 매달린 링크도 --force 없이는 조용히 교체되지 않게 한다(init은 이미 Link.existsSync()로 이를 검사).

리뷰 반영 (GLM packet-review, 차단 0건)

  • cleanup 이중 실패가 원래 예외를 가리는 문제 → best-effort 정리로 수정
  • 접미사 엔트로피 8→16 hex, Random.secure static hoist
  • 배타적 생성(헤드라인 보장)의 실패-close 회귀 테스트 추가(접미사 주입 파라미터)
  • 대상 자리 매달린 링크 교체 단위 테스트, skill/init CLI symlink 회귀 테스트
  • 기각: NAME_MAX 인접 경로(기존 패턴·영향 낮음), 디렉터리 fsync(CLI 무관), rename 전 링크 재검사(경쟁 조건 하에서 의미 없음)

검증

  • dart format clean, dart analyze clean, 전체 테스트 304개 통과(신규 6개 포함)
  • tool/verify-false-positive-corpus.sh 통과, tool/verify-cli-contract.sh 통과
  • dart pub publish --dry-run 경고 0(clean git)
  • 커버리지 97.04%(≥90 게이트)

init은 PR #80에서 임시 파일+rename 교체로 심볼릭 링크 대상 오염을 막았지만
skill --install은 writeAsString이 링크를 따라가 신뢰할 수 없는 디렉터리의
링크 대상 파일(체크아웃 밖 경로 포함)을 그대로 덮어썼다. baseline 임시
이름도 PID만으로 예측 가능해 미리 심어둔 링크를 절단·관통할 수 있었다.

- core/atomic_write.dart 신설: PID+무작위 접미사 임시 이름, 배타적 생성
  (이미 존재하면 실패 — 심어둔 링크 관통 불가), rename 교체(대상 자리
  링크는 링크 자체를 교체), 실패 시 임시 파일 정리
- init·skill·BaselineStore.write를 공용 경계로 이관해 정책 일원화
- skill 매달린 링크(--force 불필요 케이스)와 살아 있는 링크(--force)에
  대한 CLI 회귀, init 링크 교체 회귀, AtomicWrite 단위 테스트 추가
@ictechgy
ictechgy merged commit e40e8c3 into main Sep 13, 2026
2 checks passed
ictechgy added a commit that referenced this pull request Sep 13, 2026
docs: HANDOFF 전체 개선 검토 반영 기록 — PR #82~#85 결과 전사
ictechgy pushed a commit that referenced this pull request Sep 13, 2026
이번 세션 기록을 최신 상태로 닫는다 — main cf8a029, 미릴리스 누적
5건(#80, #82~#84, #87), 테스트 308개·커버리지 97.00%. 리뷰 조건부
항목(bridge_index pubspec 읽기)의 실측 기각 근거를 함께 남긴다.
ictechgy pushed a commit that referenced this pull request Sep 13, 2026
#80(init 명령)·#82(원자적 쓰기 경화)·#83(진단 메시지·help)·#84(RegExp
hoist·메모)·#87(YAML 설정 읽기 상한)을 0.8.0으로 발행한다. 새 CLI 표면이
있으므로 semver minor다. 버전 정합 6곳과 CHANGELOG 두 언어를 맞췄다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant