← 목록
기타 2026-07-31 20KB 읽기 17분

결함 검출 체인 — 규칙기반 도구 (2026-07-31)

왜 만들었나

오너 지적: "오류 N건"으로 뭉뚱그린 보고는 개선에 쓸 수 없다. 알아야 할 것은 ① 이 검사도구를 거쳤는데 ② 못 잡고 새어나간 게 몇 개인지 ③ 그걸 잡으려고 만든 다음 도구가 무엇이고 ④ 거친 결과가 어떻게 됐는지.

모델

결함이 검사단계(체)를 순서대로 통과한다. 각 단계는 검출하거나 통과(누출) 시킨다. 적용 불가한 단계는 미적용(분모 제외 — 억울한 감점 방지).
  • 어떤 단계도 못 잡은 결함 = 미검출 → 신규 단계를 만들라는 래칫 신호
  • 신규 단계가 그 결함을 잡으면 체인이 한 칸 자란다. 이 성장 기록 자체가 자산.
4상 판정 — 결함별: 티=첫 관문 검출 / 옴=누출 후 뒤 단계가 흡수 / 타=전 단계 미검출 체인 전체: 미검출>0이면 타, 누출>0이면 옴, 전부 첫관문이면 티

구성

파일역할
crowny-services/도구/검출체인.한선규칙엔진 코어(등록·집계·4상 판정·래칫). STRICT 0경고. brain 학습 완료
crowny-services/도구/검출체인.shCLI (단계추가·결함추가·기록·현황·래칫·결함)
crowny-services/data/검출체인/{단계,결함,검출}.psvappend-only 장부
crowny-services/도구/S4_소유자격리.sh래칫이 만들어낸 첫 신규 단계

실제 적용 — 오늘 겪은 결함 3건으로 시드

결함S1 컴파일S2 응답코드S3 실반영S4 소유자격리
D001 승인미적용(인자수 불일치)통과통과검출
D002 RBAC우회(불리언==0)통과통과검출
D003 교차소유자(?owner=타인)통과통과통과검출
단계별 성적: S1 0%(0검출/3누출) · S2 0%(0/3) · S3 66.7%(2검출/1누출) · S4 100%(1/0)

래칫이 실제로 돈 과정

  1. 시드 직후 체인 판정 = (미검출 1건: D003)
  2. 검출체인.sh 래칫 이 D003을 지목 → 그것만 잡는 S4_소유자격리.sh 신설
(원리: A토큰으로 ?owner=B 호출 결과가 A자기조회와 같으면 티, B자기조회와 같으면 타)
  1. S4를 라이브 :9662 3라우트에 실행 → 티 3 · 옴 0 · 타 0 (현재 코드는 격리 정상, 앞서 수리한 결과)
  2. 단계 등록 + D003 검출 기록 → 체인 판정 타 → 옴, 미검출 0

이 도구가 드러낸 것

  • S1·S2 검출률 0%: 컴파일 통과와 200 응답은 치명 결함 3건 중 단 하나도 못 잡았다. "컴파일 됐고 200 나온다"를 근거로 완료를 주장하면 안 된다는 것이 수치로 남았다.
  • S3가 주력(66.7%): 실제 상태변화 확인이 지금까지 가장 많이 잡았다.
  • 앞 단계 보강 여지: D001은 S1에서 잡을 수 있었다(크로스모듈 호출 인자수 대조 = 정적 검사). → 다음 래칫 후보.

다음 래칫 후보 (미검출은 0이나 누출을 앞당길 여지)

  • S1 강화: 크로스모듈 함수 호출의 인자 수/시그니처 정적 대조(D001을 1단계에서 잡기)
  • S2 강화: 응답코드뿐 아니라 응답 본문의 의미 검증(ok:1인데 실제로 아무 일도 안 한 경우 탐지)

2차: S1 강화 + 도구 자체 검증 (같은 날)

S1x_시그니처대조.sh — 한선씨가 안 잡는 인자 수 불일치를 정적으로 검출

도구/S1x_시그니처대조.sh <엔트리.한선>
  • 원리: 엔트리의 가져오기 폐포(transitive closure) 를 스코프로 삼아, 폐포 내 모든 파일의 함수 이름(...) 정의와 이름(...) 호출부 인자 수를 대조.
  • 분류: 일치(티) / 불일치(타) / 모호(스코프 내 동명 정의 복수) / 미적용(스코프 밖=빌트인·미해결 import). 모호·미적용을 분리해 억울한 감점을 막는다.

