AI 컨텍스트

guards, callees, sources, sinks, validators, signals를 모아 엔드포인트별로 제공하는 AI 리뷰 컨텍스트입니다.

Noir는 각 엔드포인트에 구조화된 AI 리뷰 컨텍스트를 붙일 수 있습니다. LLM 기반 SAST 도구, 코드 리뷰어, 보안 에이전트가 라우트를 검토할 때 필요한 정적 신호를 한곳에 모아 주므로, 저장소를 직접 훑지 않아도 됩니다.

--ai-context로 켭니다.

noir scan . --ai-context

버킷 이름을 쉼표로 구분해 넘기면 필요한 카테고리만 남습니다. 필터가 데이터 자체에 적용되므로 JSON, SARIF, YAML, Postman, OAS 출력도 plain 출력과 같은 선택을 따릅니다.

noir scan . --ai-context=guards,sinks       # auth + sink, 연결된 시그널 포함
noir scan . --ai-context=callee             # 1-hop handler callee만
noir scan . --ai-context=all                # "전부"의 명시적 형태
noir scan . --ai-context                    # bare 형태, 동일하게 "전부"

쓸 수 있는 이름: guards, callee, sources, sinks, validators, signals(그리고 all). 대소문자는 구분하지 않습니다.

파생 시그널은 근거 버킷과 연결된 채로 남습니다. signals를 선택하면 그 시그널에 필요한 증거(예: ssrf → outbound_http sink)가 함께 유지되고, 증거 버킷만 선택하면 그에 연결된 시그널만 남습니다(예: guards,sinks에도 authz_absence가 남음). 이렇게 남은 시그널은 자기 근거도 함께 가져오므로 guards,sinks를 골라도 sources가 나올 수 있습니다. 여러 버킷을 묶어 판단하는 priority_review는 선택에 signals가 있을 때만 나옵니다(인자 없는 --ai-context와 all에는 포함됩니다).

plain 출력에서는 컨텍스트가 있는 엔드포인트마다 ai_context: 블록이 붙습니다. 구조화된 출력 형식에서는 같은 구조가 다음 위치에 들어갑니다.

출력 형식 AI 컨텍스트 위치
JSON / JSONL / YAML / TOML endpoints[].ai_context
OpenAPI 2.0 / 3.0 오퍼레이션 단위 x-noir-ai-context 확장
SARIF result.properties.noir.ai_context
Postman 아이템 설명 뒤에 덧붙임
cURL / HTTPie / PowerShell / only-url / only-param 본래 출력이 바뀌지 않도록 일부러 넣지 않음

무엇이 들어가나

각 버킷은 kind, name, 선택적인 source, description, path, line, confidence, snippet을 가진 항목 배열입니다. 버킷은 다음과 같습니다.

버킷 의미
guards 라우트에 감지된 인증/인가 게이트(미들웨어, 데코레이터, requires_auth, 역할 체크 등)
callees --include callee로 모으는 1-hop 핸들러 callee. 한곳에서 볼 수 있도록 AI 컨텍스트 안에도 다시 넣습니다
sources 핸들러에 도달하는 공격자 제어 입력. 요청 파라미터·헤더·쿠키·GraphQL 필드와 모바일 딥링크 페이로드가 여기 들어갑니다. 검토할 만한 서버 측 값 출처(Spring @Value 시크릿, WebSocket CORS 정책 등)도 함께 기록됩니다
sinks 핸들러 본문이나 callee 이름에서 추론된 위험 가능 동작(SQL, 명령 실행, 역직렬화, 템플릿 렌더링, 파일 I/O, 리다이렉트 등)
validators 입력 검증/정제 신호(스키마 검증기, 파라미터 형 변환, 허용 목록 패턴). sink 위험을 줄여 주는 단서
signals 그 외 라우트 형태 힌트(가드 없는 상태 변경 메서드, object-level 인가 점검이 필요한 path-id 사용, 파일 업로드 동작 등)

버킷마다 나올 수 있는 kind는 버킷별 kind에 정리했습니다.

