truffle-scan: 2초 안에 비밀 및 인젝션을 포착하는 결정적 보안 스캐너

발행: (2026년 6월 9일 PM 02:56 GMT+9)
9 분 소요
원문: Dev.to

출처: Dev.to

AI 코드 생성이 그 어느 때보다 많은 프로덕션 코드를 만들어내고 있습니다. GitHub Copilot, ChatGPT, Claude — 이들은 모두 우리의 일상 업무 흐름에 녹아들었습니다. 그런데 아무도 이야기하지 않는 사실이 하나 있습니다:

AI 모델은 보안 실수를 그대로 재현합니다.

이 모델들은 오픈소스 생태계에서 학습했으며, 그 생태계는 수십 년 동안 같은 실수를 반복해 왔습니다 — 하드코딩된 API 키, SQL 인젝션, eval 호출, pickle 역직렬화 등. AI는 “틀렸다”는 것을 알지 못합니다. 단지 훈련 데이터에 이런 패턴이 있었기 때문에 그 패턴이 타당해 보인다고 판단할 뿐입니다.

그때 truffle-scan이 등장합니다.

pip install truffle-scan
truffle-scan .

A security scanner that’s deterministic (no ML), fast (under 2 seconds for most projects), and aims for zero false positives.

왜 또 다른 보안 스캐너가 필요할까?

보안 도구는 많이 있습니다: Bandit, Semgrep, SonarQube, Snyk. 각각의 역할은 훌륭합니다. 하지만 몇 가지 공통적인 문제점이 있습니다:

  • Bandit은 파이썬 전용이며 대규모 코드베이스에서는 느립니다
  • Semgrep은 강력하지만 커스텀 규칙을 만들기 위한 학습 곡선이 가파릅니다
  • Snyk은 전체 기능을 사용하려면 SaaS 계정이 필요합니다
  • 대부분의 도구는 노이즈—즉, 팀이 무시하게 되는 거짓 양성—를 많이 생성합니다

truffle-scan은 단순하고, 빠르며, 의견이 명확한 접근 방식을 취합니다.

  • 한 번의 명령으로 전체 프로젝트 스캔
  • 결정론적 패턴 매칭 — 휴리스틱도, ML도 없으며 거짓 양성이 없습니다
  • 1초 미만 또는 저지연 스캔 (47개 파일 프로젝트에서 183 ms)
  • CI 친화적인 JSON 출력 및 발견 시 비정상 종료 코드 제공
  • 우선순위가 매겨진 액션 플랜 — 먼저 중요한 문제부터 해결

탐지 대상

truffle-scan은 파이썬, JavaScript/TypeScript, Go를 아우르는 6가지 카테고리로 규칙을 구성합니다.

🔴 Credentials (자격 증명)

레포에 절대 포함돼서는 안 되는 하드코딩된 비밀:

  • AWS Access Keys (AKIA...)
  • GitHub 토큰 (ghp_..., gho_...)
  • Stripe API 키 (sk_live_..., pk_live_...)
  • 개인 키 (RSA, EC, DSA)
  • 일반 비밀번호 및 API 비밀

🔴 Code Execution (코드 실행)

임의의 코드 실행을 허용하는 함수:

  • eval() — 파이썬 고전 함정
  • exec() — 같은 위험, 다른 이름
  • Function() 생성자 (JavaScript)
  • os.system() — 쉘 인젝션 위험
  • subprocess.call(..., shell=True) — 동일 문제

🟠 Deserialization (역직렬화)

불안전한 역직렬화는 원격 코드 실행으로 이어질 수 있습니다:

  • pickle.loads() / pickle.load()
  • yaml.load() (안전 로더 없이)

🟡 Injection (인젝션)

검증되지 않은 사용자 입력이 위험한 싱크로 전달될 때:

  • Go에서 원시 SQL 쿼리 (db.Raw(...))
  • innerHTML 할당 (JavaScript XSS)
  • document.write() (XSS)
  • 검증되지 않은 request.args / request.form

🔵 Crypto & Quality (암호화 및 품질)

  • 보안에 민감한 상황에서 Math.random() 사용
  • 과도하게 긴 라인 (>100자)
  • 깊은 중첩 (depth > 4)
  • TODO / FIXME 마커

내부 동작 방식

truffle-scan은 이중 전략을 사용합니다.

AST Analysis (Python)

파이썬 파일에 대해서는 추상 구문 트리를 파싱하고 함수 호출을 순회합니다. 정규식보다 구조를 이해하기 때문에 더 정확합니다.

# scanner.py (simplified)
def _check_python_ast(self, filepath, code, lines, rule):
    tree = ast.parse(code, filename=filepath)
    for node in ast.walk(tree):
        if isinstance(node, ast.Call):
            func_name = self._get_call_name(node)
            if pattern in func_name:
                # 매치 발견 — Finding 생성
                findings.append(Finding(
                    severity=rule.severity,
                    message=rule.message,
                    file=filepath,
                    line=node.lineno,
                    rule_id=rule.rule_id,
                    snippet=lines[node.lineno - 1].strip(),
                    recommendation=rule.recommendation,
                ))
    return findings

