Skip to content

Commit d9b2530

Browse files
committed
feat(code-review): SDLC 코드 보안 리뷰를 6번째 증적 소스로 추가
- /ingest/code-review: claude-code-security-review findings(SARIF/JSON) push → 호스트 없는 alert(source=code_review) 정규화 → Alert Triage 재사용 - 통제 매핑: 2.8.1/2.8.5/A.8.25/A.8.28 evidence_sources=code_review + DEF-DEV-001 - PDCA 분리 집계(code_review_pending) + 대시보드 작업큐 타일 + 한/영 i18n + 소스 뱃지 - schema 012: alerts/source_syncs CHECK에 code_review 허용 - GitHub 원격 트리거 /controls/code-review/scan (workflow_dispatch·코드 미접촉·토큰 미저장) + UI - security-review.yml: 자기 레포 도그푸딩 + findings push 스텝 - 테스트 16건 추가, 전체 213건 통과 주: compliance.py에는 동시 진행 중인 접속기록 로그검토 라우트가 함께 있어 분리 없이 보존.
1 parent ae09660 commit d9b2530

24 files changed

Lines changed: 938 additions & 22 deletions
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: security-review
2+
3+
# Track 1 — 도그푸딩: MORI 자기 레포의 매 PR을 Claude 보안 리뷰에 태운다.
4+
# "보안 제품을 스스로 보안 리뷰 루프 안에서 만든다" — Phase 0(신뢰의 토대) 증적.
5+
# 리뷰 결과는 PR 인라인 코멘트로 남고, Track 2가 붙으면 같은 findings를
6+
# /ingest/code-review 로 흘려보내 2.8 개발보안 증적으로 재사용한다.
7+
8+
on:
9+
pull_request:
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
pull-requests: write
15+
16+
jobs:
17+
security-review:
18+
runs-on: ubuntu-latest
19+
# ANTHROPIC_API_KEY 시크릿이 없으면 스킵(포크 PR·키 미설정 환경에서 실패 방지).
20+
if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository }}
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
with:
25+
fetch-depth: 0
26+
27+
- name: Claude Code security review
28+
id: review
29+
uses: anthropics/claude-code-security-review@main
30+
with:
31+
claude-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
32+
comment-pr: true
33+
# 데모 스택·서드파티 벤더 파일은 리뷰 대상에서 제외(노이즈 감소).
34+
exclude-directories: backups,reports,docs
35+
36+
# Track 1 → Track 2 연결: 리뷰 findings 를 MORI 로 보내 2.8 개발보안 증적화.
37+
# results-file(.findings[]: file·line·severity·category·description)을 그대로 POST —
38+
# /ingest/code-review 가 {findings:[...]} 를 파싱한다. MORI 접속정보(시크릿)가 있을
39+
# 때만 실행하고, 실패해도 PR 체크를 막지 않는다(도그푸딩은 비필수).
40+
- name: Push findings to MORI (evidence layer)
41+
if: ${{ always() }}
42+
continue-on-error: true
43+
env:
44+
MORI_INGEST_URL: ${{ secrets.MORI_INGEST_URL }}
45+
MORI_INGEST_TOKEN: ${{ secrets.MORI_INGEST_TOKEN }}
46+
RESULTS_FILE: ${{ steps.review.outputs.results-file }}
47+
run: |
48+
if [ -z "$MORI_INGEST_URL" ] || [ -z "$MORI_INGEST_TOKEN" ]; then
49+
echo "MORI_INGEST_URL/TOKEN 미설정 — 증적 push 스킵"; exit 0
50+
fi
51+
if [ -z "$RESULTS_FILE" ] || [ ! -f "$RESULTS_FILE" ]; then
52+
echo "results 파일 없음($RESULTS_FILE) — 스킵"; exit 0
53+
fi
54+
count=$(jq '.findings | length' "$RESULTS_FILE" 2>/dev/null || echo '?')
55+
echo "MORI 로 findings ${count}건 push…"
56+
curl -fsS -X POST \
57+
"${MORI_INGEST_URL%/}/ingest/code-review?repo=${GITHUB_REPOSITORY}" \
58+
-H "Authorization: Bearer ${MORI_INGEST_TOKEN}" \
59+
-H "Content-Type: application/json" \
60+
--data-binary "@${RESULTS_FILE}"
61+
echo; echo "pushed."
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
id: "DEF-DEV-001"
2+
controls: ["2.8.1", "2.8.5", "A.8.25", "A.8.28"]
3+
title_ko: "코드 보안 리뷰는 수행하나 처리 기록이 없음"
4+
title_en: "Code security review runs but there is no handling record"
5+
symptom_ko: "PR마다 보안 리뷰가 돌지만 findings를 누가 언제 확인·판단·조치했는지 기록이 없어, 개발보안 통제의 이행을 증명하지 못한다."
6+
symptom_en: "Security review runs on each PR, but there is no record of who reviewed/judged/remediated the findings, so secure-development controls can't be proven as operating."
7+
evidence_gap_ko: "\"리뷰했다\"는 있으나 \"정해진 절차로 처리했다\"(트리아지 상태·담당자·이력)의 증적이 없다."
8+
evidence_gap_en: "There is 'reviewed' but not 'handled per procedure' (triage status/owner/history)."
9+
mori_signal: code_review_pending
10+
fix_ko: "코드 리뷰 findings를 MORI Alert Triage로 물려 상태·담당자·이력을 기록하고, 미조치 건을 작업 큐에 노출한다. 매 PR 리뷰 실행 자체를 2.8/A.8.25 증적으로 스냅샷한다."
11+
fix_en: "Wire code-review findings into MORI Alert Triage to record status/owner/history and surface pending items in the work queue; snapshot each PR-review run itself as 2.8/A.8.25 evidence."
12+
severity: medium