목록은 찾을 수 있는 만큼 찾은 결과입니다. 항목마다 휴리스틱 신뢰도가 있어서 받는 쪽에서 기준값으로 거를 수 있습니다. 출력이 너무 커지지 않도록 버킷당 최대 16개 항목만 남깁니다.

비어 있지 않은 컨텍스트에는 "untrusted_fields": ["name", "path", "snippet"]도 함께 들어갑니다. 이 필드는 스캔한 저장소에서 그대로 가져온 값이라, snippet에는 그 코드를 작성한 쪽의 주석과 문자열 리터럴이 포함됩니다. 이 출력을 LLM에 넘길 때는 이 필드를 신뢰할 수 없는 데이터로 다루고 지시문과 분리하세요.

이 구조는 JSON Schema로 schemas/ai-context.schema.json에 공개되어 있어, 검증기나 안정적인 형식이 필요한 프롬프트에서 그대로 쓸 수 있습니다. 신뢰할 수 없는 필드에는 "x-noir-untrusted": true가 붙어 있습니다. 항목의 kind 값은 열린 어휘라서 새 값이 추가되어도 스키마는 바뀌지 않습니다.

항목 읽는 법

Phoenix 앱의 엔드포인트 하나를 --ai-context=guards,sinks로 좁힌 결과입니다. 뒤쪽 스니펫은 줄였습니다.

DELETE /settings/keys/:id
  ○ path: id
  ○ tags: idor auth
  ○ ai_context:
    - guards:
      * auth_guard: Phoenix :account pipeline [elixir_auth] (./lib/app_web/router.ex:36) (conf 86) - Protected by Phoenix :account pipeline :: 34: pipe_through [:browser, :account] | 36: delete "/keys/:id", PageController, :delete_key | 37: end
    - sources:
      * request_input: path.id [param] (./lib/app_web/router.ex:36) (conf 88) - Route path parameter supplied by the caller. :: ...
    - sinks:
      * redirect: redirect [callee] (./lib/app_web/controllers/page_controller.ex:43) (conf 74) - Potential redirect sink inferred from code or callee name :: ...
    - signals:
      * authz_absence: /settings/keys/:id [heuristic] (./lib/app_web/router.ex:36) (conf 44) - Authenticated endpoint uses a path identifier with no authorization check detected; review for horizontal / vertical privilege escalation. :: ...

한 줄의 형식은 kind: name [source] (path:line) (conf N) - description :: snippet입니다. authz_absence 시그널은 guards에 연결되어 있어서 따라 나왔고, 자기 근거인 sources도 함께 가져왔습니다. 같은 가드를 JSON으로 보면 이렇습니다.

{
  "kind": "auth_guard",
  "name": "Phoenix :account pipeline",
  "source": "elixir_auth",
  "description": "Protected by Phoenix :account pipeline",
  "path": "./lib/app_web/router.ex",
  "line": 36,
  "confidence": 86,
  "snippet": "34: pipe_through [:browser, :account] | 36: delete \"/keys/:id\", PageController, :delete_key | 37: end"
}
필드 내용
kind 항목의 종류. 버킷별 종류는 아래 표에 있습니다
name 구체적인 대상. callee 표현식, <type>.<name> 형태의 파라미터(query.id, json.password), 가드 이름, 라우트 등
source Noir가 이 항목을 찾은 경로(아래 참고)
description 왜 살펴볼 만한지 한 문장으로 적은 설명
path, line 근거의 위치. callee는 호출 지점, 가드는 그것을 선언한 미들웨어나 어노테이션, 나머지는 대부분 라우트를 가리킵니다
confidence 0부터 100. 휴리스틱끼리의 상대적인 순위이며 확률이 아닙니다
snippet line 주변 소스 몇 줄. 줄 번호를 붙여 |로 잇고 약 240자에서 자릅니다

source 값은 다음 중 하나입니다.