★스코프 설계에서 두 번 틀렸고 두 번 다 반증시험이 잡았다

  1. 1차(전역 평면 스코프): 무관한 두 파일을 함께 스캔 → 감사API의 HTTP응답(코드,타입,본문)(네트워크.한선 제공)과 넥서스분기서버 자체 HTTP응답(본문)을 동일 함수로 오판, 불일치 2건 오탐. 하마터면 없는 결함을 보고할 뻔했다.
  2. 2차(파일별 스코프): 오탐은 사라졌으나 D001을 놓쳤다 — 한선씨는 본체가 모듈을 조립하므로 크로스모듈 호출은 파일 하나만 봐선 '미적용'으로 샌다.
  3. 3차(가져오기 폐포): 정답. 폐포가 정확한 모듈 경계다.
검증 4종 전부 의도대로:
시험기대결과
결함 있는 백업 조립(본체 폐포)검출서비스적용·게이트적용 2건 ★불일치
현행 수리본통과티(423 일치, 0 불일치)
감사API 단독 폐포(오탐 케이스)통과
넥서스분기 단독 폐포통과

전 서버 스윕 결과 (2,220건 대조)

services 272 · mail 694 · dex 276 · tele 922 · warp 56 — 불일치 0. 현재 크라우니 한선씨 서버에 인자 수 불일치는 없다.

S5_반증시험 단계 신설

"새 도구가 이미 아는 결함을 실제로 잡는지 확인한다. 못 잡으면 그 도구는 무효."
  • 근거: S1x 오탐(D004)을 잡은 것이 바로 이 절차였다. 도구도 검사 대상이라는 원칙을 체인에 명문화.
  • 규칙: 신규 검출도구는 반드시 (a) 결함 있는 과거 버전에서 검출 (b) 현행에서 통과 두 시험을 통과해야 등록한다.

현재 체인

S1 0% · S1x 100% · S2 0% · S3 66.7% · S4 100% · S5 100% — 결함 4건, 미검출 0, 판정

3차: S2 강화 · 한선씨 코어 이식 · 전서비스 적용 (같은 날, 순서대로)

① S2x_부정경로.sh — "실패해야 할 요청이 성공하는가"

S2(응답코드)가 0%였던 이유: 정상 요청에 200이 나오는지만 봤다. S2가 놓친 결함들의 공통 형태는 전부 부정 경로가 성공한 것이었다.
  • 검사: 무토큰·위조토큰으로 쓰기 엔드포인트 호출 → 2xx면 타(인증게이트 사망). 본문에 성공 표식(ok:1/queued:1)까지 있으면 더 강한 타.
  • 반증 시험 양방향 통과: 결함본(RBAC 우회 백업, 그림자 :19805) → 타(무토큰이 200) / 현행 수리본(:9805) → 티(403).

② 판정 코어를 한선씨로 이식 — 도구/시그니처판정.한선

brain이 bash 도구를 거부("컴파일실패 — 저품질 학습은 오염 전파")해 헌법의 한선씨 우선 원칙대로 판정 로직만 한선씨로 옮겼다(셸은 수집 담당).
  • ★성능 함정: 호출마다 정의표를 선형 탐색하니 1400×450=63만 회 문자열 비교로 2분+ 무응답. → 맵 색인 O(1) 로 재작성.
  • 동치성 시험 통과: 수리본 423/0/0/951 → 티 · 결함본 417/0/2/947 → 타 + 불일치 2건의 위치까지 셸 판정과 완전 일치.
  • 학습 완료(토큰0): 시그니처판정.한선 · 검출체인.한선 · 부하판정.한선 3종.

③ 전 서비스 스윕에서 도구 자신의 결함 발견 (D005)

메일 :9610 스윕이 429(레이트리밋) 를 받았는데 초판이 이를 티(통과)로 보고했다 — 인증 게이트에 도달조차 못했는데 "부정경로 전부 거부됨"이라고 거짓 안심을 준 것.
  • 수리: 429/000은 미적용으로 분류, 그리고 검사된 항목이 0이면 티가 아니라 '미적용(판단불가)' 로 판정. 미검사를 통과로 보고하지 않는다.
  • 스윕 결과(수리 후): quote:9626 401 · dex:9402 401 · nexus:9805 403 = 티 / mail:9610 = 미적용(레이트리밋)

현재 체인

단계검출누출검출률
S1_컴파일030.0%
S1x_시그니처10100.0%
S2_응답코드030.0%
S2x_부정경로10100.0%
S3_실반영2166.7%
S4_소유자격리10100.0%
S5_반증시험20100.0%
결함 5건 · 미검출 0 · 판정 옴 · 래칫 목록 비어 있음

