출력과 리포트

모든 스캔은 동일한 내부 결과 구조를 만듭니다. Dalfox는 이를 선택한 형식으로 렌더링합니다. plain을 제외한 모든 형식은 배너를 빼므로, 파일로 리디렉션한 리포트가 깔끔하게 유지됩니다.

형식 선택

dalfox scan https://target.app -f json -o report.json
형식 플래그 기계 판독 가능 적합한 용도
plain -f plain (기본값) 아니오 사람이 읽는 터미널 출력
json -f json 예 단일 JSON 문서, 대시보드, jq
jsonl -f jsonl 예 스트리밍, 로그 파이프라인
markdown -f markdown 아니오 리포트, 풀 리퀘스트 코멘트
sarif -f sarif 예 GitHub 코드 스캐닝, SARIF 소비자
toml -f toml 예 사람 + 파이프라인

파일로 저장하기

dalfox scan https://target.app -f jsonl -o findings.jsonl

-o가 없으면 출력은 stdout으로 나갑니다.

결과 필드

모든 탐지 결과에는 다음이 포함됩니다.

필드 예시 의미
type V, A, R, I 탐지 티어: Vulnerable / AST 탐지 / Reflected / Informational
type_description "Vulnerable - dalfox asserts this input is exploitable; act on it" 사람이 읽는 라벨(한 단어가 아니라 문장 전체)
detection_method "ast" 어떻게 찾았는지: reflection, dom-verification, ast, oob, library
confidence "high" 증거가 그 주장을 얼마나 강하게 뒷받침하는지 (high / low). I에는 없음
confidence_reason "URL-carried source; inline script permitted" 판단 근거 신호
inject_type "inHTML" 탐지 라벨: 주입한 페이로드는 inHTML(--sxss에서는 sxss-inHTML, 해당하면 -CSTI 접미어나 -VHtml 같은 프레임워크 싱크 접미어가 붙음), inHTML-HPP, DOM-XSS(AST), blind-oob-<location>-<protocol>, OutdatedComponent(I)
method "GET" HTTP 메서드
data "https://target.app/?q=%3Csvg%20onload%3Dalert%281%29%20class%3Ddlx1ec4110f%3E" PoC URL
param "q" 공격에 사용된 파라미터
location "Query" 파라미터가 실리는 위치: Query, Body, JsonBody, MultipartBody, GraphqlBody, XmlBody, Header(쿠키 포함), Path, Fragment. 알 수 없으면 생략
payload <svg onload=alert(1)> 정확한 페이로드
evidence "DOM verification successful for param q (DOM marker)" Dalfox가 그렇게 판단한 근거
cwe "CWE-79" 표준 CWE
severity "High" High / Medium / Low / Info
message_id 606 카탈로그 메시지 ID
message_str "Triggered XSS Payload (DOM marker): q=<svg onload=alert(1) class=dlx1ec4110f>" 짧은 메시지

다음 세 필드는 요청했을 때만 나타납니다: new(--baseline-mode annotate), request(--include-request), response(--include-response).

각 티어가 실제로 어떤 증거인지, 그리고 순수 클라이언트 사이드 DOM-XSS가 왜 V에 도달하지 못하는지는 탐지 모델 문서에서 다룹니다.

V / A / R은 XSS 탐지 결과입니다. I(Informational)는 공격에 사용할 수 없는 관찰 항목으로, 현재는 오래되었거나 알려진 취약점이 있는 JS 라이브러리 (inject_type: "OutdatedComponent", CWE-1104)만 해당하며, 페이로드나 파라미터가 없는 간결한 [INF] 라인으로 렌더링됩니다. 이 항목은 옵트인입니다. Dalfox는 기본적으로 검증된 XSS에 집중하므로, --detect-outdated-libs를 전달하지 않는 한 라이브러리 리포팅은 꺼져 있습니다 (추가 요청은 0건이며, 프리플라이트 응답의 <script> 태그를 검사합니다). --only-poc v,a,r로 걸러낼 수 있습니다.

