프로세스 영향도 위젯 4-레포 동시 구현, MongoDB 트랜잭션 인프라 전환, 파일 이벤트 4천만배 절감 throttle — 월간 리뷰 (2026-05)

서버 백엔드 파트가 한 달 178커밋으로 프로세스 영향도 위젯 서버(rest-api+service+migrations+package)·MongoDB single-node replica set 전환·KISA 시큐어코딩 20건 Phase 1~3·fail2ban 통합을 묶고, 분석 서비스 파트가 매니저 RBAC 권한 체계(엔드포인트·위젯·기능 ACL 3축)와 Baseline 일상 분류 서비스 Sprint 1+1.5(5조건 룰 + LightGBM-ready)를 동시 진행, 매니저·에이전트 파트가 c3→c4 분리 잔재인 p18 Agent UI를 패키지에서 도려내고 broker OpenSSL 잔여 경로 회피·MSI 노트북 BSOD 두 케이스를 windbg로 추적, 커널 드라이버 파트가 17조 회 누적 파일 이벤트를 4천만배 절감하는 시간 기반 LRU throttle을 본격 적용한 한 달.

2026년 5월(5/1 ~ 5/31) 월간 리뷰. 6명이 8개 레포에서 393커밋, +59,572 / −35,594 라인(대량 커밋 제외). 한 달 동안 푼 문제 중심으로 정리한다.

룰·데이터 분석 파트의 산출물은 코드가 아니라 커밋 수로 잡히지 않으므로, 아래 별도 섹션에 따로 정리한다.


서버 백엔드·인프라 전방위

5월 한 달 178커밋, Jira 40건 완료 — 기간 최다. 신규 큰 작업 세 개를 동시에 굴리면서 보안·인프라 마무리까지 같이 묶었다.

프로세스 영향도 위젯 — 서버 구현 4-레포 동시 정리 (OR-1442). OR-1334 분석가 가이드(V5.15)를 서버 입장에서 검토·확장해 rest-api · service · migrations · package 네 서브모듈에 동시 머지. system aggregation의 평균값 소스가 OHLC close(통상 마지막 raw)와 어긋나는 점을 잡아 average 필드를 OHLC와 별도로 신설했고(MIN_COUNT_FOR_OHLC=5 미달이어도 첫 sample부터 sum/count로 채움), daily 경계는 서버 로컬 timezone 자정으로 통일하되 DB 저장 값은 모두 UTC로 일관화. aggregated/list는 같은 SUID가 여러 hour|day에 등장할 때 row가 중복되던 P-1을 버리고, aggregator가 이미 보관해 둔 *_sum / CounterCount_sum으로 SUID 단위 가중평균(cpu_percentage = ProcessTime_total / SystemTime_total * 100)을 즉시 산출하는 P-2로 재설계.

핵심은 subscriber-driven publish 모델이다. 31k SUID 전수 publish는 시간당 ~224k msg로 99%+가 아무도 안 듣는 publish이므로, manager-web이 관심 SUID를 PUT /v3/sprocess/subscribe로 등록하면 Redis ZSET에 ZADD-only(3분 keepalive · 5분 TTL score 자연 만료)로 추적하고 aggregator는 ZRANGEBYSCORE로 활성 SUID set만 publish한다. abuse 가드로 한 bucket의 ZSET member 수가 MAX_ZSET_SIZE = 100,000을 넘으면 방금 추가한 SUID들을 ZREM으로 롤백 + 429. 별도 systemd 서비스(orange.process-aggregator.service)로 격리, hourly cron :02 + partial :05/:15/:25/:35/:45/:55로 위젯이 새 hour 시작 후 최대 5분 안에 진행 중 hour를 본다. 5월 한 달 39커밋의 최대 작업.

조직도 일괄 로드 단일 트랜잭션화 + Beanie bulk_writer 단일 RTT (OR-1435). POST /v3/organization/load의 clear → load_groups → load_users 사이 중간 실패에 따른 부분 적재 노출을 단일 MongoDB 트랜잭션으로 묶었다(전제: 아래 OR-1439). load_groups · load_users · recompute_all_node_user_fields를 Beanie bulk_writer로 전환해 RTT를 1회로 줄였고, User.group_code 사전 검증을 엄격 모드로 추가. 조직도 로드 후 ghost 그룹 활성 Command 자동 정리·Node 비정규화 필드 일괄 재계산·고아 parent 처리 옵션화(strict_orphan_parents)까지 시나리오 6번을 모두 정리.

MongoDB single-node replica set 전환 — multi-document 트랜잭션 인프라 (OR-1439). OR-1435 트랜잭션을 가능하게 하기 위해 standalone MongoDB를 single-node replica set으로 전환. setup.sh / update.sh에 keyFile(openssl rand -base64 756, mongodb:mongodb, 400) 생성과 rs.initiate 3단 분기(무인증 OK / AlreadyInitialized SKIP / Unauthorized → admin auth fallback)를 idempotent로 짜고, PyMongo의 hello 응답 자동 발견을 위해 멤버 host를 rest-api · service 양쪽이 도달 가능한 docker network gateway IP로 설정(orange_net 172.30.0.1 / default bridge 172.17.0.1). 테스트 · 개발 서버 모두 데이터 100% 보존 마이그레이션 검증, rest-api/service 코드 변경 0건.