_get_call_name 메서드는 os.system이나 pickle.loads 같은 점 표기 이름을 AST 속성 체인을 따라 해석합니다. 따라서 import os; os.system(...) 은 잡아내면서, 근처에 있는 system 변수는 오탐하지 않습니다.

Regex Patterns (JS/Go/General)

JavaScript, TypeScript, Go 및 다언어 패턴에 대해서는 정교하게 만든 정규식을 사용합니다. 각 규칙은 데이터클래스로 정의됩니다.

@dataclass
class Rule:
    rule_id: str
    name: str
    severity: Severity
    category: str
    message: str
    pattern: str
    language: str
    is_regex: bool = True
    recommendation: str = ""
    confidence: float = 1.0

규칙에는 confidence 필드가 있어, 설정된 임계값 이하의 패턴은 무시해 노이즈를 낮춥니다.

Parallel Scanning

스캐너는 ThreadPoolExecutor를 이용해 파일을 병렬로 스캔합니다 (기본 8 워커).

with ThreadPoolExecutor(max_workers=self.max_workers) as pool:
    fut_map = {}
    for fp in files:
        lang = language or self._detect_language(fp)
        if not lang:
            continue
        fut = pool.submit(self._scan_file, str(fp), lang)
        fut_map[fut] = fp

    for fut in as_completed(fut_map):
        file_result = fut.result()
        if file_result:
            for finding in file_result.findings:
                result.add(finding)
            result.lines_scanned += file_result.lines_scanned

숨김 파일, __pycache__, node_modules, 빌드 아티팩트 등은 기본적으로 건너뜁니다.

Risk Scoring (위험 점수)

각 발견에는 심각도가 부여됩니다. 전체 프로젝트 점수(0–100)는 다음과 같이 계산됩니다.

@property
def score(self) -> int:
    if not self.findings:
        return 0
    raw = sum(f.severity.score_value() for f in self.findings)
    capped = min(raw * 5, 100)
    return capped

심각도 값: CRITICAL=4, HIGH=3, MEDIUM=2, LOW=1, INFO=0.

판정은 다음과 같습니다:

  • 0–14: ✅ 안전
  • 15–39: ⚠️ 사소한 문제
  • 40–69: 🔍 검토 필요
  • 70–100: 🚨 위험

시작하기

설치

pip install truffle-scan

그게 전부입니다. 파이썬 표준 라이브러리 외에 다른 의존성이 없습니다.

프로젝트 스캔

# 현재 디렉터리 스캔
truffle-scan .

# 특정 디렉터리 스캔
truffle-scan /path/to/your/project

# 자세한 출력 — 코드 스니펫 포함 모든 발견 표시
truffle-scan . --verbose

# CI 파이프라인용 JSON 출력
truffle-scan . --format json

# 우선순위가 매겨진 수정 계획 받기
truffle-scan . --plan

예시 출력

========================================================
  Truffle Security Scan Report
========================================================

  Verdict   : 🚨 Dangerous
  Score     : 75/100
  Files     : 47
  Duration  : 183ms

  Issues by severity:
    🔴 Critical: 2
    🟠 High: 5
    🟡 Medium: 3

========================================================
  🚨 Dangerous. Found 10 issues across 47 files.
========================================================

액션 플랜 모드

--plan 플래그는 단순히 발견을 나열하는 것을 넘어 어떤 문제를 먼저 해결해야 할지 알려줍니다.

========================================================
  📋 Truffle Action Plan
========================================================

  🔴 Critical — 즉시 수정
  ─────────────────────────────────────────────────────
    • Hardcoded AWS Access Key ID
      config/aws_credentials.py:42  (GEN001)
      💡 이 키를 즉시 교체하고 AWS Secrets Manager에 저장하세요.

    • Arbitrary code execution via eval()
      scripts/process.py:108  (EVAL001)
      💡 eval 사용을 중단하고 안전한 파싱 로직으로 교체하세요.

  🟠 High — 빠른 시일 내에 해결
  ─────────────────────────────────────────────────────
    • Hardcoded GitHub token
      utils/github.py:27  (GH001)
      💡 토큰을 재생성하고 환경 변수 또는 비밀 관리 서비스에 저장하세요.

    • Insecure pickle deserialization
      data/loader.py:55  (PICKLE001)
      💡 pickle 대신 json 또는 안전한 직렬화 포맷을 사용하세요.

  🟡 Medium — 차후에 해결
  ─────────────────────────────────────────────────────
    • Long lines (>100 chars)
      src/main.py:73  (STYLE001)
      💡 라인을 100자 이하로 리팩터링하세요.

    • TODO comments left in production code
      services/payment.py:12  (TODO001)
      💡 남은 TODO를 정리하거나 이슈 트래커에 등록하세요.

이와 같이 truffle-scan은 **빠르고, 결정

0 조회
Back to Blog

관련 글

더 보기 »

Eidentic 소개

Today we're releasing Eidentic, an open-source TypeScript SDK for building AI agents with self-improving memory and the production fundamentals built in — not b...

Typescript의 타입

Introdução Tipos são uma forma de definir a “forma” ou o contrato dos dados que estamos usando no código. Pensando em Javascript puro, ele é dinâmico: você pode...