@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/assess/lsa/CHANGELOG.md +15 -0
  3. package/assess/lsa/scripts/attack_chains.py +573 -0
  4. package/assess/lsa/scripts/baseline.py +14 -0
  5. package/assess/lsa/scripts/detect_stack.py +2 -1
  6. package/assess/lsa/scripts/findings_to_sarif.py +11 -0
  7. package/assess/lsa/scripts/forecast.py +544 -0
  8. package/assess/lsa/scripts/lsa_common.py +1 -5
  9. package/assess/lsa/scripts/merge_findings.py +14 -0
  10. package/assess/lsa/scripts/redaction.py +118 -0
  11. package/assess/lsa/scripts/render_report.py +86 -0
  12. package/assess/lsa/tests/test_attack_chains.py +292 -0
  13. package/assess/lsa/tests/test_forecast.py +160 -0
  14. package/assess/lsa/tests/test_modern_stacks.py +13 -0
  15. package/contracts/schemas/api-inventory-diff.schema.json +213 -0
  16. package/contracts/schemas/attack-chain.schema.json +181 -0
  17. package/contracts/schemas/bas-scenario.schema.json +217 -0
  18. package/contracts/schemas/evidence-manifest.schema.json +117 -0
  19. package/contracts/schemas/finding.schema.json +7 -0
  20. package/contracts/schemas/forecast.schema.json +172 -0
  21. package/governance/scs/__init__.py +21 -0
  22. package/governance/scs/mapping.py +133 -0
  23. package/governance/scs/pack.py +354 -0
  24. package/operate/ai/context.py +22 -2
  25. package/operate/cli/__init__.py +1 -1
  26. package/operate/cli/commands/evidence.py +138 -0
  27. package/operate/cli/commands/inventory.py +187 -0
  28. package/operate/cli/commands/simulate.py +175 -58
  29. package/operate/cli/main.py +7 -3
  30. package/operate/inventory/__init__.py +22 -0
  31. package/operate/inventory/api_inventory.py +566 -0
  32. package/operate/simulate/__init__.py +16 -0
  33. package/operate/simulate/engine.py +370 -0
  34. package/operate/simulate/loader.py +158 -0
  35. package/operate/simulate/scenarios/idc-frontier-ransomware.json +47 -0
  36. package/operate/simulate/scenarios/metabase-cve-2026-72898.json +84 -0
  37. package/operate/simulate/scenarios/times-car-breach.json +74 -0
  38. package/operate/simulate/scenarios.py +52 -0
  39. package/operate/tests/test_ai_redaction.py +181 -0
  40. package/operate/tests/test_api_inventory.py +239 -0
  41. package/operate/tests/test_declarative_scenarios.py +169 -0
  42. package/operate/tests/test_evidence_pack.py +128 -0
  43. package/operate/tests/test_headless_ai.py +16 -1
  44. package/package.json +1 -1
