TrendShift 딥다이브 · 2026-07-13 · Daily #3

dcg (Destructive Command Guard) 딥다이브
— AI 에이전트가 rm -rf를 치기 직전, 서브밀리초 만에 가로막는 Rust 방패

dcgAI 코딩 에이전트(Claude Code·Codex·Gemini·Copilot 등)가 파괴적인 셸/git 명령을 실행하기 직전에 가로채 차단하는 고성능 훅(hook)이다. git reset --hard, rm -rf ./src, DROP TABLE users 같은 명령이 커밋 안 된 작업을 수초 만에 날리는 사고를, 명령이 실행되기 전 정규식·AST 패턴 매칭으로 붙잡는다. 순수 Rust로 쓰였고 SIMD 가속 + Aho-Corasick quick-reject로 판정 지연이 1밀리초 미만이라, 매 명령마다 끼어들어도 개발 흐름을 체감상 방해하지 않는다. 이 문서는 "위험한 명령 목록"을 외우기 위한 게 아니라, 어떻게 셸 명령을 안전/위험으로 실시간 분류하는 엔진을 Rust로 설계했는가를 공부하기 위한 자료다 — 다층 필터링, 컨텍스트 분류, 오탐 최소화, fail-open 설계가 핵심. (저장소: Dicklesworthstone/destructive_command_guard · 순수 Rust · MIT + OpenAI/Anthropic Rider · v0.6.6 · TrendShift 오늘 Daily #3)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 지금 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트
  7. 하드웨어 · 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

이름부터 뜯어보기 — Destructive Command Guard = "파괴적 명령 경비원"

dcg"AI 에이전트가 실행하려는 셸 명령을 실행 직전에 검사해, 파괴적이면 막고 안전하면 통과시키는 경비원(guard)"이다. 이름 그대로 Destructive Command Guard의 약자다. 에이전트가 매 명령을 실행하기 전 dcg를 PreToolUse 훅으로 호출하면, dcg는 명령 문자열을 받아 위험도를 판정하고 "허용/차단"을 응답한다.

한 문장 비유

"위험물 반입 검색대를 AI 에이전트의 손끝에 붙인 것"

공항 보안 검색대는 승객이 게이트를 지나기 전에 짐을 스캔해, 위험물이면 통과를 막고 안전하면 조용히 보내준다. dcg는 이 검색대를 AI 에이전트가 실행하는 모든 셸 명령 앞에 세워둔 것이다. ls·cat 같은 평범한 명령은 눈치도 못 챌 속도로 통과시키고, rm -rf /·git push --force 같은 파괴적 명령만 붙잡아 "왜 위험한지 + 안전한 대안"까지 알려준다.

중요한 건 검색대가 절대 승객 줄을 멈추지 않는다는 원칙이다 — dcg는 판정이 애매하거나 시간이 초과되면 무조건 통과시킨다(fail-open). 안전 도구가 오히려 작업을 마비시키는 사태를 설계로 막았다.

핵심 용어
PreToolUse 훅 (Pre-Tool-Use Hook)
Claude Code 같은 에이전트가 도구(예: 셸 명령 실행)를 쓰기 직전에 외부 프로그램을 호출해 "이 도구 써도 되나?"를 물어보는 확장 지점. dcg는 바로 이 자리에 꽂혀, 명령을 stdin으로 JSON으로 받고 "deny(차단)" 또는 조용한 허용을 응답한다. 에이전트 코드를 고칠 필요 없이 훅 설정 파일 한 줄로 붙는다.
핵심 용어
Fail-Open (실패 시 열림)
시스템이 오류·타임아웃 등으로 정상 판정을 못 하면 차단이 아니라 통과를 택하는 설계. 보안 도구는 보통 "막고 보자"(fail-closed)가 안전하지만, dcg는 개발 흐름을 절대 마비시키지 않는 것을 최우선으로 두어 반대 선택을 했다. "가끔 놓치더라도 개발자를 방해하지 않는다"는 철학.

2왜 지금 주목받는가

TrendShift Daily #3 · "AI가 코드를 날렸다"는 공포에 대한 실용적 답

2025~2026년 코딩 에이전트가 폭발적으로 퍼지면서, "에이전트가 자율적으로 git reset --hard를 쳐서 하루치 작업이 사라졌다"는 사고담이 커뮤니티에 쌓였다. dcg가 트렌딩에 오른 건 이 구체적이고 흔한 공포를, 에이전트를 갈아치우지 않고 기존 에이전트에 훅 하나로 덧붙여 해결하기 때문이다.

주목 포인트 1 — 에이전트를 안 바꾼다, 앞에 방패만 세운다

dcg는 새 에이전트도, 새 셸도 아니다. Claude Code·Codex·Gemini·Copilot·Cursor·Aider 등 이미 쓰는 도구의 훅 자리에 끼어들 뿐이다. 설치 스크립트가 각 에이전트의 설정 파일(~/.claude/settings.json 등)에 dcg 훅을 기존 훅을 보존한 채 병합해준다. 도입 비용이 "명령 한 줄"이라는 점이 확산의 결정적 이유다.

주목 포인트 2 — 안전 도구인데 개발을 안 막는다 (속도 + fail-open)

매 명령마다 끼어드는 검사기는 느리면 곧바로 버려진다. dcg는 Aho-Corasick 키워드 quick-reject로 비파괴 명령 99% 이상을 정규식 없이 O(n) 한 패스로 걸러내 서브밀리초를 달성했고, 파싱 오류·타임아웃 시 무조건 통과(fail-open)라 "dcg 때문에 막혔다"가 원천적으로 안 생긴다. 안전과 무마찰(frictionless)을 동시에 잡은 설계.

주목 포인트 3 — "grep은 통과, 실행은 차단"하는 문맥 이해

단순 문자열 매칭이면 grep "rm -rf" log.txt처럼 rm -rf가 데이터로 등장하는 안전한 명령까지 막아 오탐 지옥이 된다. dcg는 모든 토큰을 Executed(실행됨)·Data(따옴표 문자열)·Comment(주석)·HeredocBody 등으로 분류해, 실제로 실행되는 파괴적 명령만 잡는다. 오탐을 줄이는 이 문맥 분류가 실사용 만족도의 핵심이다.

주목 포인트 4 — 50개 이상의 보안 팩 + heredoc/인라인 스크립트 탐지

git·파일시스템은 기본으로 막고, 그 위에 DB(PostgreSQL·MySQL)·Docker·Kubernetes·AWS/GCP/Azure·Terraform 등 50+ 팩을 옵트인으로 켤 수 있다. 게다가 python -c "import os; os.remove('/etc/x')"처럼 인라인 스크립트나 heredoc 안에 숨은 파괴 행위까지 AST(tree-sitter)로 파고든다. "명령줄만 본다"는 얕은 검사기와 근본적으로 다른 지점.

비교 축dcg의 선택흔한 대안
도입 방식기존 에이전트에 훅으로 첨부에이전트 자체 교체 / 수동 승인
속도서브밀리초 (SIMD·Aho-Corasick)LLM 재질의(수백 ms~초)
오판 시Fail-open (개발 안 막음)Fail-closed 또는 무보호
문맥 이해토큰 분류 + AST로 실행부만 검사단순 substring 매칭(오탐 多)
커버리지git·fs 기본 + 50+ 팩 옵트인rm -rf 정도만 하드코딩
덧붙임 — "LLM에게 물어보면 되지 않나?"

"위험한지 LLM에게 다시 물어보면 되잖아?"라고 생각하기 쉽다. 하지만 그건 느리고(수백 ms~초), 비싸고, 비결정적이다. dcg는 판정을 결정론적 정규식·AST 규칙으로 하기 때문에 빠르고, 재현 가능하고, 오프라인에서 돈다. "매 명령마다" 끼어들려면 이 속도와 결정성이 필수다 — LLM 게이트로는 불가능한 자리다.

3기술 스택 전체 지도

순수 Rust · 이중 정규식 엔진 · tree-sitter AST · MCP 서버까지

dcg는 package.json도 pyproject.toml도 없는 순수 Rust 프로젝트다(edition 2024, Rust 1.85+). 바이너리명은 dcg 하나이고, 런타임 의존성이 전혀 없는 단일 정적 바이너리로 배포된다. 흥미로운 기원: Jeffrey Emanuel의 Python 스크립트에서 출발 → Darin Gordon(Dowwie)이 성능을 위해 Rust로 포팅 → 다시 모듈형 팩 시스템·AST 탐지로 대폭 확장된 2인 릴레이 프로젝트다.

핵심 크레이트 — "빠른 판정"을 위한 도구 선택

영역크레이트역할 / 왜 골랐나
정규식(백트래킹)fancy-regexlookahead/lookbehind가 필요한 패턴용 (예: (?!--staged))
정규식(선형)regex (RegexSet)safe 패턴 다수를 한 번에 선형시간 검사
빠른 필터aho-corasick여러 위험 키워드를 하나의 오토마톤으로 묶어 O(n) quick-reject
SIMD 검색memchrsubstring 탐색을 CPU SIMD로 가속
AST 매칭ast-grep-core + tree-sitterbash·python·js·ts·ruby·go·php 스크립트 내부 파싱
설정toml · serde_yaml · schemarsconfig.toml + 외부 팩 YAML + JSON Schema 생성
CLI/TUIclap · ratatui · inquire · indicatif서브커맨드·대화형 설정·진행바
히스토리fsqlite (FrankenSQLite, FTS5)차단/허용 이력을 전문검색 가능한 SQLite로 기록
MCP 서버rust-mcp-sdk + tokiodcg 자신이 MCP 서버로도 동작
보안sha2 · hmac · self_updateallow-once 단축코드 하드닝 · 서명 검증 자동 업데이트
핵심 용어
Aho-Corasick (아호-코라식)
여러 개의 검색어를 하나의 상태 기계(오토마톤)로 합쳐, 긴 문자열을 딱 한 번만 훑으면서 그중 어떤 검색어가 들어있는지 동시에 찾아내는 고전 알고리즘. dcg는 rm·git·dd·docker 같은 위험 키워드 전부를 여기에 넣고, 명령에 위험 키워드가 하나도 없으면 즉시 통과시킨다. 비싼 정규식은 키워드가 걸렸을 때만 돌린다.
핵심 용어
tree-sitter / AST 매칭
소스 코드를 추상 구문 트리(AST)로 파싱하는 라이브러리. dcg는 python -c "..."의 따옴표 안이나 heredoc 안에 든 진짜 코드를 tree-sitter로 파싱해, os.remove(...)·shutil.rmtree(...) 같은 파괴 함수 호출을 잡는다. 명령줄 표면만 보는 게 아니라 코드 구조 수준에서 위험을 탐지하는 것.

정규식을 왜 두 개나 쓰나 — 성능/기능 분리

이게 dcg 스택에서 가장 배울 점이다. 정규식 엔진에는 트레이드오프가 있다: Rust의 regex 크레이트는 선형시간을 보장하지만 lookahead/lookbehind를 못 쓴다. 반대로 fancy-regex는 그걸 쓸 수 있지만 백트래킹이라 최악의 경우 느려질 수 있다. dcg는 둘을 용도별로 나눠 쓴다 — 백트래킹이 필요 없는 다수의 safe 패턴은 regex::RegexSet으로 한 방에, lookahead가 꼭 필요한 소수만 fancy-regex로.

4아키텍처 심화 분석

4단계 파이프라인 · 팩 구조 · 컨텍스트 분류 · 3-tier heredoc 탐지

dcg의 핵심은 딱 하나의 질문에 답하는 것이다: "이 명령 문자열, 실행해도 되나?" 그 판정이 어떻게 흐르는지 전체 그림부터 보자. 에이전트가 stdin으로 JSON을 넣어주면 dcg가 파이프라인을 태우고, stdout으로 허용/차단 결정을, stderr로 사람용 컬러 경고를 뱉는다.

┌──────────────────────────────────────────────────────────────┐ │ AI 에이전트: 셸 명령 실행 시도 │ │ 예) {"tool":"Bash","command":"git reset --hard HEAD~3"} │ └───────────────────────────┬──────────────────────────────────┘ ▼ stdin (JSON payload) ┌───────────────────────────────────────────┐ │ ① JSON 파싱 (hook.rs) │ │ 에이전트별 포맷(Claude/Codex/Copilot…) │ │ → 명령 문자열 추출. 셸 아니면 즉시 허용 │ └───────────────┬───────────────────────────┘ ▼ ┌───────────────────────────────────────────┐ │ ② 정규화 (normalize) │ │ /usr/bin/git → git (절대경로 제거) │ └───────────────┬───────────────────────────┘ ▼ ┌───────────────────────────────────────────┐ │ ③ Quick-Reject (Aho-Corasick, O(n)) │ │ 위험 키워드(rm·git·dd…) 없으면 → 통과 ✅ │ │ (비파괴 명령 99%+ 가 여기서 조기 종료) │ └───────────────┬───────────────────────────┘ ▼ 키워드 걸림 → 정밀 검사 ┌───────────────────────────────────────────┐ │ ④ 패턴 매칭 (evaluator.rs) │ │ safe 패턴 먼저(match=허용) │ │ → destructive 패턴(match=차단+설명) │ │ → 둘 다 no-match=허용 │ │ + heredoc/인라인 스크립트 AST 스캔 │ └───────────────┬───────────────────────────┘ ▼ ┌──────────────────────────────────────────────────────┐ │ 차단 → stdout에 deny JSON + stderr에 컬러 경고 │ │ "왜 위험한지 + 안전한 대안" 제시 │ │ 허용 → 조용히 exit 0 │ │ 타임아웃/오류 → 무조건 허용 (Fail-Open) │ └──────────────────────────────────────────────────────┘