source 의미
route, detector 라우트 자체, 그리고 그 라우트를 보고한 기술
param 분석기가 추출한 파라미터에서 읽음
param_tagger:<tagger> 태거가 파라미터에 붙인 태그(param_tagger:Hunt)
callee 1-hop callee의 이름이나 그 호출 줄에서 매칭
route_source 라우트 근처 핸들러 소스 텍스트에서 매칭
heuristic 같은 컨텍스트의 다른 항목에서 파생
http_method, realtime_method HTTP 메서드, 또는 WebSocket 메시지 핸들러에서 읽음
mobile_deep_link 모바일 딥링크나 외부에 공개된 컴포넌트
endpoint, resolver 아웃바운드 클라이언트 선언, 그리고 오퍼레이션에 연결된 GraphQL 리졸버
태거나 분석기 이름 해당 태거(Admin, spring_auth, go_security)나 분석기(nginx_analyzer, odoo_analyzer)가 붙인 태그

--ai-context를 켜면 callee 수집이 켜지고 모든 태거가 실행됩니다. 둘 다 컨텍스트의 재료이기 때문입니다. --use-taggers로 태거를 지정하면 지정한 것만 실행됩니다.

버킷별 kind

현재 Noir가 내보내는 kind입니다.

guards

Kind 의미
auth_guard 인증. 호출자가 누구인지 확인합니다(로그인 필수, 세션 확인, JWT나 API 키 검증)
authz_guard 인가. 호출자가 무엇을 할 수 있는지 확인합니다(역할, 권한, 정책, 소유자 비교)
csrf_guard CSRF 보호. 토큰 검사, protect_from_forgery, SameSite=Strict
rate_limit_guard 스로틀링이나 rate limit 계층

가드가 이 버킷에 들어오는 길은 세 가지입니다. 먼저 프레임워크별 인증 태거가 프레임워크 고유의 장치(Spring Security 체인, Phoenix 파이프라인, Express 미들웨어, Noir가 본문까지 확인한 Go 미들웨어)를 읽어 auth_guard나 authz_guard를 보고합니다. 이때 source는 태거 이름이고 신뢰도는 86입니다. 다른 하나는 핸들러 소스의 이름 매칭(login_required, @PreAuthorize, before_action :authenticate_user!)으로, source는 route_source이고 신뢰도는 44에서 58 사이입니다. 마지막으로 핸들러가 호출자를 직접 확인하는 함수(validateSession(req, res), verifyAuth(...), principal.validate_admin_session())를 부르면, 본문 어디에서 부르든 source가 callee이고 신뢰도가 56 또는 60인 가드로 보고합니다. 이 callee 매칭은 verifyToken이나 validateApiKey 호출을 가드로 보지 않습니다. 그 토큰은 호출자의 자격 증명일 수도 있지만 요청에 실려 온 재설정 토큰이나 일회용 코드인 경우도 그만큼 많기 때문입니다. 본문이나 쿼리로 자격 증명을 받는 로그인 흐름 라우트(/login, /verify, /reset-password)에서도 물러납니다. 그런 라우트에서는 이 호출이 로그인 자체이기 때문입니다. 두 이름 매칭은 태거가 채우지 못한 kind만 채우며, csrf_guard와 rate_limit_guard는 핸들러 소스의 이름 매칭으로만 나옵니다.

프레임워크가 익명 호출을 허용한다고 명시한 라우트(tRPC publicProcedure, Micronaut @Secured(IS_ANONYMOUS))에는 anonymous 시그널이 붙고, auth_guard와 authz_guard의 이름 매칭은 건너뜁니다.

callees

kind는 callee 하나입니다. 핸들러 본문에서 직접 호출하는 함수이며, name은 호출 표현식(App.Keys.delete), path:line은 호출 지점입니다. callee를 보고하는 프레임워크는 Callee 커버리지에 있습니다.

sources

Kind 의미
request_input 호출자가 제어하는 값. 파라미터마다 하나씩 <type>.<name> 이름으로 나오며, type은 path, query, header, cookie, json, form, file, xml입니다. GraphQL 선택 필드는 graphql.field.<name>, 모바일 진입점은 deep_link.mobile_scheme, deep_link.universal_link, deep_link.android_intent, ipc.content_provider입니다. 쿠키 파라미터가 추출되지 않았는데 핸들러가 쿠키를 직접 읽으면 route_source에서 cookie.<name>으로 나옵니다
server_secret_source 핸들러가 반환하는 Spring @Value 시크릿
cors_policy Spring WebSocket/STOMP 엔드포인트에 등록된 CORS 정책