@@ -0,0 +1,187 @@
1
+ """Inventory subcommand: compare code routes, OpenAPI spec, and gateway traffic to find Shadow/Zombie APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from operate.cli.ui import color, print_summary_table, step, step_fail, step_info, step_ok, step_warn
11
+ from operate.inventory.api_inventory import (
12
+ diff_api_inventory,
13
+ load_code_routes,
14
+ load_openapi_spec,
15
+ load_traffic_log,
16
+ )
17
+
18
+
19
+ def register_subcommand(subparsers: argparse._SubParsersAction) -> None:
20
+ """Register the inventory subcommand parser."""
21
+ parser = subparsers.add_parser(
22
+ "inventory",
23
+ help="Reconcile code routes, OpenAPI spec, and gateway traffic to detect Shadow and Zombie APIs",
24
+ description="Detects Shadow APIs (undocumented code/traffic) and Zombie APIs (deprecated routes still active).",
25
+ formatter_class=argparse.RawDescriptionHelpFormatter,
26
+ epilog="""
27
+ Examples:
28
+ # Reconcile LSA code routes against the published OpenAPI spec
29
+ komagu inventory --routes OUT/routes.json --openapi openapi.json
30
+
31
+ # Include Kong Gateway traffic log to spot Zombie APIs and active Shadow endpoints
32
+ komagu inventory --routes OUT/routes.json --openapi openapi.json --traffic-log /var/log/kong/access.log
33
+
34
+ # CI gate: fail if any undocumented Shadow API is detected
35
+ komagu inventory --routes OUT/routes.json --openapi openapi.json --fail-on-shadow
36
+
37
+ # Output machine-readable JSON diff to a custom path
38
+ komagu inventory --routes OUT/routes.json --openapi openapi.json --out OUT/api-diff.json
39
+ """,
40
+ )
41
+ parser.add_argument(
42
+ "--routes",
43
+ "-r",
44
+ type=Path,
45
+ required=True,
46
+ help="Path to code routes JSON (e.g. OUT/routes.json or routes.candidates.json from LSA)",
47
+ )
48
+ parser.add_argument(
49
+ "--openapi",
50
+ "-s",
51
+ type=Path,
52
+ required=True,
53
+ help="Path to OpenAPI / Swagger JSON or YAML specification",
54
+ )
55
+ parser.add_argument(
56
+ "--traffic-log",
57
+ "--kong-log",
58
+ "-t",
59
+ type=Path,
60
+ default=None,
61
+ help="Path to Kong HTTP-Log (JSON lines) or NGINX Combined access log",
62
+ )
63
+ parser.add_argument(
64
+ "--out",
65
+ "-o",
66
+ type=Path,
67
+ default=None,
68
+ help="Path to write the api-inventory-diff.json output (defaults to OUT/api-inventory-diff.json)",
69
+ )
70
+ parser.add_argument(
71
+ "--fail-on-shadow",
72
+ action="store_true",
73
+ help="Exit with non-zero code if any shadow API is detected",
74
+ )
75
+ parser.add_argument(
76
+ "--fail-on-zombie",
77
+ action="store_true",
78
+ help="Exit with non-zero code if any zombie API is detected",
79
+ )
80
+ parser.set_defaults(func=run_inventory)
81
+
82
+
83
+ def run_inventory(args: argparse.Namespace) -> int:
84
+ """Execute API inventory reconciliation."""
85
+ routes_file = Path(args.routes).resolve()
86
+ openapi_file = Path(args.openapi).resolve()
87
+ traffic_file = Path(args.traffic_log).resolve() if args.traffic_log else None
88
+
89
+ if not routes_file.is_file():
90
+ step_fail(f"Routes file not found: {routes_file}")
91
+ return 2
92
+
93
+ if not openapi_file.is_file():
94
+ step_fail(f"OpenAPI spec file not found: {openapi_file}")
95
+ return 2
96
+
97
+ step("Parsing routes extracted from source code")
98
+ code_routes = load_code_routes(routes_file)
99
+ step_ok(f"Loaded {len(code_routes)} endpoints from code routes")
100
+
101
+ step("Parsing OpenAPI specification")
102
+ spec_endpoints = load_openapi_spec(openapi_file)
103
+ step_ok(f"Loaded {len(spec_endpoints)} endpoints from OpenAPI specification")
104
+
105
+ traffic_data = {}
106
+ if traffic_file:
107
+ if not traffic_file.is_file():
108
+ step_warn(f"Traffic log file not found: {traffic_file} (skipping traffic analysis)")
109
+ else:
110
+ step("Analyzing gateway traffic log (Kong / NGINX)")
111
+ traffic_data = load_traffic_log(traffic_file)
112
+ total_hits = sum(t["traffic_hits"] for t in traffic_data.values())
113
+ step_ok(f"Parsed {len(traffic_data)} active endpoints ({total_hits} total requests) from traffic log")
114
+
115
+ step("Reconciling API inventories across 3 surfaces")
116
+ diff_doc = diff_api_inventory(
117
+ code_routes=code_routes,
118
+ spec_endpoints=spec_endpoints,
119
+ traffic_data=traffic_data,
120
+ routes_file_name=routes_file.name,
121
+ openapi_file_name=openapi_file.name,
122
+ traffic_file_name=traffic_file.name if traffic_file else None,
123
+ )
124
+
125
+ s = diff_doc["summary"]
126
+ shadow_count = s["shadow_count"]
127
+ zombie_count = s["zombie_count"]
128
+ missing_count = s["missing_code_count"]
129
+ dormant_count = s["dormant_count"]
130
+
131
+ # Determine output file path
132
+ out_file = args.out
133
+ if not out_file:
134
+ out_dir = routes_file.parent
135
+ out_file = out_dir / "api-inventory-diff.json"
136
+
137
+ out_file.parent.mkdir(parents=True, exist_ok=True)
138
+ with open(out_file, "w", encoding="utf-8") as fh:
139
+ json.dump(diff_doc, fh, ensure_ascii=False, indent=2)
140
+ fh.write("\n")
141
+ step_ok(f"Saved inventory diff to {out_file}")
142
+
143
+ # Display summary card
144
+ c_yellow_bold = "\033[33m\033[1m"
145
+ c_red_bold = "\033[31m\033[1m"
146
+ c_green = "\033[32m"
147
+ c_bold = "\033[1m"
148
+
149
+ summary_rows = [
150
+ ("Code Routes Discovered", str(s["total_code_routes"])),
151
+ ("OpenAPI Endpoints Documented", str(s["total_spec_endpoints"])),
152
+ ("Gateway Traffic Endpoints", str(s["total_traffic_endpoints"])),
153
+ ("Shadow APIs (Undocumented)", color(str(shadow_count), c_yellow_bold if shadow_count > 0 else c_green)),
154
+ ("Zombie APIs (Deprecated Active)", color(str(zombie_count), c_red_bold if zombie_count > 0 else c_green)),
155
+ ("Documented Missing in Code", str(missing_count)),
156
+ ("Dormant Code Endpoints", str(dormant_count)),
157
+ ]
158
+ print_summary_table(
159
+ "API Inventory Reconciliation Summary",
160
+ summary_rows,
161
+ footer="Shadow APIs expose undocumented attack surface; Zombie APIs must be retired."
162
+ )
163
+
164
+ if shadow_count > 0:
165
+ print(" " + color("⚠ Detected Shadow APIs (Top 5):", "\033[33m", c_bold))
166
+ for sh in diff_doc["shadow_apis"][:5]:
167
+ hits = f"({sh['traffic_hits']} traffic hits)" if sh["traffic_hits"] > 0 else ""
168
+ norm_str = color(sh["normalized"], c_bold)
169
+ disc_str = sh["discovered_in"]
170
+ print(f" • {norm_str} [{disc_str}] {hits}")
171
+
172
+ if zombie_count > 0:
173
+ print(" " + color("✖ Detected Zombie APIs (Top 5):", "\033[31m", c_bold))
174
+ for z in diff_doc["zombie_apis"][:5]:
175
+ norm_str = color(z["normalized"], c_bold)
176
+ hits_str = f"{z['traffic_hits']} active traffic hits!"
177
+ print(f" • {norm_str} ({hits_str})")
178
+
179
+ exit_code = 0
180
+ if args.fail_on_shadow and shadow_count > 0:
181
+ step_fail(f"Failing gate: {shadow_count} shadow APIs detected")
182
+ exit_code = 1
183
+ if args.fail_on_zombie and zombie_count > 0:
184
+ step_fail(f"Failing gate: {zombie_count} zombie APIs detected")
185
+ exit_code = 1
186
+
187
+ return exit_code
@@ -1,4 +1,4 @@
1
- """Command: komagu simulate - Empirical Breach & Attack Simulation (BAS)."""
1
+ """Command: komagu simulate - Empirical Breach & Attack Simulation (BAS) (#317)."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -7,7 +7,7 @@ import json
7
7
  import sys
8
8
  import tempfile
9
9
  from pathlib import Path
10
- from typing import Any, List
10
+ from typing import Any, Dict, List, Optional
11
11
 
12
12
  from operate.cli.ui import (
13
13
  BOLD,
@@ -23,12 +23,18 @@ from operate.cli.ui import (
23
23
  step_ok,
24
24
  step_warn,
25
25
  )
26
+ from operate.simulate.engine import (
27
+ DeclarativeScenario,
28
+ generate_execution_evidence,
29
+ )
30
+ from operate.simulate.loader import (
31
+ discover_scenarios,
32
+ load_scenario_file,
33
+ validate_scenario,
34
+ )
26
35
  from operate.simulate.scenarios import (
27
36
  ExecutiveReportGenerator,
28
- IDCFrontierRansomwareScenario,
29
- MetabaseZeroDayScenario,
30
37
  ScenarioResult,
31
- TimesCarBreachScenario,
32
38
  )
33
39
 
34
40
 
@@ -41,28 +47,71 @@ def register_subcommand(subparsers: argparse._SubParsersAction) -> None:
41
47
  formatter_class=argparse.RawDescriptionHelpFormatter,
42
48
  epilog="""
43
49
  Examples:
50
+ # List all discovered attack scenarios
51
+ komagu simulate --list
52
+
53
+ # Validate a custom declarative scenario JSON against bas-scenario.schema.json
54
+ komagu simulate --validate ./custom-scenario.json
55
+
44
56
  # Run all simulated attack scenarios against a target staging API
45
57
  komagu simulate --target http://localhost:5001
46
58
 
47
59
  # Simulate the Times Car mass IDOR harvesting attack specifically
48
60
  komagu simulate --scenario times-car-breach --target http://staging-api:8000
49
61
 
50
- # Simulate Metabase unauthenticated SQL injection probe
51
- komagu simulate --scenario metabase-cve-2026-72898 --target http://localhost:5001
52
-
53
- # Simulate IDC Frontier ransomware immutable storage attack
54
- komagu simulate --scenario idc-frontier-ransomware --target /tmp/backup-dir
62
+ # Run a custom scenario file with parameter override
63
+ komagu simulate --scenario-file ./my-scenario.json --set endpoint_pattern=/api/v2/items/{id}
55
64
 
56
- # Export Executive C-Level Report for Board of Directors
57
- komagu simulate --target http://localhost:5001 --output ./executive-bas-report.md --format markdown
65
+ # Export Executive C-Level Report and METI SCS audit evidence
66
+ komagu simulate --target http://localhost:5001 --output ./bas-report.md --evidence ./bas-evidence.json
58
67
  """,
59
68
  )
69
+ parser.add_argument(
70
+ "--list",
71
+ action="store_true",
72
+ help="List all available built-in and discovered scenarios",
73
+ )
74
+ parser.add_argument(
75
+ "--validate",
76
+ metavar="PATH",
77
+ help="Validate a scenario JSON file against schema and exit",
78
+ )
60
79
  parser.add_argument(
61
80
  "--scenario",
62
81
  "-s",
63
82
  default="all",
64
- choices=["all", "times-car-breach", "metabase-cve-2026-72898", "idc-frontier-ransomware"],
65
- help="Attack scenario to execute (default: all)",
83
+ help="Attack scenario ID to execute, or 'all' (default: all)",
84
+ )
85
+ parser.add_argument(
86
+ "--scenario-file",
87
+ action="append",
88
+ dest="scenario_files",
89
+ metavar="PATH",
90
+ help="Path to custom scenario file (repeatable)",
91
+ )
92
+ parser.add_argument(
93
+ "--scenario-dir",
94
+ action="append",
95
+ dest="scenario_dirs",
96
+ metavar="DIR",
97
+ help="Path to directory containing scenario files (repeatable)",
98
+ )
99
+ parser.add_argument(
100
+ "--set",
101
+ action="append",
102
+ dest="param_sets",
103
+ metavar="KEY=VALUE",
104
+ help="Override scenario parameter (repeatable, e.g. --set token=secret)",
105
+ )
106
+ parser.add_argument(
107
+ "--allow-draft",
108
+ action="store_true",
109
+ help="Allow execution of scenarios with status != 'approved'",
110
+ )
111
+ parser.add_argument(
112
+ "--allow-destructive",
113
+ action="store_true",
114
+ help="Allow execution of steps marked destructive: true",
66
115
  )
67
116
  parser.add_argument(
68
117
  "--target",
@@ -89,6 +138,10 @@ Examples:
89
138
  "-o",
90
139
  help="Output file path for detailed executive report",
91
140
  )
141
+ parser.add_argument(
142
+ "--evidence",
143
+ help="Output file path for METI SCS / ISO 27001 audit evidence JSON (#271)",
144
+ )
92
145
  parser.add_argument(
93
146
  "--format",
94
147
  default="table",
@@ -100,59 +153,118 @@ Examples:
100
153
 
101
154
  def execute_simulate(args: argparse.Namespace) -> int:
102
155
  """Execute Breach & Attack Simulation scenarios."""
156
+ # Mode 1: Validate scenario file
157
+ if args.validate:
158
+ v_path = Path(args.validate).resolve()
159
+ if not v_path.is_file():
160
+ step_fail(f"Scenario file not found: {v_path}")
161
+ return 2
162
+ try:
163
+ doc = json.loads(v_path.read_text(encoding="utf-8"))
164
+ except Exception as ex:
165
+ step_fail(f"Invalid JSON in {v_path}: {ex}")
166
+ return 2
167
+
168
+ errs = validate_scenario(doc)
169
+ if errs:
170
+ step_fail(f"Scenario {v_path.name} failed schema validation:")
171
+ for err in errs:
172
+ print(f" • {err}")
173
+ return 2
174
+ step_ok(f"Scenario {color(doc.get('id', v_path.name), BOLD)} is valid ({doc.get('status', 'draft')})")
175
+ return 0
176
+
177
+ # Discover all scenarios
178
+ scenarios_map = discover_scenarios(
179
+ scenario_files=args.scenario_files,
180
+ scenario_dirs=args.scenario_dirs,
181
+ include_builtins=True,
182
+ )
183
+
184
+ # Mode 2: List scenarios
185
+ if args.list:
186
+ step("Discovered Breach & Attack Simulation (BAS) Scenarios:")
187
+ rows = []
188
+ for sid, sc in sorted(scenarios_map.items()):
189
+ status = sc.get("status", "draft")
190
+ status_color = GREEN if status == "approved" else (YELLOW if status == "draft" else RED)
191
+ date_str = sc.get("incident", {}).get("date", "-")
192
+ title = sc.get("title", sid)
193
+ rows.append((f"{color(sid, BOLD)} (v{sc.get('version', '1.0.0')})", f"[{color(status, status_color)}] {title} ({date_str})"))
194
+
195
+ print_summary_table(
196
+ title="Available BAS Scenarios",
197
+ rows=rows,
198
+ footer="Execute with: komagu simulate --scenario <id> --target <url>"
199
+ )
200
+ return 0
201
+
202
+ # Mode 3: Run simulation
103
203
  step(f"Initializing Komagu Breach & Attack Simulation against {color(args.target, BOLD)}")
104
204
 
205
+ # Parse --set parameter overrides
206
+ overrides: Dict[str, Any] = {}
207
+ if args.param_sets:
208
+ for p_set in args.param_sets:
209
+ if "=" in p_set:
210
+ k, v = p_set.split("=", 1)
211
+ overrides[k.strip()] = v.strip()
212
+
213
+ # Legacy CLI flag bridges
214
+ if args.endpoint:
215
+ overrides["endpoint_pattern"] = args.endpoint
216
+ overrides["query_path"] = args.endpoint
217
+ if args.param:
218
+ overrides["target_param"] = args.param
219
+ if args.token:
220
+ overrides["attacker_token"] = args.token
221
+
222
+ if args.scenario == "all":
223
+ scenarios_to_run = list(scenarios_map.keys())
224
+ else:
225
+ if args.scenario not in scenarios_map:
226
+ step_fail(f"Unknown scenario ID: '{args.scenario}'. Use --list to view available scenarios.")
227
+ return 2
228
+ scenarios_to_run = [args.scenario]
229
+
105
230
  results: List[ScenarioResult] = []
106
- scenarios_to_run = [args.scenario] if args.scenario != "all" else [
107
- "times-car-breach",
108
- "metabase-cve-2026-72898",
109
- "idc-frontier-ransomware",
110
- ]
111
-
112
- for sc in scenarios_to_run:
113
- if sc == "times-car-breach":
114
- ep = args.endpoint or "/api/legacy-orders/{id}"
115
- step_info(f"Launching Scenario: Times Car Mass BOLA/IDOR Harvesting ({ep})")
116
- runner = TimesCarBreachScenario(
117
- target=args.target,
118
- endpoint_pattern=ep,
119
- attacker_token=args.token,
120
- )
121
- res = runner.run()
122
- results.append(res)
123
- if res.passed:
124
- step_ok(f"Times Car Simulation: {res.resilience_score}% resilient (0 breaches)")
125
- else:
126
- step_fail(f"Times Car Simulation: {res.breaches_leaked} data leaks detected ({res.resilience_score}% resilient)")
127
-
128
- elif sc == "metabase-cve-2026-72898":
129
- ep = args.endpoint or "/api/products"
130
- step_info(f"Launching Scenario: Metabase Zero-Day SQLi Simulation ({ep}?{args.param}=...)")
131
- runner = MetabaseZeroDayScenario(
132
- target=args.target,
133
- query_path=ep,
134
- target_param=args.param,
135
- )
136
- res = runner.run()
137
- results.append(res)
138
- if res.passed:
139
- step_ok(f"Metabase Zero-Day Simulation: {res.resilience_score}% resilient (All SQLi probes blocked)")
140
- else:
141
- step_fail(f"Metabase Zero-Day Simulation: SQLi probes unhandled ({res.resilience_score}% resilient)")
231
+ runners: List[DeclarativeScenario] = []
232
+
233
+ for sid in scenarios_to_run:
234
+ sc_data = scenarios_map.get(sid)
235
+ if not sc_data:
236
+ continue
142
237
 
143
- elif sc == "idc-frontier-ransomware":
144
- # A fresh private dir, not a fixed /tmp path another local user could pre-create or symlink
145
- storage_path = (
146
- args.target if not args.target.startswith("http") else tempfile.mkdtemp(prefix="komagu-backup-sim-")
238
+ step_info(f"Launching Scenario: {sc_data.get('title', sid)} ({sid})")
239
+
240
+ # Handle local dir target if needed for idc-frontier-ransomware
241
+ eff_target = args.target
242
+ if sid == "idc-frontier-ransomware" and args.target.startswith("http"):
243
+ eff_target = tempfile.mkdtemp(prefix="komagu-backup-sim-")
244
+ overrides["target_dir"] = eff_target
245
+
246
+ try:
247
+ runner = DeclarativeScenario(
248
+ scenario_data=sc_data,
249
+ target=eff_target,
250
+ param_overrides=overrides,
251
+ allow_draft=args.allow_draft,
252
+ allow_destructive=args.allow_destructive,
147
253
  )
148
- step_info(f"Launching Scenario: IDC Frontier Cloud Storage Ransomware Simulation ({storage_path})")
149
- runner = IDCFrontierRansomwareScenario(target_dir=storage_path)
254
+ runners.append(runner)
150
255
  res = runner.run()
151
256
  results.append(res)
257
+
152
258
  if res.passed:
153
- step_ok(f"IDC Frontier Simulation: {res.resilience_score}% resilient (WORM lock enforced)")
259
+ step_ok(f"{sc_data.get('title', sid)}: {res.resilience_score}% resilient (0 breaches)")
154
260
  else:
155
- step_warn(f"IDC Frontier Simulation: Writable storage ({res.resilience_score}% resilient)")
261
+ step_fail(f"{sc_data.get('title', sid)}: {res.breaches_leaked} data leaks detected ({res.resilience_score}% resilient)")
262
+ except PermissionError as p_err:
263
+ step_warn(f"Skipping {sid}: {p_err}")
264
+
265
+ if not results:
266
+ step_fail("No scenarios were executed.")
267
+ return 1
156
268
 
157
269
  generator = ExecutiveReportGenerator(results, args.target)
158
270
  overall_score = generator.overall_resilience_score
@@ -177,6 +289,11 @@ def execute_simulate(args: argparse.Namespace) -> int:
177
289
  footer="Empirical simulation against landmark real-world enterprise breaches.",
178
290
  )
179
291
 
292
+ # Generate METI SCS audit evidence JSON if requested
293
+ if args.evidence:
294
+ generate_execution_evidence(runners, results, args.target, output_path=args.evidence)
295
+ step_ok(f"Saved METI SCS Audit Evidence to {color(args.evidence, BOLD)}")
296
+
180
297
  # Save output if requested
181
298
  if args.output or args.format in ("markdown", "json"):
182
299
  if args.format == "json":
@@ -7,7 +7,7 @@ import sys
7
7
  from typing import List, Optional
8
8
 
9
9
  from operate.cli import __version__
10
- from operate.cli.commands import demo, devsecops, fix, init, protect, scan, simulate, test
10
+ from operate.cli.commands import demo, devsecops, evidence, fix, init, inventory, protect, scan, simulate, test
11
11
  from operate.cli.ui import print_banner
12
12
 
13
13
 
@@ -27,6 +27,8 @@ Quick Start Workflow:
27
27
  6. Remediate vulnerabilities: komagu fix ./legacy-app --dry-run
28
28
  7. Install AI audit agents: komagu init --ai codex ~/audits/acme
29
29
  8. Simulate breach scenarios: komagu simulate --scenario times-car-breach
30
+ 9. Reconcile API inventory: komagu inventory --routes OUT/routes.json --openapi openapi.json
31
+ 10. Bundle METI SCS evidence: komagu evidence --audit-dir OUT/ --output-dir ./evidence-pack
30
32
 
31
33
  Documentation & Guides:
32
34
  Comprehensive User Guide: docs/cli-guide.md
@@ -62,6 +64,8 @@ Learn more about a specific command:
62
64
  fix.register_subcommand(subparsers)
63
65
  init.register_subcommand(subparsers)
64
66
  simulate.register_subcommand(subparsers)
67
+ inventory.register_subcommand(subparsers)
68
+ evidence.register_subcommand(subparsers)
65
69
 
66
70
  help_parser = subparsers.add_parser(
67
71
  "help",
@@ -71,8 +75,8 @@ Learn more about a specific command:
71
75
  help_parser.add_argument(
72
76
  "topic",
73
77
  nargs="?",
74
- choices=["scan", "protect", "test", "demo", "devsecops", "fix", "init", "simulate"],
75
- help="Subcommand topic to display help for (scan, protect, test, demo, devsecops, fix, init, simulate)",
78
+ choices=["scan", "protect", "test", "demo", "devsecops", "fix", "init", "simulate", "inventory", "evidence"],
79
+ help="Subcommand topic to display help for (scan, protect, test, demo, devsecops, fix, init, simulate, inventory, evidence)",
76
80
  )
77
81
 
78
82
  return parser
@@ -0,0 +1,22 @@
1
+ """API inventory and shadow/zombie API detection package."""
2
+ from operate.inventory.api_inventory import (
3
+ diff_api_inventory,
4
+ load_code_routes,
5
+ load_openapi_spec,
6
+ load_traffic_log,
7
+ normalize_path,
8
+ normalize_route_key,
9
+ render_inventory_html,
10
+ render_inventory_md,
11
+ )
12
+
13
+ __all__ = [
14
+ "diff_api_inventory",
15
+ "load_code_routes",
16
+ "load_openapi_spec",
17
+ "load_traffic_log",
18
+ "normalize_path",
19
+ "normalize_route_key",
20
+ "render_inventory_html",
21
+ "render_inventory_md",
22
+ ]