controls/isms-p/2.8.1.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@ domain: "2. 보호대책 요구사항"
55
section: "2.8 정보시스템 도입 및 개발 보안"
66
title_ko: "보안 요구사항 정의"
77
title_en: "Security-requirements definition"
8-
evidence_sources: []
8+
intent_ko: "개발·도입 시 보안 요구사항을 정의하고, 변경마다 보안 검토가 이뤄짐을 증적으로 남긴다."
9+
intent_en: "Define security requirements for development/acquisition and evidence a security review on each change."
10+
evidence_hint_ko: "매 PR에 대한 AI 코드 보안 리뷰 수행 기록(리뷰 실행·findings·트리아지)이 보안 검토 증적이 된다."
11+
evidence_hint_en: "The per-PR AI code security review record (run, findings, triage) serves as security-review evidence."
12+
evidence_sources: [code_review]
913
status: "draft"

controls/isms-p/2.8.5.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@ domain: "2. 보호대책 요구사항"
55
section: "2.8 정보시스템 도입 및 개발 보안"
66
title_ko: "소스 프로그램 관리"
77
title_en: "Source-code management"
8-
evidence_sources: []
8+
intent_ko: "소스 코드의 취약점·보안결함을 지속적으로 점검하고 조치 이력을 관리한다."
9+
intent_en: "Continuously review source code for vulnerabilities/security defects and manage remediation history."
10+
evidence_hint_ko: "AI 코드 보안 리뷰 findings(취약점·결함)와 트리아지·조치 이력이 소스 관리 증적이 된다."
11+
evidence_hint_en: "AI code review findings (vulns/defects) plus triage/remediation history serve as source-management evidence."
12+
evidence_sources: [code_review]
913
status: "draft"

controls/iso27001/A.8.25.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ version: "2022"
44
domain: "A.8 Technological controls"
55
title_ko: "안전한 개발 수명주기"
66
title_en: "Secure development life cycle"
7-
evidence_sources: []
7+
intent_ko: "개발 수명주기 전반에 보안 검토를 내재화하고, 각 변경마다 리뷰가 실행됨을 증적으로 남긴다."
8+
intent_en: "Embed security review across the development life cycle and evidence a review on each change."
9+
evidence_hint_ko: "매 PR AI 보안 리뷰 실행·findings·트리아지 기록이 SDLC 보안 내재화 증적이 된다."
10+
evidence_hint_en: "Per-PR AI security review runs, findings and triage records evidence security embedded in the SDLC."
11+
evidence_sources: [code_review]
812
status: "draft"

