@orionscanner/cli 0.1.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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +248 -0
  3. package/assets/logo.png +0 -0
  4. package/bin/orion.js +48 -0
  5. package/docs/API_REFERENCE.md +211 -0
  6. package/docs/ARCHITECTURE.md +183 -0
  7. package/docs/CHANGELOG.md +112 -0
  8. package/docs/CONTRIBUTING.md +157 -0
  9. package/docs/ENGINE_CATALOG.md +87 -0
  10. package/docs/GETTING_STARTED.md +226 -0
  11. package/package.json +48 -0
  12. package/pyproject.toml +16 -0
  13. package/src/orion/__init__.py +0 -0
  14. package/src/orion/__pycache__/__init__.cpython-313.pyc +0 -0
  15. package/src/orion/engine/__init__.py +17 -0
  16. package/src/orion/engine/__pycache__/__init__.cpython-313.pyc +0 -0
  17. package/src/orion/engine/__pycache__/config.cpython-313.pyc +0 -0
  18. package/src/orion/engine/__pycache__/core.cpython-313.pyc +0 -0
  19. package/src/orion/engine/__pycache__/ir.cpython-313.pyc +0 -0
  20. package/src/orion/engine/__pycache__/languages.cpython-313.pyc +0 -0
  21. package/src/orion/engine/__pycache__/registry.cpython-313.pyc +0 -0
  22. package/src/orion/engine/__pycache__/results.cpython-313.pyc +0 -0
  23. package/src/orion/engine/__pycache__/runner.cpython-313.pyc +0 -0
  24. package/src/orion/engine/api_security/__pycache__/engine.cpython-313.pyc +0 -0
  25. package/src/orion/engine/api_security/engine.py +125 -0
  26. package/src/orion/engine/architecture/__pycache__/engine.cpython-313.pyc +0 -0
  27. package/src/orion/engine/architecture/engine.py +463 -0
  28. package/src/orion/engine/authorization/__pycache__/engine.cpython-313.pyc +0 -0
  29. package/src/orion/engine/authorization/engine.py +337 -0
  30. package/src/orion/engine/callgraph/__pycache__/engine.cpython-313.pyc +0 -0
  31. package/src/orion/engine/callgraph/engine.py +236 -0
  32. package/src/orion/engine/cfg/__pycache__/engine.cpython-313.pyc +0 -0
  33. package/src/orion/engine/cfg/engine.py +509 -0
  34. package/src/orion/engine/chaos/__pycache__/engine.cpython-313.pyc +0 -0
  35. package/src/orion/engine/chaos/engine.py +268 -0
  36. package/src/orion/engine/chaos_config/__pycache__/engine.cpython-313.pyc +0 -0
  37. package/src/orion/engine/chaos_config/engine.py +322 -0
  38. package/src/orion/engine/complexity/__pycache__/engine.cpython-313.pyc +0 -0
  39. package/src/orion/engine/complexity/engine.py +144 -0
  40. package/src/orion/engine/compliance/__pycache__/engine.cpython-313.pyc +0 -0
  41. package/src/orion/engine/compliance/engine.py +325 -0
  42. package/src/orion/engine/config.py +41 -0
  43. package/src/orion/engine/config_engine/__pycache__/engine.cpython-313.pyc +0 -0
  44. package/src/orion/engine/config_engine/engine.py +364 -0
  45. package/src/orion/engine/config_validation/__pycache__/engine.cpython-313.pyc +0 -0
  46. package/src/orion/engine/config_validation/engine.py +313 -0
  47. package/src/orion/engine/container/__pycache__/engine.cpython-313.pyc +0 -0
  48. package/src/orion/engine/container/engine.py +546 -0
  49. package/src/orion/engine/contract_probe/__pycache__/engine.cpython-313.pyc +0 -0
  50. package/src/orion/engine/contract_probe/engine.py +316 -0
  51. package/src/orion/engine/core.py +75 -0
  52. package/src/orion/engine/crypto_guard/__pycache__/engine.cpython-313.pyc +0 -0
  53. package/src/orion/engine/crypto_guard/engine.py +134 -0
  54. package/src/orion/engine/data_residency/__pycache__/engine.cpython-313.pyc +0 -0
  55. package/src/orion/engine/data_residency/engine.py +238 -0
  56. package/src/orion/engine/dataflow/__pycache__/engine.cpython-313.pyc +0 -0
  57. package/src/orion/engine/dataflow/engine.py +398 -0
  58. package/src/orion/engine/dead_import/__pycache__/engine.cpython-313.pyc +0 -0
  59. package/src/orion/engine/dead_import/engine.py +293 -0
  60. package/src/orion/engine/deps/__pycache__/engine.cpython-313.pyc +0 -0
  61. package/src/orion/engine/deps/engine.py +244 -0
  62. package/src/orion/engine/dispatcher.py +350 -0
  63. package/src/orion/engine/docs/AUDIT_PROMPT.md +1248 -0
  64. package/src/orion/engine/duplication/__pycache__/engine.cpython-313.pyc +0 -0
  65. package/src/orion/engine/duplication/engine.py +120 -0
  66. package/src/orion/engine/error_handling/__pycache__/engine.cpython-313.pyc +0 -0
  67. package/src/orion/engine/error_handling/engine.py +256 -0
  68. package/src/orion/engine/fp_learn/__pycache__/engine.cpython-313.pyc +0 -0
  69. package/src/orion/engine/fp_learn/engine.py +248 -0
  70. package/src/orion/engine/hunter/__pycache__/engine.cpython-313.pyc +0 -0
  71. package/src/orion/engine/hunter/engine.py +324 -0
  72. package/src/orion/engine/iac_security/__pycache__/engine.cpython-313.pyc +0 -0
  73. package/src/orion/engine/iac_security/engine.py +218 -0
  74. package/src/orion/engine/ir.py +22 -0
  75. package/src/orion/engine/languages.py +454 -0
  76. package/src/orion/engine/license/__pycache__/engine.cpython-313.pyc +0 -0
  77. package/src/orion/engine/license/engine.py +172 -0
  78. package/src/orion/engine/lint/__pycache__/engine.cpython-313.pyc +0 -0
  79. package/src/orion/engine/lint/engine.py +558 -0
  80. package/src/orion/engine/llm_security/__pycache__/engine.cpython-313.pyc +0 -0
  81. package/src/orion/engine/llm_security/engine.py +125 -0
  82. package/src/orion/engine/logging_c/__pycache__/engine.cpython-313.pyc +0 -0
  83. package/src/orion/engine/logging_c/engine.py +164 -0
  84. package/src/orion/engine/noise/__pycache__/engine.cpython-313.pyc +0 -0
  85. package/src/orion/engine/noise/engine.py +140 -0
  86. package/src/orion/engine/orphan/__pycache__/engine.cpython-313.pyc +0 -0
  87. package/src/orion/engine/orphan/engine.py +244 -0
  88. package/src/orion/engine/perf_guard/__pycache__/engine.cpython-313.pyc +0 -0
  89. package/src/orion/engine/perf_guard/engine.py +345 -0
  90. package/src/orion/engine/prompt_security/__pycache__/engine.cpython-313.pyc +0 -0
  91. package/src/orion/engine/prompt_security/detectors/__init__.py +4 -0
  92. package/src/orion/engine/prompt_security/engine.py +170 -0
  93. package/src/orion/engine/provenance/__pycache__/engine.cpython-313.pyc +0 -0
  94. package/src/orion/engine/provenance/engine.py +242 -0
  95. package/src/orion/engine/registry.py +55 -0
  96. package/src/orion/engine/regression/__pycache__/engine.cpython-313.pyc +0 -0
  97. package/src/orion/engine/regression/engine.py +213 -0
  98. package/src/orion/engine/results.py +21 -0
  99. package/src/orion/engine/runner.py +1310 -0
  100. package/src/orion/engine/sandbox_escape/__pycache__/engine.cpython-313.pyc +0 -0
  101. package/src/orion/engine/sandbox_escape/engine.py +284 -0
  102. package/src/orion/engine/sast/__pycache__/engine.cpython-313.pyc +0 -0
  103. package/src/orion/engine/sast/engine.py +217 -0
  104. package/src/orion/engine/sast/patterns/__init__.py +513 -0
  105. package/src/orion/engine/sast/patterns/__pycache__/__init__.cpython-313.pyc +0 -0
  106. package/src/orion/engine/secrets/__pycache__/engine.cpython-313.pyc +0 -0
  107. package/src/orion/engine/secrets/engine.py +77 -0
  108. package/src/orion/engine/secrets/patterns/__init__.py +74 -0
  109. package/src/orion/engine/secrets/patterns/__pycache__/__init__.cpython-313.pyc +0 -0
  110. package/src/orion/engine/sensitive_data/__pycache__/engine.cpython-313.pyc +0 -0
  111. package/src/orion/engine/sensitive_data/engine.py +143 -0
  112. package/src/orion/engine/state/__pycache__/engine.cpython-313.pyc +0 -0
  113. package/src/orion/engine/state/engine.py +218 -0
  114. package/src/orion/engine/supply_chain_sbom/__pycache__/engine.cpython-313.pyc +0 -0
  115. package/src/orion/engine/supply_chain_sbom/engine.py +290 -0
  116. package/src/orion/engine/symbol/__pycache__/engine.cpython-313.pyc +0 -0
  117. package/src/orion/engine/symbol/engine.py +463 -0
  118. package/src/orion/engine/taint/__pycache__/engine.cpython-313.pyc +0 -0
  119. package/src/orion/engine/taint/engine.py +464 -0
  120. package/src/orion/engine/test_coverage/__pycache__/engine.cpython-313.pyc +0 -0
  121. package/src/orion/engine/test_coverage/engine.py +445 -0
  122. package/src/orion/engine/threat_model/__pycache__/engine.cpython-313.pyc +0 -0
  123. package/src/orion/engine/threat_model/engine.py +126 -0
  124. package/src/orion/engine/wiring_trace/__pycache__/engine.cpython-313.pyc +0 -0
  125. package/src/orion/engine/wiring_trace/engine.py +200 -0
  126. package/src/orion_engine.egg-info/PKG-INFO +7 -0
  127. package/src/orion_engine.egg-info/SOURCES.txt +65 -0
  128. package/src/orion_engine.egg-info/dependency_links.txt +1 -0
  129. package/src/orion_engine.egg-info/entry_points.txt +2 -0
  130. package/src/orion_engine.egg-info/top_level.txt +1 -0
