feat: GHAS급 시크릿 탐지 품질 자율층 M0~M5 (parity SLO, report-only)#58
Conversation
… 리포인트
- docs/workbench/specs/ghas-quality-secrets/{requirements,design,review}.md (리뷰 반영 v2 승격)
- docs/workbench/agentic-workflows/2026-06-21-ghas-quality-secrets-goal.md (실행 패킷)
- governance/autopilot_goal.yml → goal_id ghas-quality-secrets-parity
(governance/** 광역 금지·parity_slo.py만, acceptance_checks 정렬, stop_conditions 정본 16)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TwGs78e6Rb7P5BDe2ezQEh
…rity goal-setup 커밋 07c4e82가 autopilot_goal.yml의 goal_id만 새 goal로 리포인트하고 governance/current.yml의 active_goal 동기화를 빠뜨려 autopilot_gate(render.py active_goal-must-match)가 영구 fail이었음. 직전 goal 전환(5fdc16a)처럼 goal_id·active_goal·CURRENT.md를 함께 맞추는 goal-activation 완성. orchestrator-authorized 정정(범위 한정: current.yml + render 파생 CURRENT.md만; allowed_writes/gate/public_safety 불변). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TwGs78e6Rb7P5BDe2ezQEh
… 적대적 fixture
자율층 M1. 시크릿 탐지의 per-repo 1:1 GHAS parity 측정 harness를
synthetic fixture로 TDD 완성. 실 GHAS 무접촉.
- baseline/ghas_api/normalize.py: secret_type↔rule_id 정규화 맵(1급 산출물,
양방향 lookup + 미등록 식별 + type-coverage 메타). 초기 커버리지
github-pat/discord/aws.
- baseline/ghas_api/parity.py: GHAS alert→EvaluationKey 어댑터.
- state-aware truth: open + resolved-as-true_positive만 positive,
dismissed/resolved-FP/revoked는 recall 분모 제외 + GHAS-confirmed-FP 분리집계.
- line tolerance: ±k(기본 2) 매칭, full-history universe.
- 정규화 미등록 쌍은 type-unmatched-but-colocated 버킷으로 분리(precision/recall
미오염).
- per-repo micro → macro 집계.
- load_parity_snapshot: source=synthetic provenance fail-closed.
- 신규 precision/recall 공식·gate-threshold 판정 코드 0줄 —
core/evaluation/metrics.py(EvaluationResult/evaluate_evaluation_gate) 재사용,
어댑터는 정규화·truth필터·tolerance 매칭만. GhasComparisonResult/
compare_ghas_alerts_with_findings do-not-modify(제3 엔진 신설 방지).
- eval/ghas-parity-corpus/synthetic-snapshot.json: 적대적 fixture.
실 GHAS secret_type 토큰 + type-mismatch/line-drift/dismissed + tolerance
경계 음성대조(±k 안 must-match / 밖 must-NOT-match). 정규화/tolerance/state
필터를 끄면 특정 지표가 red로 떨어짐을 테스트로 증명(self-fulfilling 차단).
fake 토큰·repo·path만(public-safe), source=synthetic marker.
- design.md: pre-impl arch gate 반영 — "0 lines" 인변을 공식·gate 코드로 한정,
어댑터 EvaluationKey 수렴, tolerance 경계 음성대조 요구.
검증: uv run pytest 1058 passed, public_safety green, autopilot_gate green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TwGs78e6Rb7P5BDe2ezQEh
자율층 M2. 11-FP 실관측(docs-example/test-fixture/manifest-hash) 클래스를
scan 시점에 즉시 억제하되 canary TP는 위치 무관 보존. 기존 secret default 불변.
- scanners/gitleaks/context_filter.py(신규): suppression_reason(finding).
- noise_reason(item:dict) 계약 불변 — path-role은 정규화된
Finding.location.file_path가 필요해 map 이후·append 이전의 별도 scan-time
단계로 분리(pre-impl arch gate 권고 준수).
- default-on 결정적·no-network: context-class manifest-hash(lockfile),
path-role documentation/example/test 억제.
- FP-floor: 강토큰(FALSE_NEGATIVE_PATTERN, ghp_/AKIA)은 docs/test/manifest
어디서도 보존(canary 가드를 첫 분기로 강제) → existing-secret-default
-behavior-change 안 건드림(억제율 회귀 테스트로 보장).
- path-role 어휘는 llm/common/prompt._path_role과 동일하게 scanners 레이어에
재구현(scanners→llm import 없음, 테스트로 등가 강제).
- partner-pattern은 default-off gated 신규 동작분(이 scan-time 티어에선 KEEP
신호, 실 boost는 M3 검증 티어 소관 — design Open Questions에 재평가 노트).
- scanners/gitleaks/parser.py: map 이후·append 이전 suppression_reason 배선,
enable_noise_filter로 게이트. 억제=finding 미생성(scan-time 경계, post-scan
disposition 아님).
- core/scan/options.py: enable_noise_filter docstring에 path-role 억제도 이
스위치에 묶임 명시(post-M2 arch gate P1).
- design.md: partner-boost 위치·path-role 공통추출을 Open Questions에 deferred.
post-M2 아키텍처 리뷰 PASS(blocking 0). 검증: uv run pytest 1095 passed,
public_safety green, autopilot_gate --base 81d59d0 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TwGs78e6Rb7P5BDe2ezQEh
자율층 M3. scan_worker 핫패스에 동기 인라인 싼 티어(parser/filter 자동공유) +
비동기 LLM verdict 큐 2경로. 인라인 LLM 호출 0(비용 NFR). 비동기 LLM 티어는
gated default-off.
- runtime/verify_queue.py(신규): 비동기 verify 큐 seam.
- enqueue: 애매한 finding(terminal disposition 없음)당 멱등 verify 잡.
verify-job-id를 finding의 content-stable match_key에서 결정적 도출 + 기존
enqueue_commit_scan_job의 CAS(attribute_not_exists)로 재큐잉 폭주 차단
(NEEDS_REVIEW backoff 택1). 새 테이블/GSI/projection/attribute 없이 기존
job_type(free-form 문자열) "verify" 확장으로만 표현.
- drain: 별도 경로가 verify 잡을 lease→verify→record_verifier_disposition.
NEEDS_REVIEW는 무기록 consume(record_verifier_disposition 재사용).
- enqueue_errors를 CAS duplicate와 분리 집계(post-M3 arch gate D1 nit).
- runtime/scan_worker.py: verify_enqueue 훅(default None → pre-M3 byte-identical).
핫패스 완료 후 애매 건 enqueue-only. **D3 가드(post-M3 arch gate)**: leased
job_type=="verify"는 코드-스캔 worker가 처리하지 않고 pending 반환 →
fetch/scan/_advance_repo_health 미도달(freshness 오염 차단).
- scan_all은 기존 verifier/disposition 동기 경로 그대로(주간 배치, 회귀만 확인).
- salt provenance: tests/test_secret_hash_salt_provenance.py — secretHash가 LLM
티어로 나가는 유일 secret-파생값, per-deployment salt(SECURITY_SCANNER_HASH_SALT)
주입 시 digest 변화·set-but-empty 폴백 검증(model.py 미수정).
- design.md: NEEDS_REVIEW backoff 택1 확정 + drain 실제 store 구현 후속(D3)
Open Questions 기록.
post-M3 아키텍처 리뷰 PASS(blocking 0, storage-projection 미트리거). 검증:
uv run pytest 1115 passed, public_safety green, autopilot_gate --base 81d59d0 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TwGs78e6Rb7P5BDe2ezQEh
…ive 노출 자율층 M4. non-GHAS repo는 per-repo truth가 없어 SLO 측정 불가(B-floor+C-monitor, requirements Q6) — 증류한 품질 머신 전이의 건전성을 분포-shift 조기경보로 감시. - runtime/drift_monitor.py(신규): DriftBaseline.from_macro_parity가 M1 parity 집계(aggregate_repo_parity → EvaluationResult.true_positives = GHAS-매칭 TP)의 canonical-type 분포를 GHAS-calibrated 기준선으로 도출. non-GHAS는 GHAS-매칭 TP가 0이라 self-baseline 구조적 불가(테스트 증명). evaluate_distribution_drift는 finding rule_id 분포만으로 total variation distance 산출(stdlib Counter, 신규 의존성 0) — verifier verdict 절대 미참조(verifier-직교, common-cause bias 완화). verifier disposition 비율은 별도 필드로 cross-reference만(distance에 미혼입). 전이 한계(early-warning 전용, SLO 아님) 모듈 docstring에 명시. - runtime/notification_log.py: drift_record(type:"drift") 빌더 추가(cadence_overrun 선례 패턴, append-only JSONL, 기존 consumer 영향 0). - runtime/scan_all.py: DriftConfig(default-off) + ScanAllRequest.drift_config + _maybe_write_drift_record passive 훅. config None/disabled/baseline None이면 즉시 return → 기존 notification 스트림 byte-identical. drift는 별도 record로만, parity/ verification summary 슬롯과 분리(SLO 미오염). drift_monitor import는 함수-로컬 (default-off 경로 미탑재, circular import 회피). 폴링/타이머/스케줄 신설 없음 — scan-all 완료 시점에 1회 piggyback(능동 drift 비채택 준수). - design.md: drift 노출 표면 택1=notification_log 확정(근거 명시). 검증: uv run pytest 1130 passed(+15), default-off byte-identical 증명, parity.py/ metrics.py 무수정, public_safety green, autopilot_gate --base 81d59d0 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TwGs78e6Rb7P5BDe2ezQEh
…lo --check
자율층 M5(자율 goal done). 시크릿 GHAS parity를 frozen synthetic snapshot 대비
재현 측정하는 CI SLO 게이트. threshold 부재 → 영구 report-only(자율층은 실
baseline 없이 목표 못 만들므로). enforce·threshold 커밋은 H3 human-gated.
- governance/parity_slo.py(신규, allowed_writes 유일 governance 파일): three-mode.
- threshold yml 부재/빈값 → report-only(항상 exit 0, 차단 안 함).
- threshold 존재 → enforce(macro precision/recall vs precision_min/recall_min).
- snapshot 나이>임계 또는 fetched_at 부재 → stale-degraded. report-only는
warn+exit 0, enforce는 hard fail(silent pass 금지, design staleness-passive-only).
- 측정은 M1(load_parity_snapshot provenance fail-closed → evaluate_repo_parity →
aggregate_repo_parity, metrics.py 재사용) 소비. 신규 precision/recall 산식 0줄.
- non-synthetic snapshot은 provenance fail-closed로 게이트 입력 거부(실 GHAS
export 구동 불가).
- tests/test_governance_parity_slo.py: report-only/enforce(pass·fail)/stale-degraded
(report-only warn·enforce block)/provenance fail-closed/CLI exit/committed corpus 증명.
- design.md: 현재 상태(SLO enforce 미달성·H-track 대기) + CI 배선 한계(.github
scope 밖, report-only라 미배선이어도 차단 동일) + Component 표 governance→src
의존·drift 노출 표면 명문화.
final 아키텍처 리뷰(system + codebase, opus) PASS: blocking 0, 자율 goal done=M5
달성. 검증: uv run pytest 1142 passed, required local checks 8종 전부 green(이제
parity_slo --check 포함), autopilot_gate --base 81d59d0 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TwGs78e6Rb7P5BDe2ezQEh
There was a problem hiding this comment.
Code Review
This pull request implements the GHAS secret-parity SLO goal, introducing a synthetic adversarial snapshot fixture, an M1 parity harness with bidirectional token normalization, an M2 inline cheap tier for scan-time path-role and context-class suppression, an M3 async LLM verifier queue seam, an M4 non-GHAS drift monitor, and an M5 CI SLO gate. The reviewer feedback highlights a potential busy-loop issue in scan_worker.py when verify jobs are repeatedly leased and returned, and suggests several defensive programming guards to prevent potential AttributeError or TypeError exceptions across verify_queue.py, context_filter.py, and parity.py.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if job.job_type == JOB_TYPE_VERIFY: | ||
| request.store.return_job_to_pending( | ||
| job.job_id, "verify job is not handled by the code-scan worker" | ||
| ) | ||
| continue |
There was a problem hiding this comment.
여기에는 잠재적인 효율성 및 안정성 문제가 있습니다. verify 작업이 code-scan 워커에 의해 리스(lease)되면, 대기 상태(pending)로 반환되고 루프가 계속됩니다. 그러나 대기 중인 다른 작업이 없거나 다른 verify 작업만 있는 경우, 동일한 루프 반복 내에서 lease_next_scan_job을 다음으로 호출할 때 방금 반환된 동일한 verify 작업을 즉시 다시 리스할 수 있습니다. 이로 인해 워커가 단일 호출 내에서 동일한 작업을 최대 max_jobs번 반복해서 리스하고 반환하게 되어 데이터베이스 작업과 CPU 사이클이 낭비될 수 있습니다.
이러한 중복 리스 루프를 방지하기 위해, 현재 호출에서 대기 상태로 반환된 작업 ID를 추적하고 중복 리스가 발생할 경우 루프를 중단(break)하거나 건너뛰는 방안을 고려해 주세요.
| secret_hash = finding.evidence.secret_hash | ||
| if not secret_hash: | ||
| return "" |
There was a problem hiding this comment.
안정성을 확보하고 방어적 프로그래밍 관행을 준수하기 위해, finding.evidence가 None이거나 누락된 경우에 대비한 가드가 필요합니다. 만약 finding.evidence가 None인 경우, finding.evidence.secret_hash에 접근하면 AttributeError가 발생합니다.
evidence = getattr(finding, "evidence", None)
if not evidence or not evidence.secret_hash:
return ""
secret_hash = evidence.secret_hash| file_path = finding.location.file_path | ||
|
|
There was a problem hiding this comment.
finding.location 또는 finding.location.file_path가 None이거나 누락된 경우 발생할 수 있는 AttributeError 또는 TypeError를 방지하기 위해, 이 속성들을 _is_manifest_hash_location에 전달하기 전에 방어적으로 검증해야 합니다.
| file_path = finding.location.file_path | |
| location = getattr(finding, "location", None) | |
| file_path = location.file_path if location else None | |
| if not file_path: | |
| return None |
| data = json.loads(Path(path).read_text(encoding="utf-8")) | ||
| source = str(data.get("source", "")).strip().lower() |
There was a problem hiding this comment.
파싱된 JSON 필드에 대한 방어적 타입 검증을 구현할 때, 키에 접근하거나 .get()을 호출하기 전에 파싱된 JSON이 실제로 딕셔너리 객체인지 확인하여 안정성을 확보해야 합니다. 만약 JSON 파일이 잘못 형성되었거나 객체 대신 리스트/원시 타입을 포함하고 있다면, 처리되지 않은 AttributeError가 발생할 수 있습니다. 또한, 이와 같은 방어적 타입 검증을 구현할 때는 None, 숫자, 불리언, 리스트, 딕셔너리 등 다양한 예기치 않은 타입을 다루는 포괄적인 단위 테스트를 추가하여 견고함을 보장해 주세요.
data = json.loads(Path(path).read_text(encoding="utf-8"))
if not isinstance(data, dict):
raise ValueError("parity snapshot JSON must be a dictionary object")
source = str(data.get("source", "")).strip().lower()References
- When implementing defensive type validation for parsed JSON fields (e.g., verifying a field is a string), ensure robustness by adding comprehensive unit tests that cover various unexpected types, including None, numbers, booleans, lists, and dictionaries.
| config = config or ParityConfig() | ||
|
|
origin/main이 personal-prod-deploy 계열(PR #51~#55, M8 dashboard)로 전진해 충돌. 사용자 승인 결정대로 해소: - governance 3파일(autopilot_goal.yml/current.yml/CURRENT.md) → main(theirs) 채택. main의 active_goal(personal-prod-deploy)을 그대로 유지 = governance를 main에 맞춤(우리 goal-activation 폐기). 이 3파일은 origin/main과 byte-identical → governance self-modification도 scope-expansion도 아님. ghas는 active_goal 슬롯 불필요(M1~M5 전부 default-off/report-only라 슬롯 없이 안전 동작). - 코드 2파일(scan_worker.py/test_scan_worker.py) → 양쪽 로직 병합. main의 baseline-job full-history(_scan_options_for_job/_is_baseline_job)와 우리 M3 verify job pending-반환 가드 + enqueue가 양립(auto-merge 성공, 테스트 green). - 우리 신규 파일(parity/normalize/context_filter/verify_queue/parity_slo, eval/ghas-parity-corpus, spec docs)은 충돌 없음. 검증: uv run pytest 1253 passed, 4 skipped(env-gated). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TwGs78e6Rb7P5BDe2ezQEh
| from security_scanner.runtime.drift_monitor import ( | ||
| DRIFT_MONITOR_ENV_VAR, | ||
| DriftBaseline, | ||
| ScanAllDriftSummary, | ||
| drift_config_from_env, | ||
| evaluate_distribution_drift, | ||
| rule_id_distribution, | ||
| ) |
| from security_scanner.baseline.ghas_api.parity import ( | ||
| GHAS_POSITIVE_TRUTH_STATES, | ||
| ParityConfig, | ||
| aggregate_repo_parity, | ||
| evaluate_repo_parity, | ||
| load_parity_snapshot, | ||
| ) |
GHAS급 시크릿 탐지 품질 — 자율층 M0~M5 (parity SLO)
시크릿 탐지를 GHAS parity SLO에 맞추는 측정 harness + 티어드 FP-억제 품질 머신 + report-only CI SLO 게이트를 synthetic fixture로만 TDD 완성. 실 GHAS 무접촉. autopilot 단일 goal의 자율 done = M5.
SoT:
docs/workbench/specs/ghas-quality-secrets/{requirements,design,review}.md(design v2 리뷰 반영본).active_goal 슬롯 결정 (사용자 승인)
origin/main이
personal-prod-deploy를 active_goal로 유지 중. 이 PR은 슬롯을 밀어내지 않는다 — governance(autopilot_goal.yml/current.yml/CURRENT.md)는 머지 시 main에 맞춰 personal-prod 유지, ghas는 default-off/report-only 코드만 머지한다. M1M5는 전부 active_goal 슬롯 없이 안전하게 동작(기존 secret default 불변, 신규 동작은 전부 gated/default-off/report-only). autonomous M1M5 done, H1~H3(실 GHAS·baseline·enforce)는 human-gated 후속.Milestones (각 커밋)
0bdf939— per-repo 1:1 GHAS parity 측정 harness.core/evaluation/metrics.py재사용(신규 precision/recall·gate 계산 코드 0줄),baseline/ghas_api는 alert→EvaluationKey어댑터.secret_type↔rule_id정규화 맵 + type-coverage, state-aware truth(open+resolved-TP만 positive, dismissed 분리), line tolerance(±k), full-history universe. 적대적 fixture(type-mismatch/line-drift/dismissed + tolerance 경계 음성대조)가 정규화/필터/tolerance 누락을 red로.739fac3— 인라인 싼 FP-억제 티어.scanners/gitleaks/context_filter.pyscan-time path-role(docs/example/test)/context-class(manifest-hash) 억제.noise_reason(item:dict)계약 불변(별도 단계), canary FP-floor(강토큰 위치 무관 보존) → 기존 secret default 불변. partner-pattern default-off gated.b2e90e5— LLM 티어 disposition 배선(scan_all + scan_worker 2경로). 동기 인라인 자동공유 + 비동기 verify 큐(enqueue-only, 인라인 LLM 호출 0). 멱등 verify-job-id + enqueue CAS로 NEEDS_REVIEW 폭주 차단. 새 테이블/GSI/projection/attribute 없이 기존job_type="verify"로만. verify job pending-반환 가드. salt provenance.d905e95— non-GHAS drift monitor. M1 GHAS-derived 분포 기준선 + rule_id TV distance(verifier-직교), parity/SLO 물리 분리 필드, notification_log 노출, default-off·passive(폴링 신설 없음, byte-identical 증명).75df354— report-only CI SLO 게이트governance.parity_slo --check. threshold 부재→report-only(영구·자율층), 존재→enforce(H3), 나이>임계→stale-degraded(enforce hard fail). provenance fail-closed.아키텍처 게이트 (mandatory, 전부 PASS)
pre-implementation / post-M2 / post-M3 / final — 4지점 모두 blocking finding 0.
충돌 해소 (머지 커밋
7d45648)origin/main이 personal-prod 계열(PR #51~#55, M8 dashboard)로 전진해 충돌 → 사용자 승인대로 해소:
검증
uv run pytest→ 1253 passed, 4 skipped(env-gated)autopilot_gate --base origin/maingreen(governance가 main과 동일하니 base 트릭 불필요)자율 done = M5 (SLO enforce 미달성 — H-track 대기)
자율층은 영구 report-only(실 baseline 없이 목표 못 만들므로). v1 done(baseline+목표 도달)은 H1~H3 후:
ghas-live-fetch-or-mutation-requiredstop → 사람 PR, local 비커밋)governance/parity_slo_thresholds.yml) 커밋 → report-only→enforce 자동 전환H-track 메모(final 리뷰 권고): H3 enforce 시 빈/누락 코퍼스 macro=1.0 silent green 방지를 위해 threshold 옆
snapshot_count ≥ N가드 권고. H1 실 local snapshot 경로를.gitignore에 명시 추가. M5parity_slo --check의 ci.yml 한 줄 배선은.githubscope 밖이라 후속(report-only라 미배선이어도 차단 동일).🤖 Generated with Claude Code
https://claude.ai/code/session_01TwGs78e6Rb7P5BDe2ezQEh