diff --git a/README.md b/README.md index 312dc13..8ad745d 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ core <- scanners/storage/llm/adapters <- cli/runtime - [프로젝트 개요와 추진 전략](docs/views/project-overview-and-strategy.md) - [시스템 구조와 실행 환경](docs/views/system-architecture-and-runtime.md) - [소스 스캔 결과 NoSQL Schema](docs/views/source-scan-results-nosql-schema.md) +- [스캔 동작 방식과 메커니즘](docs/views/scan-behavior-and-mechanism.md) - [탐지 결과와 지표](docs/views/secret-detection-results-and-metrics.md) - [Local Scan Orchestration 목표 구조](docs/views/local-scan-orchestration-target-architecture.md) - [확장 경로와 운영 기준](docs/views/operations-transition-architecture.md) diff --git a/docs/README.md b/docs/README.md index dc395d8..ebf3800 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,10 +24,12 @@ 1. [프로젝트 개요와 추진 전략](views/project-overview-and-strategy.md) 2. [시스템 구조와 실행 환경](views/system-architecture-and-runtime.md) 3. [소스 스캔 결과 NoSQL Schema](views/source-scan-results-nosql-schema.md) -4. [탐지 결과와 지표](views/secret-detection-results-and-metrics.md) -5. [Local Scan Orchestration 구조](views/local-scan-orchestration-target-architecture.md) -6. [시작하기](views/getting-started.md) -7. [progress dashboard](dashboards/progress.html) +4. [스캔 동작 방식과 메커니즘](views/scan-behavior-and-mechanism.md) +5. [탐지 결과와 지표](views/secret-detection-results-and-metrics.md) +6. [Local Scan Orchestration 구조](views/local-scan-orchestration-target-architecture.md) +7. [제품 카탈로그](views/product-catalog.md) +8. [시작하기](views/getting-started.md) +9. [progress dashboard](dashboards/progress.html) ## 공개 후보 문서 @@ -36,8 +38,10 @@ | [project-overview-and-strategy.md](views/project-overview-and-strategy.md) | 이 프로젝트가 해결하려는 문제, 현재 지원 범위, 성공 기준 | | [system-architecture-and-runtime.md](views/system-architecture-and-runtime.md) | 전체 구조, 실행 흐름, codebase dependency boundary | | [source-scan-results-nosql-schema.md](views/source-scan-results-nosql-schema.md) | 스캔 결과를 어떻게 저장하고 어떤 질문에 답하려는지 | +| [scan-behavior-and-mechanism.md](views/scan-behavior-and-mechanism.md) | local one-shot scan과 periodic queue/worker scan의 단계별 동작 방식 | | [secret-detection-results-and-metrics.md](views/secret-detection-results-and-metrics.md) | 어떤 결과를 공개할 수 있고, 지표를 어떻게 해석하는지 | | [local-scan-orchestration-target-architecture.md](views/local-scan-orchestration-target-architecture.md) | Local Scan Orchestration의 Python file 참조 관계와 runtime call chain | +| [product-catalog.md](views/product-catalog.md) | 관리자 UX와 운영자 시나리오 관점에서 현재 제품 표면과 미완성 gap | | [operations-transition-architecture.md](views/operations-transition-architecture.md) | 로컬 실행 경로에서 반복 실행과 확장 adapter로 넘어가는 기준 | | [research-and-technical-decisions.md](views/research-and-technical-decisions.md) | 왜 Gitleaks-first인지, 다른 도구는 어떤 위치인지 | | [getting-started.md](views/getting-started.md) | 이미 클론된 로컬 저장소로 첫 스캔을 끝까지 돌리는 절차 | diff --git a/docs/_harness/doc-map.yml b/docs/_harness/doc-map.yml index 85f9af9..dce39f3 100644 --- a/docs/_harness/doc-map.yml +++ b/docs/_harness/doc-map.yml @@ -79,6 +79,16 @@ human_views: docs/workbench/context/legacy-source/02-local-db-and-dynamodb-compatible-schema.md: 57a1638a804eb21c4775f85d0657460cce25f5111635f28bfcf117d2f49eb006 assets: - docs/assets/schema-access-patterns.svg + - view_id: scan-behavior-and-mechanism + title: 스캔 동작 방식과 메커니즘 + path: docs/views/scan-behavior-and-mechanism.md + output_path: confluence://프로젝트-개요-및-추진-전략/스캔-동작-방식-및-메커니즘 + generated_by: human_doc_curator + generated_at: 2026-06-21 + safety_class: public + source_docs: [] + source_hashes: {} + assets: [] - view_id: secret-detection-results-and-metrics title: 탐지 결과와 지표 path: docs/views/secret-detection-results-and-metrics.md @@ -116,6 +126,34 @@ human_views: - docs/assets/local-scan-target-runtime-flow.drawio - docs/assets/local-scan-target-scenario-seams.svg - docs/assets/local-scan-target-scenario-seams.drawio + - view_id: product-catalog + title: 제품 카탈로그 + path: docs/views/product-catalog.md + output_path: confluence://프로젝트-개요-및-추진-전략/제품-카탈로그 + generated_by: human_doc_curator + generated_at: 2026-06-21 + safety_class: public + source_docs: + - src/security_scanner/runtime/read_api.py + - src/security_scanner/cli/commands/read_api.py + - src/security_scanner/cli/commands/scan.py + - src/security_scanner/cli/commands/scan_health.py + - src/security_scanner/cli/commands/disposition.py + - tests/test_read_api.py + - tests/test_queue_status.py + - tests/test_cli_disposition.py + - docs/workbench/context/legacy-source/08-milestones.md + source_hashes: + src/security_scanner/runtime/read_api.py: f5af4a9840239078647c81a5bc3381316f81c12b54f3145a3d9e91fb8051fe9d + src/security_scanner/cli/commands/read_api.py: 83f77aa0223619f00de6ccc82ecc7de70dc94d0016f492ba2f8701cc6ea9aa68 + src/security_scanner/cli/commands/scan.py: 2d03278acbb08dd56a0c79c7e390cb2375a7b41dbefde777ee7c4e79bdc274cd + src/security_scanner/cli/commands/scan_health.py: 255b6243d2f05a4b6c59e0f5056b0b27f1db15e52ac0d77219a97586d3621b61 + src/security_scanner/cli/commands/disposition.py: 200a9198f8bcf8d7c80afc77e103bb22bdcc4d1169441d5451375ce0caf36bb5 + tests/test_read_api.py: ac327e19be8dec2f525701a54115d74d707e2c553f768b31ca4e0b6663a840eb + tests/test_queue_status.py: 448b89ce7d8aba6abfa9a71206aba827814087b3b2c3c0d5413c11104ce82fc3 + tests/test_cli_disposition.py: 7c9027b821521b3336e2c0da4ce0c2bb498a2e80e038d2b4e137070f0998225e + docs/workbench/context/legacy-source/08-milestones.md: 121170acde8ccdb4ca8ba50e22faf7ae5ecd79c33b76294ba852e1e7a1675c1b + assets: [] - view_id: operations-transition-architecture title: 확장 경로와 운영 기준 path: docs/views/operations-transition-architecture.md diff --git a/docs/views/product-catalog.md b/docs/views/product-catalog.md new file mode 100644 index 0000000..8bc2cac --- /dev/null +++ b/docs/views/product-catalog.md @@ -0,0 +1,45 @@ +# 제품 카탈로그 + +`security-scanner`는 local-first Secret Detection 파이프라인을 운영자가 반복 실행, 관찰, 분류할 수 있게 만드는 제품입니다. 현재 제품 표면은 CLI와 read API 중심이며, 전체 관리자 콘솔보다는 dashboard data contract와 운영 판단 신호를 먼저 안정화하고 있습니다. + +## 관리자 UX/운영자 시나리오 + +관리자 UX의 목적은 "스캔이 돌았는가"를 넘어서, 어느 대상이 빠졌고, 어떤 finding이 아직 판단되지 않았으며, 운영자가 지금 어떤 조치를 해야 하는지 알려주는 것입니다. 현재 구현은 health, coverage, backlog, freshness, disposition-filtered findings를 읽는 기반을 제공합니다. 다음 단계는 그 신호를 ack, suppress, assign, dispatch, drilldown, RBAC가 있는 action UX로 묶는 일입니다. + +### 사용자 여정 단계 + +| 단계 | 운영자가 확인하는 질문 | 필요한 화면/정보 | 현재 상태 | +| --- | --- | --- | --- | +| 1. 범위 확인 | 어떤 repository가 관리 대상이고, 무엇이 빠졌는가? | 전체 대상 수, 포함/제외 수, covered count, coverage gap | `read-api` coverage panel이 org total, included, excluded, covered, gap을 제공합니다. | +| 2. 신선도 판단 | 최근 스캔이 SLA 안에 들어왔는가? | incremental/baseline breach count, evaluated time, repo별 stale signal | `scan-health`, `freshness-eval`, `read-api` freshness rollup이 집계 신호를 제공합니다. | +| 3. 작업량 파악 | 워커가 밀렸는가, 실패가 누적되는가? | pending, leased, completed, dead-letter, expired lease, backlog 추세 | `queue-status`와 `read-api` backlog panel이 queue 상태를 제공합니다. | +| 4. finding 분류 | 무엇이 verified, false positive, unreviewed인가? | disposition filter, severity, confidence, rule, repo, redacted location, secret hash | `read-api --include-findings --disposition ...`가 public-safe finding DTO를 제공합니다. | +| 5. 조치 기록 | 사람이 어떤 판단을 남겼는가? | true positive / false positive 판정, reason, actor/source, audit event | `disposition set`이 human/manual 판정을 `FINDING_STATE`와 `STATE_EVENT`에 기록합니다. | +| 6. 후속 작업 연결 | 누가 rotation, suppression, exception review를 맡는가? | assignee, due date, acknowledgement, dispatch target, reopen rule | 아직 제품 UX로 묶이지 않았습니다. | + +### 현재 UI 기능 + +- **Dashboard/read API**: `read-api`는 한 번의 dashboard refresh payload로 freshness, coverage, backlog를 반환하고, 요청 시 disposition-filtered findings를 포함합니다. +- **Health와 freshness**: `scan-health`는 stale repo가 있으면 fail-closed gate로 동작합니다. `freshness-eval`은 freshness breach를 평가하고 `BREACH_COUNTER` rollup을 materialize합니다. +- **Coverage**: catalog와 repo health를 비교해 포함 대상 중 아직 스캔되지 않은 coverage gap을 보여줍니다. +- **Queue 상태**: `queue-status`는 pending, leased, completed, dead-letter와 expired lease count를 사람이 읽는 형식으로 보여줍니다. read API backlog panel은 poll-friendly count를 제공합니다. +- **Finding disposition**: findings DTO는 raw secret과 scanner match를 노출하지 않고 repo, rule, severity, confidence, status, disposition, redacted location, salted secret hash만 제공합니다. `disposition set`은 human/manual true positive 또는 false positive 판정을 기록합니다. +- **Alert dispatch substrate**: freshness evaluator는 notification-log sink와 de-dup/re-notify window를 갖춘 alert dispatcher를 경유합니다. 실제 Slack, email, webhook 같은 delivery channel은 deploy-gated입니다. + +### 미완성 UX gap + +| Gap | 현재 한계 | 카탈로그상 의미 | +| --- | --- | --- | +| Action UX | read API는 상태를 보여주지만, dashboard 안에서 ack, suppress, reopen, bulk action을 수행하는 화면은 없습니다. | "관찰 가능" 단계이며 "운영 워크벤치"는 다음 제품 slice입니다. | +| Ack | alert나 stale 상태를 사람이 확인했다는 별도 acknowledgement state가 없습니다. | 반복 알림 억제는 시간 기반 de-dup이고, 사람의 확인 행위와 분리되어 있지 않습니다. | +| Suppress | false positive disposition과 match-key suppression 기반은 있으나, 만료일, 범위, 승인자, 사유 템플릿을 가진 suppression policy UX는 없습니다. | 억제 메커니즘은 존재하지만 exception management UX는 미완성입니다. | +| Dispatch | notification-log sink와 dispatcher seam은 있으나, 실제 외부 채널 routing, escalation, delivery status 화면은 deploy-gated입니다. | 알림 연결 지점은 준비됐고, 운영 채널 제품화는 별도 결정이 필요합니다. | +| Assign | finding이나 stale repo를 담당자에게 배정하는 assignee, due date, ownership model이 없습니다. | 개인 작업 관리가 아니라 scanner state 관찰에 머물러 있습니다. | +| Drilldown | public-safe finding row는 제공하지만, repo history, scan run, lifecycle event, queue job을 한 화면에서 연결하는 상세 화면은 없습니다. | 원인 추적은 CLI와 저장소 조회 조합에 의존합니다. | +| RBAC/Auth | read API는 loopback 기본값과 non-loopback auth gate만 검증합니다. 사용자, 역할, 권한, audit session 모델은 없습니다. | co-located admin UI 전제의 read-only contract이며 multi-user admin console은 아닙니다. | + +### 카탈로그용 concise copy + +관리자 UX는 local-first scanner를 운영자가 믿고 반복 실행할 수 있게 하는 관찰·판단 계층입니다. 현재 제품은 dashboard/read API로 freshness, coverage, queue backlog, disposition-filtered findings를 public-safe JSON으로 제공하고, `scan-health`, `freshness-eval`, `queue-status`, `disposition set`으로 운영자가 stale 상태와 queue 정체, human triage를 확인할 수 있게 합니다. + +아직 완성된 관리자 콘솔은 아닙니다. 다음 UX slice는 read-only dashboard를 action workspace로 확장해 ack, scoped suppression, alert dispatch, assignee, drilldown, RBAC를 연결하는 것입니다. 이 전환이 끝나면 운영자는 단순히 "문제가 있다"를 보는 데서 멈추지 않고, 누가 무엇을 판단했고 다음 조치가 어디에 걸려 있는지 한 화면에서 확인할 수 있습니다. diff --git a/docs/views/project-overview-and-strategy.md b/docs/views/project-overview-and-strategy.md index e0a6d0c..dc526c7 100644 --- a/docs/views/project-overview-and-strategy.md +++ b/docs/views/project-overview-and-strategy.md @@ -22,6 +22,7 @@ Secret scanner는 많은 후보를 찾아냅니다. 하지만 실제 운영에 - 스캔 실행, finding, lifecycle 상태를 로컬 저장소에 남깁니다. - Synthetic corpus로 precision, recall, false negative를 측정합니다. - Ollama-compatible verifier는 redacted metadata만 받아 triage 상태를 보조합니다. +- `code-vuln` category는 opt-in SARIF import와 Semgrep-compatible local adapter로 다룹니다. ## 현재 지원하지 않는 일 @@ -30,7 +31,7 @@ Secret scanner는 많은 후보를 찾아냅니다. 하지만 실제 운영에 - 관리형 클라우드 실행 환경 연동. - 내부 endpoint 또는 알림 시스템 연동. - Gitleaks를 대체하는 primary scanner 도입. -- SAST/SCA 확장. +- 기본 Secret Detection 경로를 대체하는 SAST/SCA 확장. ## 현재 상태 @@ -38,6 +39,23 @@ Secret scanner는 많은 후보를 찾아냅니다. 하지만 실제 운영에 따라서 지금의 핵심 메시지는 “로컬 Secret Detection 파이프라인을 안정화하는 중”입니다. 현재 공개 기본 경로는 local scan, local store, report, gate, evaluation입니다. +## 카탈로그용 요약 + +`security-scanner`는 GHAS 전체를 대체하는 managed SaaS가 아니라, local-first 환경에서 Secret Detection 결과를 재현 가능하게 만들고 GHAS Secret Scanning 품질에 가까워지도록 보정하는 제품입니다. + +PR #58 이후 자율층 M0~M5는 synthetic fixture 기준으로 GHAS parity harness, 인라인 FP 억제, LLM tier disposition 배선, non-GHAS drift monitor, report-only parity SLO gate까지 갖췄습니다. 다만 실 GHAS snapshot 취득, baseline 측정, threshold 커밋, enforce 전환은 human-gated H1~H3로 남아 있습니다. + +## GHAS 대체 매트릭스 + +| 영역 | 우리 제품 기능 | 현재 성숙도 | gap | +| --- | --- | --- | --- | +| GHAS Secret Scanning | Gitleaks-first local checkout scan, `Finding` 표준화, local store, report/gate/evaluate, read-only GHAS Secret Scanning alert/location fetch, redacted `GHAS_ALERT` 비교 input, incomplete metadata bucket | **부분 대체 + 보완.** Local secret detection과 redacted GHAS 비교는 가능하고, PR #58 이후 GHAS `secret_type` ↔ gitleaks `rule_id` 정규화, state-aware truth, line tolerance가 parity harness에 반영됨 | GitHub native push protection, secret validity/revocation, alert mutation, org-level managed rollout, GHAS scan trigger는 제공하지 않음. 실 GHAS snapshot 기반 enforce는 H1~H3 대기 | +| GHAS Code Scanning | `code-vuln` opt-in 경로: SARIF import, `VULN_FINDING` JSONL artifact, Semgrep-compatible local adapter, code-vuln report/gate/evaluate, vulnerability verifier/explainer | **보완.** SARIF-compatible local slice는 있으나 기본 경로가 아니며 GHAS Code Scanning alert lifecycle과 분리됨 | GHAS Code Scanning alert fetch/comparison, SARIF upload, code scanning alert mutation, security campaign, SCA/SBOM/dependency vulnerability는 future track | +| Alert lifecycle | Secret finding은 `FINDING_STATE`와 disposition ledger로 재검토 상태를 보존하고, verifier terminal verdict를 disposition으로 반영. `NEEDS_REVIEW`는 fail-closed review-needed 상태로 남김 | **부분 대체.** Local triage lifecycle은 갖췄고 PR #58 M3에서 `scan_all` + `scan_worker`의 LLM tier disposition/async verify queue가 배선됨 | GHAS alert open/resolved/dismissed 상태를 원격으로 변경하지 않음. GitHub UI와 양방향 lifecycle sync, assignment, dismissal comment, notification workflow는 미대체 | +| Dashboard / overview | Read API와 read-only dashboard/admin UI로 catalog, scan status, findings, repo health, backlog 상태를 보는 운영 화면 제공 | **보완.** M8 dashboard가 merged되어 local 운영 가시성은 강화됨 | GHAS organization security overview, enterprise dashboard, cross-org trend, native GitHub permission model, managed notification UX는 미대체 | +| Quality / parity | GHAS parity SLO 모델, per-repo 1:1 macro precision/recall, synthetic GHAS parity corpus, type coverage, GHAS-confirmed-FP bucket, report-only `governance.parity_slo --check`, stale-degraded 표시 | **보완에서 enforce 전 단계.** PR #58 M0~M5 완료로 측정/품질 자율층은 존재. Drift monitor는 GHAS-calibrated baseline을 쓰지만 non-GHAS에서는 early-warning일 뿐 SLO가 아님 | Threshold file 부재 시 parity gate는 report-only라 차단하지 않음. 실 baseline 측정과 목표 확정, enforce 전환, fixture-vs-real divergence 보고는 human-gated H-track 필요 | +| Managed SaaS | Local-first CLI/runtime, DynamoDB-compatible local storage pattern, systemd-oriented personal runtime, public-safe synthetic fixtures | **미대체.** 운영자가 소유하는 로컬/자체 실행 제품 | Hosted multi-tenant service, GitHub-native entitlement, managed scaling, SLA, billing, centralized secrets/revocation, enterprise policy management는 제공하지 않음 | + ## 성공 기준 - 같은 입력에서 같은 report와 gate 결과를 다시 만들 수 있습니다. diff --git a/docs/views/scan-behavior-and-mechanism.md b/docs/views/scan-behavior-and-mechanism.md new file mode 100644 index 0000000..b01e038 --- /dev/null +++ b/docs/views/scan-behavior-and-mechanism.md @@ -0,0 +1,268 @@ +# 스캔 동작 방식과 메커니즘 + +이 문서는 제품 카탈로그에서 스캔이 실제로 어떤 단계를 거치는지 설명합니다. + +핵심은 두 실행 형태를 구분하는 것입니다. 첫째는 사용자가 CLI로 한 번 실행하는 +local one-shot scan입니다. 둘째는 저장소 catalog와 durable queue를 기반으로 +반복 실행되는 periodic queue/worker scan입니다. 두 흐름 모두 public-safe 결과 +모델을 만들고, 저장된 결과에서 report와 gate를 다시 계산하는 방향을 공유합니다. + +## 한 줄 설명 + +`security-scanner`는 대상 checkout을 준비한 뒤 Gitleaks 또는 opt-in +Semgrep-compatible scanner를 실행하고, scanner output을 canonical finding으로 +정규화합니다. 일반 결과 저장소에는 redacted evidence만 남기고, report와 gate는 +저장된 snapshot을 다시 읽어 계산합니다. 반복 실행 경로는 durable queue, ledger, +repo lease, freshness marker를 사용해 같은 commit을 중복 처리하지 않고 오래된 +coverage를 드러냅니다. + +## Flow 1. Local one-shot scan + +```text +manifest + -> enabled target 선택 + -> workspace/local checkout 확인 + -> Gitleaks 실행 + -> Finding 정규화 + -> optional encrypted raw evidence 기록 + -> JSONL 또는 DynamoDB-compatible store write + -> report / gate / evaluate에서 저장 결과 재조회 +``` + +### 1. Manifest + +Local one-shot scan은 명시적인 manifest 파일에서 시작합니다. + +- `version`은 현재 `1`만 지원합니다. +- `targets[]`는 `name`, `path`, `enabled`를 가집니다. +- `scan`은 `include_history`, `git_log_opts`, `exclude`, + `enable_noise_filter` 같은 scanner option을 담습니다. +- `gitleaks_config`가 있으면 Gitleaks 실행 시 `--config`로 전달됩니다. + +Manifest는 자동 발견되지 않습니다. 사용자가 넘긴 파일만 읽고, `enabled=true`인 +target만 스캔합니다. + +### 2. Workspace / clone + +현재 local one-shot 경로의 workspace 단계는 remote clone이 아닙니다. `Target.path` +를 절대 경로로 해석하고, 해당 directory가 local filesystem에 존재하는지만 +검증합니다. + +제품 관점에서 이 단계의 의미는 “scanner가 실행될 checkout을 보장한다”입니다. +Phase 1에서는 이미 준비된 local checkout만 대상으로 삼습니다. + +### 3. Gitleaks 실행 + +Secret Detection 기본 scanner는 Gitleaks입니다. + +- `include_history=true`이면 `gitleaks git`을 사용합니다. +- `include_history=false`이면 `gitleaks dir`을 사용합니다. +- `git_log_opts`가 있으면 git scan mode에서 `--log-opts`로 전달됩니다. +- report는 임시 JSON 파일로 받고, `--exit-code 0`을 사용해 “finding 있음”을 + process failure와 구분합니다. + +Gitleaks binary 없음, timeout, non-zero process exit은 scanner/runtime 실패로 +취급됩니다. Local scan summary에는 private path나 raw output 대신 public-safe +오류 형태로 축약됩니다. + +### 4. Finding normalization + +Gitleaks JSON item은 core `Finding`으로 변환됩니다. + +- file path는 repository root 밖으로 escape하지 못하도록 정규화됩니다. +- raw secret은 `secretHash`로 바뀌고, evidence summary는 항상 `redacted=true` + 입니다. +- Gitleaks fingerprint가 있으면 repo scope와 결합해 dedup 안정성을 높입니다. +- 기본 verdict는 `NEEDS_REVIEW`입니다. +- template placeholder, known dummy value, repeated character, low entropy 후보는 + parser 단계에서 scan-time noise로 제거됩니다. +- docs/example/test path-role이나 lockfile hash context 같은 deterministic + suppression도 scan-time에서 처리됩니다. + +이 단계에서 제거된 항목은 `Finding`으로 저장되지 않습니다. LLM verifier가 내리는 +판단은 scan-time suppression이 아니라 post-scan disposition입니다. + +### 5. Raw evidence / redaction + +일반 finding snapshot에는 raw `secret`과 raw `match`가 저장되지 않습니다. +`Finding.to_dict()`는 scanner-native Gitleaks payload에서도 `secret`과 `match`를 +`null`로 redaction합니다. + +정확한 runtime review가 필요한 경우에만 별도 raw evidence path를 지정할 수 +있습니다. 이때 `RawEvidenceJsonlStore`는 raw secret과 match를 암호화한 metadata +row로 append합니다. 이 artifact는 public docs, examples, committed fixtures에 +넣지 않습니다. + +### 6. Store write + +Local scan은 storage backend에 따라 결과를 기록합니다. + +- JSONL backend는 redacted finding snapshot을 append합니다. +- DynamoDB-compatible backend는 finding, repo metadata, scan run summary를 + 저장합니다. +- store가 repo freshness capability를 제공하면, 성공적으로 스캔된 target마다 + baseline freshness가 전진합니다. + +Store write는 scanner 실행과 report rendering을 분리합니다. 이후 report/gate는 +scanner를 다시 실행하지 않고 저장소를 읽습니다. + +### 7. Report / gate / evaluate + +`report`, `gate`, `evaluate`는 finding consumer입니다. + +- `--scan-run-id`가 있으면 특정 scan run만 읽습니다. +- 없으면 backend가 볼 수 있는 전체 finding을 읽습니다. +- Secret gate는 `OPEN` 상태이고 verdict가 `NEEDS_REVIEW` 또는 `TRUE_POSITIVE`인 + finding을 blocking으로 계산합니다. +- `FALSE_POSITIVE`, `RESOLVED`, `IGNORED` 상태는 blocking에서 제외됩니다. + +이 구조 때문에 scanner 실행, 저장, 검토, gate 판단을 각각 재현할 수 있습니다. + +## Flow 2. Periodic queue/worker scan + +```text +catalog / ref discovery / baseline enqueue + -> SCAN_JOB 생성 + -> scan-worker가 job lease + -> repo lease 획득 + -> workspace fetch hook으로 checkout 확보 + -> Gitleaks 실행 + -> Finding 정규화 + -> findings + ScanLedger write + -> job completed 또는 retry/dead-letter + -> repo freshness 전진 + -> ambiguous finding verifier job enqueue + -> 별도 verify drain이 disposition 기록 +``` + +### 1. Catalog와 job 생성 + +Periodic path는 local manifest를 매번 직접 읽는 구조가 아닙니다. catalog, +ref state, baseline enqueue 같은 runtime 입력이 durable `ScanJob`을 만듭니다. + +`ScanJob`은 repo, ref, commit, scanner tuple, priority, retry 정보, job type을 +가집니다. `job_type`은 freshness 의미를 나눕니다. + +- `incremental` completion은 incremental freshness를 전진합니다. +- `baseline` completion은 full scan freshness를 전진합니다. +- `verify` job은 scanner worker가 처리하지 않으며 freshness를 전진하지 않습니다. + +### 2. Worker lease와 repo lease + +`scan-worker`는 pending job을 lease하고, 별도로 repo lease를 획득한 뒤 스캔합니다. +이중 lease는 두 가지 문제를 줄입니다. + +- 여러 worker가 같은 job을 동시에 완료하는 문제 +- 같은 repo workspace를 동시에 조작하는 문제 + +Lease에는 fence token이 붙습니다. lease가 만료되어 reaper나 다른 worker가 소유권을 +가져가면, 오래된 worker의 completion/failure write는 조건부 write에서 거절될 수 +있습니다. + +### 3. Worker scan + +Worker의 기본 scanner도 Gitleaks입니다. + +- baseline job은 full-history scan으로 실행됩니다. +- incremental job은 commit 단위 `git_log_opts`로 실행됩니다. +- scanner 결과는 local one-shot과 같은 Gitleaks parser/mapper를 통과합니다. +- branch와 commit context는 finding occurrence에 덧붙습니다. + +이미 같은 scanner tuple의 `ScanLedger`가 있으면 worker는 scanner를 다시 실행하지 +않고 job을 completed로 처리합니다. + +### 4. Store write와 ledger + +Worker completion은 findings와 `ScanLedgerEntry`를 함께 기록하고 job을 completed로 +전환합니다. 실패는 retryable failure로 pending에 되돌아가거나, retry 한도를 넘으면 +dead-letter가 됩니다. + +Ledger의 의미는 “이 repo commit은 이 scanner/rule/config tuple로 이미 처리됐다”는 +내구성 있는 증거입니다. 이는 periodic scan의 idempotency 기준입니다. + +### 5. Verifier queue + +Periodic worker hot path는 LLM을 직접 호출하지 않습니다. 스캔이 성공한 뒤 ambiguous +finding에 대해 best-effort로 `job_type="verify"` job을 enqueue합니다. + +Verifier job은 같은 queue table을 사용하지만 논리적으로 별도 작업입니다. + +- code-scan worker가 `verify` job을 받으면 pending으로 돌려 dedicated drain이 + 처리하게 합니다. +- verify job id는 finding의 content-stable key에서 결정되어 중복 enqueue를 줄입니다. +- 별도 drain path가 verifier를 호출하고 terminal disposition을 기록합니다. +- `NEEDS_REVIEW`는 disposition을 쓰지 않고 job만 소비해 tight loop를 막습니다. + +Verifier는 detector가 아닙니다. Finding을 삭제하지 않고 review/gate에 쓰일 +disposition signal만 보강합니다. + +### 6. Freshness + +Periodic path는 freshness를 scan result와 별도로 다룹니다. + +- repo별 freshness row는 incremental과 baseline timestamp를 따로 가집니다. +- timestamp update는 advancing-only 조건부 write라 오래된 completion이 최신 + freshness를 되돌리지 못합니다. +- freshness evaluator는 repo health를 읽어 breach counter를 materialize합니다. +- read API나 dashboard는 materialized counter를 O(1)로 읽을 수 있습니다. + +Freshness는 “finding이 몇 개인가”가 아니라 “coverage가 최근에도 성공했는가”를 보는 +신호입니다. + +## Opt-in code vulnerability path + +Secret Detection 기본 경로와 별도로 `code-vuln` category가 있습니다. + +```text +local checkout + -> Semgrep-compatible CLI 실행 + -> SARIF 생성 + -> SARIF importer + -> VULN_FINDING 정규화 + -> vulnerability JSONL write + -> report/gate/evaluate --category code-vuln +``` + +이 경로는 `Finding` 모델을 재사용하지 않습니다. SARIF result는 +`VulnerabilityFinding`으로 변환되고, JSONL에는 `entityType=VULN_FINDING`으로 +기록됩니다. + +Code vulnerability path의 redaction 기준은 secret path와 다릅니다. + +- absolute path와 external URI는 hash 기반 redacted path로 바뀝니다. +- `scan-vuln`은 기본 `path_policy=redacted`를 사용합니다. +- message, rule name, help text, tags, partial fingerprint도 sanitizer를 거칩니다. +- gate는 severity와 precision threshold를 기준으로 blocking 여부를 계산합니다. + +현재 제품 설명에서는 이 경로를 opt-in 확장으로 다룹니다. Periodic queue/worker의 +기본 scanner path는 Gitleaks입니다. + +## 단계별 의미 요약 + +| 단계 | Local one-shot | Periodic queue/worker | 의미 | +| --- | --- | --- | --- | +| 대상 선택 | manifest의 enabled target | catalog/ref/baseline enqueue가 만든 `ScanJob` | 무엇을 스캔할지 고정 | +| workspace | local directory 존재 확인 | fetch hook과 repo lease로 checkout 확보 | scanner 입력 준비 | +| scanner | Gitleaks, opt-in `scan-vuln` | Gitleaks worker path | raw detector output 생성 | +| normalization | `Finding` 또는 `VULN_FINDING` | `Finding` | 저장 가능한 canonical model | +| raw evidence | 기본 redacted, optional encrypted artifact | 기본 redacted, verify용 metadata 중심 | 공개 output과 exact evidence 분리 | +| store write | JSONL 또는 DynamoDB-compatible | findings, job, ledger, health | 재조회와 재현성 확보 | +| report/gate | 저장 결과 재조회 | 저장 결과 재조회 | scanner 실행과 판단 분리 | +| verifier | post-scan artifact/CLI 경로 | async verify job drain | detector가 아닌 triage 보조 | +| freshness | successful target별 full scan freshness | incremental/baseline freshness 분리 | coverage staleness 감지 | + +## Gap / 주의 + +- Local one-shot의 `workspace/clone` 단계는 아직 remote clone/update가 아닙니다. + 이미 존재하는 local checkout을 확인하는 단계입니다. +- Periodic worker의 기본 scanner는 Gitleaks입니다. `code-vuln`의 Semgrep-compatible + path는 현재 opt-in local/SARIF flow이며 worker queue 기본 경로로 합쳐졌다고 + 설명하면 안 됩니다. +- Scanner-native raw `secret`과 `match`는 일반 finding snapshot에 남기지 않습니다. + exact evidence가 필요하면 암호화 raw evidence artifact를 별도로 관리해야 합니다. +- `NEEDS_REVIEW`는 안전한 최종 허용 상태가 아닙니다. Secret gate 기본값에서는 + `OPEN + NEEDS_REVIEW`가 blocking입니다. +- Freshness는 finding count가 아닙니다. 성공 timestamp가 오래되면 finding이 없어도 + coverage gap으로 봐야 합니다. +- Public repo에는 실제 target 목록, private path, raw findings, external alert data, + credential, internal endpoint를 넣지 않습니다. diff --git a/docs/views/source-scan-results-nosql-schema.md b/docs/views/source-scan-results-nosql-schema.md index d18b578..a7d423f 100644 --- a/docs/views/source-scan-results-nosql-schema.md +++ b/docs/views/source-scan-results-nosql-schema.md @@ -1,83 +1,64 @@ # 소스 스캔 결과 NoSQL Schema -이 schema는 스캔 결과를 저장하기 위한 것입니다. Source code 원문을 저장하지 않습니다. 목적은 로컬에서 필요한 조회 패턴을 먼저 검증하는 것입니다. +이 섹션은 제품 카탈로그의 **DB schema/storage architecture** 설명입니다. 목표는 single-table NoSQL schema를 구현 세부가 아니라 제품 동작 관점에서 이해할 수 있게 정리하는 것입니다. -![Schema access patterns](../assets/schema-access-patterns.svg) - -## 저장소가 답해야 하는 질문 - -Schema는 데이터 모양보다 질문에서 출발합니다. +`security-scanner`는 Gitleaks-first local scanner의 결과를 표준 `Finding` 모델로 바꾸고, 같은 저장소에서 target catalog, scan run, finding lifecycle, incremental queue, freshness, GHAS comparison metadata를 함께 다룹니다. 테이블은 물리적으로 하나이며, 각 row는 `entityType`, `PK`, `SK`와 필요한 GSI projection을 통해 access pattern별로 읽힙니다. -- 최근에 스캔한 대상은 무엇인가? -- 특정 대상의 스캔 이력은 어떻게 바뀌었는가? -- 특정 scan run에서 어떤 finding이 나왔는가? -- 같은 finding의 triage 상태는 무엇인가? -- Report와 gate 판단을 저장된 결과만으로 다시 만들 수 있는가? - -## 저장하는 단위 +![Schema access patterns](../assets/schema-access-patterns.svg) -| Entity | 저장하는 내용 | 주로 쓰는 곳 | -| --- | --- | --- | -| `REPO_META` | 대상 repository의 현재 정보와 최근 스캔 요약 | 대상 목록 | -| `SCAN_RUN` | 한 번의 스캔 실행 요약 | 실행 이력 | -| `FINDING` | dedup된 finding identity | identity lookup | -| `FINDING_OBSERVATION` | 특정 scan run에서 관측된 finding snapshot | 상세 검토 | -| `FINDING_STATE` | dedup된 finding의 lifecycle/triage 상태 | 재검토와 gate | +## Schema 설명문 -## CORE item shape +이 schema는 table을 entity별로 쪼개는 대신, 제품이 답해야 하는 질문을 기준으로 row를 배치합니다. -현재 구현 범위는 CORE row만 다룹니다. +- 어떤 repository가 스캔 대상인가? +- 특정 repository의 최신 스캔 상태와 실행 이력은 무엇인가? +- 특정 scan run에서 어떤 finding이 관측되었는가? +- 같은 finding의 현재 lifecycle/triage 상태는 무엇인가? +- incremental scan queue에서 다음에 처리할 commit은 무엇인가? +- dashboard/read API가 freshness, coverage, backlog를 full scan 없이 볼 수 있는가? +- GHAS Secret Scanning과 local finding을 raw secret 없이 비교할 수 있는가? -| Entity | PK | SK | 핵심 내용 | -| --- | --- | --- | --- | -| `REPO_META` | `REPO#` | `META` | repository metadata와 최근 스캔 요약 | -| `SCAN_RUN` | `REPO#` | `SCAN_RUN##` | scan run summary와 artifact pointer | -| `FINDING` | `FINDING#` | `META` | repo, rule, source tool, location, fingerprint 같은 identity field | -| `FINDING_OBSERVATION` | `RUN#` | `OBS##` | run-scoped finding snapshot | -| `FINDING_STATE` | `FINDING#` | `STATE#GLOBAL` | status와 triage lifecycle state | +핵심 분리는 `FINDING`, `FINDING_OBSERVATION`, `FINDING_STATE`입니다. `FINDING`은 dedup identity, `FINDING_OBSERVATION`은 scan-run별 관측 snapshot, `FINDING_STATE`는 사람이 바꿀 수 있는 lifecycle 상태입니다. Report와 gate는 observation을 읽은 뒤 state를 overlay해서 다시 계산합니다. -`FINDING` identity row에는 scanner evidence snapshot이나 triage state를 넣지 않습니다. -Scan run별 evidence snapshot은 `FINDING_OBSERVATION`에 두고, runtime read는 -observation snapshot에 `FINDING_STATE`를 overlay해서 `Finding`을 복원합니다. +스캔 대상에는 두 축이 있습니다. `SCAN_TARGET`은 CLI가 관리하는 scan-all 대상 catalog입니다. `CATALOG`는 org repository membership을 기록하는 coverage/freshness catalog입니다. 둘 다 repository 목록을 다루지만, 전자는 사용자가 켜고 끄는 스캔 대상이고 후자는 org N 대비 covered M을 계산하는 제품 상태입니다. -`occurrenceKey`는 redacted canonical observation identity의 deterministic hash입니다. -재료는 `repo`, `ruleId`, `sourceTool`, `file`, `startLine`, `fingerprint`를 기본으로 -하고, redacted fallback으로 `secretHash`, `matchHash`를 사용할 수 있습니다. Raw -secret이나 raw match 문자열은 occurrence key material에 넣지 않습니다. +Queue와 freshness는 반복 실행 경로를 위한 보조 entity입니다. `REF_STATE`가 ref cursor를 저장하고, `SCAN_JOB`이 commit scanner tuple을 대기열로 만들며, `SCAN_LEDGER`가 이미 처리한 tuple을 idempotency ledger로 남깁니다. 성공한 작업은 `REPO_HEALTH`를 전진시키고, evaluator는 `BREACH_COUNTER`를 materialize해서 dashboard가 O(1)에 freshness rollup을 읽게 합니다. -`FINDING_STATE`는 현재 `GLOBAL` scope만 사용합니다. Scan write는 state row가 없을 -때만 default state를 만들고, 이미 존재하는 manual triage verdict/verifier/reason을 -blind overwrite하지 않습니다. Observation write는 state와 분리되어 idempotent하게 -처리합니다. +GHAS 비교 경로는 local scanner를 대체하지 않습니다. `GHAS_ALERT`는 read-only GHAS Secret Scanning alert의 redacted comparison metadata이고, 필요한 raw local/GHAS evidence는 plaintext가 아니라 `SECRET_EVIDENCE`의 encrypted metadata로만 보관합니다. -## 조회 기준 +## Entity table data -| 알고 싶은 것 | 접근 방식 | -| --- | --- | -| 최근 대상 목록 | repo list index를 page 단위로 조회 | -| 대상별 스캔 이력 | 대상 partition에서 scan run만 조회 | -| 특정 실행의 finding | scan run partition에서 `OBS#` item 조회 | -| finding 상태 | finding별 `STATE#GLOBAL` item 조회 | -| report/gate 판단 | finding snapshot에 lifecycle state를 merge한 뒤 계산 | +| Entity | 목적 | PK/SK shape | 주요 access pattern | Raw secret 저장/노출 경계 | +| --- | --- | --- | --- | --- | +| `REPO_META` | local scan 대상 repository의 현재 metadata와 최신 scan summary를 저장합니다. | `PK=REPO#`, `SK=META` | 최근 대상 목록은 sharded `REPO_LIST#ALL` GSI1 axis를 최신순으로 page 조회합니다. 특정 repo의 run history와 같은 partition을 공유합니다. | Raw secret 없음. 실제 repo URL, owner, path는 운영 DB에서는 민감할 수 있으므로 public docs/tests에는 synthetic 값만 둡니다. | +| `SCAN_RUN` | 한 번의 repository scan 실행 요약과 artifact pointer를 저장합니다. | `PK=REPO#`, `SK=SCAN_RUN##` | repo별 실행 이력은 `PK=REPO#` + `SK begins_with SCAN_RUN#`로 읽습니다. 날짜별 실행 조회는 sharded `SCAN_DATE#` GSI1 axis를 사용합니다. | Raw secret 없음. `artifactUri`는 pointer일 뿐이며, 실제 scanner output이나 private artifact는 `private/` 또는 저장소 밖에 둡니다. | +| `FINDING` | dedup된 finding identity를 저장합니다. Rule, source tool, 위치, fingerprint 같은 identity field 중심입니다. | `PK=FINDING#`, `SK=META` | finding 단건 identity lookup, rule 기준 future/API 조회(`gsi2pk=RULE#`), repo-axis 조회에 사용합니다. | Raw secret을 넣지 않습니다. 파일/라인/fingerprint도 실제 repo에서는 민감할 수 있어 공개 예시는 synthetic만 사용합니다. | +| `FINDING_OBSERVATION` | 특정 scan run에서 관측된 finding snapshot을 저장합니다. 같은 finding이 run마다 다르게 보일 수 있는 부분을 여기에 둡니다. | `PK=RUN#`, `SK=OBS##` | scan-run 상세는 `PK=RUN#` + `SK begins_with OBS#`로 읽고, repo residual query는 sharded repo-axis GSI1에서 `RUN#` prefix로 읽습니다. | Runtime exact evidence review가 필요한 제한 경로에서는 scanner-native snapshot이 포함될 수 있습니다. Public report, gate, export, verifier prompt에는 raw secret/match를 내보내지 않고 hash/redacted field를 사용합니다. | +| `FINDING_STATE` | dedup finding의 현재 lifecycle/triage 상태를 저장합니다. Observation과 분리해 manual verdict를 blind overwrite하지 않습니다. | `PK=FINDING#`, `SK=STATE#GLOBAL` | finding state point read, scan-run/read-all 복원 시 observation에 state overlay, disposition update transaction에 사용합니다. | Raw secret 없음. Triage reason에는 secret, private path, raw match를 쓰지 않는 운영 규칙이 필요합니다. | +| `SCAN_TARGET` | CLI가 관리하는 scan-all 대상 catalog입니다. URL/name/enabled를 저장합니다. | `PK=SCAN_TARGET#`, `SK=META` | `add-target`, `list-targets`, `enable-target`, `disable-target`, `scan-all`이 사용합니다. 목록 조회는 sharded `TARGET_LIST#ALL` GSI1 axis입니다. | Raw secret 없음. 실제 private repo URL은 public fixture/docs에 쓰지 않습니다. | +| `CATALOG` | org repository membership catalog입니다. Coverage gap 계산과 future GHAS reconcile의 기준 목록입니다. | `PK=CATALOG#`, `SK=META` | reconcile timer가 upsert하고, coverage 계산은 bounded org-size enumeration으로 읽습니다. Read API hot path는 직접 scan하지 않고 `BREACH_COUNTER.coverageGap`을 읽습니다. | Raw secret 없음. Opt-out repo도 삭제하지 않고 `included=false`와 `excludedReason`으로 기록합니다. Public docs에는 실제 org/repo 값을 쓰지 않습니다. | +| `REF_STATE` | incremental discovery가 마지막으로 본 ref SHA를 저장합니다. | `PK=REPO#`, `SK=REF#` | ref point lookup과 `PK=REPO#` + `SK begins_with REF#` list로 cursor를 읽습니다. GSI list key는 제거되어 base-table query가 기준입니다. | Raw secret 없음. Ref name과 commit SHA는 공개 예시에 실제 private repo 값을 쓰지 않습니다. | +| `SCAN_JOB` | commit scanner tuple의 queue row입니다. Lease, retry, priority, status, fencing 정보를 담습니다. | `PK=SCAN_JOB#`, `SK=META` | pending/leased dequeue와 backlog count는 sharded `SCAN_JOB_STATUS#` GSI1 axis를 사용합니다. Repo별 job 조회는 `gsi2pk=REPO#` 축을 둡니다. | Raw secret 없음. `lastError`에는 scanner output snippet, raw match, credential-like 문자열을 넣지 않는 것이 경계입니다. | +| `SCAN_LEDGER` | 이미 처리한 `(repo, commit, scanner, rule pack, config)` tuple을 기록하는 idempotency ledger입니다. | `PK=SCAN_LEDGER##`, `SK=###` | enqueue 전 `has_scan_ledger` point lookup으로 중복 scan을 막고, repo-axis GSI1로 repo별 completion history를 볼 수 있습니다. | Raw secret 없음. Completion marker와 finding count만 저장합니다. | +| `REPO_HEALTH` | repo별 마지막 성공 incremental/baseline scan timestamp를 저장합니다. | `PK=REPO_HEALTH#`, `SK=META` | worker가 successful completion 후 advancing-only conditional update로 갱신합니다. Evaluator는 batch read 또는 evaluator-only enumeration으로 freshness breach를 계산합니다. | Raw secret 없음. Timestamp와 repo id만 다룹니다. | +| `BREACH_COUNTER` | freshness/coverage rollup을 materialized singleton으로 저장합니다. | `PK=BREACH_COUNTER`, `SK=META` | dashboard/read API가 O(1) point read로 incremental breach, baseline breach, total breach, coverage gap을 표시합니다. | Raw secret 없음. Aggregate count만 저장합니다. | +| `GHAS_ALERT` | GHAS Secret Scanning alert/location의 redacted comparison metadata를 저장합니다. | `PK=GHAS_ALERT#`, `SK=META` | `compare-ghas`가 redacted inputs를 저장하고, comparison read는 `entityType=GHAS_ALERT` scan을 사용합니다. Repo-axis GSI1 projection은 repo-scoped analysis를 위해 보존합니다. | Raw API response, alert URL, user object, resolution comment, raw secret, raw match context를 저장하지 않습니다. `hide_secret=true` fetch와 redacted comparison key가 기본 경계입니다. | +| `SECRET_EVIDENCE` | local 또는 GHAS raw evidence가 꼭 필요할 때 encrypted evidence metadata를 저장합니다. | `PK=SECRET_EVIDENCE#`, `SK=META` | linked finding이면 `gsi1pk=FINDING#`, linked GHAS alert이면 `gsi1pk=GHAS_ALERT#`, unlinked fallback은 `SECRET_EVIDENCE#ALL`입니다. Product list/read API는 raw evidence list를 제공하지 않습니다. | Plaintext secret/match를 저장하지 않습니다. `encryptedSecret`, `encryptedMatch`, `secretHash`, `keyId`, `nonce`, `authTag`, `algorithm`, retention metadata만 저장합니다. Public output에는 ciphertext도 노출하지 않습니다. | -## 안전 규칙 +## 핵심 design principle 5개 -- Exact evidence가 필요한 runtime review 경로에서만 scanner-native evidence를 제한적으로 둡니다. -- Report, gate, export, verifier prompt는 hash와 redacted evidence를 사용합니다. -- 공개 문서, tests, examples에는 synthetic 값만 둡니다. -- 실제 외부 export, 비공개 finding, DB dump는 이 저장소 밖에 둡니다. -- TTL, streams, transaction, 운영 DynamoDB behavior는 현재 기본 요구사항이 아닙니다. +1. **Access pattern first**: entity shape는 "무엇을 저장할까"보다 "어떤 질문에 bounded read로 답할까"에서 출발합니다. 최근 repo, 특정 run, queue head, freshness rollup은 각각 명시된 key path를 가집니다. +2. **Identity, observation, state 분리**: finding identity와 scan-run observation, lifecycle state를 분리해 rescan이 manual triage를 덮어쓰지 않게 합니다. Runtime read는 observation에 state를 overlay합니다. +3. **Raw evidence 최소화**: public docs, tests, reports, verifier prompt, dashboard는 raw secret을 다루지 않습니다. Exact evidence가 필요한 runtime review는 제한 경로로 두고, 장기 보관이 필요하면 `SECRET_EVIDENCE`에 encrypted metadata로만 둡니다. +4. **Hot partition 회피와 bounded reads**: `REPO_LIST`, `TARGET_LIST`, `SCAN_DATE`, `SCAN_JOB_STATUS`는 sharded list axis를 사용하고, per-repo finding/ledger/GHAS axis도 sharded repo-axis를 사용합니다. Dashboard hot path는 `BREACH_COUNTER`와 queue count처럼 bounded read를 우선합니다. +5. **Idempotent and fenced writes**: `SCAN_LEDGER`는 중복 scan을 막고, `SCAN_JOB` lease/fence는 stale worker completion을 막으며, `REPO_HEALTH`는 advancing-only update로 timestamp regression을 막습니다. Lifecycle decision은 latest state update와 append-only event 기록을 함께 남깁니다. ## 현재 non-goals -다음 row/table은 CORE schema split 범위가 아닙니다. - -- `FindingFingerprintMap` -- `ScanRunQueryRows` -- `PatternQueryRows` -- standalone Artifacts table/item -- TTL, streams, Lambda, production DynamoDB behavior +- Public repository에 실제 GHAS alert export, alert URL, private path, raw scanner output, raw secret을 커밋하지 않습니다. +- Managed production DynamoDB 운영, TTL, streams, notification adapter는 이 schema 설명의 필수 조건이 아닙니다. +- GHAS는 read-only comparison baseline입니다. 이 프로젝트가 GHAS scan을 trigger하거나 alert state를 mutate하지 않습니다. ## 로컬 실행 환경 -Dynalite는 로컬 검증 후보입니다. DynamoDB Local과 LocalStack은 parity 또는 adapter integration을 확인할 때 검토할 수 있지만, 현재 기본 운영 결정은 아닙니다. +로컬 검증은 DynamoDB-compatible adapter로 access pattern을 먼저 검증합니다. Table name, endpoint, region, credential placeholder는 CLI flag 또는 환경 변수로 주입하며, public docs에는 실제 운영 table name, endpoint, account, host identifier를 쓰지 않습니다.