MCP 서버
Model Context Protocol(MCP)은 AI 클라이언트가 외부 도구(tool)와 통신할 수 있게 해주는 개방형 표준입니다. dalfox mcp는 stdio 기반 MCP 서버를 실행하여 Claude Desktop, Claude Code, Cursor를 비롯한 모든 MCP 호환 클라이언트가 Dalfox 스캔을 직접 구동할 수 있게 합니다.
서버 시작하기
dalfox mcp
이 서버는 stdin/stdout을 통해 MCP로 통신합니다. 클라이언트에서 실행하세요. 터미널에서 직접 실행하지 않습니다.
Claude Desktop 설정
Claude Desktop MCP 설정(claude_desktop_config.json)에 Dalfox를 추가합니다:
{
"mcpServers": {
"dalfox": {
"command": "dalfox",
"args": ["mcp"]
}
}
}
Claude Desktop을 재시작합니다. Dalfox가 dalfox라는 이름의 도구 제공자(tool-provider)로 나타납니다.
Claude Code(및 기타 CLI)
claude mcp add dalfox -- dalfox mcp
사용 가능한 도구(tool)
여섯 개의 도구(tool)가 노출됩니다. 모두 비동기이며 논블로킹입니다. 스캔을 제출하고, 결과를 폴링한 뒤, 다음 작업으로 넘어갑니다.
scan_with_dalfox
스캔을 제출합니다. 즉시 반환합니다.
{
"target": "https://example.com/search?q=test",
"method": "GET",
"param": ["q"],
"headers": ["Authorization: Bearer token"],
"encoders": ["url", "html"],
"timeout": 10,
"scan_timeout": 0,
"workers": 50,
"rate_limit": 0,
"insecure": true,
"blind_callback_url": "https://callback.example",
"deep_scan": false,
"skip_ast_analysis": false,
"analyze_external_js": false,
"detect_outdated_libs": false
}
encoders는 구현된 페이로드 인코더의 어떤 조합이든 받습니다:
url, html, htmlpad, 2url, 3url, 4url, base64, unicode,
zwsp. 위 예시는 ["url", "html"]을 보여줍니다. 변형(mutation) 커버리지를
높이려면 더 추가하세요. 순서는 중요하지 않습니다. 스캐너는 인코더를
고정된 우선순위 순서(url → html → htmlpad → 2url → 3url
→ 4url → base64 → unicode → zwsp)로 적용하고 출력을 중복
제거합니다. 인코딩을 완전히 비활성화하려면 ["none"]을 사용하세요.
--encoders / -e CLI 플래그와 대응됩니다. 알 수 없는 인코더 이름은 곧바로
거부됩니다 — 그렇지 않으면 어떤 인코더에도 매칭되지 않아 스캔의 페이로드
커버리지가 조용히 줄어듭니다.
method는 CLI가 허용하는 것과 동일한 메서드 집합으로 검증되며 자동으로 대문자로
변환됩니다("post" → "POST"). 지원하지 않는 메서드를 보내면 잘못된 메서드를
전송하는 스캔이 실행되는 대신 오류가 반환됩니다.
blind_callback_url은 비어 있거나(= 블라인드 XSS 사용 안 함) http:// /
https://로 시작해야 합니다. 이 값을 설정하면 저장형 블라인드 XSS 주입이
켜집니다 — <script src=...> 페이로드가 모든 쿼리·바디·헤더·쿠키 파라미터에
기록되어 대상에 그대로 남습니다. 그래서 콜백을 받을 수 없는 값은 그 페이로드만
남기고 아무 소득도 없으므로 아예 거부합니다. remote_payloads /
remote_wordlists도 등록된 프로바이더인지 검사합니다. 인식되지 않는 이름은
조용히 아무것도 받아오지 않은 채, 요청한 페이로드 커버리지가 빠진 스캔을
done으로 보고하게 만들기 때문입니다.
insecure는 TLS 인증서 검증을 제어합니다(기본값 true, 스캐너 친화적).
인증서 검증을 강제하고 자체 서명되었거나 만료된 인증서를 거부하려면 false로
설정하세요. --insecure CLI 플래그와 대응됩니다.
analyze_external_js는 옵트인 방식입니다(기본값 false). 프리플라이트
시점에 동일 출처의 <script src> 번들을 가져와 AST DOM-XSS 분석을
실행하려면 true로 설정하세요. 모든 싱크(sink) 로직이 외부 번들에 있고
페이지에 서버 측 반사가 없는 SPA에 유용합니다. 제한: 파일 16개,
파일당 512 KiB. (--include-url / --exclude-url 스코프 필터는 CLI
전용입니다. MCP 인자가 따로 없으며, 보내면 에러가 납니다.)
detect_outdated_libs는 옵트인 방식입니다(기본값 false). 오래되었거나
알려진 취약점이 있는 JS 라이브러리에 대해 정보성 [I] 탐지 결과도
내보내려면 true로 설정하세요(CWE-1104, 추가 요청 0건). 꺼두면 스캔은 XSS만
보고합니다.
rate_limit은 스캔의 아웃바운드 초당 요청 수를 제한합니다(0 = 무제한,
기본값). 모든 워커 태스크에 걸쳐 적용됩니다 — 불안정한 대상을 부드럽게
다루거나 WAF 임계값 아래로 유지하는 데 사용하세요.
scan_timeout은 스캔 전체에 허용되는 실제 경과 시간(초)입니다(기본값 0 =
무제한). 요청별 timeout과는 구별됩니다. 시간이 초과되면 스캔이 중단되고
부분 탐지 결과를 유지하며 scan_timeout을 언급하는 error_message와 함께
cancelled 상태로 정리됩니다. 길거나 deep_scan 실행에 한도를 두어
에이전트의 폴링 루프가 반드시 종료되도록 설정하세요.
위 블록은 발췌입니다. 이 도구가 받는 모든 필드와 각각의 기본값은 다음과
같습니다. 필수 필드는 target 하나뿐입니다.
{
"target": "https://example.com/search?q=test",
"param": [],
"method": "GET",
"data": null,
"headers": [],
"cookies": [],
"user_agent": null,
"encoders": ["url", "html"],
"timeout": 10,
"scan_timeout": 0,
"delay": 0,
"follow_redirects": false,
"insecure": true,
"proxy": null,
"include_request": false,
"include_response": false,
"skip_mining": false,
"skip_discovery": false,
"deep_scan": false,
"skip_ast_analysis": false,
"analyze_external_js": false,
"detect_outdated_libs": false,
"blind_callback_url": null,
"workers": 50,
"rate_limit": 0,
"waf_bypass": "auto",
"skip_waf_probe": false,
"force_waf": null,
"waf_evasion": false,
"waf_min_confidence": 0.3,
"remote_payloads": [],
"remote_wordlists": [],
"max_payloads_per_param": 0,
"wait": false,
"wait_timeout_sec": 300
}
data는 POST/PUT의 요청 본문으로, form-urlencoded
("user=admin&pass=test") 또는 JSON 문자열을 받습니다. cookies는
"name=value" 형태의 항목을, headers는 "Name: Value" 형태의 전체 줄을
받으며, user_agent는 User-Agent 헤더를 덮어씁니다.
도구가 모르는 필드명은 조용히 버려지지 않고 거부됩니다. 호출은
isError: true와 함께 문제가 된 키, 그리고 허용되는 키 전체 목록을 담아
돌아옵니다. 그렇지 않으면 cookies를 한 글자만 틀려도 대상이 비인증 상태로
스캔되고, 아무것도 찾지 못한 채 status: "done"으로 끝나 실제 클린 결과와
구분할 수 없게 됩니다. 거부된 호출에는 scan_id가 없으므로 폴링할 대상도
없고, 실행된 스캔으로 오해할 여지도 없습니다.
그래서 REST API 쪽 철자도 별칭으로 받습니다.
target에 url, cookies에 cookie, headers에 header, workers에
worker, blind_callback_url에 blind입니다. cookie는 리스트 대신
Cookie: 헤더 문자열 하나("sid=abc; lang=en")도 받습니다. 도구 스키마가
알리는 이름은 위의 MCP 정식 명칭이며, 별칭은 REST 문서를 보고 작성한 인자도
의도한 그대로 스캔되게 하려고 존재합니다.
REST 옵션 중 둘은 별칭을 두지 않고 의도적으로 빼두었으며, 요청하면 에러가
납니다. callback_url(모델이 원하는 호스트로 스캔 결과를 내보낼 수 있는
웹훅)과 cookie_from_raw(서버 측 파일 읽기)입니다. 쿠키는 cookies로 직접
넘기세요.
별칭을 일반적인 REST 호환 모드로 오해하지 않도록, 세 가지 한계를 밝혀둡니다.
- 인자는 평평합니다. REST는 옵션을
options아래에 중첩하지만 이 도구는 최상위에서 받으며,options는 알 수 없는 필드입니다. - 이름만 매핑합니다. 별칭은 철자를 옮길 뿐이고, 타입이 틀린 값은 그대로
거부됩니다. 예외는
cookie하나로, REST의Cookie:헤더 문자열 하나(그리고 쿠키 없음을 뜻하는null)를 받습니다. - 공개 스키마에는 없습니다. 스키마는 MCP 정식 철자만 알립니다. 호출 전에
inputSchema로 인자를 검증하는 클라이언트는 REST 철자로 된 호출을 서버에 닿기도 전에 거부합니다. 별칭은 인자를 검증 없이 그대로 넘기는 호출자를 위한 것이니, 가급적 정식 이름을 쓰세요.
preflight_dalfox는 scan_with_dalfox보다 의도적으로 좁은 집합을 받습니다.
페이로드를 보내지 않으므로 속도 조절·워커·WAF 처리·블라인드 XSS·대기에 관한
옵션은 작용할 대상이 없어 거부됩니다. 받는 필드는 아래에 따로 적어두었습니다.
(REST의 POST /preflight는 스캔 본문을 그대로 재사용하고 쓸 수 없는 값은
무시하므로, 두 표면이 실제로 갈리는 유일한 지점입니다.) 자격 증명과 대상은
그대로 전달됩니다. 쿠키 없이 preflight를 돌리면 인증된 스캔이 찾아낼 파라미터를
적게 보고하게 되기 때문입니다.
delay는(기본값 0, 범위 0~9999) 요청 사이에 그만큼의 밀리초를 대기하고,
follow_redirects는(기본값 false) 스캐너가 3xx 응답을 따라가게 하며,
proxy는 모든 요청을 HTTP 또는 SOCKS 프록시("http://127.0.0.1:8080")로
보냅니다.
include_request와 include_response는(둘 다 기본값 false) 원본 HTTP 요청
텍스트와 원본 응답 본문을 각 탐지 결과에 첨부해 포렌식 분석에 쓰이도록 합니다.
응답이 클 수 있으므로 증거가 필요할 때만 켜세요.
WAF 관련 다섯 개 필드는 CLI의 WAF 플래그와 대응됩니다. waf_bypass는 처리
모드를 고릅니다: "auto"(탐지 후 우회, 기본값), "force"(force_waf를 사용),
"off"(탐지만). skip_waf_probe는(기본값 false) WAF 핑거프린팅 프로브를 아예
건너뜁니다. force_waf는 WAF를 탐지하는 대신 특정 프로필(예: "cloudflare",
"akamai", "modsec")을 고정합니다. waf_evasion은(기본값 false) 적응형
우회를 켭니다. waf_min_confidence는 [0.0, 1.0] 범위의 탐지 신뢰도
하한이며(기본값 0.3), 이보다 낮은 핑거프린트는 버려집니다. waf_bypass나
force_waf에 알 수 없는 값을 주거나 waf_min_confidence가 범위를 벗어나면
invalid_params로 거부됩니다.
remote_payloads와 remote_wordlists는(둘 다 기본값 []) 스캔을 시작하기 전에
원격 제공자로부터 추가 XSS 페이로드("portswigger", "payloadbox")와 파라미터
워드리스트("burp", "assetnote")를 가져옵니다.
max_payloads_per_param은 각 파라미터를 테스트할 페이로드 수의 상한입니다(기본값
0 = 내장 안전 상한을 제외하면 무제한). 에이전트의 스모크 스캔에는 10~50
같은 작은 값을 쓰세요.
wait는(기본값 false) 이 호출을 블로킹 방식으로 바꿉니다. 즉시
{scan_id, status: "queued"}를 반환하는 대신 스캔이 done / error /
cancelled에 도달할 때까지 블로킹한 뒤 get_results_dalfox와 동일한 형태를
반환합니다. wait_timeout_sec는(기본값 300, 범위 1~86400) 그 대기의 실제
경과 시간 예산이며 wait가 false이면 무시됩니다. 시간이 초과되면 작업은 계속
실행되고 응답에 wait_timed_out: true가 담깁니다.
응답:
{ "scan_id": "9f2c…", "target": "https://example.com/search?q=test", "status": "queued" }
get_results_dalfox
스캔을 폴링합니다. 준비되면 상태, 진행률, 결과를 반환합니다.
{ "scan_id": "9f2c…" }
응답(진행 중):
{
"scan_id": "9f2c…",
"target": "…",
"status": "running",
"progress": {
"params_total": 10,
"params_tested": 4,
"requests_sent": 215,
"findings_so_far": 1,
"estimated_completion_pct": 40,
"suggested_poll_interval_ms": 3000
}
}
응답(완료):
{
"scan_id": "9f2c…",
"status": "done",
"results": [
{
"type": "V",
"type_description": "Vulnerable - dalfox asserts this input is exploitable; act on it",
"detection_method": "dom-verification",
"confidence": "high",
"confidence_reason": "DOM verification confirmed an executable position (DOM marker)",
"inject_type": "inHTML",
"method": "GET",
"param": "q",
"payload": "<svg/onload=alert(1)>",
"evidence": "DOM verification successful for param q (DOM marker)",
"cwe": "CWE-79",
"severity": "High"
}
]
}
탐지 결과를 담은 모든 응답에는 _untrusted_content_notice가 함께 실립니다.
경고가 경고 대상보다 먼저 읽히도록 JSON의 첫 번째 키로 직렬화됩니다.
evidence, response, request, payload, param, location,
message_str 필드는 스캔 대상이 고른 바이트를 그대로 인용한 것이고, 그 대상은
지금 시험받고 있는 쪽입니다 — 에이전트는 이를 보고할 데이터로만 읽어야 하며
절대 지시로 받아들이면 안 됩니다. 스캔된 페이지는 모델에게 건네는 지시문처럼
보이는 텍스트를 심어둘 수 있고, 거기에 따르면 다음 호출의 target, proxy,
blind_callback_url, include_*를 대상이 고르게 됩니다. preflight_dalfox도
파라미터를 발견했다면 같은 안내를 붙입니다. 발견된 각 파라미터의 name은 대상의
마크업에서 뽑아낸 것이기 때문입니다.
offset과 limit으로 큰 결과 집합을 페이지 단위로 넘길 수 있고, pagination은
{total, offset, limit, returned, has_more}를 보고합니다. 한 페이지는 추가로
4 MiB로 제한됩니다: 탐지 결과가 몇 건 나올지는 대상이 정하고, 각 건은
evidence 64 KiB에 response 64 KiB까지 실을 수 있기 때문입니다. 이 예산으로
페이지가 잘리면 pagination에 truncated_by_size: true와 max_page_bytes가
추가됩니다 — limit이 요청한 것보다 적게 돌아왔을 뿐, 나머지는 다음
offset에 그대로 있습니다. 예산보다 큰 단일 탐지 결과는 버리지 않고 혼자
내보내므로 페이지 넘기기가 멈추지 않습니다.
progress.estimated_completion_pct와 params_tested는 발견된 각 파라미터가
완료될 때마다 실시간으로 증가합니다. 따라서 폴링 간격을 조절하는 데 사용할 수 있습니다 —
suggested_poll_interval_ms를 따르세요.
대상에 도달할 수 없으면(DNS 실패, 연결 거부, TLS 오류, 타임아웃) 스캔은 빈
results와 함께 done으로 끝나는 대신 CONNECTION_FAILED를 포함하는
error_message와 함께 status: "error"로 끝납니다 — preflight_dalfox가
reachable: false로 보고하는 것과 동일한 구분입니다. target은
http:// 또는 https://로 시작해야 합니다.
인증 세션이 스캔 도중 끊어진 경우도 마찬가지입니다. 호출이 자격증명(cookies,
또는 headers의 Cookie / Authorization 항목)을 담고 있으면, Dalfox는 스캔 전에
인증된 응답의 지문을 잡아 두고 스캔이 끝날 때 다시 확인합니다. 그 사이에 세션이
만료됐다면 빈 results와 함께 done으로 끝나는 대신 SESSION_LOST:로 시작하는
error_message와 함께 status: "error"로 종료됩니다. 이런 스캔을 "XSS 없음"으로
요약하지 마십시오 — 실제로 테스트된 것이 없습니다. 자격증명을 넘기지 않으면 모니터링은
비용이 들지 않습니다.
list_scans_dalfox
추적 중인 모든 스캔을 나열합니다. 선택적 필터:
{ "status": "running" }
total, scans: [{scan_id, target, status, result_count}]을 반환합니다.
cancel_scan_dalfox
대기 중이거나 실행 중인 스캔을 중단합니다:
{ "scan_id": "9f2c…" }
delete_scan_dalfox
추적 중인 스캔을 메모리에서 영구적으로 제거합니다. 종료된 스캔(done, error, cancelled)만 삭제할 수 있습니다. 실행 중이거나 대기 중인 스캔은 먼저 취소해야 합니다. 종료된 스캔은 1시간 후 자동으로 정리되기도 합니다.
{ "scan_id": "9f2c…" }
{scan_id, deleted: true, previous_status}를 반환합니다.
preflight_dalfox
페이로드를 보내지 않고 대상을 분석합니다. 스캔을 확정하기 전에 범위를 정하는 데 유용합니다.
{
"target": "https://example.com",
"method": "GET",
"skip_discovery": false,
"skip_mining": false,
"encoders": ["url", "html"],
"max_payloads_per_param": 0,
"deep_scan": false
}
도달 가능 여부, 발견된 파라미터, 예상 요청 수를 반환합니다.
encoders, max_payloads_per_param, deep_scan는 그 자체로 요청을 보내지 않습니다. 뒤이어 실행할 scan_with_dalfox 호출을 설명하는 값이며, estimated_total_requests가 그 스캔의 확장 폭을 반영하도록 합니다. 실제로 스캔할 때 쓸 값을 그대로 넘기세요.
추정치는 스캔이 파라미터마다 실행하는 두 단계(리플렉션, DOM 검증)를 모두 세며, 각 단계를 파라미터당 페이로드 상한으로 자릅니다. --dry-run과 동일한 계산입니다. 다만 하한값입니다: WAF 변형/인코더 확장과 상한 적용 이후 덧붙는 공용 CSP/tech 페이로드는 세지 않습니다.
일반적인 에이전트 흐름
- 에이전트가
preflight_dalfox를 호출하여 대상을 확인하고 파라미터 수를 셉니다. - 에이전트가
scan_with_dalfox를 호출하여scan_id를 받습니다. - 에이전트가 진행률 객체의
suggested_poll_interval_ms를 사용하여get_results_dalfox를 폴링합니다. status == "done"이 되면 에이전트가 탐지 결과를 요약하여 사용자에게 다시 보고합니다.
모든 도구(tool)가 비동기이므로 에이전트는 응답성을 유지합니다. 오래 실행되는 도구(tool) 호출이 대화를 차단하지 않습니다.
권한 및 안전
MCP 서버는 CLI와 동일한 규칙을 적용합니다: 테스트 권한이 있는 대상만 스캔하세요. 에이전트의 시스템 프롬프트에서 "모든 스캔 전에 범위를 확인하세요"와 같은 명시적 사용자 확인 단계 뒤에 Dalfox MCP 호출을 두는 것을 고려하세요.
탐지 결과는 에이전트에게 신뢰할 수 없는 입력입니다. CLI나 REST API와 달리 MCP는 스캔 출력을 "읽은 대로 행동하는" 모델에게 건네고, 탐지 결과에 인용된 바이트는 전부 대상이 고른 것입니다. Dalfox는 그런 응답에 _untrusted_content_notice를 붙이지만 이는 상기시키는 라벨이지 샌드박스가 아닙니다 — 범위 결정(어느 대상, 어느 프록시, 어느 콜백)은 운영자가 쥐고 있어야 하며, 스캐너가 페이지에서 읽어온 무언가가 그것을 바꾸게 두면 안 됩니다.
문제 해결
- 도구(tool)가 표시되지 않나요? MCP 클라이언트가 사용하는 PATH에
dalfox바이너리가 있는지 확인하세요. macOS의 Claude Desktop에서는 대개/usr/local/bin또는/opt/homebrew/bin입니다. - 결과가 비어 있나요? 다시 폴링하세요. 스캔은 비동기입니다.
suggested_poll_interval_ms를 폴링 주기로 사용하세요. - 로그를 보고 싶나요? 설정하는 동안
dalfox mcp --debug를 실행하세요. 디버그 라인은 stderr로 가므로 MCP 채널을 오염시키지 않습니다.