2025년 말쯤부터 AI가 개발 방식을 크게 바꿔놓았고, 소스코드 취약점 분석도 같은 방향으로 옮겨가고 있습니다.
AI 기반 소스코드 분석 도구 상당수가 코드를 LLM에 던지고 결과를 받는 패턴을 따릅니다. 그런데 이게 실제로 잘 동작하려면 LLM에게 "무엇을 봐야 하는지"를 알려주는 게 중요합니다. 그냥 코드베이스를 통째로 던지면 토큰만 태우고, 정작 봐야 할 지점은 놓치는 경우가 많거든요.
Noir v1에는 그 "무엇을 봐야 하는지"를 미리 추출해서 출력에 실어주는 두 플래그가 있습니다.
--include callee: 각 엔드포인트 핸들러의 1-hop 호출 그래프를callees배열로 추가--ai-context: 같은 callee 정보를 포함해 5개 보안 카테고리로 분류하고ai_context아래에 추가
--include-callee: 누가 누구를 호출하나
둘 중 더 단순한 쪽입니다. 엔드포인트 핸들러 본문을 tree-sitter로 파싱해서 1-hop callee의 {name, path, line}을 뽑습니다.
$ noir -b ./flask_app --include callee -f json
{
"url": "/sign",
"method": "POST",
"callees": [
{ "name": "get_hash", "path": "app/utils.py", "line": 3 },
{ "name": "User.query.filter", "path": "app/app.py", "line": 21 },
{ "name": "User", "path": "app/app.py", "line": 24 },
{ "name": "db_session.add", "path": "app/app.py", "line": 25 },
{ "name": "db_session.commit", "path": "app/app.py", "line": 26 }
]
}
LLM 입장에서는 "이 엔드포인트를 보려면 utils.py:3과 app.py:21,24,25,26을 같이 봐야 한다"는 hint가 됩니다. 토큰 효율도 좋습니다. 코드베이스 전체를 던질 필요 없이 핸들러와 참조된 몇 줄만 있으면 되니까요.
--ai-context: 같은 데이터를 보안 카테고리로 분류
--ai-context는 같은 callee 정보를 가져다가 추가 분석을 거쳐 5가지 카테고리로 분류합니다.
- guards: 인증 미들웨어 / 데코레이터 등 접근 제어
- callees: 위 callee와 동일하지만 snippet과 confidence가 함께 붙음
- sinks: SQL / command exec / file I/O / redirect / template render 같은 잠재적 sink
- validators: 입력 검증 호출
- signals:
state_change,credential_input,guard_absence같은 휴리스틱 신호
같은 Flask /sign POST 엔드포인트를 --ai-context로 돌리면
$ noir -b ./flask_app --ai-context -f json
{
"url": "/sign",
"method": "POST",
"ai_context": {
"sinks": [
{
"kind": "sql",
"name": "query",
"description": "Potential SQL/data-store sink inferred from code or callee name",
"path": "app/app.py", "line": 21, "confidence": 78,
"snippet": "20: password = get_hash(request.form['password'], ...) | 21: if User.query.filter(...).first(): | 22: return render_template('error.html')"
}
],
"signals": [
{ "kind": "state_change", "name": "POST", "confidence": 88 },
{ "kind": "credential_input", "name": "form.password", "confidence": 86 },
{ "kind": "guard_absence", "name": "POST", "confidence": 28,
"description": "No auth guard was detected for this state-changing endpoint." }
]
}
}
이 엔드포인트 하나로 LLM이 받는 정보를 정리하면
credential_input이 있다 (password를 form으로 받음)state_change다 (POST)guard_absence다 (auth 데코레이터 안 보임)sqlsink가 있다 (User.query.filter)
사람 리뷰어든 LLM이든 이 네 신호가 한곳에 모여 있다면 자연스럽게 "이 핸들러 먼저 보세요"라는 결론이 나옵니다. credential을 다루는 엔드포인트에 auth까지 없다면 더 따질 것도 없이 1순위죠.
반대로 auth가 잡힌 케이스도 봅니다 (flask_auth fixture 출력)
{
"url": "/profile",
"method": "GET",
"ai_context": {
"guards": [{
"kind": "auth_guard",
"name": "flask-login login_required",
"description": "Protected by flask-login login_required",
"confidence": 86,
"snippet": "12: | 13: @login_required | 14: @app.route('/profile') | 15: def profile():"
}]
}
}
@login_required 데코레이터가 잡혀서 guards에 들어갔습니다. 이건 LLM에게 "이 엔드포인트는 auth가 걸려 있으니 다른 류의 취약점에 집중하라"는 negative signal로도 쓰입니다.
동시에 hint이자 sink
이 두 플래그를 동시에 hint이자 sink로 쓸 수 있다는 게 핵심입니다.
- Hint로: 엔드포인트 단위로 컨텍스트를 미리 추려 LLM의 attention을 좁혀줍니다. "전체 코드베이스" 대신 "이 핸들러 + 이 파일들"만 보면 됩니다.
- Sink로(source-to-sink의 그 sink):
sinks카테고리는 데이터 흐름의 종착점 후보를 framework-aware하게 알려줍니다. 일반 LLM도User.query.filter가 SQL이라는 것 정도는 추론할 수 있지만, 매번 토큰을 태워야 합니다. Noir가 미리 라벨링해주면 그 추론 단계를 건너뛸 수 있습니다.
특히 framework-aware라는 점이 큽니다. 같은 query라는 callee 이름이라도 컨텍스트에 따라 의미가 다릅니다.
- Flask + SQLAlchemy:
User.query.filter→ SQL sink - Express + MongoDB:
User.find→ NoSQL sink - 그냥 generic LLM은 이 차이를 모르거나 매번 추론을 다시 합니다
Noir는 detector 단계에서 framework를 식별하고, augmentor가 그 framework의 idiom에 맞춰 sink / guard 패턴을 적용합니다. LLM이 받는 건 raw 코드가 아니라 이미 framework-aware하게 정리된 context입니다.
권장 사용법
AI 기반 코드 리뷰 파이프라인을 짠다면
# 모든 카테고리
noir -b ./app --ai-context -f json
# 필요한 카테고리만 (token 절약)
noir -b ./app --ai-context=guards,sinks,signals -f json
# callee만 (가벼운 hint)
noir -b ./app --include callee -f json
전체 카테고리는 첫 audit 때 한 번 돌리는 정도가 적당합니다. 이후 incremental review나 PR 단위에서는 guards,sinks,signals만으로도 충분한 경우가 많습니다.
다음 계획
--ai-context의 sink / guard 패턴은 현재 정규식 기반 휴리스틱입니다. 데이터 흐름 자체를 따라가지는 않고, callee 이름과 코드 패턴으로 "여기 sink 같은데" 정도까지만 표시합니다. 이건 의도된 trade-off예요. 정확한 taint analysis는 전용 도구가 더 잘하고, Noir는 그 도구나 LLM에게 focus point를 빠르게 던지는 역할에 충실하려고 합니다.
앞으로는 패턴 카탈로그를 더 framework-aware하게 확장하고 signal 종류도 늘릴 계획입니다. 피드백이나 새 패턴 제안은 언제나 환영합니다.