13. E2E 샘플 평가 리포트
평가일: 2026-07-14 · 도구:
jam v0.38.0· Metrics Spec:v13.0.0· 지표 세트:jam-lite
이 문서는 Metrics v13의 측정 모집단 변경을 고정 SHA 16개 저장소에서 실제로 검증한 기준선이다. 핵심 판정 대상은 단순한 등급 유지가 아니라 다음 세 가지다.
- production과 test/documentation/configuration/generated가 안정적으로 분리되는가.
- 카테고리가 구현된 언어의 production CLOC만 분모에 들어가며 coverage가 공개되는가.
- 감사 finding은 보존하면서 점수 제외 finding이 debt·risk profile·상한·회귀 게이트에 영향을 주지 않는가.
v13 점수는 모집단과 부채 집계가 달라 v12.x 점수와 직접 비교할 수 없다. v12 기준선은 변화 방향을 설명하는 참고값으로만 사용한다. jam-full의 과거 외부 도구 평가는 14-jam-full-e2e-evaluation.md에 별도로 보존한다. 취약점 정답 라벨과 비교하는 외적 타당화는 이 regression 점수와 섞지 않고 16-independent-security-validation.md에서 관리한다.
1. 방법과 재현 계약
저장소 URL, commit SHA, 기대 grade와 exact score는 testdata/e2e-corpus.yaml이 단일 기준이다. manifest v2는 project profile, partition, 예상 integrity와 result/run/summary schema도 함께 고정한다. 기존 16종은 이미 기준 개발에 반복 사용됐으므로 독립 validation이 아닌 partition=regression으로 정직하게 분류한다. 2026-07-14에 16개 저장소를 새로 clone한 뒤 고정 SHA를 checkout하고, 같은 바이너리로 전부 스캔했다.
CGO_ENABLED=0 go build -o /tmp/jam-v13 ./cmd/jam
/tmp/jam-v13 version
# 전체 자동 검증(로컬 cache/out 경로는 필요에 맞게 지정)
go run ./tools/e2e-corpus \
--jam /tmp/jam-v13 \
--cache-dir /tmp/jam-e2e-cache \
--out-dir /tmp/jam-v13-results
관측 환경과 산출물:
- Linux amd64,
CGO_ENABLED=0 - clone:
/tmp/jam-v13-corpus.gCF0Xj - 결과:
/tmp/jam-v13-results.2cf2mF - 각 샘플에
jam-report.md,jam-detail.csv,jam-result.json,jam-run.json, SARIF 생성 경로 검증 jam-run.json의 Git commit/tree/dirty, source hash, policy/config hash, role CLOC, category coverage 검증
외부 저장소 HEAD는 사용하지 않는다. SHA가 고정되어 있고 JAM 스캔이 결정적이므로 기대 점수 범위는 완화된 구간이 아니라 실제 관측값 하나로 고정했다.
2. 샘플 세트
| 언어 | 역할 | Profile | 샘플 | 고정 SHA | 선정 이유 |
|---|---|---|---|---|---|
| Go | best | library | go-cleanhttp-best | 58d0437 | 작고 단순한 라이브러리 |
| Go | stress | application | govwa-stress | 4058f79 | 취약 웹앱 |
| TypeScript | best | library | ts-is-best | 7821031 | 타입 유틸리티 라이브러리 |
| JavaScript | stress | application | js-nodegoat-stress | c5cb68a | OWASP 취약 앱 |
| Python | best | library | py-itsdangerous-best | 672971d | 보안 관련 라이브러리 |
| Python | stress | application | py-pygoat-stress | 19d17cc | Django 취약 앱 |
| Java | best | application | java-gs-rest-best | e9efc9d | Spring 공식 가이드 |
| Java | stress | application | java-dvja-stress | 597ece1 | Java 취약 앱 |
| C# | best | library | cs-stateless-best | 588f1a1 | 상태 머신 라이브러리 |
| C# | stress | application | cs-dvcsharp-stress | 76c1de3 | 취약 API 예제 |
| Rust | best | cli | rust-hyperfine-best | f12f3d9 | CLI 프로젝트 |
| Rust | stress | educational | rust-rustlings-stress | 4bab596 | 미완성 연습 코드 |
| C | best | library | c-linenoise-best | a473823 | 소형 단일 파일 중심 라이브러리 |
| C | stress | educational | c-how2heap-stress | 02da6aa | heap exploit 교육 코드 |
| C++ | best | library | cpp-fmt-best | e8deaf2 | 대형 라이브러리 |
| C++ | stress | vulnerability_corpus | cpp-fuzzer-test-suite-stress | 6955fc9 | 퍼저 취약 샘플 모음 |
best와 stress는 표본 선정 역할이지 목표 등급 라벨이 아니다. 정상 프로젝트도 실제 production 부채가 크면 B/C가 될 수 있고, 보안 교육 프로젝트도 jam-lite가 의미론적 취약성을 다 보지 못하면 높은 점수가 나올 수 있다.
3. v13 실측 결과
Findings는 CSV 전체 행 수이고 괄호는 그중 score_exclusion 또는 suppression으로 점수에서 빠진 건수다. CLOC는 전체 / production이다.
| 언어 | 역할 | 샘플 | Score | Grade | Findings (제외) | CLOC 전체 / production | Integrity | 하드 조건 |
|---|---|---|---|---|---|---|---|---|
| Go | best | go-cleanhttp-best | 100 | A | 3 (1) | 214 / 74 | complete | - |
| Go | stress | govwa-stress | 72 | C | 57 (0) | 11,349 / 11,197 | complete | SEC critical |
| TypeScript | best | ts-is-best | 86 | B | 38 (7) | 5,130 / 1,723 | complete | - |
| JavaScript | stress | js-nodegoat-stress | 74 | C | 55 (4) | 5,298 / 3,990 | complete | SEC critical |
| Python | best | py-itsdangerous-best | 91 | A | 25 (4) | 1,580 / 647 | complete | - |
| Python | stress | py-pygoat-stress | 69 | C | 135 (3) | 14,857 / 13,636 | complete | SEC critical |
| Java | best | java-gs-rest-best | 100 | A | 2 (0) | 965 / 471 | complete | - |
| Java | stress | java-dvja-stress | 76 | C | 19 (0) | 7,887 / 6,968 | complete¹ | SEC critical |
| C# | best | cs-stateless-best | 80 | B | 218 (33) | 12,415 / 5,380 | complete | - |
| C# | stress | cs-dvcsharp-stress | 65 | C | 26 (0) | 2,774 / 1,029 | complete | SEC critical |
| Rust | best | rust-hyperfine-best | 87 | B | 152 (6) | 6,573 / 4,480 | complete | - |
| Rust | stress | rust-rustlings-stress | 82 | B | 467 (4) | 11,496 / 8,700 | complete | - |
| C | best | c-linenoise-best | 73 | C | 98 (0) | 2,753 / 2,744 | complete | - |
| C | stress | c-how2heap-stress | 80 | B | 14 (0) | 767 / 444 | complete | - |
| C++ | best | cpp-fmt-best | 88 | B | 440 (193) | 56,847 / 14,708 | complete | - |
| C++ | stress | cpp-fuzzer-test-suite-stress | 79 | C | 42 (0) | 3,756 / 3,270 | incomplete² | SEC critical |
¹ java-dvja의 docs/development/application-bootstrap.md는 NUL byte가 있어 감사 목록의 skipped file로 남지만 documentation 역할이므로 production integrity를 낮추지 않는다.
² cpp-fuzzer-test-suite의 production 파일 sqlite-2016-11-14/sqlite3.c가 7,012,677 bytes로 1 MiB 한도를 넘어 스킵되므로 측정은 incomplete다. strict 모드에서는 산출물을 쓴 뒤 exit 3이어야 한다.
4. v12 기준선과의 변화
| 샘플 | v12 기준 | v13 실측 | 변화 | 주된 이유 |
|---|---|---|---|---|
go-cleanhttp-best | 100/A | 100/A | 0 | production finding 없음 |
govwa-stress | 79/C | 72/C | -7 | production-only eligible 밀도 |
ts-is-best | 97/A | 86/B | -11 | test 7건 제외 후 production DUP 27 노출 |
js-nodegoat-stress | 79/C | 74/C | -5 | production-only 밀도 + SEC 상한 |
py-itsdangerous-best | 98/A | 91/A | -7 | production SIZE/DUP/HYG 밀도 |
py-pygoat-stress | 79/C | 69/C | -10 | production-only 밀도 + SEC 상한 |
java-gs-rest-best | 100/A | 100/A | 0 | eligible production finding 없음 |
java-dvja-stress | 79/C | 76/C | -3 | production SEC critical |
cs-stateless-best | 92/A | 80/B | -12 | test 33건 제외, production DUP 5/SIZE 75 |
cs-dvcsharp-stress | 79/C | 65/C | -14 | production DUP/ARCH 밀도 + SEC 상한 |
rust-hyperfine-best | 92/A | 87/B | -5 | 가산 production debt |
rust-rustlings-stress | 87/B | 82/B | -5 | 가산 production CPLX/DUP debt |
c-linenoise-best | 84/B | 73/C | -11 | 단일 파일 집중 부채 할인 제거 |
c-how2heap-stress | 94/A | 80/B | -14 | 작은 production 모집단의 실제 CPLX/HYG 밀도 |
cpp-fmt-best | 95/A | 88/B | -7 | test 193건 제외, production CPLX/DUP/SIZE 가산 |
cpp-fuzzer-test-suite-stress | 79/C | 79/C | 0 | 기존 SEC C-band 상한과 동일 |
14종 하락과 2종 불변은 임계값을 바꾼 결과가 아니다. v13은 다음 두 비표준 희석 경로를 제거했다.
- test/docs/config CLOC가 product debt의 분모를 키우는 효과
- 결함이 한 파일에 모였을 때 remediation cost를 30%까지만 세던 할인
따라서 best 6종의 등급 하락은 회귀로 간주하지 않는다. 원래 finding과 비용은 그대로이며, 점수만 더 좁고 일관된 production 모집단에서 계산된다. 반대로 이전 A를 보존하려고 가중치나 grade cut을 조정하는 것은 독립 calibration/validation 근거 없이 기준을 재튜닝하는 것이므로 하지 않았다.
5. 범위와 coverage 검증
5.1 역할 분리
대표적으로 다음 finding이 감사 출력에는 남고 점수에서는 빠졌다.
cpp-fmt-best: test finding 193건 제외, production 14,708 CLOC만 산입cs-stateless-best: test finding 33건 제외, production 5,380 CLOC만 산입ts-is-best: test finding 7건 제외, production 1,723 CLOC만 산입go-cleanhttp-best: test finding 1건 제외, production finding 0건이므로 100/A
CSV의 원 severity, raw debt_minutes, message와 location은 보존되고 debt_counted=0, score_exclusion=test가 기록된다. 제외 finding은 SEC/집중/등급 cap과 diff --fail-on-added를 발동시키지 않는다.
5.2 카테고리×언어 coverage
혼합 저장소에서 “점수가 높음”과 “분석 범위가 넓음”을 분리할 수 있다.
govwa: production 11,197 CLOC 중 core 카테고리 eligible Go/JS는 1,160 CLOC(10.4%), SIZE는 11,197 CLOC(100%).java-gs-rest: production 471 CLOC 중 core eligible Java는 34 CLOC(7.2%), SIZE는 471 CLOC(100%), RES는 not assessed.cpp-fmt: production 14,708 CLOC 중 core eligible C/C++은 14,594 CLOC(99.2%), SIZE는 100%.ts-is: production 1,723 CLOC가 core 카테고리에 100% eligible이며 RES는 not assessed.
이제 unsupported other CLOC를 추가해 core category 점수를 올릴 수 없다. 동시에 낮은 coverage는 jam-run.json, jam-result.json, Markdown 부록에 명시되어 사용자가 점수를 과대해석하지 않게 한다.
6. 무결성 판정 검증
integrity도 점수와 같은 production 모집단에 맞췄다.
- production source skip/parse failure/analyzer error는
incomplete이며scan --strictexit 3을 유지한다. - test/documentation/configuration에서만 생긴 skip/parse failure는 감사 배열에는 남지만 production score의 완전성을 낮추지 않는다.
- suppression 정책 오류와 전역 analyzer 오류는 역할과 무관하게 계속
incomplete다.
이 구분이 없으면 NUL byte가 든 문서 한 개 때문에 동일한 production 코드의 품질 게이트가 실패하는 모순이 생긴다.
7. 판정과 한계
v13은 수백 개 프로젝트를 같은 기준으로 비교하기 위한 기본 계약을 강화했다.
- 점수 대상, 제외 대상, 미지원 대상이 기계 판독 가능한 필드로 분리됐다.
- 카테고리마다 분모와 coverage가 공개되어 다언어 저장소의 과대평가를 확인할 수 있다.
- exact pinned corpus가 score drift를 즉시 드러낸다.
- production 밖의 신호는 삭제하지 않아 감사와 원인 분석이 가능하다.
남은 한계도 명확하다.
- 16개 코퍼스는 E2E 회귀 세트이지 통계적 대표 표본이나 grade cut의 독립 타당화 세트가 아니다.
how2heap같은 exploit 의미, 프레임워크 데이터 흐름, 의존성 취약점은 jam-lite만으로 충분히 평가할 수 없다.best/stress역할은 정답 라벨이 아니므로 precision/recall을 계산할 수 없다.- C/C++ 1 MiB 초과 translation unit은 현재 스킵되며 integrity로 드러난다.
다음 타당화 단계는 08-roadmap.md의 profile·holdout 계획대로 app/library/CLI/embedded/monorepo 층화, 독립 calibration/validation/holdout 분리, OWASP Benchmark/NIST SARD 교차 검증을 수행하는 것이다. 그 근거가 생기기 전에는 grade cut·가중치·cap을 코퍼스 등급에 맞춰 조정하지 않는다.
8. 자동 CI 게이트
.github/workflows/e2e-corpus.yml은 PR·main push·주간 schedule에서 16종을 4개 shard로 나눈다. 각 shard는 고정 저장소 clone을 cache하고 다음 계약을 모두 검증한다.
- manifest v2 문법, profile/partition, SHA pin, exact score/grade, expected integrity
--strictexit 0(complete) 또는 exit 3(incomplete)jam-result.jsonv3와jam-run.jsonv2의 도구/스펙/점수/production scope 일치- Git HEAD와 run manifest commit 일치, clean tree, source/policy/config/comparison hash 유효성
- Markdown/CSV/JSON/run manifest/SARIF 5종 존재와 비어 있지 않음
- CSV 26열, findingCounts와 행 수, suppressed/excluded
debt_counted=0감사 계약 - SARIF 2.1.0 identity와 result 수
결과는 shard별 jam/e2e-summary@1과 전체 원본 산출물을 Actions artifact로 14일 보존한다. JSON Schema에 고정된 요약에는 profile/partition, production CLOC, category coverage, finding/exclusion 수가 있어 이후 독립 holdout과 같은 형식으로 층화 비교할 수 있다.
공식 GitHub cache v6는 Node.js 24 및 최신 self-hosted runner를 요구한다. 이 저장소는 이미 checkout v6/upload-artifact v7을 쓰는 동일 runner 계열을 전제로 한다.
9. 검증 명령
gofmt -w $(find . -name '*.go' -not -path './.git/*' -not -path './dist/*')
go test -count=1 ./...
go vet ./...
go run ./tools/e2e-corpus --validate-only
go run ./tools/release-metadata --check
go run ./tools/render-docs-site --check
bash -n scripts/build-release-artifacts.sh
bash -n docs/install.sh
실측 score/grade는 testdata/e2e-corpus.yaml과 이 문서가 일치해야 하며, 생성된 docs/reference/13-e2e-sample-evaluation/index.html도 함께 커밋한다.