sinks

Kind 의미
sql SQL 호출. raw SQL, DB 계열 리시버의 execute / query, Spring Data 리포지토리 메서드
data_store_query 문서 저장소나 그래프 저장소 질의(mongo.query, Neo4j 클라이언트)
command_exec OS 명령 실행. exec, system, spawn, popen, subprocess, ProcessBuilder
code_eval 프로세스 내부 코드 평가. eval, new Function, instance_eval, ScriptEngine
deserialization 안전하지 않은 역직렬화. pickle.loads, yaml.load, Marshal.load, unserialize, ObjectInputStream.readObject
template_injection 문자열로 만든 템플릿. render_template_string, ERB.new, Handlebars.compile
template_render 일반적인 템플릿/뷰 렌더링
xss 자동 이스케이프를 우회하는 HTML 출력. innerHTML, dangerouslySetInnerHTML, html_safe, mark_safe, v-html, 템플릿의 safe 필터
file_io 파일 시스템 읽기/쓰기, 파일 전송, 디렉터리 목록
redirect 리다이렉트 응답
outbound_http 외부로 나가는 HTTP 요청(fetch, axios, requests, http.Get, RestTemplate, reqwest). 대상 URI가 리터럴이면 name에 함께 들어갑니다
mass_assignment 요청 파라미터를 모델에 그대로 쓰는 경우(update_attributes, Model.create(req.body))
crypto_weak 약한 암호 요소. MD5나 SHA-1, DES, ECB 모드, RC4, 암호용이 아닌 난수
webview_load 모바일 전용. WebView가 URL이나 HTML을 로드
intent_redirect 모바일 전용. 들어온 딥링크 데이터로 인텐트를 실행

callee 이름이 매칭되면 sink의 source는 callee, 핸들러 텍스트가 매칭되면 route_source입니다. 핸들러 텍스트에서는 kind당 sink가 하나까지만 나옵니다.

validators

Kind 의미
validation 입력에 대한 validate, verify, permit 단계. 호출자를 확인하는 validate/verify(validateSession, verifyAuth)는 가드로 분류합니다
schema_validation 선언하지 않은 필드를 거부하는 스키마 검증. Pydantic, Zod, Joi, Yup, marshmallow, JSON Schema
type_coercion 입력을 숫자나 타입이 정해진 파라미터로 강제(parseInt, Integer(), 타입이 있는 @PathVariable)
allowlist_check 값을 고정된 집합과 비교
sanitization sanitize, escape, encode, normalize 단계
query_parameter_binding 쿼리 파라미터를 문자열로 이어 붙이지 않고 클라이언트 API로 바인딩
existence_validation 쓰기 전에 존재 여부를 확인(existsBy..., 또는 없으면 예외를 던지는 조회)
uniqueness_validation 중복/유일성 검사
expiry_validation 토큰이나 세션의 만료 시각 비교
credential_hashing, credential_verification Spring Security 패스워드 인코더의 encode, matches
cookie_httponly, cookie_secure HttpOnly, Secure로 설정한 쿠키

signals

모든 엔드포인트에 두 시그널이 붙습니다. <METHOD> <url> 이름의 route_definition과, 라우트를 보고한 기술 이름의 technology입니다. 나머지는 다섯 묶음으로 나뉩니다.

라우트 형태:

Kind 의미
state_change 메서드가 POST, PUT, PATCH, DELETE이거나, 권한이 걸린 WebSocket 메시지 핸들러인 경우. 순수 gRPC/Thrift 메서드, GraphQL 쿼리, SOAP 읽기 오퍼레이션, 본문 파라미터 없이 읽기 callee만 호출하는 POST에는 붙지 않습니다
path_param 경로 파라미터마다 하나
internal_endpoint 아웃바운드 클라이언트 선언. 애플리케이션이 제공하는 엔드포인트가 아니라 호출하는 엔드포인트입니다
deep_link_input 모바일 딥링크나 외부에 공개된 ContentProvider가 앱 밖에서 데이터를 받음
graphql_resolver 이 오퍼레이션을 구현하는 Spring GraphQL 리졸버