controls/iso27001/A.8.28.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ version: "2022"
44
domain: "A.8 Technological controls"
55
title_ko: "시큐어 코딩"
66
title_en: "Secure coding"
7-
evidence_sources: []
7+
intent_ko: "코드에 시큐어 코딩 원칙을 적용하고, 위반·취약점을 지속 탐지·조치한다."
8+
intent_en: "Apply secure-coding principles and continuously detect/remediate violations and vulnerabilities."
9+
evidence_hint_ko: "AI 코드 보안 리뷰가 탐지한 시큐어 코딩 위반·취약점 findings와 조치 이력이 증적이 된다."
10+
evidence_hint_en: "Secure-coding violations/vulnerabilities found by AI code review, plus remediation history, serve as evidence."
11+
evidence_sources: [code_review]
812
status: "draft"

controls/schema/control.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"evidence_sources": {
1919
"type": "array",
2020
"description": "MORI sources that produce evidence for this control",
21-
"items": { "enum": ["zabbix", "wazuh", "fleet", "trivy", "loki", "mori"] }
21+
"items": { "enum": ["zabbix", "wazuh", "fleet", "trivy", "loki", "mori", "code_review"] }
2222
},
2323
"evidence_hint_ko": { "type": "string", "description": "How this control is satisfied, as evidence (KO)" },
2424
"evidence_hint_en": { "type": "string", "description": "How this control is satisfied, as evidence (EN)" },

controls/validate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def main() -> int:
105105

106106
known_signals = {
107107
"", "vuln_pending", "exceptions_expiring", "untriaged_alerts",
108-
"overdue", "control_pending", "unmapped_assets",
108+
"overdue", "control_pending", "unmapped_assets", "code_review_pending",
109109
}
110110
n_def = 0
111111
for f in sorted((ROOT / "common_defects").glob("*.yaml")):

schema/012_code_review_source.sql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- 012 — code_review 를 alert/source_sync 소스로 허용 (Track 2: SDLC 증적 소스)
2+
-- claude-code-security-review findings 를 트리아지 가능한 alert 로 적재하기 위해
3+
-- alerts / source_syncs 의 소스 CHECK 제약에 'code_review' 를 추가한다.
4+
-- 재실행 안전(idempotent): 기존 제약을 DROP 후 재생성.
5+
6+
ALTER TABLE alerts DROP CONSTRAINT IF EXISTS alerts_source_check;
7+
ALTER TABLE alerts ADD CONSTRAINT alerts_source_check
8+
CHECK (source IN ('wazuh', 'zabbix', 'host_log', 'code_review'));
9+
10+
ALTER TABLE source_syncs DROP CONSTRAINT IF EXISTS source_syncs_source_check;
11+
ALTER TABLE source_syncs ADD CONSTRAINT source_syncs_source_check
12+
CHECK (source IN ('fleet', 'wazuh', 'zabbix', 'host_log', 'trivy', 'code_review'));