4-1. 4단계 파이프라인 — "싼 검사 먼저, 비싼 검사 나중"

설계 철학은 단계별 조기 종료(short-circuit)다. 대부분의 명령(ls·cat·echo)은 위험 키워드가 없으니 ③번 Aho-Corasick에서 정규식 한 번 안 돌리고 통과한다. 비싼 정규식·AST 검사는 키워드가 걸린 소수의 명령에만 적용된다. 이 순서 덕에 "매 명령마다" 끼어들어도 서브밀리초가 나온다.

비유 — 검색대의 2단계

공항 검색대도 모든 짐을 열어보지 않는다. 먼저 금속 탐지기(빠름)를 통과시키고, 삐 소리가 난 사람만 정밀 수색(느림)을 한다. dcg의 Aho-Corasick이 금속 탐지기, 정규식·AST가 정밀 수색이다. 99%는 삐 소리 없이 지나간다.

4-2. 팩(Pack) 구조 — 규칙을 모듈로 쪼갠다

모든 탐지 규칙은 이라는 단위로 묶인다. core.git·core.filesystem은 항상 켜지고, database.postgresql·containers.docker·cloud.aws 같은 50+ 팩은 옵트인이다. 각 팩은 아래 구조를 갖는다:

// src/packs/mod.rs — 팩 하나의 뼈대 (개념 요약)
pub struct Pack {
    id: &str,                      // "core.git"
    keywords: &[&str],             // Aho-Corasick에 넣을 트리거 단어
    safe_patterns: &[SafePattern], // 먼저 검사 → 걸리면 "허용"
    destructive_patterns: &[DestructivePattern], // 걸리면 "차단"
    keyword_matcher: AhoCorasick,  // 팩별 quick-reject 오토마톤
    safe_regex_set: RegexSet,      // safe 패턴 선형시간 일괄 검사
}