선택적으로 전체 요청/응답을 포함할 수 있습니다.

dalfox scan https://target.app -f json --include-all -o report.json
# 또는 세부적으로:
dalfox scan ... --include-request
dalfox scan ... --include-response

기록되는 요청은 Dalfox가 실제로 보낸 것 그대로입니다. -H로 준 헤더와 쿠키가 전부 원문으로 들어갑니다. --include-request / --include-all로 만든 리포트는 공유하기 전에 내용을 확인하세요. 유닉스에서는 -o 파일을 0600으로 생성해 같은 호스트의 다른 계정이 읽지 못하게 하지만, 그 파일이 이후에 어디로 가는지는 별개의 문제입니다.

JSON과 JSONL 형태

-f json은 엔벨로프를 meta 아래에, 탐지 결과를 findings 아래에 담은 문서 하나를 씁니다.

{
  "findings": [
    {
      "confidence": "high",
      "confidence_reason": "payload reached an executable position in the parsed response",
      "cwe": "CWE-79",
      "data": "https://target.app/?q=%3Csvg%20onload%3Dalert%281%29%20class%3Ddlx1ec4110f%3E",
      "detection_method": "reflection",
      "evidence": "DOM verification successful for param q (DOM marker)",
      "inject_type": "inHTML",
      "location": "Query",
      "message_id": 606,
      "message_str": "Triggered XSS Payload (DOM marker): q=<svg onload=alert(1) class=dlx1ec4110f>",
      "method": "GET",
      "param": "q",
      "payload": "<svg onload=alert(1) class=dlx1ec4110f>",
      "severity": "High",
      "type": "V",
      "type_description": "Vulnerable - dalfox asserts this input is exploitable; act on it"
    }
  ],
  "meta": {
    "dalfox_version": "3.2.3",
    "dedup_mode": "exact",
    "failed_requests": 0,
    "findings_count": 1,
    "incomplete": false,
    "scan_duration_ms": 1234,
    "target_summary": [
      { "findings_count": 1, "status": "findings", "target": "https://target.app/?q=a" }
    ],
    "targets": ["https://target.app/?q=a"],
    "targets_deduplicated": 0,
    "total_requests": 87
  }
}

-f jsonl은 같은 데이터를 한 줄에 객체 하나씩 씁니다. 첫 줄은 {"meta": {…}}이고, 그 뒤의 각 줄이 탐지 결과 하나입니다. 탐지 결과만 필요하다면 첫 줄을 건너뛰거나, jq 'select(.severity=="High")'처럼 탐지 결과 필드로 거르세요.

스캔 메타데이터 엔벨로프

JSON, JSONL, SARIF, TOML, Markdown 출력은 모두 동일한 스캔 수준 메타데이터 엔벨로프를 담습니다.

  • dalfox_version
  • targets (입력 대상)
  • scan_duration_ms
  • total_requests
  • failed_requests — 재시도를 다 쓰고도 응답을 받지 못한 요청 수(리셋, 거부, 타임아웃). 대상에 닿지 못한 페이로드는 테스트되지 않은 것입니다
  • findings_count
  • target_summary[] — 대상마다 항목 하나: target, status(findings, clean, skipped, incomplete), findings_count, 건너뛰었거나 세션이 끊긴 경우 error_code(Ctrl-C / --limit / --scan-timeout으로 도중에 끊긴 대상은 error_code 없이 incomplete. 세션이 끊긴 경우에는 감지된 신호를 담은 error_message도), 그리고 WAF가 탐지된 경우 waf 객체(type / confidence / evidence를 담은 detected[]와, 추가 인코더·변형 수·우회 중 보낸/차단된 요청 수를 담은 bypass 블록)
  • dedup_mode / targets_deduplicated — 적용된 --dedup-urls 모드와 그것이 병합한 대상 수. 축소된 입력 목록이 리포트에 드러나도록 합니다(Markdown은 실제로 병합이 있었을 때만 행을 표시합니다)
  • targets_unparsable — 대상 목록의 줄을 파싱하지 못해 건너뛴 경우에만 포함됩니다. 파일 모드 참고
  • baseline — --baseline을 쓴 경우에만 포함됩니다. 베이스라인 참고
  • resumed — --state-file을 쓴 경우에만 포함됩니다. state_file(경로)과 targets_skipped_completed(이전 실행에서 끝나 건너뛴 대상 수)
  • incomplete — 실행이 완전히 테스트되지 않았을 때 true입니다. 스캔 도중 대상의 인증 세션이 끊어졌거나(세션 모니터링 참고), 전체 요청의 10% 이상(최소 3건)이 응답을 받지 못했거나, Ctrl-C / --limit / --scan-timeout으로 모든 대상이 끝나기 전에 실행이 멈춘 경우입니다. target_summary 항목을 전부 훑는 대신 이 필드 하나만 보세요. "findings_count": 0과 "incomplete": true가 함께 있다면 안전하다는 뜻이 아닙니다

