@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,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
|
-
#
|
|
51
|
-
komagu simulate --scenario
|
|
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
|
|
57
|
-
komagu simulate --target http://localhost:5001 --output ./
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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"
|
|
259
|
+
step_ok(f"{sc_data.get('title', sid)}: {res.resilience_score}% resilient (0 breaches)")
|
|
154
260
|
else:
|
|
155
|
-
|
|
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":
|
package/operate/cli/main.py
CHANGED
|
@@ -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
|
+
]
|