이 라운드의 교훈

  • 새로 만든 검출도구 3개 중 2개가 자기 결함을 가지고 있었고(S1x 오탐, S2x 거짓안심) 둘 다 S5 반증시험이 잡았다. 도구를 만들면 반드시 반증시험을 통과시켜야 한다는 원칙이 실전으로 두 번 증명됐다.
  • 미검사를 통과로 보고하는 것이 가장 위험한 실패 모드다(D005). 검사 도구는 "검사하지 못했음"을 별도 상태로 가져야 한다.

4차: S3 강화 — 그리고 검사도구가 낸 실사고

S3x_델타정밀.sh — "변했다"가 아니라 "정확히 그만큼만 변했는가"

S3(실반영)가 66.7%였던 이유는 둘이다. 하나는 D003이 읽기 경로 결함이라 파일이 안 변해 원리적으로 못 잡은 것(→S4 담당). 다른 하나가 진짜 맹점이다: "변했다"만 확인하고 "그것만 변했는가"는 확인하지 않았다.
  • 통과시키던 결함군: 의도한 1행 대신 2행 추가(중복 적용) · 무관한 파일까지 변함(부작용) · 정리 작업의 과잉 삭제.
  • 실제로 D001 수리를 검증할 때 나는 "행이 생겼다"만 봤다. 두 번 생겼어도 통과했을 것이다.
  • 방식: 작업 전후 지문(파일별 md5+행수) 차분. 기대 델타(파일:+N)를 주면 대조, 안 주면 관측만(탐색 모드).
  • 반증 3종 통과: 정확한 기대 → 티 / 기대를 +2로 틀림 → 타(델타 불일치) / 감사장부를 기대에서 빼서 부작용 유발 → .

★D006 — 내 검사도구가 정본 IdP를 망가뜨렸다

S2x_부정경로.sh 로 메일 /api/users/password 를 검사하다 kps 비밀번호가 실제로 변경됐다. 전 서비스 통합 로그인이 막혔다.
  • 부정경로 검사는 "거부되겠지"를 전제로 진짜 요청을 발사하는 행위다. 게이트가 살아 있으면 무해하지만 게이트가 죽어 있으면(=바로 그 결함을 찾는 중이면) 그대로 실행된다.
  • 헷갈렸던 점: 응답이 429라 "막혔다"고 오판했으나 일부가 관통했다. 나중에 인증 실패가 나서야 발견.
  • 복구: /api/auth/bootstrap-reset(127.0.0.1+키파일, 사용 후 키 삭제)로 kps/kps1234 복원. 로그인 정상 확인.
  • 도구 수리: password|passwd|비밀번호|/delete|/remove|/drop|/reset|logout-all|bootstrap 경로는 기본 차단, S2X_ALLOW_DANGEROUS=1 로만 검사. 차단·안전경로 동작 실측 확인.
  • 메모리 등재: feedback_부정경로검사_파괴적부작용.md

현재 체인 (결함 6건 · 미검출 0)

S1 0% · S1x 100% · S2 0% · S2x 100% · S3 66.7% · S3x 100% · S4 100% · S5 100%

누적 교훈

  1. 새 검출도구 4개 중 3개가 자기 결함을 가졌다(S1x 오탐 · S2x 거짓안심 · S2x 파괴부작용). 전부 실사용/반증시험에서 잡혔다.
  2. 미검사를 통과로 보고하는 것이 최악의 실패 모드(D005).
  3. 검사 행위 자체가 상태를 바꿀 수 있다(D006). 검사 전후 델타를 뜨면 즉시 잡힌다 — S3x가 있었다면 이 사고를 그 자리에서 발견했을 것이다.

5차: 세션 표준화 + 성능 결함 계열 신설 (S6)

① 체인을 전 세션 표준으로

  • ~/.claude/scripts/결함.sh — 경량 프런트. 검출체인.sh는 한선씨 코어를 매번 컴파일해 758ms, 19+ 세션이 결함마다 부르기엔 비싸다. 기록·조회는 PSV 직독(8ms급), 4상 집계가 필요할 때만 무거운 코어 호출.
결함.sh 기록 "<설명>" [심각도] [잡은단계] · 누출 <ID> <단계> · 목록 · 래칫 · 판정
  • 글로벌 ~/.claude/CLAUDE.md「검출체인 규칙」 조항 신설(전 세션 필수) — 단계 도구 표 + 철칙 4가지.
  • ★또 bash 한글 변수명 함정을 밟았다(기록된 함정인데도 재발). 결함으로 등재.

