@hoanghainh/komagu 2.2.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/assess/lsa/CHANGELOG.md +15 -0
- package/assess/lsa/scripts/attack_chains.py +573 -0
- package/assess/lsa/scripts/baseline.py +14 -0
- package/assess/lsa/scripts/detect_stack.py +2 -1
- package/assess/lsa/scripts/findings_to_sarif.py +11 -0
- package/assess/lsa/scripts/forecast.py +544 -0
- package/assess/lsa/scripts/lsa_common.py +1 -5
- package/assess/lsa/scripts/merge_findings.py +14 -0
- package/assess/lsa/scripts/redaction.py +118 -0
- package/assess/lsa/scripts/render_report.py +86 -0
- package/assess/lsa/tests/test_attack_chains.py +292 -0
- package/assess/lsa/tests/test_forecast.py +160 -0
- package/assess/lsa/tests/test_modern_stacks.py +13 -0
- package/contracts/schemas/api-inventory-diff.schema.json +213 -0
- package/contracts/schemas/attack-chain.schema.json +181 -0
- package/contracts/schemas/bas-scenario.schema.json +217 -0
- package/contracts/schemas/evidence-manifest.schema.json +117 -0
- package/contracts/schemas/finding.schema.json +7 -0
- package/contracts/schemas/forecast.schema.json +172 -0
- package/governance/scs/__init__.py +21 -0
- package/governance/scs/mapping.py +133 -0
- package/governance/scs/pack.py +354 -0
- package/operate/ai/context.py +22 -2
- package/operate/cli/__init__.py +1 -1
- package/operate/cli/commands/evidence.py +138 -0
- package/operate/cli/commands/inventory.py +187 -0
- package/operate/cli/commands/simulate.py +175 -58
- package/operate/cli/main.py +7 -3
- package/operate/inventory/__init__.py +22 -0
- package/operate/inventory/api_inventory.py +566 -0
- package/operate/simulate/__init__.py +16 -0
- package/operate/simulate/engine.py +370 -0
- package/operate/simulate/loader.py +158 -0
- package/operate/simulate/scenarios/idc-frontier-ransomware.json +47 -0
- package/operate/simulate/scenarios/metabase-cve-2026-72898.json +84 -0
- package/operate/simulate/scenarios/times-car-breach.json +74 -0
- package/operate/simulate/scenarios.py +52 -0
- package/operate/tests/test_ai_redaction.py +181 -0
- package/operate/tests/test_api_inventory.py +239 -0
- package/operate/tests/test_declarative_scenarios.py +169 -0
- package/operate/tests/test_evidence_pack.py +128 -0
- package/operate/tests/test_headless_ai.py +16 -1
- package/package.json +1 -1
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""METI SCS Evidence Pack Bundler and Gap Report Generator (#271).
|
|
3
|
+
|
|
4
|
+
Assembles technical audit artifacts into a timestamped, cryptographically
|
|
5
|
+
hashed ZIP archive with evidence-manifest.json and bilingual gap reports.
|
|
6
|
+
|
|
7
|
+
Standard library only (Python 3.8+) per ADR-0006.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
import time
|
|
17
|
+
import zipfile
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
20
|
+
|
|
21
|
+
from governance.scs.mapping import (
|
|
22
|
+
LEGAL_DISCLAIMER_EN,
|
|
23
|
+
LEGAL_DISCLAIMER_JA,
|
|
24
|
+
SCS_CONTROLS,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def compute_file_sha256(path: Path) -> str:
|
|
29
|
+
"""Compute standard SHA-256 hex digest of a file."""
|
|
30
|
+
h = hashlib.sha256()
|
|
31
|
+
with open(path, "rb") as f:
|
|
32
|
+
while chunk := f.read(65536):
|
|
33
|
+
h.update(chunk)
|
|
34
|
+
return h.hexdigest()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EvidencePackBuilder:
|
|
38
|
+
"""Evaluates audit directories and packages METI SCS evidence."""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
audit_dirs: List[Union[str, Path]],
|
|
43
|
+
project_name: str = "Komagu-Protected-Project",
|
|
44
|
+
target_system: str = "Production-Environment",
|
|
45
|
+
) -> None:
|
|
46
|
+
self.audit_dirs = [Path(d).resolve() for d in audit_dirs]
|
|
47
|
+
self.project_name = project_name
|
|
48
|
+
self.target_system = target_system
|
|
49
|
+
|
|
50
|
+
def discover_artifacts(self) -> Dict[str, Path]:
|
|
51
|
+
"""Discover available technical evidence artifacts across provided directories."""
|
|
52
|
+
discovered: Dict[str, Path] = {}
|
|
53
|
+
for d in self.audit_dirs:
|
|
54
|
+
if not d.is_dir():
|
|
55
|
+
continue
|
|
56
|
+
for root, _, files in os.walk(d):
|
|
57
|
+
for f in files:
|
|
58
|
+
fp = Path(root) / f
|
|
59
|
+
# Key by filename for mapping match
|
|
60
|
+
if f not in discovered:
|
|
61
|
+
discovered[f] = fp
|
|
62
|
+
return discovered
|
|
63
|
+
|
|
64
|
+
def evaluate_controls(self, artifacts: Dict[str, Path]) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
|
|
65
|
+
"""Map discovered artifacts against METI SCS controls and compute readiness rating."""
|
|
66
|
+
evaluated_controls: List[Dict[str, Any]] = []
|
|
67
|
+
covered_count = 0
|
|
68
|
+
partial_count = 0
|
|
69
|
+
|
|
70
|
+
for ctrl in SCS_CONTROLS:
|
|
71
|
+
matched_files: List[str] = []
|
|
72
|
+
for exp in ctrl["expected_artifacts"]:
|
|
73
|
+
if exp in artifacts:
|
|
74
|
+
matched_files.append(exp)
|
|
75
|
+
|
|
76
|
+
if ctrl["control_id"] == "SCS-OG-01":
|
|
77
|
+
# Organizational governance requires manual policy attachment
|
|
78
|
+
status = "partially_covered" if matched_files else "out_of_scope_organizational"
|
|
79
|
+
elif matched_files:
|
|
80
|
+
status = "covered"
|
|
81
|
+
else:
|
|
82
|
+
status = "missing_evidence"
|
|
83
|
+
|
|
84
|
+
if status == "covered":
|
|
85
|
+
covered_count += 1
|
|
86
|
+
elif status == "partially_covered":
|
|
87
|
+
partial_count += 1
|
|
88
|
+
|
|
89
|
+
entry = dict(ctrl)
|
|
90
|
+
entry["status"] = status
|
|
91
|
+
entry["evidence_files"] = matched_files
|
|
92
|
+
evaluated_controls.append(entry)
|
|
93
|
+
|
|
94
|
+
total = len(SCS_CONTROLS)
|
|
95
|
+
compliance_pct = round(((covered_count + (0.5 * partial_count)) / total) * 100.0, 1)
|
|
96
|
+
|
|
97
|
+
if compliance_pct >= 85.0 and any(c["tier"] == "★4" and c["status"] == "covered" for c in evaluated_controls):
|
|
98
|
+
rating = "SCS-★4-READY"
|
|
99
|
+
elif compliance_pct >= 60.0:
|
|
100
|
+
rating = "SCS-★3-READY"
|
|
101
|
+
elif compliance_pct > 0:
|
|
102
|
+
rating = "GAP-IDENTIFIED"
|
|
103
|
+
else:
|
|
104
|
+
rating = "INSUFFICIENT-EVIDENCE"
|
|
105
|
+
|
|
106
|
+
readiness = {
|
|
107
|
+
"rating": rating,
|
|
108
|
+
"total_controls": total,
|
|
109
|
+
"covered_controls": covered_count,
|
|
110
|
+
"partial_controls": partial_count,
|
|
111
|
+
"compliance_percentage": compliance_pct,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return evaluated_controls, readiness
|
|
115
|
+
|
|
116
|
+
def generate_manifest(
|
|
117
|
+
self,
|
|
118
|
+
evaluated_controls: List[Dict[str, Any]],
|
|
119
|
+
readiness: Dict[str, Any],
|
|
120
|
+
artifacts: Dict[str, Path],
|
|
121
|
+
) -> Dict[str, Any]:
|
|
122
|
+
"""Generate evidence-manifest.json conforming to evidence-manifest.schema.json."""
|
|
123
|
+
bundled_records: List[Dict[str, Any]] = []
|
|
124
|
+
|
|
125
|
+
# Collect artifacts referenced in controls
|
|
126
|
+
used_files = set()
|
|
127
|
+
for ctrl in evaluated_controls:
|
|
128
|
+
for ef in ctrl["evidence_files"]:
|
|
129
|
+
used_files.add(ef)
|
|
130
|
+
|
|
131
|
+
for fname in sorted(used_files):
|
|
132
|
+
fp = artifacts.get(fname)
|
|
133
|
+
if fp and fp.is_file():
|
|
134
|
+
bundled_records.append({
|
|
135
|
+
"filename": fname,
|
|
136
|
+
"artifact_type": fp.suffix.lstrip(".") or "txt",
|
|
137
|
+
"sha256": compute_file_sha256(fp),
|
|
138
|
+
"size_bytes": fp.stat().st_size,
|
|
139
|
+
"description": f"Audit artifact: {fname}",
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
manifest = {
|
|
143
|
+
"$schema": "https://raw.githubusercontent.com/hoanghainh1188/komagu/main/contracts/schemas/evidence-manifest.schema.json",
|
|
144
|
+
"manifest_version": "1.0.0",
|
|
145
|
+
"project_name": self.project_name,
|
|
146
|
+
"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
147
|
+
"target_system": self.target_system,
|
|
148
|
+
"frameworks": [
|
|
149
|
+
"METI Supply Chain Security Evaluation Scheme (SCS ★3/★4)",
|
|
150
|
+
"NIST Cybersecurity Framework (NIST CSF 2.0)",
|
|
151
|
+
"Japan Active Cyber Defense Act (能動的サイバー防御 2025)",
|
|
152
|
+
],
|
|
153
|
+
"overall_readiness": readiness,
|
|
154
|
+
"controls": evaluated_controls,
|
|
155
|
+
"bundled_artifacts": bundled_records,
|
|
156
|
+
"disclaimer": LEGAL_DISCLAIMER_JA + " " + LEGAL_DISCLAIMER_EN,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return manifest
|
|
160
|
+
|
|
161
|
+
def render_gap_report_ja(self, manifest: Dict[str, Any]) -> str:
|
|
162
|
+
"""Render Japanese METI SCS Gap Analysis Report (gap-report.ja.md)."""
|
|
163
|
+
r = manifest["overall_readiness"]
|
|
164
|
+
lines = [
|
|
165
|
+
"# 経済産業省 サプライチェーン・サイバーセキュリティ (SCS ★3/★4) 適合性ギャップ分析レポート",
|
|
166
|
+
"",
|
|
167
|
+
"> **対象プロジェクト / システム:** `" + manifest["project_name"] + " (" + manifest["target_system"] + ")` ",
|
|
168
|
+
"> **評価実施日時:** `" + manifest["generated_at"] + "` ",
|
|
169
|
+
"> **総合適合性ステータス:** **`" + r["rating"] + "`** (適合率: **" + str(r["compliance_percentage"]) + "%**) ",
|
|
170
|
+
"",
|
|
171
|
+
"## 1. 概要及び評価結果サマリー",
|
|
172
|
+
"",
|
|
173
|
+
"本レポートは、2026年10月施行の**能動的サイバー防御法**および経済産業省**サプライチェーン・サイバーセキュリティ評価制度(SCS ★3/★4、NIST CSF 2.0準拠)**に基づき、"
|
|
174
|
+
"Komaguが検出・収集したセキュリティ監査成果物を自動照合した結果です。",
|
|
175
|
+
"",
|
|
176
|
+
"| 評価項目 | 結果 / 指標 |",
|
|
177
|
+
"|---|---|",
|
|
178
|
+
f"| **評価対象統制数** | {r['total_controls']} 項目 |",
|
|
179
|
+
f"| **技術的エビデンス充足 (Covered)** | {r['covered_controls']} 項目 |",
|
|
180
|
+
f"| **部分的充足 / 組織的対応要 (Partial)** | {r['partial_controls']} 項目 |",
|
|
181
|
+
f"| **総合適合率** | **{r['compliance_percentage']}%** |",
|
|
182
|
+
f"| **判定ランク** | **{r['rating']}** |",
|
|
183
|
+
"",
|
|
184
|
+
"## 2. METI SCS / NIST CSF 2.0 統制項目別 照合結果マトリクス",
|
|
185
|
+
"",
|
|
186
|
+
"| 統制ID | 統制名 / カテゴリ | 要求水準 | NIST CSF 2.0 | ステータス | 添付エビデンス |",
|
|
187
|
+
"|---|---|---|---|---|---|",
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
status_ja = {
|
|
191
|
+
"covered": "✅ 適合 (Covered)",
|
|
192
|
+
"partially_covered": "⚠️ 部分的充足 (Partial)",
|
|
193
|
+
"out_of_scope_organizational": "ℹ️ 組織的対応 (Organizational)",
|
|
194
|
+
"missing_evidence": "❌ 証跡不足 (Missing)",
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for c in manifest["controls"]:
|
|
198
|
+
st = status_ja.get(c["status"], c["status"])
|
|
199
|
+
ev_str = ", ".join(f"`{f}`" for f in c["evidence_files"]) if c["evidence_files"] else "*(なし)*"
|
|
200
|
+
csf = ", ".join(c["nist_csf_v2"])
|
|
201
|
+
lines.append(f"| **{c['control_id']}** | {c['name_ja']} | `{c['tier']}` | `{csf}` | {st} | {ev_str} |")
|
|
202
|
+
|
|
203
|
+
lines.extend([
|
|
204
|
+
"",
|
|
205
|
+
"## 3. 残存ギャップおよび是正アクション (Action Items)",
|
|
206
|
+
"",
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
gap_found = False
|
|
210
|
+
for c in manifest["controls"]:
|
|
211
|
+
if c["status"] != "covered":
|
|
212
|
+
gap_found = True
|
|
213
|
+
lines.append(f"### 【{c['control_id']}】{c['name_ja']} ({c['tier']})")
|
|
214
|
+
lines.append(f"- **NIST CSF 2.0 カテゴリ:** `{', '.join(c['nist_csf_v2'])}`")
|
|
215
|
+
lines.append(f"- **現在の状態:** {status_ja.get(c['status'], c['status'])}")
|
|
216
|
+
lines.append(f"- **推奨是正アクション:** {c.get('action_required_ja', '社内規程の整備')}")
|
|
217
|
+
lines.append("")
|
|
218
|
+
|
|
219
|
+
if not gap_found:
|
|
220
|
+
lines.append("🎉 すべての統制項目に対して技術的エビデンスが充足しています。元請企業への提出が可能です。\n")
|
|
221
|
+
|
|
222
|
+
lines.extend([
|
|
223
|
+
"## 4. 添付エビデンスファイル一覧 (SHA-256 チェックサム)",
|
|
224
|
+
"",
|
|
225
|
+
"| ファイル名 | 種別 | SHA-256 チェックサム | サイズ |",
|
|
226
|
+
"|---|---|---|---|",
|
|
227
|
+
])
|
|
228
|
+
|
|
229
|
+
for a in manifest["bundled_artifacts"]:
|
|
230
|
+
lines.append(f"| `{a['filename']}` | `{a['artifact_type']}` | `{a['sha256'][:16]}...` | {a['size_bytes']} bytes |")
|
|
231
|
+
|
|
232
|
+
lines.extend([
|
|
233
|
+
"",
|
|
234
|
+
"---",
|
|
235
|
+
"### 法的免責事項 (Legal Disclaimer)",
|
|
236
|
+
LEGAL_DISCLAIMER_JA,
|
|
237
|
+
"",
|
|
238
|
+
"*Report generated autonomously by Komagu Security-First Modernization Platform.*",
|
|
239
|
+
])
|
|
240
|
+
|
|
241
|
+
return "\n".join(lines) + "\n"
|
|
242
|
+
|
|
243
|
+
def render_gap_report_en(self, manifest: Dict[str, Any]) -> str:
|
|
244
|
+
"""Render English METI SCS Gap Analysis Report (gap-report.md)."""
|
|
245
|
+
r = manifest["overall_readiness"]
|
|
246
|
+
lines = [
|
|
247
|
+
"# METI Supply Chain Security (SCS ★3/★4) & NIST CSF 2.0 Gap Analysis Report",
|
|
248
|
+
"",
|
|
249
|
+
"> **Target Project / System:** `" + manifest["project_name"] + " (" + manifest["target_system"] + ")` ",
|
|
250
|
+
"> **Evaluation Timestamp:** `" + manifest["generated_at"] + "` ",
|
|
251
|
+
"> **Readiness Verdict:** **`" + r["rating"] + "`** (Compliance Score: **" + str(r["compliance_percentage"]) + "%**) ",
|
|
252
|
+
"",
|
|
253
|
+
"## 1. Executive Summary",
|
|
254
|
+
"",
|
|
255
|
+
"This report maps automated Komagu technical security audit artifacts against the Japanese Ministry of Economy, "
|
|
256
|
+
"Trade and Industry (METI) Supply Chain Security Evaluation Scheme (SCS ★3/★4) and NIST Cybersecurity Framework (CSF 2.0).",
|
|
257
|
+
"",
|
|
258
|
+
"| Metric | Value |",
|
|
259
|
+
"|---|---|",
|
|
260
|
+
f"| **Total Controls Evaluated** | {r['total_controls']} |",
|
|
261
|
+
f"| **Technically Covered** | {r['covered_controls']} |",
|
|
262
|
+
f"| **Partially Covered / Organizational** | {r['partial_controls']} |",
|
|
263
|
+
f"| **Overall Compliance Score** | **{r['compliance_percentage']}%** |",
|
|
264
|
+
f"| **Readiness Status** | **{r['rating']}** |",
|
|
265
|
+
"",
|
|
266
|
+
"## 2. Control Evaluation Matrix",
|
|
267
|
+
"",
|
|
268
|
+
"| Control ID | Name | Tier | NIST CSF 2.0 | Status | Evidence Artifacts |",
|
|
269
|
+
"|---|---|---|---|---|---|",
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
status_en = {
|
|
273
|
+
"covered": "PASSED (Covered)",
|
|
274
|
+
"partially_covered": "PARTIAL (Action Req)",
|
|
275
|
+
"out_of_scope_organizational": "ORGANIZATIONAL",
|
|
276
|
+
"missing_evidence": "MISSING",
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for c in manifest["controls"]:
|
|
280
|
+
st = status_en.get(c["status"], c["status"])
|
|
281
|
+
ev_str = ", ".join(f"`{f}`" for f in c["evidence_files"]) if c["evidence_files"] else "*(none)*"
|
|
282
|
+
csf = ", ".join(c["nist_csf_v2"])
|
|
283
|
+
lines.append(f"| **{c['control_id']}** | {c['name_en']} | `{c['tier']}` | `{csf}` | `{st}` | {ev_str} |")
|
|
284
|
+
|
|
285
|
+
lines.extend([
|
|
286
|
+
"",
|
|
287
|
+
"## 3. Gap Analysis & Remediation Action Plan",
|
|
288
|
+
"",
|
|
289
|
+
])
|
|
290
|
+
|
|
291
|
+
for c in manifest["controls"]:
|
|
292
|
+
if c["status"] != "covered":
|
|
293
|
+
lines.append(f"### [{c['control_id']}] {c['name_en']} ({c['tier']})")
|
|
294
|
+
lines.append(f"- **NIST CSF 2.0 Category:** `{', '.join(c['nist_csf_v2'])}`")
|
|
295
|
+
lines.append(f"- **Current Status:** `{status_en.get(c['status'], c['status'])}`")
|
|
296
|
+
lines.append(f"- **Recommended Action:** {c.get('action_required_en', 'Document internal policy.')}")
|
|
297
|
+
lines.append("")
|
|
298
|
+
|
|
299
|
+
lines.extend([
|
|
300
|
+
"## 4. Bundled Artifact Manifest & Cryptographic Hashes",
|
|
301
|
+
"",
|
|
302
|
+
"| Filename | Type | SHA-256 Digest | Size (Bytes) |",
|
|
303
|
+
"|---|---|---|---|",
|
|
304
|
+
])
|
|
305
|
+
|
|
306
|
+
for a in manifest["bundled_artifacts"]:
|
|
307
|
+
lines.append(f"| `{a['filename']}` | `{a['artifact_type']}` | `{a['sha256']}` | {a['size_bytes']} |")
|
|
308
|
+
|
|
309
|
+
lines.extend([
|
|
310
|
+
"",
|
|
311
|
+
"---",
|
|
312
|
+
"### Disclaimer",
|
|
313
|
+
LEGAL_DISCLAIMER_EN,
|
|
314
|
+
"",
|
|
315
|
+
"*Report generated autonomously by Komagu Security-First Modernization Platform.*",
|
|
316
|
+
])
|
|
317
|
+
|
|
318
|
+
return "\n".join(lines) + "\n"
|
|
319
|
+
|
|
320
|
+
def build_pack(self, output_dir: Union[str, Path]) -> Tuple[Path, Path, Path, Path]:
|
|
321
|
+
"""Build evidence pack ZIP, manifest JSON, and bilingual reports.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
(zip_path, manifest_path, gap_report_ja_path, gap_report_en_path)
|
|
325
|
+
"""
|
|
326
|
+
out_p = Path(output_dir).resolve()
|
|
327
|
+
out_p.mkdir(parents=True, exist_ok=True)
|
|
328
|
+
|
|
329
|
+
artifacts = self.discover_artifacts()
|
|
330
|
+
evaluated_controls, readiness = self.evaluate_controls(artifacts)
|
|
331
|
+
manifest = self.generate_manifest(evaluated_controls, readiness, artifacts)
|
|
332
|
+
|
|
333
|
+
manifest_path = out_p / "evidence-manifest.json"
|
|
334
|
+
manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
335
|
+
|
|
336
|
+
gap_report_ja_path = out_p / "gap-report.ja.md"
|
|
337
|
+
gap_report_ja_path.write_text(self.render_gap_report_ja(manifest), encoding="utf-8")
|
|
338
|
+
|
|
339
|
+
gap_report_en_path = out_p / "gap-report.md"
|
|
340
|
+
gap_report_en_path.write_text(self.render_gap_report_en(manifest), encoding="utf-8")
|
|
341
|
+
|
|
342
|
+
# Create evidence-pack.zip
|
|
343
|
+
zip_path = out_p / "evidence-pack.zip"
|
|
344
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
345
|
+
zf.write(manifest_path, arcname="evidence-manifest.json")
|
|
346
|
+
zf.write(gap_report_ja_path, arcname="gap-report.ja.md")
|
|
347
|
+
zf.write(gap_report_en_path, arcname="gap-report.md")
|
|
348
|
+
|
|
349
|
+
for item in manifest["bundled_artifacts"]:
|
|
350
|
+
fname = item["filename"]
|
|
351
|
+
if fname in artifacts and artifacts[fname].is_file():
|
|
352
|
+
zf.write(artifacts[fname], arcname=f"artifacts/{fname}")
|
|
353
|
+
|
|
354
|
+
return zip_path, manifest_path, gap_report_ja_path, gap_report_en_path
|
package/operate/ai/context.py
CHANGED
|
@@ -5,18 +5,38 @@ Standard-library only (Python 3.8+ compatible).
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import importlib.util
|
|
8
9
|
import re
|
|
9
10
|
from pathlib import Path
|
|
11
|
+
from types import ModuleType
|
|
10
12
|
from typing import Any, Dict, List, Optional
|
|
11
13
|
|
|
14
|
+
LSA_REDACTION = Path(__file__).resolve().parents[2] / "assess" / "lsa" / "scripts" / "redaction.py"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_redaction() -> ModuleType:
|
|
18
|
+
"""Load the LSA kit's stdlib-only redaction module, so Tier 1 and the agents share one set of patterns."""
|
|
19
|
+
spec = importlib.util.spec_from_file_location("komagu_lsa_redaction", LSA_REDACTION)
|
|
20
|
+
if spec is None or spec.loader is None:
|
|
21
|
+
raise ImportError(f"cannot load LSA redaction from {LSA_REDACTION}")
|
|
22
|
+
module = importlib.util.module_from_spec(spec)
|
|
23
|
+
spec.loader.exec_module(module)
|
|
24
|
+
return module
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_redact_lines = _load_redaction().redact_lines
|
|
28
|
+
|
|
12
29
|
|
|
13
30
|
def _read_snippet(target_dir: Path, rel_file: str, line: int, radius: int = 15) -> str:
|
|
14
|
-
"""Read a window of code around target line from the audited repository.
|
|
31
|
+
"""Read a window of code around target line from the audited repository, with secrets redacted.
|
|
32
|
+
|
|
33
|
+
Every code excerpt sent to the model goes through here (#350).
|
|
34
|
+
"""
|
|
15
35
|
full_path = target_dir / rel_file
|
|
16
36
|
if not full_path.is_file():
|
|
17
37
|
return f"[File not found: {rel_file}]"
|
|
18
38
|
try:
|
|
19
|
-
lines = full_path.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
39
|
+
lines = _redact_lines(full_path.read_text(encoding="utf-8", errors="replace").splitlines())
|
|
20
40
|
start = max(0, line - radius - 1)
|
|
21
41
|
end = min(len(lines), line + radius)
|
|
22
42
|
numbered = [f"{i + 1:4d} | {lines[i]}" for i in range(start, end)]
|
package/operate/cli/__init__.py
CHANGED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Command: komagu evidence - METI SCS ★3/★4 & NIST CSF 2.0 Evidence Pack Bundler (#271)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
from governance.scs.pack import EvidencePackBuilder
|
|
11
|
+
from operate.cli.ui import (
|
|
12
|
+
BOLD,
|
|
13
|
+
CYAN,
|
|
14
|
+
GREEN,
|
|
15
|
+
RED,
|
|
16
|
+
YELLOW,
|
|
17
|
+
color,
|
|
18
|
+
print_summary_table,
|
|
19
|
+
step,
|
|
20
|
+
step_fail,
|
|
21
|
+
step_info,
|
|
22
|
+
step_ok,
|
|
23
|
+
step_warn,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def register_subcommand(subparsers: argparse._SubParsersAction) -> None:
|
|
28
|
+
"""Register the evidence subcommand parser."""
|
|
29
|
+
parser = subparsers.add_parser(
|
|
30
|
+
"evidence",
|
|
31
|
+
help="Generate METI Supply Chain Security (SCS ★3/★4) evidence pack and gap report",
|
|
32
|
+
description="Bundles technical audit artifacts into a timestamped, cryptographically hashed evidence pack (ZIP + JSON manifest) with Japanese/English gap reports.",
|
|
33
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
34
|
+
epilog="""
|
|
35
|
+
Examples:
|
|
36
|
+
# Generate METI SCS evidence pack from default audit output directory (OUT/)
|
|
37
|
+
komagu evidence --audit-dir OUT --output-dir ./meti-evidence
|
|
38
|
+
|
|
39
|
+
# Generate evidence pack specifying project and system names
|
|
40
|
+
komagu evidence \
|
|
41
|
+
--audit-dir OUT \
|
|
42
|
+
--project "FinTech Core Banking" \
|
|
43
|
+
--system "Staging API Cluster" \
|
|
44
|
+
--output-dir ./audit-pack
|
|
45
|
+
|
|
46
|
+
# Enforce minimum readiness rating (e.g. fail CI if SCS-★3-READY is not met)
|
|
47
|
+
komagu evidence --audit-dir OUT --require-tier 3
|
|
48
|
+
""",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--audit-dir",
|
|
52
|
+
"-d",
|
|
53
|
+
action="append",
|
|
54
|
+
dest="audit_dirs",
|
|
55
|
+
metavar="DIR",
|
|
56
|
+
help="Path to directory containing audit outputs (e.g. OUT/, findings.sarif, sbom.json). Repeatable.",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--output-dir",
|
|
60
|
+
"-o",
|
|
61
|
+
default="./evidence-pack",
|
|
62
|
+
help="Directory to save evidence-pack.zip, evidence-manifest.json, and gap reports (default: ./evidence-pack)",
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--project",
|
|
66
|
+
default="Target Modernization System",
|
|
67
|
+
help="Project or organization name (default: Target Modernization System)",
|
|
68
|
+
)
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"--system",
|
|
71
|
+
default="Production Architecture",
|
|
72
|
+
help="Target system or environment name (default: Production Architecture)",
|
|
73
|
+
)
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--require-tier",
|
|
76
|
+
type=int,
|
|
77
|
+
choices=[3, 4],
|
|
78
|
+
help="Fail with exit code 1 if evaluated readiness does not meet the specified star tier (3 or 4)",
|
|
79
|
+
)
|
|
80
|
+
parser.set_defaults(func=execute_evidence)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def execute_evidence(args: argparse.Namespace) -> int:
|
|
84
|
+
"""Execute evidence pack generation and gap analysis."""
|
|
85
|
+
audit_dirs = args.audit_dirs or ["OUT", "audit-out", "delivery/pilot/audit-out", "."]
|
|
86
|
+
|
|
87
|
+
step(f"Assembling METI SCS Evidence Pack for {color(args.project, BOLD)} ({args.system})")
|
|
88
|
+
step_info(f"Scanning audit source directories: {', '.join(audit_dirs)}")
|
|
89
|
+
|
|
90
|
+
builder = EvidencePackBuilder(
|
|
91
|
+
audit_dirs=audit_dirs,
|
|
92
|
+
project_name=args.project,
|
|
93
|
+
target_system=args.system,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
artifacts = builder.discover_artifacts()
|
|
97
|
+
step_info(f"Discovered {len(artifacts)} audit artifacts across directories")
|
|
98
|
+
|
|
99
|
+
evaluated_controls, readiness = builder.evaluate_controls(artifacts)
|
|
100
|
+
rating = readiness["rating"]
|
|
101
|
+
score = readiness["compliance_percentage"]
|
|
102
|
+
|
|
103
|
+
# Build ZIP and reports
|
|
104
|
+
zip_path, manifest_path, gap_ja, gap_en = builder.build_pack(args.output_dir)
|
|
105
|
+
|
|
106
|
+
step_ok(f"Generated Evidence Manifest: {manifest_path}")
|
|
107
|
+
step_ok(f"Generated Japanese Gap Report: {gap_ja}")
|
|
108
|
+
step_ok(f"Generated English Gap Report: {gap_en}")
|
|
109
|
+
step_ok(f"Packaged Signed ZIP Archive: {zip_path}")
|
|
110
|
+
|
|
111
|
+
# Display Executive Summary Table
|
|
112
|
+
rating_color = GREEN if "READY" in rating else (YELLOW if rating == "GAP-IDENTIFIED" else RED)
|
|
113
|
+
summary_rows = [
|
|
114
|
+
("Project Name", args.project),
|
|
115
|
+
("Target System", args.system),
|
|
116
|
+
("Total SCS Controls Evaluated", str(readiness["total_controls"])),
|
|
117
|
+
("Technically Covered Controls", color(str(readiness["covered_controls"]), GREEN, BOLD)),
|
|
118
|
+
("Partial / Organizational", color(str(readiness["partial_controls"]), YELLOW)),
|
|
119
|
+
("Compliance Score", color(f"{score}%", rating_color, BOLD)),
|
|
120
|
+
("METI SCS Readiness Rating", color(rating, rating_color, BOLD)),
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
print_summary_table(
|
|
124
|
+
title="METI Supply Chain Security (SCS ★3/★4) Assessment Summary",
|
|
125
|
+
rows=summary_rows,
|
|
126
|
+
footer="Evidence pack ready for submission to prime contractor / accredited auditor."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Gating enforcement if requested
|
|
130
|
+
if args.require_tier:
|
|
131
|
+
if args.require_tier == 4 and rating != "SCS-★4-READY":
|
|
132
|
+
step_fail(f"Quality Gate Failed: Required SCS-★4-READY, evaluated as {rating} ({score}%)")
|
|
133
|
+
return 1
|
|
134
|
+
elif args.require_tier == 3 and rating not in ("SCS-★3-READY", "SCS-★4-READY"):
|
|
135
|
+
step_fail(f"Quality Gate Failed: Required SCS-★3-READY, evaluated as {rating} ({score}%)")
|
|
136
|
+
return 1
|
|
137
|
+
|
|
138
|
+
return 0
|