파라미터 이름. source는 param이고 이름은 <type>.<name>입니다.

Kind 대표적인 파라미터 이름
credential_input password, token, secret, api_key, authorization, jwt, bearer. 핸들러가 req.body.password를 읽는데 분석기가 그 파라미터를 뽑지 못했을 때는 route_source에서도 나옵니다. 엔드포인트에 프레임워크 가드가 보고된 경우 헤더와 쿠키는 제외합니다. 그 값은 가드가 읽는 자격 증명이기 때문입니다
identifier_input id, _id나 Id로 끝나는 이름, user, account, order, project
redirect_input url, uri, redirect, return, next, continue, dest, callback
file_input file, upload, attachment, image, filename, path
query_builder_input sort, order, filter, where, search, select, field, column
pii_input email, phone, ssn, dob, passport, national_id
html_content_input description, html, message, comment, note, markdown
code_input script, formula, expression, command, template

태그. 엔드포인트 태그는 전부 시그널이 되며, kind는 태그 이름이고 source는 태거입니다. admin, payment, webhook, file_upload를 비롯한 태거의 태그, cors, csrf-protection, rate-limit, security-headers 같은 프레임워크 보호 장치 태그, graphql-return이나 nginx-host 같은 분석기 메모가 모두 여기로 옵니다. 파라미터 태그(sqli, ssrf, idor)도 같은 방식이고 source는 param_tagger:<tagger>입니다. 인증 태그만 예외로, 가드가 됩니다.

약해진 보호 장치와 노출. 핸들러 소스에서 매칭합니다.

Kind 의미
csrf_exempt 이 라우트의 CSRF 보호가 꺼져 있음(@csrf_exempt, csrf().disable(), protect_from_forgery with: :null_session). 상태 변경 메서드에만 붙습니다
jwt_unsafe JWT 검증이 꺼졌거나 약해짐. verify=False, 알고리즘 none, ignoreExpiration: true
cors_open 와일드카드 origin과 credentials: true가 함께 있음
sensitive_response 응답에 자격 증명으로 보이는 키(token, password, secret)가 들어 있음
server_secret_source 핸들러가 Spring @Value 시크릿을 반환함. sources에도 들어갑니다
log_injection 핸들러가 요청 입력이나 자격 증명을 로그에 남김
foreign_identifier_write 요청 본문의 id가 다른 ...Id 필드에 대입되어 저장되는데 조회나 존재 확인이 없음

파생 시그널. 위 항목들을 조합해 만들며 source는 heuristic입니다.

Kind 나오는 조건
guard_absence 상태 변경 엔드포인트에 가드가 하나도 없고 경로에 식별자도 없음. 로그인, 로그아웃, 비밀번호 재설정 라우트는 건너뜁니다
idor_review 위와 같지만 경로에 식별자가 있음
authz_absence 상태 변경 엔드포인트에 auth_guard는 있고 authz_guard는 없으며 경로에 식별자가 있음
rate_limit_absence 상태 변경 엔드포인트가 본문, 쿼리, 경로로 자격 증명을 받는데 rate_limit_guard가 없음
ssrf outbound_http sink와 redirect_input이 함께 있음
open_redirect redirect sink와 redirect_input이 함께 있음
path_traversal file_io sink와 file_input이 함께 있음
object_lookup 식별자 입력과 조회 callee(findById, getOne, repo.get)가 함께 있음
object_write 경로에 식별자가 있는 상태 변경 엔드포인트가 조회 callee 없이 변경 callee(create, update, delete, save)를 호출. 감사 로그 기록과 메모리 안에서 값을 만드는 호출(createFormBuilder, CreateAPIError)은 변경으로 보지 않습니다
unsafe_method GET, HEAD, OPTIONS, QUERY 엔드포인트가 변경 callee를 호출. 핸들러가 요청 메서드(request.method ==, $request->isMethod())나 폼 제출 확인($form->isSubmitted(), form.validate_on_submit())으로 분기하면 건너뜁니다
priority_review 종합 점수. 아래에서 설명합니다