세션이 끊어진 대상은 "status": "incomplete"(아예 실행되지 않았다면 "skipped")에 "error_code": "SESSION_LOST", 그리고 감지된 신호가 "error_message"에 담겨 보고됩니다. 절대 "clean"으로는 표시되지 않습니다. Ctrl-C, --limit, --scan-timeout으로 도중에 끊긴(또는 실행이 그 전에 멈춰 도달하지 못한) 대상도 탐지 결과가 없으면 error_code 없이 "incomplete"로 표시됩니다.

SARIF에서는 엔벨로프가 runs[0].properties와 runs[0].tool.driver.properties 아래에 중복으로 실려, GitHub 코드 스캐닝을 비롯한 소비 도구가 컨텍스트를 잃지 않습니다. 각 결과의 ruleId는 dalfox/cwe-<n>(XSS는 dalfox/cwe-79, 오래된 라이브러리는 dalfox/cwe-1104)이고, level은 severity를 따르며(High → error, Medium → warning, Low / Info → note), PoC URL은 location의 uri에 들어갑니다. partialFingerprints["vulnIdentity/v1"]은 코드 스캐닝이 실행 간에 같은 건을 맞춰 볼 수 있게 하는 안정적인 해시입니다. 탐지 결과 필드(type, inject_type, param, payload, severity, detection_method, confidence 등)는 결과의 properties 아래에 있고, message.text에는 message_str과 근거가 함께 담깁니다.

TOML에서는 최상위 [meta] 테이블로 나타납니다(탐지 결과는 [[results]] 아래).