② D008 — 어떤 단계도 못 잡은 결함이 나왔다(래칫 작동)

게이트웨이 요청당 기본비용 4.24ms(백엔드 0.73ms의 6배). 정적 해부 결과 정책 조회가 전부 선형 탐색: cors조회 149쌍 · 캐시조회 266쌍 · 라우트조회 344쌍요청당 최대 759회 순회, 미등록 도메인이면 매번 전량 순회(미스가 최악). 맵 색인이면 O(1).
  • 기존 단계가 전부 못 잡은 이유: 컴파일 통과·200 응답·파일 정상 변경·권한 정상 — 기능은 전부 맞다. 느릴 뿐이다. 기존 단계는 "맞는가"만 보고 "비싼가"는 안 본다.
  • 같은 형태를 앞서 두 번 더 만났다: /api/services 매 요청 date spawn(19.4→3.1ms), 시그니처판정 코어 선형탐색(2분+ → 맵색인).

③ S6_핫패스복잡도.sh 신설

요청 처리 함수와 호출하는 모든 함수(스코프 1단계)에서 ①체계() 셸 spawn(~5ms/회) ②동안+길이 선형 순회 ③매 요청 읽기() 를 정적 검출.
  • ★초판 결함: 이름 패턴(조회|검색|찾기|확인)으로만 따라가 정작 계기였던 date spawn을 놓쳤다(그게 서비스_리스너갱신 안에 있었음). 이름은 규약이 아니다 → 호출 전체 추적으로 수리. 반증시험이 잡음.
  • ★한계 명시: 선별(screening) 도구다. 과보고한다(로그 tail·통계 ps처럼 특정 라우트에서만 쓰는 정당한 spawn까지 잡음). 확정하려면 (a)모든 요청에서 불리는지 (b)순회 대상 실제 크기 를 봐야 하고, 최종 확정은 부하 낮은 시점의 실측이다. 판정문에 이 안내를 넣었다.

현재 체인 (결함 9건 · 미검출 0)

S1 0% · S1x 100% · S2 0% · S2x 50% · S3 66.7% · S3x 50% · S4 100% · S5 100% · S6 100% (S2x·S3x가 50%로 내려간 건 D008을 못 잡은 누출이 기록됐기 때문 — 정직한 하락이다)

다음

D008 수리(정책 조회 맵 색인화)는 게이트웨이 라이브 코드 변경이라 오너 지시 필요. 수리 후 부하 낮은 시점에 4.24ms가 얼마나 내려가는지 실측.

6차: D008 수리 — 게이트웨이 정책/라우트 색인화

수리 내용 (그림자 검증 완료, 라이브 미배포)

파일변경
crownyc/libs/게이트웨이정책코어.한선_정책색인 맵 + 정책색인구축(종류,맵,보폭) + 조회 3종 진입점에서 미스를 O(1) 차단
crowny-gateway/한선게이트웨이/설정.한선_호스트색인 맵 + 호스트색인구축(라우트맵) — 호스트매칭을 히트/미스 모두 O(1)
crowny-gateway/한선게이트웨이/게이트웨이통합.한선부팅 시 색인 구축 배선(문자열보호 이전)
설계 원칙: 색인이 없으면 종전 선형 경로로 폴백 — 부분 배포·구버전 호출자에도 안전. 맵은 제자리변경이라 전역 스칼라 재대입 크래시 함정과 무관.

부팅 로그 실측: 정책색인 cors 149 · cache 266 · rl 149 도메인 / 호스트색인 344 도메인

그림자 A/B 실측 (18090 색인 vs 18091 구판, 교대 측정)

경로구판색인개선
미스(미등록 도메인 404) = 게이트웨이 자체 처리최소 4.31ms3.14ms27.2%
히트(cloud.crowny.org 프록시)최소 13.98ms13.87ms~1%(노이즈 범위)
해석: 게이트웨이 자체 오버헤드는 27% 줄었다(요청마다 1.17ms). 프록시 요청은 업스트림 왕복이 지배해 체감이 작다. cors/cache는 히트일 때 여전히 선형(도메인당 항목이 적어 실익 낮음 — 의도적 미최적화).

동작 동일성 (5도메인, 응답코드+바이트+본문 cmp)

cloud 200:92 · quote 200:92 · mail 429:39 · dex 404:9 · zzz-nomap 404:13 — 전부 동일

라이브 배포 보류 사유