부재(absence) 시그널 넷은 신뢰도가 일부러 낮습니다(28에서 44). 게이트웨이나 Noir가 읽지 않은 부모 클래스에 있는 가드는 놓칠 수 있으므로, 이 시그널은 "이 라우트를 보호하는 것이 없다"가 아니라 "여기서 감지된 것이 없다"는 뜻입니다.

priority_review는 세 가지를 더합니다. 첫째는 엔드포인트의 리뷰 시그널 수(부재 시그널, csrf_exempt, jwt_unsafe, cors_open, open_redirect, ssrf, path_traversal, sensitive_response, server_secret_source, unsafe_method, log_injection, deep_link_input)입니다. 둘째는 무거운 sink의 kind 수(sql, data_store_query, command_exec, code_eval, deserialization, template_injection, xss, mass_assignment, crypto_weak, webview_load, intent_redirect)이고, 셋째는 csrf_exempt, jwt_unsafe, cors_open, open_redirect 중 하나라도 있으면 붙는 1점입니다. query_parameter_binding validator가 있으면 sql과 data_store_query sink는 세지 않고, sanitization이나 schema_validation이 있으면 xss는 세지 않습니다. 합이 2 미만이면 아무것도 나오지 않습니다. 합이 3 이상이거나, 위 네 시그널 중 하나가 있거나, 무거운 sink kind가 둘 이상이면 name이 high(신뢰도 90)이고, 나머지는 medium(신뢰도 70)입니다. kind == "priority_review"와 confidence >= 80으로 거르면 우선순위가 높은 라우트만 남습니다.

버킷이 가득 차면 새 항목은 순위가 가장 낮은 항목보다 높을 때만 그 자리를 대신합니다. 파생 시그널과 부재 시그널은 파라미터 단위 항목보다 순위가 높으므로, 파라미터가 많은 라우트에서도 종합 시그널은 남습니다.

대표 사용 사례

  • AI SAST: 엔드포인트 인벤토리와 AI 컨텍스트를 LLM에 함께 넘기면, 라우트 구조를 다시 발견하지 않아도 공격 표면에서 도달 가능한 취약점에 집중할 수 있습니다.
  • 수동 트리아지: 큰 JSON/SARIF 리포트를 sink 종류나 signals(예: guard_absence)로 정렬하고 거를 수 있습니다.
  • CI 게이팅: sinks는 있고 validators는 없는 엔드포인트를 골라 PR에서 위험 엔드포인트로 표시합니다.

다른 플래그와 조합

# 1-hop callee와 AI 컨텍스트 블록을 함께 출력
noir scan . --include callee --ai-context

# LLM 기반 SAST 파이프라인용 JSON 출력
noir scan . --ai-context -f json -o noir-context.json

# 오퍼레이션마다 `x-noir-ai-context`가 붙은 OpenAPI 내보내기
noir scan . --ai-context -f oas3 -o spec.json

완성도 참고 사항

  • AI 컨텍스트는 데이터를 덧붙이기만 합니다. 휴리스틱에 걸리지 않는 엔드포인트는 플래그 없이 돌렸을 때와 똑같이 나옵니다(직렬화된 모델에 ai_context 키가 추가되지 않음).
  • 휴리스틱은 보수적으로 튜닝되어 있지만 거짓 양성/음성 모두 존재합니다. 항목은 확정된 결론이 아니라 검토를 시작할 단서로 다루세요.
  • guard/sink/validator 패턴은 계속 개선됩니다. 패턴을 일부러 언어에 묶지 않았기 때문에, 컨텍스트 스키마 하나가 Noir가 지원하는 모든 프레임워크에 그대로 적용됩니다.
  • AI 컨텍스트의 많은 부분이 callee 커버리지에서 비롯됩니다. 현재 어떤 프레임워크가 핸들러 callee를 내보내는지는 Callee 커버리지를 참고하세요.