pub struct DestructivePattern {
    regex: LazyCompiledRegex,      // 처음 쓸 때만 컴파일(지연)
    reason: &str,                  // "왜 위험한가" 설명
    severity: Severity,            // Critical / High / Medium / Low
    suggestions: &[PatternSuggestion], // 안전한 대안 제시
}

safe 패턴을 destructive보다 먼저 검사하는 순서가 핵심이다. 예를 들어 git checkout -b feature(새 브랜치, 안전)는 safe 패턴에 먼저 걸려 통과하고, git checkout -- file.txt(작업 내용 폐기, 위험)는 safe에 안 걸린 뒤 destructive에 걸려 차단된다. "허용을 먼저 확정"하는 whitelist-first 방식이라 오탐이 줄어든다.

4-3. 컨텍스트 분류(SpanKind) — 오탐을 죽이는 핵심

같은 rm -rf / 문자열이라도, 그게 실행되는지 vs 데이터로 등장하는지는 하늘과 땅 차이다. dcg는 명령의 모든 토큰을 다음처럼 분류한다:

SpanKind의미검사 여부
Executed실제로 실행되는 명령 부분반드시 검사
InlineCode-c/-e 플래그 뒤의 코드반드시 검사(AST로 escalate)
Data작은따옴표 문자열 등 데이터건너뛸 수 있음
HeredocBodyheredoc(<<EOF) 본문Tier2/3 스캔으로 escalate
Comment# 뒤 주석건너뜀
Unknown애매한 토큰보수적으로 Executed 취급