KISA SW 개발보안 가이드 — Phase 1~3 시큐어코딩 20건 마무리 (OR-994). KISA(2021) 제4장 구현단계를 기준으로 servers/rest-api와 servers/service에서 도출한 20건의 보안 결함을 일괄 처리하고 본 이슈 종료. CWE-798 마스터키·MD5·deploy.sh 평문 비밀번호, CWE-22 Path Traversal·Zip Slip(extractall 시 ../../ 차단), CWE-918 모니터 SSRF(loopback 127.0.0.0/8 ::1 · link-local 169.254.0.0/16 fe80::/10 차단을 문자열 비교가 아닌 DNS 후 IP 레벨에서), CWE-307 로그인 IP Rate Limiting(POST /v3/manager/login 60초/20회 Redis pipeline INCR+EXPIRE 원자 처리), CWE-330 Node ticket UUID v1→v4, CWE-434 업로드 크기·Zip bomb 가드(/v3/rule/upload 10MB + 전개 200MB), CWE-312 v1 logon 응답에서 비밀번호 해시 제거까지. CORS 와일드카드와 verify=False 두 건은 사내 PKI 미비를 사유로 위협 모델 범위 밖으로 분리하면서도 향후 활성화를 위해 # WARN: cert verification disabled — internal CA 도입 시 활성화 주석을 7개 위치에 박아 두었다.

fail2ban 통합 + REST API 3181 HTTP jail (OR-1313, OR-1448). 인터넷 노출 SSH 무차별 대입을 systemd journal 백엔드 + nftables banaction + 지수 증가 ban + recidive 메타-jail(24h 내 5번 ban → 1주 장기차단)로 차단. firewall.rules에 __SSH_PORT__ 토큰을 도입해 install 시점에 detect_ssh_port 헬퍼가 동적 치환하도록 만들어 고객사마다 다른 sshd 포트를 자동 반영. Agent 직접 접속용으로 의도된 3181 노출에는 별도 HTTP jail을 추가 — compose 로그를 journald + logging.options.tag로 CONTAINER_TAG=orange-rest-api로 고정해 컨테이너 이름이 환경마다 달라도 한 벌의 jail이 동작하게 했고, banaction_allports로 시스템 방화벽 백엔드(nftables/iptables)를 자동 추종해 스캐너의 전 포트를 차단.

broker 잔여 SSL 버그 우회 + 폐쇄망 동봉 (OR-1414). OpenSSL 3.0.x record layer 버그(partial TLS record + close 에러 시 SSL_CTX 공유 상태 손상)를 Ubuntu 24.04가 패치하지 않는 점을 격리 환경에서 확정한 뒤, SSL_OP_IGNORE_UNEXPECTED_EOF 플래그를 LD_PRELOAD로 주입하는 shim을 운영에 안착. 5월에는 폐쇄망 update 안정성을 위한 3단계 보강을 마무리했다 — ① silent skip 방지 안전망(install.sh 실패 시 setup/update가 die 대신 경고만), ② WSL Ubuntu 24.04(glibc 2.39)에서 빌드한 사전 빌드 .so를 git에 동봉 + Makefile MAX_GLIBC=2.39 ceiling을 make verify가 강제, ③ Orange-Package CLAUDE.md에 트리·동봉 정책·setup/uninstall 단계 문서화.