@@ -0,0 +1,324 @@
1
+ """Cross-engine correlation hunter - correlates findings across engines to find attack chains."""
2
+ from typing import List, Dict, Set, Any
3
+ from dataclasses import dataclass
4
+ from collections import defaultdict
5
+
6
+ from orion.engine.config import EngineConfig
7
+ from orion.engine.ir import OrionIR
8
+ from orion.engine.results import EngineResult, Finding
9
+
10
+
11
+ @dataclass
12
+ class CorrelationRule:
13
+ name: str
14
+ required_engines: List[str]
15
+ required_patterns: List[str]
16
+ boosted_severity: str
17
+ description: str
18
+ chain_id: str
19
+
20
+
21
+ class HunterEngine:
22
+ name = "hunter"
23
+ version = "1.0.0"
24
+
25
+ # Correlation rules defining attack chains
26
+ CORRELATION_RULES = [
27
+ CorrelationRule(
28
+ name="sqli_missing_auth",
29
+ required_engines=["sast"],
30
+ required_patterns=["sql_injection", "missing_auth", "authorization_bypass"],
31
+ boosted_severity="critical",
32
+ description="SQL injection combined with missing authentication = critical data breach risk",
33
+ chain_id="CHAIN-001",
34
+ ),
35
+ CorrelationRule(
36
+ name="xss_missing_csp",
37
+ required_engines=["sast", "config_engine"],
38
+ required_patterns=["xss", "missing_csp", "csp_not_enabled"],
39
+ boosted_severity="high",
40
+ description="XSS vulnerability with no Content Security Policy = high exploitability",
41
+ chain_id="CHAIN-002",
42
+ ),
43
+ CorrelationRule(
44
+ name="secret_public_repo",
45
+ required_engines=["secrets", "deps"],
46
+ required_patterns=["hardcoded_secret", "public_repository", "git_public"],
47
+ boosted_severity="critical",
48
+ description="Hardcoded secret in public repository = immediate credential compromise",
49
+ chain_id="CHAIN-003",
50
+ ),
51
+ CorrelationRule(
52
+ name="pii_logging",
53
+ required_engines=["sast", "logging_c"],
54
+ required_patterns=["hardcoded_pii", "pii_logged", "sensitive_data_logging"],
55
+ boosted_severity="high",
56
+ description="PII in code combined with logging = data exposure risk",
57
+ chain_id="CHAIN-004",
58
+ ),
59
+ CorrelationRule(
60
+ name="monkey_patch_no_tests",
61
+ required_engines=["symbol", "test_coverage"],
62
+ required_patterns=["monkey_patch", "runtime_patch", "untested_code", "low_coverage"],
63
+ boosted_severity="medium",
64
+ description="Monkey patching without tests = high regression risk",
65
+ chain_id="CHAIN-005",
66
+ ),
67
+ CorrelationRule(
68
+ name="n1_query_no_pagination",
69
+ required_engines=["dataflow", "sast"],
70
+ required_patterns=["n_plus_one_query", "missing_pagination", "unbounded_query"],
71
+ boosted_severity="high",
72
+ description="N+1 query pattern without pagination = DoS risk",
73
+ chain_id="CHAIN-006",
74
+ ),
75
+ CorrelationRule(
76
+ name="ssrf_no_egress_control",
77
+ required_engines=["sast", "container", "config_engine"],
78
+ required_patterns=["ssrf", "missing_egress_control", "allow_all_outbound"],
79
+ boosted_severity="critical",
80
+ description="SSRF vulnerability with no egress filtering = internal network access",
81
+ chain_id="CHAIN-007",
82
+ ),
83
+ CorrelationRule(
84
+ name="weak_crypto_sensitive_data",
85
+ required_engines=["sast", "data_residency"],
86
+ required_patterns=["weak_crypto", "md5", "sha1", "sensitive_data_storage"],
87
+ boosted_severity="high",
88
+ description="Weak cryptography protecting sensitive data = compliance violation",
89
+ chain_id="CHAIN-008",
90
+ ),
91
+ CorrelationRule(
92
+ name="deserialization_rce_chain",
93
+ required_engines=["sast", "sandbox_escape"],
94
+ required_patterns=["insecure_deserialization", "pickle", "yaml_unsafe", "sandbox_escape"],
95
+ boosted_severity="critical",
96
+ description="Insecure deserialization with sandbox escape = RCE chain",
97
+ chain_id="CHAIN-009",
98
+ ),
99
+ CorrelationRule(
100
+ name="path_traversal_file_read",
101
+ required_engines=["sast", "authorization"],
102
+ required_patterns=["path_traversal", "missing_path_validation", "authorization_bypass"],
103
+ boosted_severity="high",
104
+ description="Path traversal with missing authorization = arbitrary file read",
105
+ chain_id="CHAIN-010",
106
+ ),
107
+ ]
108
+
109
+ SEVERITY_ORDER = {"low": 0, "medium": 1, "high": 2, "critical": 3}
110
+
111
+ async def check_availability(self) -> bool:
112
+ return True
113
+
114
+ def resource_limits(self):
115
+ from orion.engine.config import ResourceLimits
116
+ return ResourceLimits(max_cpu_seconds=30, max_memory_mb=128)
117
+
118
+ def _get_all_findings(self, ir: OrionIR) -> List[Finding]:
119
+ """Get all findings from context - check metrics and project context."""
120
+ # First check if findings are in metrics
121
+ all_findings = ir.metadata.get("all_findings", [])
122
+
123
+ # Also check if there's a project context with findings from all engines
124
+ project_ctx = ir.metadata.get("project_context")
125
+ if project_ctx and hasattr(project_ctx, 'findings'):
126
+ all_findings.extend(project_ctx.findings)
127
+
128
+ return all_findings
129
+
130
+ def _extract_pattern_key(self, finding: Finding) -> str:
131
+ """Extract a normalized pattern key from a finding."""
132
+ # Try metadata first
133
+ pattern = finding.metadata.get("pattern", "")
134
+ if pattern:
135
+ return pattern.lower()
136
+
137
+ # Fallback to title
138
+ title = finding.title.lower()
139
+ # Extract key terms
140
+ keywords = [
141
+ "sql_injection", "sqli", "xss", "cmd_injection", "ssrf",
142
+ "path_traversal", "hardcoded", "secret", "credential",
143
+ "weak_crypto", "md5", "sha1", "deserialization", "pickle",
144
+ "yaml", "eval", "exec", "subprocess", "shell",
145
+ "missing_auth", "authorization", "csp", "logging",
146
+ "monkey_patch", "n_plus_one", "n1", "pagination",
147
+ "egress", "sandbox", "pii", "sensitive",
148
+ ]
149
+ for kw in keywords:
150
+ if kw in title:
151
+ return kw
152
+ return title[:50]
153
+
154
+ def _match_rule(self, rule: CorrelationRule, findings_by_engine: Dict[str, List[Finding]]) -> List[Finding]:
155
+ """Check if a correlation rule matches and return boosted findings."""
156
+ matched_findings = []
157
+
158
+ # Check if all required engines have findings
159
+ for engine in rule.required_engines:
160
+ if engine not in findings_by_engine or not findings_by_engine[engine]:
161
+ return []
162
+
163
+ # Check if required patterns are present
164
+ all_patterns = set()
165
+ for engine_findings in findings_by_engine.values():
166
+ for f in engine_findings:
167
+ all_patterns.add(self._extract_pattern_key(f))
168
+
169
+ # Flexible matching - check if any required pattern is present
170
+ pattern_matched = False
171
+ for required in rule.required_patterns:
172
+ for pattern in all_patterns:
173
+ if required.lower() in pattern or pattern in required.lower():
174
+ pattern_matched = True
175
+ break
176
+ if pattern_matched:
177
+ break
178
+
179
+ if not pattern_matched:
180
+ return []
181
+
182
+ # Rule matched - boost severity of related findings
183
+ for engine in rule.required_engines:
184
+ for finding in findings_by_engine[engine]:
185
+ pattern_key = self._extract_pattern_key(finding)
186
+ # Check if this finding is part of the chain
187
+ for required in rule.required_patterns:
188
+ if required.lower() in pattern_key or pattern_key in required.lower():
189
+ original_severity = finding.severity
190
+ if self.SEVERITY_ORDER.get(rule.boosted_severity, 0) > self.SEVERITY_ORDER.get(original_severity, 0):
191
+ boosted = Finding(
192
+ engine=finding.engine,
193
+ severity=rule.boosted_severity,
194
+ title=f"[CORRELATED] {finding.title}",
195
+ description=f"{finding.description}\n\nCorrelation: {rule.description} (Chain: {rule.chain_id})",
196
+ location=finding.location,
197
+ metadata={
198
+ **finding.metadata,
199
+ "chain": rule.chain_id,
200
+ "chain_name": rule.name,
201
+ "boosted_from": original_severity,
202
+ "boosted_to": rule.boosted_severity,
203
+ "correlation_rule": rule.name,
204
+ "original_finding": True,
205
+ },
206
+ )
207
+ matched_findings.append(boosted)
208
+
209
+ return matched_findings
210
+
211
+ def _find_cross_file_chains(self, all_findings: List[Finding]) -> List[Finding]:
212
+ """Find correlation chains across different files."""
213
+ # Group findings by file
214
+ findings_by_file = defaultdict(list)
215
+ for f in all_findings:
216
+ file_path = f.metadata.get("file", f.location.split(":")[0] if ":" in f.location else "unknown")
217
+ findings_by_file[file_path].append(f)
218
+
219
+ chain_findings = []
220
+
221
+ # Look for chains within same file
222
+ for file_path, findings in findings_by_file.items():
223
+ if len(findings) < 2:
224
+ continue
225
+
226
+ # Check for specific combinations in same file
227
+ has_sqli = any("sql_injection" in self._extract_pattern_key(f).lower() for f in findings)
228
+ has_auth_issue = any("auth" in self._extract_pattern_key(f).lower() for f in findings)
229
+ has_xss = any("xss" in self._extract_pattern_key(f).lower() for f in findings)
230
+ has_secret = any("secret" in self._extract_pattern_key(f).lower() or "credential" in self._extract_pattern_key(f).lower() for f in findings)
231
+ has_logging = any("log" in self._extract_pattern_key(f).lower() for f in findings)
232
+ has_pii = any("pii" in self._extract_pattern_key(f).lower() for f in findings)
233
+
234
+ if has_sqli and has_auth_issue:
235
+ chain_findings.append(Finding(
236
+ engine=self.name,
237
+ severity="critical",
238
+ title="SQLi + Auth Bypass in same file",
239
+ description="SQL injection vulnerability combined with authentication bypass in same file",
240
+ location=file_path,
241
+ metadata={
242
+ "chain": "CHAIN-011",
243
+ "chain_name": "sqli_auth_same_file",
244
+ "components": ["sql_injection", "auth_bypass"],
245
+ "file": file_path,
246
+ },
247
+ ))
248
+
249
+ if has_xss and has_secret:
250
+ chain_findings.append(Finding(
251
+ engine=self.name,
252
+ severity="high",
253
+ title="XSS + Secret Exposure in same file",
254
+ description="XSS vulnerability could leak hardcoded secrets",
255
+ location=file_path,
256
+ metadata={
257
+ "chain": "CHAIN-012",
258
+ "chain_name": "xss_secret_same_file",
259
+ "components": ["xss", "secret_exposure"],
260
+ "file": file_path,
261
+ },
262
+ ))
263
+
264
+ if has_pii and has_logging:
265
+ chain_findings.append(Finding(
266
+ engine=self.name,
267
+ severity="high",
268
+ title="PII + Logging in same file",
269
+ description="PII data being logged - potential data exposure",
270
+ location=file_path,
271
+ metadata={
272
+ "chain": "CHAIN-013",
273
+ "chain_name": "pii_logging_same_file",
274
+ "components": ["pii", "logging"],
275
+ "file": file_path,
276
+ },
277
+ ))
278
+
279
+ return chain_findings
280
+
281
+ async def run(self, ir: OrionIR, config: EngineConfig) -> EngineResult:
282
+ all_findings = self._get_all_findings(ir)
283
+
284
+ if not all_findings:
285
+ return EngineResult(engine=self.name, ok=True, metrics={"correlations_found": 0})
286
+
287
+ # Group findings by engine
288
+ findings_by_engine = defaultdict(list)
289
+ for f in all_findings:
290
+ findings_by_engine[f.engine].append(f)
291
+
292
+ # Apply correlation rules
293
+ correlated_findings = []
294
+ for rule in self.CORRELATION_RULES:
295
+ matched = self._match_rule(rule, findings_by_engine)
296
+ correlated_findings.extend(matched)
297
+
298
+ # Find cross-file chains
299
+ cross_file_chains = self._find_cross_file_chains(all_findings)
300
+ correlated_findings.extend(cross_file_chains)
301
+
302
+ # Deduplicate by chain_id and location
303
+ seen = set()
304
+ deduped = []
305
+ for f in correlated_findings:
306
+ key = (f.metadata.get("chain", ""), f.location)
307
+ if key not in seen:
308
+ seen.add(key)
309
+ deduped.append(f)
310
+
311
+ return EngineResult(
312
+ engine=self.name,
313
+ findings=deduped,
314
+ ok=True,
315
+ metrics={
316
+ "total_findings_analyzed": len(all_findings),
317
+ "engines_analyzed": len(findings_by_engine),
318
+ "correlations_found": len(deduped),
319
+ "rules_evaluated": len(self.CORRELATION_RULES),
320
+ },
321
+ )
322
+
323
+
324
+ Engine = HunterEngine
@@ -0,0 +1,218 @@
1
+ """Infrastructure as Code (IaC) & Cloud Security Engine.
2
+
3
+ 100% Custom In-House Developed Engine for Orion Engine Platform.
4
+ Scans Terraform, Kubernetes, Dockerfile, CloudFormation, and GitHub Actions CI/CD workflows.
5
+ """
6
+ import re
7
+ from pathlib import Path
8
+ from typing import List, Dict, Any
9
+
10
+ from orion.engine.config import EngineConfig, ResourceLimits
11
+ from orion.engine.ir import OrionIR
12
+ from orion.engine.results import EngineResult, Finding
13
+
14
+
15
+ IAC_RULES = [
16
+ # Dockerfile Rules
17
+ {
18
+ "id": "IAC-DOCKER-001",
19
+ "file_types": [".dockerfile", "dockerfile"],
20
+ "pattern": r"(?i)^\s*USER\s+root\b|(?i)^\s*FROM\s+.*(?!\n\s*USER)",
21
+ "title": "Container running as root user",
22
+ "description": "Container should specify a non-root USER to prevent privilege escalation attacks",
23
+ "severity": "high",
24
+ "cwe": "CWE-250",
25
+ "confidence": 0.85,
26
+ },
27
+ {
28
+ "id": "IAC-DOCKER-002",
29
+ "file_types": [".dockerfile", "dockerfile"],
30
+ "pattern": r"(?i)^\s*ADD\s+https?://",
31
+ "title": "Insecure ADD instruction with remote URL",
32
+ "description": "Use curl/wget with checksum verification or COPY instead of remote ADD",
33
+ "severity": "medium",
34
+ "cwe": "CWE-829",
35
+ "confidence": 0.9,
36
+ },
37
+ {
38
+ "id": "IAC-DOCKER-003",
39
+ "file_types": [".dockerfile", "dockerfile"],
40
+ "pattern": r"(?i)^\s*FROM\s+[^:\n]+(?::latest)?\s*$",
41
+ "title": "Unpinned base image tag",
42
+ "description": "Base image uses 'latest' or unpinned tag; pin image to specific SHA256 digest or version",
43
+ "severity": "low",
44
+ "cwe": "CWE-1104",
45
+ "confidence": 0.8,
46
+ },
47
+
48
+ # Terraform Rules
49
+ {
50
+ "id": "IAC-TF-001",
51
+ "file_types": [".tf", ".hcl"],
52
+ "pattern": r'cidr_blocks\s*=\s*\[\s*["\']0\.0\.0\.0/0["\']\s*\]',
53
+ "title": "Security group open to the world (0.0.0.0/0)",
54
+ "description": "Security group ingress allows unrestricted inbound traffic from all IP addresses",
55
+ "severity": "critical",
56
+ "cwe": "CWE-284",
57
+ "confidence": 0.95,
58
+ },
59
+ {
60
+ "id": "IAC-TF-002",
61
+ "file_types": [".tf", ".hcl"],
62
+ "pattern": r'acl\s*=\s*["\']public-read["\']|acl\s*=\s*["\']public-read-write["\']',
63
+ "title": "S3 bucket with public read/write ACL",
64
+ "description": "Cloud storage bucket configured with public access, risk of data exposure",
65
+ "severity": "critical",
66
+ "cwe": "CWE-732",
67
+ "confidence": 0.95,
68
+ },
69
+ {
70
+ "id": "IAC-TF-003",
71
+ "file_types": [".tf", ".hcl"],
72
+ "pattern": r'encrypted\s*=\s*false',
73
+ "title": "Unencrypted storage volume",
74
+ "description": "Cloud volume or database is configured without server-side encryption enabled",
75
+ "severity": "high",
76
+ "cwe": "CWE-311",
77
+ "confidence": 0.9,
78
+ },
79
+
80
+ # Kubernetes Rules
81
+ {
82
+ "id": "IAC-K8S-001",
83
+ "file_types": [".yaml", ".yml"],
84
+ "pattern": r'privileged:\s*true',
85
+ "title": "Kubernetes container running in privileged mode",
86
+ "description": "Privileged container allows root escalation and container breakout on the host node",
87
+ "severity": "critical",
88
+ "cwe": "CWE-250",
89
+ "confidence": 0.95,
90
+ },
91
+ {
92
+ "id": "IAC-K8S-002",
93
+ "file_types": [".yaml", ".yml"],
94
+ "pattern": r'hostNetwork:\s*true|hostPID:\s*true|hostIPC:\s*true',
95
+ "title": "Kubernetes pod sharing host namespace",
96
+ "description": "Sharing host network, PID, or IPC allows pod to snoop and interact with host system",
97
+ "severity": "high",
98
+ "cwe": "CWE-668",
99
+ "confidence": 0.9,
100
+ },
101
+ {
102
+ "id": "IAC-K8S-003",
103
+ "file_types": [".yaml", ".yml"],
104
+ "pattern": r'allowPrivilegeEscalation:\s*true',
105
+ "title": "Allow Privilege Escalation enabled in pod",
106
+ "description": "Container process can gain more privileges than its parent process",
107
+ "severity": "high",
108
+ "cwe": "CWE-250",
109
+ "confidence": 0.9,
110
+ },
111
+
112
+ # CI/CD Workflow Rules (GitHub Actions, GitLab CI)
113
+ {
114
+ "id": "IAC-CICD-001",
115
+ "file_types": [".yaml", ".yml"],
116
+ "pattern": r'pull_request_target:',
117
+ "title": "Dangerous pull_request_target trigger in CI workflow",
118
+ "description": "pull_request_target runs with write permissions and secrets access on untrusted PR fork code",
119
+ "severity": "critical",
120
+ "cwe": "CWE-829",
121
+ "confidence": 0.9,
122
+ },
123
+ {
124
+ "id": "IAC-CICD-002",
125
+ "file_types": [".yaml", ".yml"],
126
+ "pattern": r'uses:\s*[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+@(main|master|latest)\b',
127
+ "title": "Unpinned GitHub Actions step reference",
128
+ "description": "Third-party action pinned to branch instead of full commit SHA hash, supply chain risk",
129
+ "severity": "medium",
130
+ "cwe": "CWE-1104",
131
+ "confidence": 0.85,
132
+ },
133
+ ]
134
+
135
+
136
+ class IacSecurityEngine:
137
+ """100% custom, in-house developed Infrastructure-as-Code & Cloud configuration scanner."""
138
+
139
+ name = "iac_security"
140
+ version = "1.0.0"
141
+
142
+ def __init__(self):
143
+ self._compiled_rules = []
144
+ for r in IAC_RULES:
145
+ self._compiled_rules.append({
146
+ **r,
147
+ "regex": re.compile(r["pattern"], re.MULTILINE)
148
+ })
149
+
150
+ async def check_availability(self) -> bool:
151
+ return True
152
+
153
+ def resource_limits(self) -> ResourceLimits:
154
+ return ResourceLimits(max_cpu_seconds=30, max_memory_mb=256)
155
+
156
+ async def run(self, ir: OrionIR, config: EngineConfig) -> EngineResult:
157
+ path_str = ir.metadata.get("path", "").lower()
158
+ ext = Path(path_str).suffix.lower()
159
+ filename = Path(path_str).name.lower()
160
+
161
+ # Check if file is relevant for IaC
162
+ is_relevant = (
163
+ ext in (".tf", ".hcl", ".dockerfile", ".yaml", ".yml", ".json") or
164
+ filename in ("dockerfile", "docker-compose.yml", "docker-compose.yaml") or
165
+ ".github" in path_str or "k8s" in path_str or "terraform" in path_str
166
+ )
167
+
168
+ if not is_relevant:
169
+ return EngineResult(engine=self.name, findings=[], ok=True)
170
+
171
+ try:
172
+ content = ir.content.decode("utf-8", errors="replace")
173
+ except Exception:
174
+ return EngineResult(engine=self.name, findings=[], ok=True)
175
+
176
+ lines = content.splitlines()
177
+ findings = []
178
+
179
+ for rule in self._compiled_rules:
180
+ # Match file type
181
+ matches_file = any(
182
+ ft == ext or ft == filename or (ft.startswith(".") and path_str.endswith(ft))
183
+ for ft in rule["file_types"]
184
+ )
185
+ if not matches_file:
186
+ continue
187
+
188
+ for match in rule["regex"].finditer(content):
189
+ line_no = content[:match.start()].count('\n') + 1
190
+ start_line = max(0, line_no - 2)
191
+ end_line = min(len(lines), line_no + 2)
192
+ snippet = '\n'.join(lines[start_line:end_line])
193
+
194
+ findings.append(Finding(
195
+ engine=self.name,
196
+ severity=rule["severity"],
197
+ title=f"{rule['id']}: {rule['title']}",
198
+ description=rule["description"],
199
+ location=f"{ir.metadata.get('path', 'unknown')}:{line_no}",
200
+ metadata={
201
+ "rule_id": rule["id"],
202
+ "cwe": rule["cwe"],
203
+ "confidence": rule["confidence"],
204
+ "snippet": snippet,
205
+ "file": ir.metadata.get("path", "unknown"),
206
+ "line": line_no,
207
+ }
208
+ ))
209
+
210
+ return EngineResult(
211
+ engine=self.name,
212
+ findings=findings,
213
+ ok=True,
214
+ metrics={"findings_count": len(findings)}
215
+ )
216
+
217
+
218
+ Engine = IacSecurityEngine
@@ -0,0 +1,22 @@
1
+ import hashlib
2
+ from dataclasses import dataclass, field
3
+ from typing import Any, Dict
4
+
5
+
6
+ @dataclass
7
+ class OrionIR:
8
+ content: bytes
9
+ metadata: Dict[str, Any] = field(default_factory=dict)
10
+ _hash: str = field(default="", repr=False)
11
+
12
+ def __post_init__(self):
13
+ if not self._hash:
14
+ self._hash = hashlib.sha256(self.content).hexdigest()[:16]
15
+
16
+ @property
17
+ def address(self) -> str:
18
+ return self._hash
19
+
20
+ def with_meta(self, **kwargs) -> "OrionIR":
21
+ meta = {**self.metadata, **kwargs}
22
+ return OrionIR(content=self.content, metadata=meta, _hash=self._hash)