그래서 grep "rm -rf" install.logrm -rfData로 분류돼 통과하고, 진짜 rm -rf /varExecuted라 차단된다. 애매하면 Executed로 취급(안전 우선)하는 보수적 기본값이 zero-false-negative(놓침 없음) 철학을 받친다.

4-4. 3-tier heredoc/인라인 탐지 — 표면 밑을 판다

진짜 위험한 명령은 종종 다른 언어 코드 속에 숨는다. python3 -c "import shutil; shutil.rmtree('/data')"는 명령줄만 보면 그냥 python 실행이다. dcg는 이걸 3단계로 판다:

Tier 1 정규식 → 명령줄 표면의 파괴 패턴 (rm -rf 등) Tier 2 heredoc 추출 → <<EOF … EOF 본문을 꺼내 재검사 Tier 3 AST (tree-sitter) → -c/-e 인라인 코드를 파싱, os.system()·subprocess.run(["sh","-c",...]) 같은 sink 함수 호출을 재귀 탐색 ⏱ 20ms hard timeout → 초과 시 ALLOW(fail-open)
배울 점 — 성능 예산(deadline budget)
AST 검사에 20ms 상한을 걸고, 넘으면 통과시킨다

AST 파싱은 정규식보다 무겁다. dcg는 ast_matcher.rs20ms hard timeout을 걸어, 이상하게 복잡한 입력으로 파싱이 오래 걸리면 차단이 아니라 허용으로 빠져나온다. 매 파이프라인 단계마다 deadline_exceeded를 확인하는 이 "시간 예산" 패턴은, 안전 도구가 절대 개발 흐름을 인질로 잡지 않게 하는 실전 설계다.

