Skip to content

fix(wiki): knowledge-wiki 후속 6건 — G1 오탐 · 커서 분리 · push 경계 · 훅 미탐 - #41

Merged
Hyeongseob91 merged 2 commits into
mainfrom
fix/knowledge-wiki-followups
Aug 4, 2026
Merged

fix(wiki): knowledge-wiki 후속 6건 — G1 오탐 · 커서 분리 · push 경계 · 훅 미탐#41
Hyeongseob91 merged 2 commits into
mainfrom
fix/knowledge-wiki-followups

Conversation

@Hyeongseob91

Copy link
Copy Markdown
Contributor

Summary

#34 리뷰에서 후속으로 넘긴 6건을 처리한다. 두 갈래다 — knowledge-wiki 의 경계 조정(#35 #36 #38 #39 #40)과
위험 명령 훅의 미탐(#37).

관통하는 문제는 하나다. 경계가 잘못된 지점에 그어져 있었다. 너무 앞(G1이 원문을 반출물 기준으로 검사),
너무 넓게(커서가 프로젝트 공용), 또는 코드가 아니라 주석으로(push 범위).

무엇이 바뀌나

#35 — G1을 입력 계층으로 좁혔다

deny 패턴을 두 계층으로 나눴다.

계층 대상 항목 위반 시
입력 (G1) transcript 원문 D1 자격증명 + 주민번호 세션 폐기
출력 (G4) 컴파일된 article D1~D8 전체 세션 폐기

원문은 기계 밖으로 나가지 않는다. 반출되는 건 G2가 새로 쓴 article이고 그건 G4가 같은 강도로 검사한다.
"대화 중에 파일 경로를 언급했다"는 이유로 세션을 버리면 보호는 그대로인데 회수만 잃는다.

D1을 G1에 남기는 근거는 다르다 — 시크릿은 모델에 넣는 순간 산출물에서 되살아날 수 있고,
시크릿이 담긴 세션은 드물어 폐기 비용이 낮다.

패턴도 정밀화했다.

사설 IP  옥텟 4개 강제   "macOS 10.15.7" · "Kafka 10.2.1" 이 사설 IP로 잡히던 문제
이메일   서비스 계정 제외  git@ · noreply@ · no-reply@ · admin@ · root@

git@github.com 오탐은 특히 나빴다. SSH remote를 언급한 세션이 전부 죽었고,
SKILL.md 자체가 git@github.com:myteam/team-wiki.git 를 담고 있어서
이 문서를 읽은 세션은 자기 자신 때문에 폐기됐다.

실측 — 최근 세션 30건에 적용:

구 G1 새 G1
전체 30건 10건 폐기 0건
긴 세션(10k자+) 16건 8건 폐기 0건

폐기 사유는 전부 D6 홈 절대경로(8) · D2 이메일(2) · D4 내부 호스트명(1)이었다.

#36 · #38 — 커서를 transcript 단위로, 상태를 위키 밖으로

커서가 wiki/<subdir>/.transcript_cursor 하나였다. 기본 설정에선 모든 repo가 같은 per-user/ 를 쓰므로
전 프로젝트가 커서를 공유했고, 재읽기(중복 article)와 구간 누락이 났다.

transcript 경로 해시로 커서를 나누고, 위치를 ~/.wigtn/state/ 로 옮겼다.
커서와 no-hooks 설정은 위키 콘텐츠 가 아니라 이 기계의 상태다. 위키 안에 두면 clone한 팀 repo
워킹카피가 지저분해지는데, 팀 repo에 .gitignore 를 써 넣는 건 요청하지 않은 공유 저장소 변경이라 답이 아니다.

no_hooks 는 fail-closed로 바꿨다(#38). 설정 파일을 못 만들면 --settings 없이 claude 를 띄웠고,
자식 세션이 같은 Stop 훅을 물고 재귀했다. 모듈의 다른 실패 경로는 전부 폐기다.

#39 — 덮어쓰기 제거

slugify 가 한글 전용 제목에서 빈 문자열을 반환해 파일명이 전부 note 로 수렴했다.
타임스탬프는 분 단위, 쓰기는 write_text — 같은 분에 끝난 두 세션이 경고 없이 서로를 덮어썼다.

slug가 비면 제목 해시로 대체(결정론), 타임스탬프에 초 추가, 그래도 겹치면 접미사.
덮어쓰기는 어떤 경우에도 하지 않는다 — 잃어버린 쪽은 로그에도 "게시 성공"으로 남아
사라진 걸 알 방법이 없다.

#40 — push 경계를 코드로

include 가 홈/루트 전체면 remote 가 있어도 push를 보류한다. 축적과 로컬 커밋은 계속하고 사유를 로그에 남긴다.

remote 한 줄을 추가하는 행위의 사용자 인지는 "팀 공유를 켰다"인데, 실제로 일어나는 일은
"이 기계의 모든 repo 지식이 원격으로 나가기 시작했다"이다. 한 기계에서 여러 조직 일을 하면 그게 곧 유출 경로다.

축적까지 끄지 않은 건, 그러면 remote 를 추가한 사용자가 아무 일도 안 일어나는 상태를 만나기 때문이다 —
이 저장소가 방금 고친 실패 모드와 같은 모양이다. include 를 좁히는 순간 push가 켜진다.

마커로 명시 opt-in한 repo는 범위가 좁으므로 보류 대상이 아니다.

#37 — 위험 명령 훅

정규식으로 쉘 인용을 다루려 하면 JSON→bash→grep 3중 이스케이프에서 관리가 안 된다.
매칭 전에 따옴표를 제거한다(tr -d, octal escape로 인용 충돌 회피).

그 위에 종료 문자에 / 를 넣어 $HOME/·~/ 를 잡되 ~/Dev/proj 같은 타깃 삭제는 통과시키고,
rm-rf 사이 플래그를 최대 3개까지 허용했다. 상한을 둔 건 백트래킹 폭발을 막기 위함이다 —
이 훅은 모든 Bash 호출에서 돈다.

이전 정규식이 통과시키던 10건: 따옴표로 감싼 홈/루트 타깃, $HOME/, ${HOME}, 플래그를 앞에 끼운 형태.

테스트

스위트 건수 비고
test_knowledge_wiki.py 33 → 76 계층 분리 · 오탐 · push 경계 · slug · 게시 충돌 · 상태 격리
test_danger_hook.sh 42 (신규) hooks.json에서 명령을 뽑아 실제 JSON→shell→grep 체인
기존 130 계약 · 36 게이트 변동 없음

둘 다 validate 워크플로에 연결했다.

테스트가 회귀를 잡는지 확인했다 — 구현을 되돌려 실패를 확인하는 방식으로:

  • 소스를 main 상태로 되돌리면 두 스위트 모두 실패
  • 계층 분리만 제거(scan_input = 전체 스캔) → G1 통과(원문 허용) 4건 실패
  • 구 이메일/사설IP 패턴만 복원 → 오탐 없음 4건 실패 (SSH remote · noreply · macOS · Kafka)
  • 이전 훅 정규식 → 10건 실패

함수가 존재하는지가 아니라 동작이 맞는지를 검사한다.

문서

contracts/INGEST-POLICY.md §2에 두 계층 표를 넣고 §3 게이트 다이어그램과 §4 게시 경계를 갱신했다.
SKILL.md 의 게이트 설명·진단표·범위 안내를 코드와 맞췄다. 정책 정본과 코드가 어긋나면 정책이 거짓말이 된다.

Test plan

  • validate_plugin.py · check_contracts.py(130) · test_gate.sh(36) 통과
  • test_knowledge_wiki.py 76건 통과 · test_danger_hook.sh 42건 통과
  • 회귀 검출 확인 (위 4가지 변조 시나리오 전부 실패 재현)
  • 실제 세션 30건으로 G1 폐기율 측정 (10/30 → 0/30, 긴 세션 8/16 → 0/16)
  • 상태 격리 — HOME 격리 프로세스에서 커서·no-hooks가 ~/.wigtn/state/ 아래에만 생성됨을 확인
  • 머지 후 실제 세션에서 article 생성 확인 (G2/G3은 모델 호출이라 CI 밖)

Closes #35, closes #36, closes #37, closes #38, closes #39, closes #40

정규식으로 쉘 인용을 다루려 하면 JSON→bash→grep 3중 이스케이프에서 관리가 안 된다.
매칭 전에 따옴표를 제거한다 (tr -d, octal escape 로 인용 충돌 회피).

그 위에 두 가지를 좁혔다.
- 종료 문자에 / 를 넣어 $HOME/ · ~/ 를 잡되, ~/Dev/proj 같은 타깃 삭제는 통과시킨다
- rm 과 -rf 사이에 오는 플래그를 최대 3개까지 허용 (--no-preserve-root 형태)
  상한을 둔 건 백트래킹 폭발을 막기 위함이다. 이 훅은 모든 Bash 호출에서 돈다.

이전 정규식이 통과시키던 것: 따옴표로 감싼 홈/루트 타깃, $HOME/, ${HOME},
플래그를 앞에 끼운 형태. 총 10건.

test_danger_hook.sh 를 붙였다. 정규식을 눈으로 읽지 않고 hooks.json 에서 명령을
그대로 뽑아 실제 JSON→shell→grep 체인을 태운다. 42건 — 미탐 21 · 오탐 방지 14 · 기타 7.
이전 정규식으로 돌리면 10건이 실패한다. 이 정규식은 두 번 연속 실측 없이 수정됐다.

Closes #37
knowledge-wiki 후속 4건. 파일이 서로 얽혀 있어 한 커밋으로 묶었다.

#35 — G1 오탐으로 유효 세션이 대량 폐기되던 문제
  deny 패턴을 두 계층으로 나눴다. G1(원문)은 D1 자격증명 + 주민번호만,
  G4(article)는 전체. 원문은 기계 밖으로 나가지 않는다 — 반출되는 건 G2 가
  새로 쓴 article 이고 그건 G4 가 같은 강도로 검사한다. "대화 중에 파일 경로를
  언급했다"는 이유로 세션을 버리면 보호는 그대로인데 회수만 잃는다.
  D1 을 G1 에 남기는 이유는 다르다. 시크릿은 모델에 넣는 순간 산출물에서
  되살아날 수 있고, 그런 세션은 드물어 폐기 비용이 낮다.

  패턴도 정밀화했다.
  - 사설 IP 는 옥텟 4개 강제. 3개만 요구해 macOS 10.15.7, Kafka 10.2.1 을 잡았다
  - 이메일에서 git@·noreply@ 등 서비스 계정 제외. SSH remote 를 언급한 세션이
    전부 죽었고, SKILL.md 자체가 git@github.com 을 담고 있어 이 문서를 읽은
    세션은 자기 자신 때문에 폐기됐다

  실측 (최근 세션 30건): 긴 세션 폐기 8/16 → 0/16.

#36 — 커서가 세션이 아니라 subdir 기준이던 문제
  기본 설정에선 모든 repo 가 같은 per-user/ 를 써서 커서 하나를 전 프로젝트가
  공유했다. 재읽기(중복 article)와 구간 누락이 난다. transcript 경로 해시로
  커서를 나누고, 위치를 ~/.wigtn/state/ 로 옮겼다.
  커서와 no-hooks 설정은 위키 콘텐츠가 아니라 머신 상태다. 위키 안에 두면
  clone 한 팀 repo 워킹카피가 지저분해지는데, 팀 repo 에 .gitignore 를 써 넣는 건
  요청하지 않은 공유 저장소 변경이라 답이 아니다.

#38 — no_hooks 확보 실패 시 fail-open 이던 문제
  설정 파일을 못 만들면 --settings 없이 claude 를 띄웠고, 자식 세션이 같은
  Stop 훅을 물고 재귀했다. 모듈의 다른 실패 경로는 전부 폐기다. 여기도 폐기한다.

#39 — 한글 제목이면 파일명이 note 로 수렴해 덮어쓰던 문제
  slug 가 비면 제목 해시로 대체하고(결정론), 타임스탬프에 초를 넣고,
  그래도 겹치면 접미사를 붙인다. 덮어쓰기는 하지 않는다 — 잃어버린 쪽은
  로그에도 "게시 성공"으로 남아 사라진 걸 알 방법이 없다.

#40 — remote + 홈 전체 include 조합
  include 가 홈/루트 전체면 remote 가 있어도 push 를 보류한다. 축적과 로컬
  커밋은 계속하고 사유를 로그에 남긴다. remote 한 줄을 추가하는 행위의 인지는
  "팀 공유를 켰다"인데 실제로는 "이 기계의 모든 repo 지식이 나가기 시작했다"가
  된다. 축적까지 끄지 않는 건, 그러면 remote 를 추가한 사용자가 아무 일도
  안 일어나는 상태를 만나기 때문이다 — 방금 고친 실패 모드와 같은 모양이다.
  마커로 명시 opt-in 한 repo 는 범위가 좁으므로 보류 대상이 아니다.

INGEST-POLICY.md 와 SKILL.md 를 코드와 맞췄다. 회귀 테스트 33 → 76건.
계층 분리를 되돌리거나 구 패턴을 복원하면 각각 4건씩 실패한다 (동작을 검사한다).

Closes #35, closes #36, closes #38, closes #39, closes #40
@Hyeongseob91
Hyeongseob91 merged commit 1f2b24e into main Aug 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment