static-analysis

4 개의 포스트

AST로 Outdated 없는 퍼널 문서 만들기 (새 탭에서 열림)

토스팀은 복잡한 판매자 입점 퍼널을 효율적으로 관리하기 위해 코드를 정적으로 분석하여 자동으로 업데이트되는 퍼널 문서를 구축했습니다. 기존의 수동 문서는 빈번한 코드 변경을 따라가지 못해 실효성을 잃었으나, `ts-morph`와 AST(Abstract Syntax Tree) 분석을 통해 코드와 100% 일치하는 흐름도를 생성할 수 있게 되었습니다. 이를 통해 개발자는 복잡한 조건부 분기와 페이지 이동 로직을 별도의 문서 작업 없이도 시각적으로 정확하게 파악할 수 있게 되었습니다. **수동 문서화의 한계와 정적 분석의 선택** * **문서의 파편화:** 수기로 작성된 다이어그램은 코드가 수정될 때마다 즉시 업데이트되지 않아 실제 동작과 문서가 불일치하는 'Outdated' 문제가 발생합니다. * **복잡한 분기 처리의 어려움:** 수십 개의 페이지와 80여 개의 조건부 분기를 시각적 도구로 일일이 표현하는 것은 휴먼 에러의 위험이 크고 관리가 불가능합니다. * **정적 분석 채택:** 런타임 분석은 모든 경로를 직접 실행해야 하는 번거로움이 있지만, AST를 활용한 정적 분석은 코드를 텍스트로 읽어 모든 잠재적 경로를 빠르고 안전하게 추출할 수 있습니다. **Navigation Edge 데이터 구조 설계** * **맥락 정보 포함:** 단순한 이동 경로(A → B)를 넘어, 이동 방식(`push` vs `replace`), 실행 조건, 쿼리 파라미터 등의 상세 데이터를 포함하는 `NavigationEdge` 인터페이스를 설계했습니다. * **추적 가능성 확보:** 코드 내 정확한 위치(`lineNumber`)와 호출 출처(`sourceType`)를 저장하여 다이어그램에서 실제 소스 코드로 즉시 연결될 수 있는 기반을 마련했습니다. **AST를 활용한 로직 추출 및 조건문 파싱** * **패턴 감지:** `ts-morph`를 사용하여 프로젝트 내 페이지 파일을 탐색하고, `router.push()` 또는 `router.replace()`와 같은 함수 호출 패턴을 감지합니다. * **상위 노드 추적:** 특정 이동 로직이 발견되면 AST의 부모 방향으로 거슬러 올라가 가장 가까운 `if`문이나 삼항 연산자의 텍스트를 추출함으로써 이동 조건(Condition)을 파악합니다. **커스텀 훅 및 URL 상수의 역추적** * **숨은 로직 탐색:** 페이지 컴포넌트 내부뿐만 아니라, `import` 구문을 분석하여 커스텀 훅 내부에 숨겨진 이동 로직까지 추적하여 데이터 누락을 방지합니다. * **상수 해독:** `URLS.FUNNEL.PAY_METHOD`와 같이 상수로 정의된 목적지를 실제 URL 경로로 변환하기 위해 상수 정의 파일을 별도로 파싱하여 매핑 테이블을 구축했습니다. **실용적인 결론** 복잡도가 높은 서비스일수록 문서와 코드의 동기화는 자동화되어야 합니다. `ts-morph`와 같은 도구를 활용해 소스 코드를 단일 진실 공급원(Single Source of Truth)으로 삼는 자동화 문서를 구축하면, 불필요한 커뮤니케이션 비용을 줄이고 퍼널 전체의 비즈니스 로직을 명확하게 시각화할 수 있습니다.

LLM을 활용한 대규모 악성 풀 리퀘스트 탐지 (새 탭에서 열림)

Datadog은 AI 코딩 어시스턴트의 도입으로 급증한 코드 작업량과 이로 인한 보안 취약점 문제를 해결하기 위해, LLM 기반의 실시간 코드 리뷰 시스템인 'BewAIre'를 구축했습니다. 이 시스템은 기존의 정적 분석 도구가 탐지하기 어려운 공격자의 의도와 교묘한 난독화 패턴을 추론하여, 매주 수만 건에 달하는 풀 리퀘스트(PR)를 실시간으로 검사합니다. 이를 통해 보안 팀의 리뷰 피로도를 대폭 줄이면서도 높은 정확도로 악성 코드 삽입을 차단하는 성과를 거두고 있습니다. **AI 시대의 코드 보안 위협과 한계** * **급증하는 코드량과 공격 표면:** AI 어시스턴트 활용으로 매주 약 10,000개의 PR이 생성되면서 보안 팀이 검토해야 할 범위가 기하급수적으로 늘어났습니다. * **정적 분석의 한계:** 기존 SAST 도구는 정해진 규칙 기반으로 작동하여, 정상적인 의존성 업데이트나 권한 변경으로 위장한 지능형 공격(예: tj-actions 해킹)의 '의도'를 파악하지 못합니다. * **교묘한 난독화 기법:** 공격자들은 Base64 인코딩을 사용해 악성 스크립트를 숨기거나, 신뢰할 수 있는 봇 계정을 도용하여 보안 검사를 우회하는 전략을 사용합니다. **LLM 기반 보안 시스템 'BewAIre'의 구조** * **데이터 전처리 및 컨텍스트 강화:** 모든 PR의 코드 차이점(Diff)을 추출하고 작성자 정보, 저장소 유형 등의 메타데이터를 결합하여 모델이 분석할 수 있는 형태로 정규화합니다. * **의도 중심의 추론(Inference):** LLM은 단순한 문법 검사를 넘어 수정 사항 뒤에 숨겨진 의도를 분석하며, 특정 변경이 악의적인지 아니면 정상적인 패턴인지 분류합니다. * **실시간 경보 체계:** 분석 결과는 Datadog 보안 신호로 변환되어 대시보드에 즉시 반영되며, 위험도가 높은 경우 보안 엔지니어에게 즉각적인 알림(Paging)을 전송합니다. **실전 검증을 통한 성능 및 성과** * **높은 탐지 정확도:** 수백 개의 PR 데이터셋을 테스트한 결과 99.3% 이상의 정확도를 기록했으며, 실제 발생했던 tj-actions 및 Nx 공격 사례를 100% 탐지해냈습니다. * **낮은 오탐률(False Positive):** 프롬프트 엔지니어링과 데이터 튜닝, 안전한 패턴에 대한 억제 규칙을 적용하여 오탐률을 0.03% 수준으로 유지하며 개발 속도 저하를 방지했습니다. * **맥락 제한 극복:** 모델의 컨텍스트 제한으로 인한 성능 저하를 방지하기 위해 데이터를 효율적으로 정제하고 프롬프트를 최적화하는 기술적 노하우를 적용했습니다. 대규모 개발 환경에서 보안을 유지하려면 인간 리뷰어의 피로도를 줄여줄 수 있는 지능형 자동화 도구가 필수적입니다. LLM을 보안 리뷰에 도입할 때는 단순한 코드 분석을 넘어 작성자의 의도와 주변 맥락을 함께 파악하도록 설계해야 하며, 이를 기존의 보안 모니터링 워크플로우에 통합함으로써 실질적인 방어 체계를 구축할 수 있습니다.

How we migrated our static analyzer from Java to Rust (새 탭에서 열림)

Datadog은 정적 분석 도구의 성능 병목 현상을 해결하고 제한된 CI 환경에서의 효율성을 극대화하기 위해 기존 Java 기반의 엔진을 Rust로 완전히 재작성했습니다. 이 과정에서 ANTLR 대신 Tree-sitter를 도입하고 JavaScript 규칙 실행 엔진을 GraalVM에서 Deno(V8)로 교체함으로써, 분석 속도는 3배 향상시키고 메모리 사용량은 10배 절감하는 성과를 거두었습니다. 결과적으로 이번 전환은 고성능 정적 분석을 위해 언어와 런타임 수준의 근본적인 변화가 필수적이었음을 보여줍니다. **Java 기반 정적 분석기의 한계와 환경적 제약** * **CI 자원 최적화 문제:** 고객의 CI 환경(예: GitHub Actions)에서 분석기를 실행할 때, 2코어 및 7GB RAM과 같은 제한된 자원 내에서 Java 분석기는 수천 개의 파일을 스캔하는 데 5분 이상 소요되어 목표치(3분 이내)를 충족하지 못했습니다. * **환경 충돌 및 오버헤드:** Java 기반 분석기는 최신 JVM(17+)을 요구하는데, 이는 고객의 기존 CI 환경에 설치된 Java 버전과 충돌을 일으키거나 불필요한 설정 부담을 주었습니다. * **파싱 성능 저하:** 기존에 사용하던 ANTLR은 대규모 저장소에서 파싱 속도가 느렸고, 특정 언어에 대한 지원이 부분적이라는 기술적 한계가 있었습니다. **Tree-sitter 도입과 Rust로의 전환 결정** * **파서 교체:** 성능 향상을 위해 C로 구현되어 속도가 빠르고 오픈소스 커뮤니티가 활발한 Tree-sitter를 채택했습니다. * **Java 라이브러리의 한계:** Tree-sitter용 Java 라이브러리는 분석기 최적화에 필수적인 패턴 매칭 기능을 지원하지 않는 등 기능이 제한적이었습니다. * **언어 선택의 기로:** Tree-sitter가 가장 견고하게 지원하는 언어가 Rust라는 점에 착안하여, 예측 가능한 성능을 위해 Rust로의 전체 재작성이라는 도전적인 경로를 선택했습니다. **Rust 기반의 새로운 아키텍처 구성** * **AST 구축(Tree-sitter):** Rust는 Tree-sitter 생태계의 "일등 시민(First-class citizen)"으로, 직접적인 라이브러리 연동을 통해 별도의 Java 바인딩 유지보수 없이도 강력한 파싱 기능을 확보했습니다. * **자바스크립트 규칙 실행(Deno):** 정적 분석 규칙은 자바스크립트로 작성되는데, 기존 Java의 GraalVM 대신 Deno(deno-core)를 런타임으로 도입했습니다. * **보안 및 효율성:** Deno의 V8 엔진을 활용하되, 분석 규칙이 디스크나 네트워크에 접근하지 못하도록 `deno-core` 크레이트만 통합하여 보안이 강화된 샌드박스 환경을 구축했습니다. **마이그레이션 결과 및 기술적 권고** 성공적인 Rust 전환을 통해 동일한 프로그램에 대해 Java 버전과 일치하는 분석 결과를 보장하면서도 성능은 3배, 메모리 효율은 10배 개선되었습니다. 특히 리소스가 제한된 CI/CD 파이프라인에서 정적 분석 도구를 운영해야 한다면, JVM과 같은 무거운 런타임보다는 Rust와 같이 저수준 제어가 가능하고 메모리 오버헤드가 적은 언어를 선택하는 것이 장기적으로 유리합니다.

C++ 빌드 시간 단축하기 (새 탭에서 열림)

피그마(Figma)는 C++ 코드베이스가 10% 증가할 때 빌드 시간이 50%나 급증하는 문제를 해결하기 위해, 컴파일러로 전송되는 데이터 양(바이트)을 줄이는 전략을 채택했습니다. 하드웨어 업그레이드나 캐싱만으로는 한계가 있음을 깨닫고, 불필요한 헤더 포함을 자동으로 찾아내고 방지하는 자체 도구인 'DIWYDU'와 'includes.py'를 개발하여 빌드 시간을 절반으로 단축했습니다. 결과적으로 빌드 시간의 핵심 지표가 전처리 후의 바이트 수에 비례한다는 점을 입증하며 대규모 개발 환경에서의 생산성을 확보했습니다. ### 헤더 포함 방식과 빌드 속도의 상관관계 * C++ 컴파일 과정에서 전처리기(Pre-processor)는 소스 파일에 포함된 모든 헤더 파일을 하나의 거대한 파일로 합치며, 이는 전이적 의존성(Transitive dependency)을 포함해 컴파일러가 처리해야 할 바이트 수를 기하급수적으로 늘립니다. * 피그마의 분석 결과, 실제 추가된 코드량보다 전처리 후 컴파일러로 전달되는 바이트 수의 증가 폭이 훨씬 컸으며, 이것이 빌드 시간 지연의 주요 원인으로 파악되었습니다. * 대형 파일에서 불필요한 헤더를 수동으로 제거하는 실험을 진행한 결과, 컴파일 바이트는 31%, 콜드 빌드 시간은 25% 감소하며 가설이 증명되었습니다. ### DIWYDU: 불필요한 헤더 제거 자동화 * 구글의 IWYU(Include What You Use)가 너무 엄격하여 적용이 어렵자, 피그마는 더 유연한 자체 도구인 DIWYDU(Don’t Include What You Don’t Use)를 개발했습니다. * 이 도구는 `libclang`의 파이썬 바인딩을 사용하여 추상 구문 트리(AST)를 분석하며, 특정 파일이 포함한 헤더에서 함수, 타입, 변수 등을 직접적으로 사용하는지 확인합니다. * 직접적인 의존성이 없는 헤더를 찾아내어 삭제하도록 플래그를 표시함으로써 모든 기능 브랜치에서 빌드 속도 저하를 방지합니다. * 다만, STL(표준 템플릿 라이브러리)의 프라이빗 헤더 구조나 `libclang` 파이썬 바인딩의 AST 노드 접근 제한(UNEXPOSED_EXPR 등)과 같은 기술적 한계는 존재합니다. ### includes.py를 통한 회귀 방지 및 측정 * 헤더를 실제로 사용하더라도 파일 크기가 너무 커서 빌드 속도를 늦추는 경우를 대비해, 전이적 바이트 수를 측정하는 `includes.py`를 구축했습니다. * Clang을 사용하지 않고 순수 파이썬으로 작성되어 실행 속도가 매우 빠르며(수 초 내외), CI(지속적 통합) 시스템에서 각 PR이 빌드 시간에 미치는 영향을 바이트 단위로 측정합니다. * 특정 PR이 컴파일 바이트 수를 과도하게 늘릴 경우 경고를 발생시켜 개발자가 전방 선언(Forward Declaration)을 사용하거나 헤더를 분리하도록 유도합니다. * 표준 라이브러리는 피그마 내부의 래퍼(Wrapper) 디렉토리를 통해 관리되므로, 표준 헤더의 바이트는 계산에서 제외하여 효율성을 높였습니다. C++ 프로젝트의 빌드 속도를 유지하기 위해서는 단순한 캐싱을 넘어 컴파일러가 처리하는 데이터의 총량을 관리해야 합니다. 불필요한 헤더 의존성을 제거하는 자동화 도구를 CI 파이프라인에 통합하고, '컴파일 바이트 수'를 성능 지표로 모니터링하는 것이 대규모 코드베이스의 개발 효율을 높이는 실질적인 방안이 될 수 있습니다.