셸 위장(obfuscation) 대응

공격자(혹은 에이전트의 실수)는 r''m -rf, r\m -rf처럼 글자 사이에 빈 따옴표·백슬래시를 끼워 정규식을 우회하려 할 수 있다. dcg는 contains_shell_word_obfuscation으로 이런 위장을 탐지해 quick-reject 단계에서 정규식 검사로 escalate한다. 실제 이슈(#121·#124)에서 배운 정규식 세그먼트 가두기([^\s&;|`()<>]+로 셸 메타문자를 만나면 멈추기)도 오탐 방지에 쓰인다.

4-5. 실제 탐지 규칙 맛보기 (core.git · core.filesystem)

규칙이 추상적으로 느껴질 테니, 항상 켜지는 두 팩의 실제 패턴을 보자. core.git은 비활성화 자체가 불가능하다.

팩 / 이름정규식(발췌)심각도
git · reset-hardreset\s+--hardCritical
git · push-forcepush\s+…--force / -[a-zA-Z]*fCritical
git · checkout-discardcheckout\s+--\s+High
git · clean-forceclean\s+(?:-[a-z]*f|--force)High
git · stash-drop/clearstash\s+(drop|clear)High
fs · rm-rf-generalrm\s+-[a-zA-Z]*[rR][a-zA-Z]*fHigh
fs · dd / shred / truncate디스크·파일 파괴 계열High

여기서 rm-rf-general 정규식이 특히 영리하다. 단순히 rm -rf만 잡는 게 아니라 rm\s+-[a-zA-Z]*[rR][a-zA-Z]*f — 즉 플래그 글자 사이에 다른 글자가 끼어도(-rvf, -fr, -Rf 등) 전부 잡는다. --recursive --force 분리형, find -delete, unlink까지 별도 패턴으로 커버한다.

// src/packs/core/filesystem.rs — 실제 매크로 (발췌)
destructive_pattern!(
    "rm-rf-general",
    r"rm\s+-[a-zA-Z]*[rR][a-zA-Z]*f|rm\s+-[a-zA-Z]*f[a-zA-Z]*[rR]",
    "rm -rf is destructive and requires human approval...",
    High,
);

반대로 safe 패턴도 세심하다. rm -rf /tmp/*, rm -rf $TMPDIR/*처럼 임시 디렉토리 청소는 명시적으로 허용 목록에 있어서 오탐을 안 낸다. git 쪽도 checkout -b(새 브랜치), restore --staged(스테이징 취소, 파일은 안전), clean --dry-run은 전부 safe로 통과시킨다.

5디렉토리 구조 해부

src/ 아래 ~15만 라인 Rust — "어디에 뭐가 있나"
destructive_command_guard/ ├── src/ │ ├── main.rs # 진입점 (바이너리명 dcg) │ ├── cli.rs # clap 서브커맨드 정의 (~17.9k줄) │ ├── evaluator.rs # 핵심 평가 파이프라인 (~6.7k줄) │ ├── hook.rs # 에이전트별 훅 입출력 프로토콜 │ ├── context.rs # SpanKind 컨텍스트 분류 │ ├── heredoc.rs # heredoc/인라인 스크립트 추출 │ ├── ast_matcher.rs # tree-sitter AST 매칭 (20ms 예산) │ ├── allowlist.rs # 허용목록 · 인스펙션 래퍼 예외 │ ├── config.rs # TOML 설정 · 오버라이드 │ ├── scan.rs # dcg scan (CI/리포 스캔, extractor 기반) │ ├── agent.rs # 에이전트 감지(환경변수 기반) │ ├── history/schema.rs # SQLite 히스토리(FTS5) │ └── packs/ │ ├── mod.rs # 팩 레지스트리 · destructive_pattern! 매크로 │ ├── core/git.rs # 항상 활성 · git 파괴 패턴 │ ├── core/filesystem.rs# 항상 활성 · rm/dd/shred… │ ├── database/* # postgresql·mysql (옵트인) │ ├── containers/* # docker·kubernetes (옵트인) │ └── cloud/* # aws·gcp·azure·terraform (옵트인) ├── tests/ │ ├── corpus/*.toml # true_positive / false_positive / bypass_attempts │ └── repro_*.rs # 개별 버그 재현 회귀 테스트 ├── benches/ # criterion 벤치마크 ├── action/ # GitHub Action ├── docs/ # ADR · 팩별 문서 · JSON schema ├── install.sh (~3.4k줄) · install.ps1 # 플랫폼별 설치기(서명 검증) ├── config.schema.json · Cargo.toml · rust-toolchain.toml

구조를 읽는 요령: "판정 로직"은 evaluator.rs + packs/, "에이전트와의 대화"는 hook.rs + agent.rs, "오탐 방지 두뇌"는 context.rs + ast_matcher.rs. 이 다섯 파일이 시스템의 90%다.

정독 우선순위파일배우는 것
★★★src/evaluator.rs4단계 파이프라인 · 조기 종료 · deadline 예산
★★★src/packs/mod.rs팩 구조 · 매크로 DSL · lazy 정규식 컴파일
★★★src/context.rsSpanKind 토큰 분류 — 오탐 제거의 핵심
★★src/ast_matcher.rstree-sitter로 인라인 코드 파고들기 + timeout
★★src/hook.rs에이전트별 wire 포맷(Claude vs Codex 차이)
tests/corpus/*.toml안전 도구를 코퍼스로 검증하는 법

6학습 포인트

이 레포에서 무엇을 훔쳐 배울 것인가 — 기술별로

다층 필터링으로 "매 요청마다"의 지연을 죽이기

"모든 입력에 끼어드는" 시스템(방화벽·WAF·린터·검색대)의 보편적 교훈. 싼 검사(Aho-Corasick) → 비싼 검사(정규식) → 더 비싼 검사(AST) 순으로 조기 종료를 설계하면, 99%의 평범한 입력이 무거운 검사에 도달하지 않는다. 이 short-circuit 사고는 언어·도메인 불문 응용 가능하다.

정규식 엔진의 트레이드오프를 실무에서 다루기

선형시간 regex(RegexSet) vs 백트래킹 fancy-regex용도별로 섞어 쓰는 실전 사례. "lookahead가 꼭 필요한가?"를 패턴마다 따져 엔진을 고르는 감각은, 정규식 성능 문제(ReDoS 포함)를 만나는 모든 개발자에게 값지다.

파싱으로 오탐 죽이기 — 표면 매칭의 한계

substring/정규식만으로는 grep "rm -rf"rm -rf를 구별 못 한다. dcg는 토큰 문맥 분류(SpanKind) + AST 파싱으로 "표면이 아니라 의미"를 본다. 코드 리뷰 도구·SAST·문법 하이라이터 등 "코드를 이해해야 하는" 모든 도구에 그대로 적용되는 발상.

Fail-open vs Fail-closed — 상황에 맞는 실패 방향

보안 교과서는 보통 "막고 보자(fail-closed)"를 가르치지만, dcg는 개발 생산성이 최우선인 맥락에서 의도적으로 fail-open을 골랐다. "우리 시스템이 실패하면 어느 쪽으로 실패해야 하나?"를 비즈니스 맥락으로 판단하는 성숙한 엔지니어링 사례.

안전 도구를 코퍼스로 검증하기

tests/corpus/에 true_positives(잡아야 할 것)·false_positives(잡으면 안 될 것)·bypass_attempts(우회 시도)를 TOML로 분리하고, 버그마다 repro_*.rs 회귀 테스트를 남긴다. "탐지기가 스스로를 어떻게 검증하는가"의 모범 사례 — 안티바이러스·스팸필터·린터 만드는 사람 필독.

주의 — 이건 "만능 방패"가 아니다
dcg는 보조 안전망이지, 백업을 대체하지 않는다

dcg는 알려진 파괴 패턴을 막는 도구다. 새로운 우회 기법, 팩에 없는 명령, 애매해서 fail-open으로 빠진 케이스는 놓칠 수 있다(README도 이를 명시). 진짜 안전망은 여전히 git 커밋·백업이고, dcg는 "실수가 재앙이 되기 전에 붙잡는 한 겹"일 뿐이다. 이 도구를 믿고 백업을 소홀히 하면 본말전도다.

실습 아이디어 (가볍게)

레포를 클론해 tests/corpus/의 TOML을 열어보라. "이건 왜 false_positive(막으면 안 됨)로 분류됐지?"를 하나씩 읽으면, 실무에서 어떤 명령이 오탐을 유발하는지에 대한 직관이 생긴다. 그다음 dcg explain "git reset --hard"를 실제로 돌려 설명·대안 출력을 확인해보라.

7하드웨어 · 시스템 요구사항

단일 정적 바이너리 — 런타임 의존성 0
항목요구사항
플랫폼Linux · macOS · Windows(WSL 및 네이티브 PowerShell)
런타임 의존성없음 (단일 정적 바이너리 dcg)
메모리/CPU사실상 무시할 수준 (매 명령마다 서브밀리초 실행 후 종료)
설치 방식install.sh(curl 파이프) / install.ps1 — 아키텍처 자동감지 + SHA256/cosign 서명 검증
소스 빌드 시Rust nightly (rust-toolchain.toml로 핀 고정, edition 2024)
설정 파일~/.config/dcg/config.toml (dcg init으로 생성, 없어도 기본 팩 동작)
지원 에이전트Claude Code · Codex CLI · Gemini CLI · Copilot CLI · Cursor · Hermes · Grok · Aider(git hook) 등

설치 원리: 설치 스크립트가 GitHub Releases에서 플랫폼별 tar.xz를 받아 체크섬(있으면 cosign 서명까지) 검증 후, 각 에이전트의 훅 설정 파일에 dcg를 PreToolUse 훅 맨 앞에 병합한다. 기존 훅은 보존하고, 손상된(malformed) JSON은 덮어쓰기를 거부하는 방어적 설치가 특징이다.

주의 — curl | bash 패턴
설치 명령의 신뢰
README의 설치법은 curl … | bash 형태다. 이 패턴은 편하지만 원격 스크립트를 그대로 실행하는 것이라, 원칙적으로는 스크립트를 먼저 내려받아 눈으로 확인한 뒤 실행하는 게 안전하다. dcg가 서명 검증을 넣은 것도 이 신뢰 문제를 의식한 설계다. (역설적이지만, 파괴 명령을 막는 도구조차 설치 단계에서는 이 신뢰 결정을 요구한다.)

8직접 해볼 수 있는 실습 과제

dcg를 "쓰는" 것을 넘어, 그 설계를 "재현"해 보는 과제들
난이도 ★ — 읽기 실습

1. 코퍼스로 규칙 감각 익히기

레포를 git clone --depth 1로 받아 tests/corpus/의 TOML을 읽고, "잡아야 할 명령(true_positive)"과 "막으면 안 될 명령(false_positive)"을 각각 10개씩 손으로 옮겨 적어보라. 안전/위험의 경계가 어디인지 몸으로 익히는 연습.

난이도 ★★ — 미니 구현

2. 2단계 필터 최소 재현 (Python/Node)

위험 키워드 리스트로 1차 quick-reject(키워드 없으면 즉시 통과)를 만들고, 걸린 것만 정규식으로 정밀 판정하는 미니 가드를 100줄로 짜보라. ls·echo 같은 명령이 정규식에 도달조차 안 하는 걸 로그로 확인할 것.

난이도 ★★ — 문맥 분류

3. "grep은 통과, 실행은 차단" 구현

과제 2를 확장해, 따옴표 안 문자열은 Data로 보고 건너뛰도록 간단한 토큰 분류를 추가하라. grep "rm -rf" a.log는 통과하고 rm -rf /tmp/x는 차단되면 성공. 셸 인용 규칙을 파싱하는 어려움을 체감할 것.

난이도 ★★★ — 훅 연동

4. 진짜 Claude Code 훅으로 붙여보기

과제 3의 가드를 stdin으로 JSON을 받아 {"permissionDecision":"deny", ...}stdout으로 뱉는 형태로 감싸, Claude Code의 PreToolUse 훅에 등록하라. 실제로 위험 명령이 막히는지 확인. (안전한 테스트 명령으로만!)

난이도 ★★★ — AST 파고들기

5. 인라인 스크립트 sink 탐지

tree-sitter(또는 언어 내장 AST)로 python -c "..."의 코드를 파싱해, os.remove·shutil.rmtree 같은 파괴 함수 호출을 탐지하라. 파싱에 타임아웃을 걸고 초과 시 fail-open으로 빠지는 것까지 구현하면 dcg의 Tier3를 재현한 것.

9관련 기술 심화 학습 로드맵

dcg를 완전히 소화하기 위한 4주 코스
주차주제학습 내용
1주차정규식 & 문자열 알고리즘정규식 엔진의 원리(NFA/DFA vs 백트래킹), ReDoS, Aho-Corasick·memchr 같은 다중 패턴/SIMD 검색. dcg의 packs/core/*.rs 패턴 읽기 + 과제 2.
2주차셸 파싱 & 문맥 분류셸 인용·이스케이프·heredoc 문법, 토큰화, SpanKind 같은 문맥 태깅. context.rs·heredoc.rs 정독 + 과제 3.
3주차AST & tree-sitter추상 구문 트리, tree-sitter 쿼리, sink 함수 재귀 탐색, 파싱 타임아웃. ast_matcher.rs 정독 + 과제 5.
4주차에이전트 훅 & 배포Claude Code/Codex 훅 프로토콜 차이, JSON wire 포맷, 서명 검증 설치, GitHub Action. hook.rs·install.sh 읽기 + 과제 4.
학습 순서 팁
"정규식 → 파싱 → AST → 훅" 순으로

먼저 빠른 패턴 매칭(1주차)을 확실히 잡아라. 그게 되면 왜 그것만으로 부족한지(오탐)가 보이고, 그때 문맥 분류·AST(2~3주차)의 필요성이 자연스럽게 이해된다. 훅 연동(4주차)은 "어떻게 에이전트에 붙느냐"의 배관 작업이라 맨 뒤로 미뤄도 된다.

10핵심 키워드 사전

문서에 나온 용어를 한눈에
용어
PreToolUse 훅
에이전트가 도구를 쓰기 직전 외부 프로그램에게 "써도 되나?"를 묻는 확장 지점. dcg가 꽂히는 자리.
용어
팩 (Pack)
탐지 규칙을 묶은 모듈. core.git·core.filesystem은 항상 켜지고, DB·Docker·클라우드 등 50+ 팩은 옵트인.
용어
SpanKind (컨텍스트 분류)
명령의 각 토큰을 Executed·Data·Comment·HeredocBody 등으로 분류하는 것. grep "rm -rf"를 통과시키는 오탐 방지의 핵심.
용어
Quick-Reject
Aho-Corasick으로 위험 키워드가 하나도 없는 명령을 정규식 없이 즉시 통과시키는 1차 필터. 비파괴 명령 99%+가 여기서 조기 종료.
용어
Fail-Open
오류·타임아웃 시 차단이 아니라 통과를 택하는 설계. dcg가 개발 흐름을 절대 마비시키지 않게 하는 원칙.
용어
Heredoc
셸에서 <<EOF … EOF로 여러 줄 텍스트를 명령에 넘기는 문법. 파괴 명령이 여기 숨을 수 있어 dcg가 본문을 추출해 재검사한다.
용어
AST / tree-sitter
코드를 추상 구문 트리로 파싱하는 것. dcg는 python -c 등 인라인 코드를 파싱해 os.remove 같은 파괴 함수 호출을 잡는다.
용어
RegexSet (선형시간 정규식)
Rust regex 크레이트의 기능. 여러 패턴을 한 번에 선형시간으로 검사하되 lookahead는 못 쓴다. dcg는 safe 패턴 다수를 여기서 일괄 검사.
용어
Deadline Budget (시간 예산)
각 검사 단계에 시간 상한(예: AST 20ms)을 두고, 초과하면 fail-open으로 빠져나오는 패턴. 안전 도구가 개발을 인질로 잡지 않게 한다.
용어
DCG_BYPASS / allow-once
사용자가 의도적으로 차단을 우회하는 escape hatch. DCG_BYPASS=1 명령은 그 한 번만, 차단 메시지의 단축코드로 dcg allow-once도 가능.

11참고 링크

공식 소스와 함께 볼 자료
구분링크 / 위치
GitHub 저장소github.com/Dicklesworthstone/destructive_command_guard
TrendShifttrendshift.io/repositories/40043
핵심 파일src/evaluator.rs (4단계 평가 파이프라인)
핵심 파일src/packs/mod.rs · packs/core/git.rs · filesystem.rs (탐지 규칙)
핵심 파일src/context.rs (SpanKind 문맥 분류)
핵심 파일src/ast_matcher.rs · heredoc.rs (인라인 스크립트 탐지)
핵심 파일src/hook.rs · agent.rs (에이전트 훅 프로토콜)
검증tests/corpus/*.toml (true/false positive 코퍼스)
연관 개념Aho-Corasick · tree-sitter · Rust regex/fancy-regex · MCP 훅