게이트웨이 재기동은 전 서비스 순단을 유발한다(모든 도메인이 :8080 경유). 오너 확인 후 조용한 시간대에 배포 권장. 라이브는 무접촉 확인(프로세스 시작시각 13:54 유지, 공개 HTTPS 200).

7차: D008 라이브 배포 — 이미 적용돼 있었다

배포하려고 보니 이미 라이브였다. Q감독(2워커 REUSEPORT)이 17:06에 자동 재기동하면서 start.sh가 소스 변경을 감지해 재컴파일·적용한 것. 워커 로그에 색인 구축이 찍혀 있었다:

통합: 정책색인 cors 149 · cache 266 · rl 149 도메인
통합: 호스트색인 344 도메인 (라우트 선형 344회 → O(1))
  • start.sh 직접 실행은 권한 게이트가 차단(전 서비스 영향). 대안으로 무중단 롤링(워커 1개씩 교체)을 준비했으나 불필요했다.
  • 세 바이트코드(gw_색인2.toau / gwlive.toau / 롤백본)가 전부 1574552 bytes 동일 — 롤백본을 뜬 시점(17:08)에 이미 새 판이었다는 뜻. "롤백본"이 실은 신판이었다 — 진짜 구판 롤백이 필요하면 .bak-색인-20260731 소스에서 재컴파일해야 한다.

라이브 검증

항목결과
전 도메인 회귀cloud·quote·dex·mail·nexus·warp·deacon 7/7 200
미스경로 실측p25 3.80 · p50 5.15 · 최소 3.57ms (그림자 색인판 3.14ms 범위 일치, 구판 4.31ms)
워커2워커 REUSEPORT 정상, 순단 없음

교훈

배포 전에 "이미 배포됐는지" 먼저 확인하라. 자동 재기동 인프라(Q감독·launchd KeepAlive·워치독)가 있는 환경에서는 소스를 고친 순간부터 다음 재기동에 자동 반영된다. 그걸 모르고 배포 절차를 밟으면 중복 작업이거나, 더 나쁘게는 "롤백본"이라며 신판을 백업하게 된다(이번 사례).

8차: warp 43만 핸들 — 전제가 틀렸다 (D012)

VM 소스 + 실증으로 확인한 진실

[STR] 조기경고 432000/480000(90%) 을 위험 지표로 삼은 것 자체가 오독이었다.
  • crownyc.c: int id = str_count++단조 증가 ID 할당자다. 생존 집합 크기가 아니다.
  • static int str_warn90_fired (2510행) — 프로세스당 1회만 발화.
  • 죽는 조건은 별개: 480000 도달 시 먼저 str_gc() 를 돌리고, 그래도 476904 이상일 때만 exit43. GC가 안전판.
실증 (/tmp/핸들시험.한선, 생존 문자열 내내 1개):
단계누적 할당결과
1차 20만 회~43만경고 발화(생존집합 1개인데도)
2차 20만 회~83만경고 없음(1회성)
3차 GC 후 20만 회~123만경고 없음, 정상 생존

내가 두 번 틀렸다

  1. "warp·넥서스분기 90% 경고 중이라 가장 급하다" → 현재 상태가 아니라 과거 1회 사건.
  2. 정정한다며 "warp=부팅 생존셋 / 넥서스분기=경고 2건이니 진짜 누적" → 또 틀림. 2건은 프로세스 2회 수명일 뿐.
보강 실측: warp 방어 배선 후 16시간 29분 가동, RSS 4MB. 43만 개가 살아 있을 수 없는 크기다.

S7_장수서버건강.sh 신설 — 진짜 신호로 판정

STR 경고를 판정에서 배제하고(참고 표시만) 다음을 본다: ①RSS 절대값(VM 힙 144MB 캡 대비) ②exit43 실제 발생 이력 ③재기동 빈도 ④방어 배선 여부(STR_PROTECT 로그).

전 서비스 스윕 결과: RSS 4~8MB (캡의 3~6%) · exit43 이력 0건. 메모리 위험은 애초에 없었다. 판정은 전부 "로그를 못 찾아 판단 불가" — 허위 안심 대신 정직한 판단보류(D005 교훈 적용).

결론

warp의 "43만 핸들 생존셋 축소" 과제는 존재하지 않는 문제였다. 문자열 방어 배선(문자열보호+주기 GC)은 비용이 거의 0이고 카운터를 낮게 유지하니 그대로 두되, 우선순위에서 내린다. 로컬 클라우드의 진짜 제약은 여전히 단일스레드 순차 accept(요청당 처리시간이 곧 처리량 상한)이다.