Markdown에서는 탐지 결과 요약 위에 사람이 읽을 수 있는 테이블(## Scan Metadata + ### Target Summary)로 렌더링됩니다. 실패한 요청, incomplete, 중복 제거된 대상, 베이스라인, 재개처럼 무슨 일이 있었을 때만 의미 있는 행은 그때만 나타납니다.

Plain 텍스트 출력은 탐지 결과만 담습니다.

사일런스 모드

로그 없이 stdout에 탐지 결과만 내보냅니다.

dalfox scan https://target.app --silence
# 탐지 결과를 다른 도구로 파이프:
cat urls.txt | dalfox scan --silence -f jsonl | jq 'select(.severity=="High")'

셸 파이프라인과 cron 작업에 유용합니다.

긴 스캔 중 탐지 결과 스트리밍

기본적으로 plain 렌더러는 각 탐지 결과 블록(POC + Issue / Payload / Line)을 스캔 종료 시점의 WRN XSS found N XSS 요약 이후에 출력하므로, 로그는 자연스러운 순서(시작 → 진행 → 요약 → 세부 정보)로 읽힙니다.

대상이 크고 스캔이 길어질 때는 --stream-findings로 스캔 도중 출력으로 전환할 수 있습니다. 각 탐지 결과는 검증되는 즉시 진행 표시줄 위에 출력됩니다.

dalfox scan https://target.app --stream-findings

--stream-findings는 plain 형식에만 영향을 미칩니다. 스캔 종료 시점에 스트리머가 그대로 반영할 수 없는 필터(--output, --limit, --only-poc, --baseline)를 적용해야 하면 자동으로 비활성화됩니다. 명령줄에서 --stream-findings를 준 경우에는 이를 끈 플래그를 짚은 Warning:이 stderr에 출력됩니다.

POC 스타일

개념 증명(proof-of-concept)을 다양한 클라이언트 형태로 다시 렌더링합니다.

dalfox scan https://target.app --poc-type curl      # curl 명령
dalfox scan https://target.app --poc-type httpie    # HTTPie
dalfox scan https://target.app --poc-type http-request  # 원시 HTTP

기본값은 plain입니다. 티켓 등록에 적합합니다. --poc-type은 plain 리포트의 POC 줄만 바꿉니다. 구조화 형식은 항상 data에 PoC URL을 담습니다. http-request는 Dalfox가 해당 건에 기록한 원시 요청을 출력하며, 기록된 요청이 없으면 URL로 대체합니다.

필터링

특정 결과 유형만 표시합니다.

dalfox scan https://target.app --only-poc v     # V(Vulnerable)만
dalfox scan https://target.app --only-poc v,a   # V + AST

결과 수를 제한합니다.

dalfox scan https://target.app --limit 50
dalfox scan https://target.app --limit 10 --limit-result-type v

베이스라인: 새로 생긴 것만 보고하기

--only-poc와 --limit은 형태로 거릅니다. 이미 트리아지를 끝낸 건과 오늘 아침에 새로 나타난 건을 구분하지 못하므로, 기존 이슈가 100건인 저장소는 PR마다 똑같은 100건을 다시 보게 되고 결국 게이트는 항상 빨간불이거나 꺼두게 됩니다.

--baseline이 이 문제를 해결합니다. 이전 리포트를 지정하면 거기에 이미 있는 건은 억제됩니다.

dalfox scan scope.txt -f json -o baseline.json      # 최초 1회, 기존 백로그 기록
dalfox scan scope.txt --baseline baseline.json      # 이후 매 실행

별도의 베이스라인 작성 명령은 없습니다. 평범한 -f json -o(또는 -f jsonl -o) 리포트가 그대로 베이스라인입니다.

모드

모드 플래그 동작
filter (기본) --baseline-mode filter 이미 알려진 건을 제거합니다. 카운트, --limit, 종료 코드가 모두 신규 건만 기준으로 결정됩니다 — CI 게이트용 모드입니다.
annotate --baseline-mode annotate 모든 건을 그대로 두고 각각에 new: true / new: false를 붙입니다. 전체 집합을 보되 신규 여부를 표시하고 싶은 대시보드용입니다.

무엇을 "같은 건"으로 볼까

지문(fingerprint)은 그 건을 드러낸 실행이 아니라 취약점 자체의 정체성을 기준으로 만듭니다.

포함: 호스트 + 경로 · 파라미터 이름 · 파라미터 위치(query / header / cookie / body / path) · 인젝션 컨텍스트 · CWE · 탐지 티어 · 증거 계열(DOM 건의 Source → Sink 쌍).

제외: 페이로드와 그것이 들어간 쿼리 스트링, 페이로드 순서, AST의 줄/열 번호, 타임스탬프, 요청/응답 캡처.

따라서 실행마다 페이로드가 달라져도 같은 스캔은 깔끔하게 매칭되고, 번들러가 app.js의 줄 번호를 밀어도 이미 처리한 DOM 건이 되살아나지 않습니다. 티어가 지문에 들어가므로, 지난주 R이던 건이 오늘 V가 되면 신규로 보고됩니다. 이런 승격이야말로 게이트가 잡아야 할 변화입니다.

meta.baseline 블록

모든 구조화 형식이 diff 결과를 함께 보고합니다.

"baseline": {
  "path": "baseline.json",
  "mode": "filter",
  "enabled": true,
  "baseline_findings": 100,
  "new": 2,
  "known": 98
}

베이스라인 파일이 없거나, 형식이 깨졌거나, 다른 메이저 버전이 쓴 것이면 stderr에 경고를 내고 diff를 비활성화할 뿐 스캔을 실패시키지는 않습니다. 파이프라인에 남은 낡은 경로 하나 때문에 멀쩡히 돌던 스캔이 아무것도 보고하지 못하는 빨간 빌드가 되어서는 안 되기 때문입니다. 이 상황은 엔벨로프에 "enabled": false와 warning으로 드러나므로, "신규 없음"과 "diff가 아예 돌지 않음"을 구분할 수 있습니다.

베이스라인 갱신

전용 명령은 없습니다. --baseline 없이 다시 실행해(그래야 리포트에 신규 건만이 아니라 전체 집합이 담깁니다) 파일을 교체하면 됩니다.

dalfox scan scope.txt -f json -o baseline.json
git commit -am "chore: refresh dalfox baseline"

--baseline을 켠 채 -o를 같은 파일로 지정하면 filter 모드에서 베이스라인이 파괴됩니다. 다시 쓰인 리포트에는 신규 건만 들어 있어서 다음 실행이 백로그 전체를 다시 보고하게 됩니다. 두 경로가 같으면 Dalfox가 경고합니다.

주의사항

  • --limit은 diff 이전에 셉니다. 스캔 중 중단 조건은 수집되는 모든 건을 세므로(베이스라인에 이미 있는 건 포함), --limit 10 --baseline b.json으로 처음 10건이 전부 기존 건인 대상을 돌리면 나머지를 테스트하지 않은 채 조기 종료하고 "신규 0"을 보고합니다. 신규 기준으로 게이트할 때는 --limit을 빼세요. 둘을 함께 쓰면 Dalfox가 경고합니다.
  • --stream-findings는 --baseline이 있으면 비활성화됩니다. --only-poc과 같은 이유입니다. 스트리머는 어떤 건이 이미 베이스라인에 있는지 알 수 없어서, 요약은 신규만 보고하는데 화면에는 트리아지가 끝난 백로그 전체가 흘러가게 됩니다.
  • CLI 전용입니다. dalfox server와 MCP 서버는 --baseline을 적용하지 않습니다. 공유 config의 scan.baseline도 그쪽에서는 조용히 무시됩니다.

색상 및 TTY 동작

dalfox scan https://target.app --no-color
# 또는
NO_COLOR=1 dalfox scan https://target.app

Dalfox는 출력이 파일이나 비 TTY로 리다이렉트될 때도 색상을 자동으로 비활성화합니다.

TOML

JSON과 동일한 데이터 형태이며(다른 형식과의 일관성을 위한 최상위 [meta] 엔벨로프 포함), TOML로 작성됩니다. 탐지 결과는 [[results]] 테이블 배열로 렌더링됩니다.

[meta]
dalfox_version = "3.2.3"
dedup_mode = "exact"
failed_requests = 0
findings_count = 1
incomplete = false
scan_duration_ms = 1234
targets = ["https://target.app/?q=a"]
targets_deduplicated = 0
total_requests = 87

[[meta.target_summary]]
findings_count = 1
status = "findings"
target = "https://target.app/?q=a"

[[results]]
type = "V"
type_description = "Vulnerable - dalfox asserts this input is exploitable; act on it"
inject_type = "inHTML"
method = "GET"
data = "https://target.app/?q=%3Csvg%20onload%3Dalert%281%29%20class%3Ddlx1ec4110f%3E"
param = "q"
payload = "<svg onload=alert(1) class=dlx1ec4110f>"
evidence = "DOM verification successful for param q (DOM marker)"
cwe = "CWE-79"
severity = "High"
message_id = 606
message_str = "Triggered XSS Payload (DOM marker): q=<svg onload=alert(1) class=dlx1ec4110f>"
location = "Query"
detection_method = "reflection"
confidence = "high"
confidence_reason = "payload reached an executable position in the parsed response"
dalfox scan https://target.app -f toml -o report.toml

SARIF → GitHub 코드 스캐닝

dalfox scan urls.txt -f sarif -o dalfox.sarif

GitHub의 upload-sarif 액션으로 dalfox.sarif를 업로드하면, 탐지 결과가 리포지토리의 Security → Code scanning 탭에 나타납니다.

CI 예시

# .github/workflows/xss-scan.yml
- name: Dalfox scan
  run: dalfox scan scope.txt -f sarif -o dalfox.sarif --silence --waf-evasion

- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: dalfox.sarif

신규 탐지 결과만으로 게이트하기

baseline.json을 스코프 파일과 함께 커밋해 두고 종료 코드로 빌드를 실패시키세요. 베이스라인에 없는 건이 나타났을 때만 빨간불이 됩니다.

# .github/workflows/xss-scan.yml
- name: Dalfox scan (new findings gate)
  run: |
    dalfox scan scope.txt \
      --baseline .dalfox/baseline.json \
      --only-poc v \
      -f json -o dalfox.json --silence    

- name: Upload report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: dalfox-report
    path: dalfox.json

트리아지 후 베이스라인을 갱신하려면 --baseline 없이 실행하고 -o를 베이스라인 파일로 지정하세요.

dalfox scan scope.txt --only-poc v -f json -o .dalfox/baseline.json

위 게이트 명령을 그대로 두고 -o .dalfox/baseline.json만 붙이면 신규 건만 담긴 리포트가 파일을 덮어써서 기록해둔 백로그가 날아갑니다. --output과 --baseline이 같은 경로로 해석되면 Dalfox가 stderr에 경고합니다.

종료 코드

Dalfox는 다음을 반환합니다.

코드 의미
0 성공적으로 완료, 탐지 결과 없음
1 성공적으로 완료, 티어와 무관하게 탐지 결과 하나 이상
2 입력/설정/런타임 오류, 또는 -o 파일을 쓰지 못한 경우. 탐지 결과가 없을 때는 다음도 해당: 모든 대상을 건너뜀(접속 불가, 맞지 않는 콘텐츠 타입 등), 대상의 스캔 워커가 중단됨(INTERNAL_ERROR), 요청의 10% 이상(최소 3건)이 응답을 받지 못함, 기본값 --on-session-loss abort에서 스캔 도중 세션이 끊어짐 (탐지 결과가 있었다면 여전히 1)

1은 모든 티어를 포함합니다. R 하나나 --detect-outdated-libs가 만든 I 하나도 V와 똑같이 빌드를 실패시킵니다. Dalfox가 악용 가능하다고 판단한 것만 게이트로 삼으려면 --only-poc v를 주고 종료 코드를 그대로 쓰세요. 코드가 정해지기 전에 필터가 적용됩니다. (JSON에 jq로 severity == "High"를 거는 방식도 오늘은 거의 같은 집합을 얻습니다. severity가 현재 티어를 따라가기 때문입니다. V는 High, A는 Medium, R은 Info입니다. 예외는 I 라이브러리 결과로, 권고(advisory)의 severity를 그대로 가지므로 High일 수 있습니다. 탐지 모델 참고.)

--baseline은 같은 종료 코드를 신규 여부로 좁힙니다. 기본 filter 모드에서는 억제된 건이 종료 코드 판정에 도달하지 않으므로, 백로그가 전부 베이스라인에 들어 있는 실행은 0으로 끝납니다. 베이스라인 참고.

다음 단계

ESC