Service 계층의 API 스키마 의존 제거 — 순환 import 근본 정리 (OR-1425). Service / Infra가 API 스키마(app/api/v3/schemas/*)를 거꾸로 import하던 layer 위반 3건(CommandTemplate · Organization · infra/cgroup)을 모두 정상화. 각 계층 전용 schemas/ 디렉터리에 입력/결과 모델 신설, usecases/mapper/ 3개(CommandTemplate · Organization · Health)로 API↔내부 변환을 일관화. __future__ + TYPE_CHECKING 회피책을 완전히 걷어내고도 외부 API 응답 변경 0건 — 순수 내부 리팩터링.

nodedetect v3 마이그레이션 + 식별자·계층 정리 (OR-1424, OR-1355, OR-1294). /v2/nodedetect/count(group_by_cuid_cgid=true)를 /v3/detect/nodes/count로 옮기며 응답을 '고유 증상 인스턴스 수(distinctCount, 기존 v2 의미)'와 '기간 내 발생 횟수(totalCount, 1시간 버킷 누적 합산)' 둘로 명확히 분리. detect/timeline/summary 도메인의 'user' 식별자를 'node' 의미로 일관 통일(OR-1355 — 22커밋), manager-web의 '사용자/부서/PC명/도메인명' 4필드 표시를 source-of-truth 정리(OR-1294)까지.

Playwright MCP + v3 비동기 I/O (OR-1361, OR-1422). Claude Code 등 AI 에이전트가 manager-web의 로그인·페이지 진입·할당 흐름을 자연어 한 번으로 검증하도록 @playwright/mcp 3채널(headed/headless/legacy-v105)을 등록. 고객사 구형 Chromium v105 호환성 회귀까지 같은 명령으로 검증. v3 파일 업로드/다운로드/삭제 경로의 sync os.path / open / os.remove 호출을 aiofiles.os 기반 비동기 syscall로 전환해 '소수 파일 경로의 sync syscall이 다수 Mongo/Redis 요청의 p99 latency까지 오염시키는 비대칭 구조'를 차단.

WebSocket 핸들러 안전화 (OR-1432, OR-1433). /v3/ws/node/stats 토큰 누락/만료 시 AttributeError: 'WebSocket' object has no attribute 'method'로 ASGI 핸들러가 2차 폭발하던 버그. WS 엔드포인트가 HTTPException만 catch하던 부분에 AppException을 추가하고, app_exception_handler가 Request로만 가정하던 부분을 WebSocket 분기로 분리. accept 이전 close 호출이 uvicorn에 의해 HTTP 403 핸드셰이크 거절로 변해 클라가 1006으로 무한 재시도 루프에 빠지던 부작용도 같이 정리. 기타 4개 WS 엔드포인트도 close-before-accept 정리.


RBAC 권한 체계 + Baseline 일상 분류 서비스

5월 한 달 94커밋. 권한 체계와 baseline 신규 서비스 두 큰 라인을 동시에 굴렸다.

관리자 계정 권한(RBAC) 시스템 — Store + Redis 캐시 + 위젯 ACL 동기화 (OR-1314). /v3/acl 통합 라우터(endpoint/widget/feature/role 14개 엔드포인트)와 Store 컬렉션 패턴(EndpointAclStore · WidgetAclStore · RoleDefinitionStore · FeaturePermissionStore) + Redis 캐시를 깐 위에서, 5월에는 _check_endpoint_acl에 _OPEN_ACCESS_OPERATIONS 바이패스 추가, 위젯 ACL R/W/D 동기화 버그 수정, role 삭제 시 FeaturePermissionStore 캐스케이드 정리, HTTPException → 커스텀 예외 전환, 로그인 응답 ACL 직렬화 camelCase 정합까지 안정화 마무리.

LiveCMD Role별 권한 적용 — Agent ACL 확인 엔드포인트 + 403 상세 응답 (OR-1415). Agent용 LiveCMD 권한 확인 엔드포인트를 신설하고, feature_permission 빈 배열을 전체차단으로 정의 + 서버 시작 시 기본값 생성. 403 응답에 거부된 method/path/operation_id를 동봉해 매니저의 PermissionErrorBoundary 오버레이가 어떤 API가 막혔는지 정확히 표시. 기능 권한에서 batch_command(1:N) 항목 제거 + ACL 직렬화 camelCase 정합.

새 엔드포인트 추가 알림 — is_reviewed 추적 + 토스트 + 미확인 배지 (OR-1359). 서버 배포 후 acl_seed가 새로 시드한 엔드포인트를 is_reviewed=False로 추적하고, admin 로그인 시 useUnreviewedEndpointAlert 훅이 sessionStorage 기반으로 세션당 1회 토스트 알림. '설정 > 권한 관리 > API 권한' 탭에 미확인 카운트 오렌지 배지, PUT 저장 시 endpoint_update_case가 자동으로 is_reviewed=True 설정, '모두 확인 완료' 일괄 처리 엔드포인트까지.

설정 영역을 위젯 → 기본 내장으로 — ACL OR 합산 버그 근원 해소 (OR-1413). 설정(Setting)을 GridLayout의 위젯 배치에서 빼내고 Content.tsx가 setting 섹션을 직접 렌더링하도록 변경(containType='global', 위젯 헤더바·드래그 핸들 없음). 위젯 패널 노출과 widget.config.json의 requiredApis 25개를 비워, 부모 setting 위젯과 자식 탭 위젯이 같은 API를 중복 보유해 _sync_endpoint_acl의 OR 합산으로 권한 박탈이 되돌려지던 버그를 근본 차단.

매니저 권한 적용 오류 수정 — 위젯별 403 + 타입 안전성 + 보안 (OR-1402). throwOnError에서 PermissionDeniedError만 선택적으로 throw하도록 바꾸고, PermissionErrorBoundary를 DynamicComponentLoader 레벨(위젯 부모)로 옮겨 위젯 단위 오버레이로 캐치. aclTypes.ts 신설로 useQuery 5개에 제네릭 타입 적용, roleId의 dangerouslySetInnerHTML 제거(XSS 방지), useUnreviewedEndpointAlert에 AbortController 추가.

Baseline 일상 분류 서비스 — Sprint 1+1.5: 5조건 룰 + 다도메인 + LightGBM-ready 스키마 (OR-1438). detect를 시작으로 system / process / nodeinfo까지 확장될 "늘 똑같이 반복되는 일상 노이즈" 자동 분류 독립 서비스. 1순위 요구사항을 "기존 데이터 / 서비스에 영향 0"으로 잡고 원본 컬렉션을 read-only(find만)로 두며 쓰기는 자체 컬렉션(baseline_routine · baseline_overrides · baseline_runs)에만 upsert, 도메인별 독립 systemd unit(baseline-detect.service, ...)로 한 도메인 장애가 다른 도메인·기존 서비스에 영향 없도록 격리.

'일상' 판정은 5조건이 모두 만족할 때만 routine. cv_count < 0.15(발생량이 일정) · k ≥ 3(서로 다른 3일 이상 발생) · max_gap ≤ 14일(2주 넘게 끊긴 적 없음) · since ≤ 14일(최근 2주 안에 발생 = 살아있음) · rep ≥ 0.6. 핵심 설계는 반복성 점수 rep = max(reg, freq) — 규칙성 reg = 1 − min(gap_cv, 1)과 빈도 freq = min(1, 2 ÷ mean_gap)를 max로 조화해서 "규칙적으로 반복"과 "자주 반복(들쭉날쭉해도)" 두 형태의 일상을 모두 포착. 다섯 조건 모두 사람이 검산 가능한 단순 통계라 운영자가 결정 근거를 그대로 본다.

운영 서버 detect_node 전체 이력(186,833행 · 206일)로 공식 도출 · 검증. 초기 시도였던 EWMA+CUSUM은 단순 cv 규칙 대비 가치가 입증되지 않아 폐기. 5월 마지막 주에는 운영 hang 수정(fork → spawn 전환), baseline settings를 store.code=baseline 패턴으로 통합, multiprocessing 벤치마크(chunksize 50이 기본, cpu_count // 2 워커가 14 코어 머신의 sweet spot)까지 Sprint 1.5 검증 마무리. Phase 2 LightGBM 학습 입력 18개 피처와 baseline_labels 컬렉션 슬롯도 baseline_routine.features 서브도큐먼트에 사전 적재 — 룰 → ML 진화 경로(룰 → 앙상블 → ML 교체)에 코드 변경 없이 진입 가능한 스키마가 이번 작업의 핵심 자산이다.


Manager·Agent 회귀 추적과 BSOD 분석

5월 한 달 55커밋, Jira 21건 완료. 매니저·Agent·BSOD·패키지 네 축을 동시에 짚었다 — '왜 일어났는가'를 격리 비교·로그 대조로 확정한 다음 코드를 건드리는 일관된 방식.

nodeInfo will aux abrupt close 우회 — 크롬 148 + OpenSSL 3.0.x 잔여 경로 회피 (OR-1418). OR-1409로 위젯 MQTT를 글로벌 일원화한 직후 nodeInfo cleanup 일부가 빠져 위젯이 닫혀도 Agent가 "이 화면 더 안 본다" 신호를 받지 못해 전용 패킷이 누적되던 회귀. broker 디버그 로그(log_type all + connection_messages true) · tcpdump · 엣지 vs 크롬 격리 비교로 크롬 148의 WebSocket close 시퀀스가 broker OpenSSL 3.0.x record-layer 버그의 error:0A000123:SSL routines::application data after close notify 잔여 경로를 다시 트리거하고, 그 결과 무관한 Agent 연결이 끊긴다는 사실을 확정. OR-1414 본문이 "운영 로그상 0건이라 100% 커버"라고 한 잔여 경로가 크롬 148 업데이트로 0건이 아니게 된 것이다.

해결: will 보조 연결을 abrupt close (end(true) = stream.destroy())로 전환. graceful close가 0123을 트리거하는 것이 핵심이므로 abrupt는 close_notify를 생략해 0123을 원천 발생 불가로 만들고, 대신 발생하는 0A000126:unexpected eof는 OR-1414 shim이 흡수한다. 동시에 abrupt close가 broker의 WILL을 즉시(~1ms) 발사시켜 통지 안전망이 살아남는다. 실측 — nodeInfo 10회+ 반복 여닫이에 0A000123 6건 → 0건, agent Protocol error drop 발생 → 0건, broker 소켓 CLOSE-WAIT 누적 0건. 근본 처방(broker OpenSSL 3.2+ 또는 1.1.1 교체)은 별도 진행.

Manager > 노드 통계 WebSocket 야간 유실 — close 코드별 정책 정립 + 인스턴스 가드 (OR-1129). OR-1105 상태 패널로 적발된 야간 노드 통계 WebSocket(/api/v3/ws/node/stats) 단일화 현상에 대해, wasClean === true라도 close 코드는 정상이 아닐 수 있다는 점을 짚고, 클라이언트가 close 코드별로 자동 복구하도록 정책을 정립. 1006(네트워크 끊김) / 1011(서버 내부 오류)은 지수 백오프 재연결(1s → 2s → … → 30s), 1000(정상) / 1005(코드 없음)은 무동작, 그 외 + 1008(인증/정책 위반)은 강제 로그아웃. 정상 메시지 수신 시 백오프 카운트 리셋으로 "수락 직후 끊김"이 1초마다 반복되는 플래핑을 차단하고, 인스턴스 가드("지금도 내가 현재 소켓일 때만" 동작)로 늦게 도착한 옛 소켓 이벤트가 새 연결을 덮어쓰는 것까지 막았다.

[Manager] ESLint no-shadow 규칙 + git pre-commit 게이트 — OR-1028 11일 잠복 버그 재발 차단 (OR-1029). OR-1028 파일 다운로드 버그가 import { downloadFile }과 컴포넌트 내부 const downloadFile = useCallback(...)의 변수 섀도잉으로 11일간 잠복했던 사고를 받아, 새로 추가·수정하는 .ts / .tsx에 no-shadow 위반이 있으면 커밋이 거부되는 pre-commit 훅을 도입. yarn install 한 번에 윈도·맥·WSL 공통 자동 설치, Node가 없으면 통과가 아니라 차단으로 통일. 의도적 섀도잉만 인라인 // eslint-disable-next-line @typescript-eslint/no-shadow 주석으로 허용. 같은 사고를 도구 차원에서 들어오지 못하게 막은 마무리.

MSI 노트북 BSOD #2 — vmmemWSL 어서션 + e1dn.sys D3 전환 두 케이스 windbg 분석 (OR-1307). WSL2 vmmemWSL이 일으킨 0x7E SYSTEM_THREAD_EXCEPTION_NOT_HANDLED(STATUS_ASSERTION_FAILURE 0xC0000420 in nt!VmpInvalidateSingleGpaRange+0x44)는 Windows 11 24H2 26100.8246의 WSL2 메모리 관리 회귀로 추정. 별도 발생한 0x9F DRIVER_POWER_STATE_FAILURE는 windbg !irp로 IRP_MJ_POWER → SET_POWER D3가 \Driver\e1dnexpress에 도달했다가 시한 내 완료되지 못한 케이스로 확정. 충돌 위치 NxNblDatapath::SetRxHandler+30에서 NetAdapterCx의 RX 핸들러 분리 단계(SetRxHandler → rundown wait)에서 멈춘 것. 2024년 9월 빌드 e1dn.sys(Intel PCI\VEN_8086&DEV_550F 이더넷 컨트롤러)가 원인이고 벤더 본 드라이버 업데이트와 어댑터 전원 관리 옵션 해제로 회피.

sprocess 1시간 단위 hourly 집계 인프라 — 부팅 누적 → 시간 집계 의미 교체 (OR-1340). OR-1306이 깐 1시간 주기 송신 인프라 위에서 송신 수치를 부팅 누적이 아니라 1시간 단위 집계로 바꾸는 작업. CSProcessHourly 클래스에 SUID 기반 메모리 버킷 + Rotate baseline carry(new.start2 = MAX(old(start2+start-stop), 0))·Cleanup 2시간 비활성 evict·DebugFlush dev 모드를 한 클래스로 모으고, 저장 Running 컬럼을 sprocess/sprocess_all에서 제거해 8개 SELECT에서 MAX(Start-Stop, 0) AS Running 파생으로 전환. 28시간 무중단 운영에서 40회 hour rollover carry mismatch 0건 검증.

Agent doc.Servers 잔재로 인한 접속 불가 — dedup_sink + UI 저장 무효 가드 제거 (OR-1449). OR-1274 이전 옛 코드가 단말의 doc.Servers에 사설망 IP(192.168.100.211 4종 포트)를 남겨두고 신 코드는 그것을 갱신하지 않아, doc.Server.Address가 이미 당사 운영 서버로 정상 설정되어 있어도 단말이 죽은 사설 IP에만 매달리는 자가 복구 불가 상태가 만들어진 케이스. UI에서 동일 값 재저장 시 CAgentHelper3.command.cpp:260~266의 O == N 가드가 SetServerOverride 호출 자체를 막아 UI는 saved:true로 응답하지만 백엔드는 그대로인 인지 부정합도 해소. server.log 폭주를 선행 정리 — module/logger.h에 dedup_sink 신설(동일 라인 반복은 메모리 카운트만, 다른 라인 도착 시 [REPEAT N] 자동 출력)해 RegisterToServer2 35회 반복이 2줄로 압축됨을 확인. 단말별 서버 주소 영구 기록·원격 매니저 reset 명령이 다음 단계.

LiveCMD @ 명령 안정화 + p18(Agent UI) 패키지 정리 (OR-1416, OR-1419, OR-1348). atLineStartRef가 Enter/Ctrl+C/Ctrl+Z에만 true로 복구되고 그 외 키 입력에는 false로 잠겨, Backspace로 줄을 비우거나 Esc 클리어·텍스트 붙여넣기 후 @reload를 치면 @가 cmd.exe echo 억제로 사라지고 reload만 실행되던 패턴을 시나리오 3가지로 분리 재현해 확정. lineInputRef로 줄 상태를 정확히 추적하도록 교체. Agent @ 명령 응답은 터미널 직접 출력에서 토스트 형태로 바꿔 ConPTY 화면 오염도 함께 해소. 동시에 c3 매니저가 LiveAgent를 p18(Agent UI) 코드와 공유하던 구조에서 c4가 LiveAgent를 별도 구현으로 분리한 뒤 남아 있던 패키징 잔재(makepackage.sh/.bat의 @user 디렉터리 생성·복사, nginx /user location, install/setup.sh·update.sh의 생성·복사·권한·검증 루프)를 일괄 제거.

Git·Jira 자동화 시스템 가동 + 3개 레포 CLAUDE.md 정비 (OR-984). 5개월 전수 분석(커밋 1,884건·미기입률 34%·작성자별 월별 처리량) 결과를 바탕으로 MCP 서버와 Claude Cloud 정기 리뷰 루틴 5종(일/주/월/분기/연간)을 구축해 운영 안착. 5월에는 Agent · Html · OldManagerWeb 3개 레포에 CLAUDE.md를 추가해 c3 레거시 · p18 Agent UI(WebView2) · 정적 페이지 코드 리뷰 가이드까지 컨텍스트 자급자족이 되도록 묶었다. 이번 월간 리뷰 자체가 그 자동화의 산출물이다.


커널 드라이버·MS 서명 필터링

5월 한 달 53커밋, Jira 17건 완료. 커널 드라이버 최하단부터 Manager UI 회귀까지 동시에 짚었다.

파일 삭제·오류 필터링 — 시간 기반 throttle + 512-entry LRU 테이블 (OR-1427). File.Failed.cpp / 삭제 이벤트의 driver→agent notify 횟수가 한 달 동안 17조 회 누적 관측됨(분당 약 39만 건 통과로 agent 큐 폭주 위험). 기존 정책 count % 100 == 1은 동일 (process+status+file) key 마다 매 100회 1회 통과라 한 key가 지속 폭주하면 100건당 1건씩 무한 통과되어 폭주 자체는 막지 못하는 한계가 있었다.

두 가지를 동시에 바꿨다. 첫째, 횟수 단위 → 시간 단위. FILTER_COUNT_NOTIFY_INTERVAL (100) → FILTER_COUNT_NOTIFY_INTERVAL_SEC (600)로 10분당 1회 전환. 시간 source는 KeQueryUnbiasedInterruptTime(100ns 단위, sleep 시간 제외)을 써서 노트북이 절전 상태였던 시간은 카운트하지 않게 했다 — 깨어난 직후 즉시 notify가 폭주하는 패턴을 차단. 둘째, 카운트 테이블을 LRU로. 기존 hash table(open addressing) + ERESOURCE는 포화 시 RtlZeroMemory로 전체 reset하여 모든 key의 LastNotify 손실 → 다음 발생들이 "첫 발생"으로 인식되어 즉시 notify 통과 → throttle 무력화되던 문제와, ERESOURCE(56B)가 read/write 구분 없는 use case에 과한 도구라는 문제를 동시에 안고 있었다. 신규 자료구조는 512-entry linear array + EX_PUSH_LOCK(8B, 1/7 크기). 포화 시는 가장 오래된 LastNotify entry 1개만 LRU evict해서 다른 entry의 LastNotify를 보존 — throttle 효과가 살아남는다. 17조 회 케이스 기준 1,700억 건 notify → 4,320건으로 약 4천만배 절감.

커널 드라이버 MS 서명 프로세스 필터링 — SL 캡처 인프라부터 게이트 0까지 (OR-1350). 파일 삭제 / 오류 룰의 노이즈를 드라이버 단에서 차단하려면 각 프로세스 SignatureLevel이 안정적으로 필요. 두 단계로 정비: ① SL 캡처 인프라 — PsGetProcessSignatureLevel을 MmGetSystemRoutineAddress로 동적 해결(import 노출 회피), GetPreCreatedProcess 경로에서 EPROCESS->SignatureLevel 직접 조회, LoadImage가 ProcessNotify보다 먼저 떨어지는 레이스(Edge CREATE_SUSPENDED) 처리 위한 SignaturePending 캐시, 모듈 콜백 분리. ② File 이벤트 Pre 필터의 게이트 0으로 SE_SIGNING_LEVEL_MICROSOFT(8) 이상이면 즉시 차단.

커널 드라이버 SE_SIGNING_LEVEL 한계 분석 + PE Authenticode 추출 방향 (OR-1423). Medium IL의 MS 서명 user-mode 앱(Code.exe, Teams.exe, Edge 메인, Office)은 OS가 CI 강제를 생략해 SignatureLevel이 영원히 0임을 Windows 11 26100.8493 실측(86건 SL=0 vs 0건 SL≥1)으로 확정. PsSetLoadImageNotifyRoutineEx + PS_IMAGE_NOTIFY_CONFLICTING_ARCHITECTURE 교체는 옳지만 실효가 제한적임을 인정하고, ProcessNotify 시점에 IL Gate(Medium/High이고 SL<8일 때만) → PE 임베디드 서명 PKCS#7 파싱 → CN prefix 'Microsoft' 매칭 → PROCESS_ENTRY 캐시하는 Stage 2 구조를 설계. 파일 이벤트당 비용 0, ProcessNotify 1회 PE 헤더 ~수 KB I/O로 한정.

WebView2 가상 호스트 매핑 — file:// → https://orange.local (OR-1404). orange.user.exe의 WebView2가 file:// 스킴을 opaque origin으로 취급해 같은 문서 안의 fragment navigation조차 cross-origin으로 분류되며 보안 경고가 source 라벨 두 개로 이중 출력되던 문제를, WebView2 가상 호스트 매핑(https://orange.local)으로 정식 origin (https, orange.local, 443) 튜플 위에서 풀었다. 프론트엔드 우회(createMemoryHistory, about:blank, v-if 가드)로는 스킴 정책을 제어할 수 없어 스킴 자체를 옮긴 정식 해결책.

orange.exe MS 시스템 프로세스 bSystem TRUE 정합 수정 (OR-1434). PROCESS_ENTRY.IsSystemProcess에 WsbClassifyBinary 결과가 정상 저장되는데도 Y_PROCESS_MESSAGE.bSystem이 false 하드코딩이라 svchost · explorer 같은 일반 시스템 프로세스가 PPL / 파일속성 단계 전까지 bSystem=false로 전달되던 정합 오류를 수정. ofilter CreateProcessMessage 오버로드 1·2 + Process.cpp ProcessStart 콜백 + yagent21 CProcess2.cpp 7개 사이트를 손봐 드라이버 경로 기반 판정 결과가 AddProcessCache 진입 시점부터 즉시 Current DB · Summary DB filelist · MQTT public/node/filelist까지 전파되도록 일원화. 3단계 false→true 단방향 승격 구조는 그대로 유지.

Live Agent 행 클릭 회귀 시리즈 — 무한 로딩 · SPROCESS_PROCESS 미발송 · 컬럼별 차등 동작 통합 (OR-1347, OR-1363, OR-1365). SUID/DUID만 있는 행을 클릭했을 때 빈 프로세스 탭이 무한 로딩으로 빠지던 회귀를 추적해 SPROCESS_PROCESS 발송 누락을 발견하고, 동시에 행 클릭 시 컬럼별 차등 동작을 사용자 일관성 관점에서 재설계 통합. 신규/오류/삭제 3개 탭이 동일한 MQTT 명령 흐름(get.{COMMAND}_SELECT_PERIOD 초기 요청 → T1 실시간 갱신, alert:false로 이탈 시 Agent 구독 해제)으로 동작하도록 정렬한 OR-1406 작업이 그 토대.


룰·분석 모델 설계

5월 한 달 GitHub · Jira 카운트는 9커밋 · 0건 완료지만 분석가 R&R이라 표면 수치에는 잡히지 않는다. 산출물은 분석서와 룰 명세다.

한 고객사 니즈 분석 — 49일 실측 + 국립대 전산실 통증 매핑 (OR-1420). 한 고객사가 우리 제품의 어떤 측면에서 가치를 느낄지 가설을 좁히기 위해, 49일치 매니저 실측 데이터(TCP 3,493 / DNS 2,574 / 비정상종료 268 / KillProcess 211 / CPU 점유 129건 등)와 7가지 데이터 축(시스템 · 앱 · 프로세스 · 네트워크 · 세션 · ETW + 메타데이터)을 정리. 동시에 국립대 전산실의 6가지 구조적 조건(인력 부족 · 예산 제약 · 인프라 5~6팀 분산 · 감사 부담 등)과 10가지 통증(헬프데스크 본업 잡아먹기 · 장애 원인 책임 회피 · 다운타임 즉시 정치 폭탄 등)을 명확히 분리. 영업의 카운터파트는 기능 매칭이 아니라 통증 매칭이라는 결론으로 다음 미팅 전략을 정렬.

자원사용률 계산을 어떻게 해야 할까? (OR-1334). '자원사용률'이라는 단어 한 단어가 사용자 / 매니저마다 다르게 해석되는 지점을 정리. 통계 정의를 통일하기 위한 작업의 초기 단계로, 룰·분석 모델 설계의 입력이 된다.


manager-web 위젯·CI 정면 진입

이 기간 manager-web의 위젯·CI·빌드 영역에 정면으로 손댔다.

조직관리 위젯 전반 개편 — 커스텀 DnD 3단계 + 사용자 CRUD 즉시 저장 + 색상 일관화 (OR-1447). 조직관리 위젯의 트리 DnD에서 @rc-component/tree 내장 DnD와 커스텀 DOM 이벤트가 혼용되어 초록/파랑 색상 충돌·드롭 위치 부정확하던 부분을 커스텀 div 렌더러로 전면 교체. 마우스 Y 위치 기준 before(상위 30%) / into(중간) / after(하위 30%) 3단계 위치 인식 + 최상위 노드 간 이동 차단을 메인 트리·편집 모달 양쪽에 동일 적용. 사용자 추가/수정/삭제는 메인 저장 버튼 거치지 않고 즉시 API 저장(트리 구조 변경 시에만 메인 저장 사용), 저장 후 window.location.reload() → reset() + organizationDataRefetch() + rebuildUserNodes()로 현재 화면 위치 유지. AG Grid 색상도 호버 #f3f4f6 · 선택 #ffedd5(연주황) · 체크박스/포커스 #f97316로 일관화, 이중 border·footer 높이(h-7 → h-8) 정렬까지 정리. 코드 정리 폭도 넓다(미사용 import · FormIf 9건 · 함수 6건 · useEffect 4건 제거, TS 0 에러).

FontAwesome Pro 제거 — 무료 버전 마이그레이션 + Menu 리팩토링 + yarn.lock 복구 (OR-1441 / OR-1230). 프로젝트의 FontAwesome Pro 의존성 5종(pro-duotone · pro-light · pro-regular · pro-solid · pro-thin)을 제거하고 @fortawesome/free-regular-svg-icons로 대체. ['far','cancel'] → ['fas','xmark'], ['fal','layer-group'] → ['fas','layer-group'] 같은 아이콘 치환을 코드 전 영역에 적용하고, CI/CD .github/workflows/release.yml · ci/develop · release Dockerfile에서 Pro 레지스트리 설정 제거 + .yarnrc.yml(nodeLinker: node-modules) 신설. PR 진행 중 손상된 yarn.lock도 복구.

OJT 계획(OR-1346)이 따로 잡혀 있던 R&R인데도 첫 주부터 PR이 머지 가능 수준까지 올라왔다.


한 달의 결

5월의 한 달은 '기존 인프라를 정리하면서 새 라인을 동시에 깐' 한 달이다.

  • 서버 인프라 본격 정리: 서버 백엔드 파트가 MongoDB single-node replica set 전환 → 조직도 단일 트랜잭션화 → Beanie bulk_writer 단일 RTT 적재의 인프라 사슬을 만들고, 그 위에 프로세스 영향도 위젯의 subscriber-driven publish 모델(Redis ZSET ZADD-only)을 4-레포 동시 머지로 안착시켰다. KISA 시큐어코딩 20건 Phase 1~3 마무리, fail2ban SSH + REST API 3181 HTTP jail, Service 계층 순환 import 정리까지 같은 달에 함께 정리.
  • 권한 체계 완성 + 신규 ML 라인 개시: 분석 서비스 파트가 매니저 RBAC 권한 체계 5축(엔드포인트·위젯·기능 ACL · 새 엔드포인트 알림 · 설정 영역 위젯→기본 전환)을 안정화 완료하고, 동시에 Baseline 일상 분류 서비스 Sprint 1+1.5(5조건 룰 + LightGBM-ready 18 피처 스키마)로 ML 진화 경로의 입구를 깔았다.
  • Manager·Agent 회귀 추적 + BSOD 분석: 매니저·에이전트 파트가 broker OpenSSL 3.0.x 잔여 경로(0A000123)의 크롬 148 트리거를 격리 비교로 확정하고 will aux abrupt close 우회로 매니저 책임 한계 안에서 해결, node-stats WebSocket 야간 유실에는 close 코드별 자동 복구 정책을 정립, MSI 노트북 BSOD 두 케이스(WSL2 + e1dn.sys D3)는 windbg !irp로 IRP 분석까지. ESLint no-shadow pre-commit 게이트로 11일 잠복 OR-1028 같은 사고를 도구 차원에서 차단. c3→c4 분리 잔재인 p18 Agent UI도 패키지에서 도려냈다.
  • 커널 드라이버 노이즈 4천만배 절감: 커널 드라이버 파트가 한 달 17조 회 누적 파일 이벤트를 분당 약 39만 건에서 4,320건(10분당 1회)으로, 시간 기반 throttle + 512-entry LRU array로 본격 적용. KeQueryUnbiasedInterruptTime으로 절전 시간을 카운트에서 제외한 디테일까지. SE_SIGNING_LEVEL의 OS-level 한계를 86건 실측으로 확정하고 PE Authenticode 추출 Stage 2 설계, WebView2 가상 호스트 매핑으로 file:// opaque origin 문제를 정식 origin 위에서 해결, Live Agent 행 클릭 회귀 시리즈(SUID/DUID-only · 무한 로딩 · 컬럼별 차등 동작 통합)도 함께.
  • 분석 가설 정렬 + manager-web 착수: 영업 카운터파트를 '기능 매칭이 아니라 통증 매칭'으로 재정렬했고, manager-web 조직관리 위젯 전반 개편(DnD 3단계 + AG Grid 색상 일관화)과 FontAwesome Pro 제거(빌드·CI 영역)를 머지 직전까지 끌어올렸다.