truffle-scan: 2초 안에 비밀 및 인젝션을 포착하는 결정적 보안 스캐너
출처: 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은 **빠르고, 결정