src/mori_soc/api/i18n.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ def _i18n_toggle_html(fixed: bool = True) -> str:
255255
"dash.gap.vuln": "조치 안 된 Critical/High",
256256
"dash.gap.exc": "예외 만료 D-7 이내",
257257
"dash.gap.alert": "미트리아지 alert",
258+
"dash.gap.code_review": "미조치 코드 보안 리뷰",
258259
"dash.gap.overdue": "조치 기한 초과",
259260
"dash.gap.unmapped": "미매핑 자산 (자산 대사)",
260261
"dash.gap.control": "미조치 통제",
@@ -279,6 +280,14 @@ def _i18n_toggle_html(fixed: bool = True) -> str:
279280
"dash.ctl.pdf": "증적 팩 PDF",
280281
"dash.ctl.download": "증적 팩 다운로드",
281282
"dash.ctl.add": "통제 추가", "dash.ctl.nlp": "법령 텍스트 임포트(NLP)",
283+
"dash.ctl.scan": "GitHub 코드 보안 리뷰",
284+
"dash.ctl.scan_ttl": "GitHub 레포 코드 보안 리뷰 요청",
285+
"dash.ctl.scan_help": "레포 URL과 GitHub 토큰(actions:write)을 넣으면 그 레포의 CI에서 보안 리뷰가 돌고 결과가 MORI로 자동 회수돼요. MORI는 코드를 가져오지 않고 토큰도 저장하지 않아요. 대상 레포에 security-review.yml이 있어야 해요.",
286+
"dash.ctl.scan_url_ph": "https://github.com/owner/repo", "dash.ctl.scan_ref_ph": "브랜치(기본 main)",
287+
"dash.ctl.scan_token_ph": "GitHub 토큰 (actions:write · 저장 안 함)", "dash.ctl.scan_run": "스캔 요청",
288+
"dash.ctl.scan_need_url": "레포 URL을 넣으세요", "dash.ctl.scan_need_tok": "GitHub 토큰을 넣으세요",
289+
"dash.ctl.scan_running": "요청 중…", "dash.ctl.scan_done": "스캔 요청됨 — 완료되면 트리아지·증적에 반영돼요",
290+
"dash.ctl.scan_fail": "요청 실패",
282291
"dash.ctl.edit": "수정", "dash.ctl.del": "삭제",
283292
"dash.ctl.add_ttl": "통제 추가", "dash.ctl.edit_ttl": "통제 수정",
284293
"dash.ctl.cancel": "취소", "dash.ctl.need_id_ttl": "ID와 제목은 필수",
@@ -1050,6 +1059,7 @@ def _i18n_toggle_html(fixed: bool = True) -> str:
10501059
"dash.gap.vuln": "Critical/High not remediated",
10511060
"dash.gap.exc": "Exceptions expiring ≤ D-7",
10521061
"dash.gap.alert": "Untriaged alerts",
1062+
"dash.gap.code_review": "Code security review pending",
10531063
"dash.gap.overdue": "Remediation overdue",
10541064
"dash.gap.unmapped": "Unmapped assets (reconciliation)",
10551065
"dash.gap.control": "Controls pending",
@@ -1074,6 +1084,14 @@ def _i18n_toggle_html(fixed: bool = True) -> str:
10741084
"dash.ctl.pdf": "Evidence pack PDF",
10751085
"dash.ctl.download": "Download evidence pack",
10761086
"dash.ctl.add": "Add control", "dash.ctl.nlp": "Import regulation text (NLP)",
1087+
"dash.ctl.scan": "GitHub code security review",
1088+
"dash.ctl.scan_ttl": "Request a code security review of a GitHub repo",
1089+
"dash.ctl.scan_help": "Enter a repo URL and a GitHub token (actions:write). The review runs in that repo's CI and results flow back to MORI automatically. MORI never fetches your code and does not store the token. The target repo must contain security-review.yml.",
1090+
"dash.ctl.scan_url_ph": "https://github.com/owner/repo", "dash.ctl.scan_ref_ph": "branch (default main)",
1091+
"dash.ctl.scan_token_ph": "GitHub token (actions:write · not stored)", "dash.ctl.scan_run": "Request scan",
1092+
"dash.ctl.scan_need_url": "Enter a repo URL", "dash.ctl.scan_need_tok": "Enter a GitHub token",
1093+
"dash.ctl.scan_running": "Requesting…", "dash.ctl.scan_done": "Scan requested — results will appear in triage/evidence when done",
1094+
"dash.ctl.scan_fail": "Request failed",
10771095
"dash.ctl.edit": "Edit", "dash.ctl.del": "Delete",
10781096
"dash.ctl.add_ttl": "Add control", "dash.ctl.edit_ttl": "Edit control",
10791097
"dash.ctl.cancel": "Cancel", "dash.ctl.need_id_ttl": "ID and title are required",

0 commit comments

Comments
 (0)