@orionscanner/cli 0.1.0 → 0.5.1

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 (129) hide show
  1. package/README.md +80 -190
  2. package/bin/orion.js +30 -29
  3. package/dist/orion.exe +0 -0
  4. package/package.json +46 -48
  5. package/docs/API_REFERENCE.md +0 -211
  6. package/docs/ARCHITECTURE.md +0 -183
  7. package/docs/CHANGELOG.md +0 -112
  8. package/docs/CONTRIBUTING.md +0 -157
  9. package/docs/ENGINE_CATALOG.md +0 -87
  10. package/docs/GETTING_STARTED.md +0 -226
  11. package/pyproject.toml +0 -16
  12. package/src/orion/__init__.py +0 -0
  13. package/src/orion/__pycache__/__init__.cpython-313.pyc +0 -0
  14. package/src/orion/engine/__init__.py +0 -17
  15. package/src/orion/engine/__pycache__/__init__.cpython-313.pyc +0 -0
  16. package/src/orion/engine/__pycache__/config.cpython-313.pyc +0 -0
  17. package/src/orion/engine/__pycache__/core.cpython-313.pyc +0 -0
  18. package/src/orion/engine/__pycache__/ir.cpython-313.pyc +0 -0
  19. package/src/orion/engine/__pycache__/languages.cpython-313.pyc +0 -0
  20. package/src/orion/engine/__pycache__/registry.cpython-313.pyc +0 -0
  21. package/src/orion/engine/__pycache__/results.cpython-313.pyc +0 -0
  22. package/src/orion/engine/__pycache__/runner.cpython-313.pyc +0 -0
  23. package/src/orion/engine/api_security/__pycache__/engine.cpython-313.pyc +0 -0
  24. package/src/orion/engine/api_security/engine.py +0 -125
  25. package/src/orion/engine/architecture/__pycache__/engine.cpython-313.pyc +0 -0
  26. package/src/orion/engine/architecture/engine.py +0 -463
  27. package/src/orion/engine/authorization/__pycache__/engine.cpython-313.pyc +0 -0
  28. package/src/orion/engine/authorization/engine.py +0 -337
  29. package/src/orion/engine/callgraph/__pycache__/engine.cpython-313.pyc +0 -0
  30. package/src/orion/engine/callgraph/engine.py +0 -236
  31. package/src/orion/engine/cfg/__pycache__/engine.cpython-313.pyc +0 -0
  32. package/src/orion/engine/cfg/engine.py +0 -509
  33. package/src/orion/engine/chaos/__pycache__/engine.cpython-313.pyc +0 -0
  34. package/src/orion/engine/chaos/engine.py +0 -268
  35. package/src/orion/engine/chaos_config/__pycache__/engine.cpython-313.pyc +0 -0
  36. package/src/orion/engine/chaos_config/engine.py +0 -322
  37. package/src/orion/engine/complexity/__pycache__/engine.cpython-313.pyc +0 -0
  38. package/src/orion/engine/complexity/engine.py +0 -144
  39. package/src/orion/engine/compliance/__pycache__/engine.cpython-313.pyc +0 -0
  40. package/src/orion/engine/compliance/engine.py +0 -325
  41. package/src/orion/engine/config.py +0 -41
  42. package/src/orion/engine/config_engine/__pycache__/engine.cpython-313.pyc +0 -0
  43. package/src/orion/engine/config_engine/engine.py +0 -364
  44. package/src/orion/engine/config_validation/__pycache__/engine.cpython-313.pyc +0 -0
  45. package/src/orion/engine/config_validation/engine.py +0 -313
  46. package/src/orion/engine/container/__pycache__/engine.cpython-313.pyc +0 -0
  47. package/src/orion/engine/container/engine.py +0 -546
  48. package/src/orion/engine/contract_probe/__pycache__/engine.cpython-313.pyc +0 -0
  49. package/src/orion/engine/contract_probe/engine.py +0 -316
  50. package/src/orion/engine/core.py +0 -75
  51. package/src/orion/engine/crypto_guard/__pycache__/engine.cpython-313.pyc +0 -0
  52. package/src/orion/engine/crypto_guard/engine.py +0 -134
  53. package/src/orion/engine/data_residency/__pycache__/engine.cpython-313.pyc +0 -0
  54. package/src/orion/engine/data_residency/engine.py +0 -238
  55. package/src/orion/engine/dataflow/__pycache__/engine.cpython-313.pyc +0 -0
  56. package/src/orion/engine/dataflow/engine.py +0 -398
  57. package/src/orion/engine/dead_import/__pycache__/engine.cpython-313.pyc +0 -0
  58. package/src/orion/engine/dead_import/engine.py +0 -293
  59. package/src/orion/engine/deps/__pycache__/engine.cpython-313.pyc +0 -0
  60. package/src/orion/engine/deps/engine.py +0 -244
  61. package/src/orion/engine/dispatcher.py +0 -350
  62. package/src/orion/engine/docs/AUDIT_PROMPT.md +0 -1248
  63. package/src/orion/engine/duplication/__pycache__/engine.cpython-313.pyc +0 -0
  64. package/src/orion/engine/duplication/engine.py +0 -120
  65. package/src/orion/engine/error_handling/__pycache__/engine.cpython-313.pyc +0 -0
  66. package/src/orion/engine/error_handling/engine.py +0 -256
  67. package/src/orion/engine/fp_learn/__pycache__/engine.cpython-313.pyc +0 -0
  68. package/src/orion/engine/fp_learn/engine.py +0 -248
  69. package/src/orion/engine/hunter/__pycache__/engine.cpython-313.pyc +0 -0
  70. package/src/orion/engine/hunter/engine.py +0 -324
  71. package/src/orion/engine/iac_security/__pycache__/engine.cpython-313.pyc +0 -0
  72. package/src/orion/engine/iac_security/engine.py +0 -218
  73. package/src/orion/engine/ir.py +0 -22
  74. package/src/orion/engine/languages.py +0 -454
  75. package/src/orion/engine/license/__pycache__/engine.cpython-313.pyc +0 -0
  76. package/src/orion/engine/license/engine.py +0 -172
  77. package/src/orion/engine/lint/__pycache__/engine.cpython-313.pyc +0 -0
  78. package/src/orion/engine/lint/engine.py +0 -558
  79. package/src/orion/engine/llm_security/__pycache__/engine.cpython-313.pyc +0 -0
  80. package/src/orion/engine/llm_security/engine.py +0 -125
  81. package/src/orion/engine/logging_c/__pycache__/engine.cpython-313.pyc +0 -0
  82. package/src/orion/engine/logging_c/engine.py +0 -164
  83. package/src/orion/engine/noise/__pycache__/engine.cpython-313.pyc +0 -0
  84. package/src/orion/engine/noise/engine.py +0 -140
  85. package/src/orion/engine/orphan/__pycache__/engine.cpython-313.pyc +0 -0
  86. package/src/orion/engine/orphan/engine.py +0 -244
  87. package/src/orion/engine/perf_guard/__pycache__/engine.cpython-313.pyc +0 -0
  88. package/src/orion/engine/perf_guard/engine.py +0 -345
  89. package/src/orion/engine/prompt_security/__pycache__/engine.cpython-313.pyc +0 -0
  90. package/src/orion/engine/prompt_security/detectors/__init__.py +0 -4
  91. package/src/orion/engine/prompt_security/engine.py +0 -170
  92. package/src/orion/engine/provenance/__pycache__/engine.cpython-313.pyc +0 -0
  93. package/src/orion/engine/provenance/engine.py +0 -242
  94. package/src/orion/engine/registry.py +0 -55
  95. package/src/orion/engine/regression/__pycache__/engine.cpython-313.pyc +0 -0
  96. package/src/orion/engine/regression/engine.py +0 -213
  97. package/src/orion/engine/results.py +0 -21
  98. package/src/orion/engine/runner.py +0 -1310
  99. package/src/orion/engine/sandbox_escape/__pycache__/engine.cpython-313.pyc +0 -0
  100. package/src/orion/engine/sandbox_escape/engine.py +0 -284
  101. package/src/orion/engine/sast/__pycache__/engine.cpython-313.pyc +0 -0
  102. package/src/orion/engine/sast/engine.py +0 -217
  103. package/src/orion/engine/sast/patterns/__init__.py +0 -513
  104. package/src/orion/engine/sast/patterns/__pycache__/__init__.cpython-313.pyc +0 -0
  105. package/src/orion/engine/secrets/__pycache__/engine.cpython-313.pyc +0 -0
  106. package/src/orion/engine/secrets/engine.py +0 -77
  107. package/src/orion/engine/secrets/patterns/__init__.py +0 -74
  108. package/src/orion/engine/secrets/patterns/__pycache__/__init__.cpython-313.pyc +0 -0
  109. package/src/orion/engine/sensitive_data/__pycache__/engine.cpython-313.pyc +0 -0
  110. package/src/orion/engine/sensitive_data/engine.py +0 -143
  111. package/src/orion/engine/state/__pycache__/engine.cpython-313.pyc +0 -0
  112. package/src/orion/engine/state/engine.py +0 -218
  113. package/src/orion/engine/supply_chain_sbom/__pycache__/engine.cpython-313.pyc +0 -0
  114. package/src/orion/engine/supply_chain_sbom/engine.py +0 -290
  115. package/src/orion/engine/symbol/__pycache__/engine.cpython-313.pyc +0 -0
  116. package/src/orion/engine/symbol/engine.py +0 -463
  117. package/src/orion/engine/taint/__pycache__/engine.cpython-313.pyc +0 -0
  118. package/src/orion/engine/taint/engine.py +0 -464
  119. package/src/orion/engine/test_coverage/__pycache__/engine.cpython-313.pyc +0 -0
  120. package/src/orion/engine/test_coverage/engine.py +0 -445
  121. package/src/orion/engine/threat_model/__pycache__/engine.cpython-313.pyc +0 -0
  122. package/src/orion/engine/threat_model/engine.py +0 -126
  123. package/src/orion/engine/wiring_trace/__pycache__/engine.cpython-313.pyc +0 -0
  124. package/src/orion/engine/wiring_trace/engine.py +0 -200
  125. package/src/orion_engine.egg-info/PKG-INFO +0 -7
  126. package/src/orion_engine.egg-info/SOURCES.txt +0 -65
  127. package/src/orion_engine.egg-info/dependency_links.txt +0 -1
  128. package/src/orion_engine.egg-info/entry_points.txt +0 -2
  129. package/src/orion_engine.egg-info/top_level.txt +0 -1
@@ -1,284 +0,0 @@
1
- """Sandbox escape pattern detection engine."""
2
- import ast
3
- import re
4
- from typing import Dict, List, Set, Optional, Any, Tuple
5
- from dataclasses import dataclass, field
6
- from orion.engine.config import EngineConfig, ResourceLimits
7
- from orion.engine.ir import OrionIR
8
- from orion.engine.results import EngineResult, Finding
9
-
10
-
11
- ESCAPE_PATTERNS = {
12
- "import_os": {
13
- "patterns": [
14
- r"__import__\s*\(\s*['\"]os['\"]",
15
- r"__import__\s*\(\s*['\"]subprocess['\"]",
16
- r"__import__\s*\(\s*['\"]sys['\"]",
17
- r"importlib\.import_module\s*\(\s*['\"]os['\"]",
18
- r"importlib\.import_module\s*\(\s*['\"]subprocess['\"]",
19
- r"importlib\.import_module\s*\(\s*['\"]sys['\"]",
20
- r"runpy\.run_module\s*\(\s*['\"]os['\"]",
21
- r"runpy\.run_module\s*\(\s*['\"]subprocess['\"]",
22
- ],
23
- "cwe": "CWE-829",
24
- "severity": "critical",
25
- "description": "Dynamic import of dangerous module (os, subprocess, sys)",
26
- },
27
- "builtin_access": {
28
- "patterns": [
29
- r"getattr\s*\(\s*__builtins__\s*,\s*['\"]eval['\"]",
30
- r"getattr\s*\(\s*__builtins__\s*,\s*['\"]exec['\"]",
31
- r"getattr\s*\(\s*__builtins__\s*,\s*['\"]compile['\"]",
32
- r"__builtins__\s*\[\s*['\"]eval['\"]",
33
- r"__builtins__\s*\[\s*['\"]exec['\"]",
34
- r"__builtins__\s*\[\s*['\"]compile['\"]",
35
- r"vars\s*\(\s*\)\s*\[\s*['\"]eval['\"]",
36
- r"vars\s*\(\s*\)\s*\[\s*['\"]exec['\"]",
37
- r"globals\s*\(\s*\)\s*\[\s*['\"]eval['\"]",
38
- r"globals\s*\(\s*\)\s*\[\s*['\"]exec['\"]",
39
- ],
40
- "cwe": "CWE-94",
41
- "severity": "critical",
42
- "description": "Access to builtin eval/exec/compile via getattr or dict access",
43
- },
44
- "eval_compile": {
45
- "patterns": [
46
- r"eval\s*\(\s*compile\s*\(",
47
- r"exec\s*\(\s*compile\s*\(",
48
- r"compile\s*\([^)]+,\s*['\"]eval['\"]",
49
- r"compile\s*\([^)]+,\s*['\"]exec['\"]",
50
- r"compile\s*\([^)]+,\s*['\"]single['\"]",
51
- ],
52
- "cwe": "CWE-94",
53
- "severity": "critical",
54
- "description": "Code execution via eval/exec with compile",
55
- },
56
- "code_type": {
57
- "patterns": [
58
- r"types\.CodeType\s*\(",
59
- r"code\.replace\s*\(",
60
- r"func_code\s*=",
61
- r"__code__\s*=",
62
- r"f\.f_code\s*=",
63
- r"frame\.f_code\s*=",
64
- ],
65
- "cwe": "CWE-94",
66
- "severity": "high",
67
- "description": "Direct code object manipulation",
68
- },
69
- "ctypes": {
70
- "patterns": [
71
- r"ctypes\.CDLL\s*\(",
72
- r"ctypes\.windll\s*\.",
73
- r"ctypes\.WinDLL\s*\(",
74
- r"ctypes\.cdll\s*\.",
75
- r"ctypes\.pythonapi",
76
- r"ctypes\.PyDLL\s*\(",
77
- r"ctypes\.CFUNCTYPE\s*\(",
78
- r"ctypes\.cast\s*\(",
79
- r"ctypes\.addressof\s*\(",
80
- r"ctypes\.string_at\s*\(",
81
- ],
82
- "cwe": "CWE-111",
83
- "severity": "critical",
84
- "description": "ctypes usage for direct memory/library access",
85
- },
86
- "introspection": {
87
- "patterns": [
88
- r"sys\._getframe\s*\(",
89
- r"inspect\.getouterframes\s*\(",
90
- r"inspect\.currentframe\s*\(",
91
- r"inspect\.stack\s*\(",
92
- r"inspect\.trace\s*\(",
93
- r"sys\.modules\s*\[\s*['\"]os['\"]",
94
- r"sys\.modules\s*\[\s*['\"]subprocess['\"]",
95
- ],
96
- "cwe": "CWE-470",
97
- "severity": "high",
98
- "description": "Frame/introspection access for escaping sandboxes",
99
- },
100
- "class_mro": {
101
- "patterns": [
102
- r"__class__\.__bases__",
103
- r"__class__\.__mro__",
104
- r"__mro__\s*\[",
105
- r"__subclasses__\s*\(",
106
- r"\.__subclasses__\s*\(",
107
- r"type\(\)\.__subclasses__",
108
- r"object\.__subclasses__\s*\(",
109
- ],
110
- "cwe": "CWE-470",
111
- "severity": "critical",
112
- "description": "Class hierarchy traversal for sandbox escape (Python 2/3)",
113
- },
114
- "pickle": {
115
- "patterns": [
116
- r"pickle\.loads\s*\(",
117
- r"pickle\.load\s*\(",
118
- r"_pickle\.loads\s*\(",
119
- r"marshal\.loads\s*\(",
120
- r"shelve\.open\s*\(",
121
- r"dill\.loads\s*\(",
122
- ],
123
- "cwe": "CWE-502",
124
- "severity": "high",
125
- "description": "Deserialization of untrusted data",
126
- },
127
- "subprocess": {
128
- "patterns": [
129
- r"subprocess\.call\s*\(",
130
- r"subprocess\.run\s*\(",
131
- r"subprocess\.Popen\s*\(",
132
- r"subprocess\.check_output\s*\(",
133
- r"subprocess\.check_call\s*\(",
134
- r"os\.system\s*\(",
135
- r"os\.popen\s*\(",
136
- r"commands\.getstatusoutput\s*\(",
137
- r"commands\.getoutput\s*\(",
138
- ],
139
- "cwe": "CWE-78",
140
- "severity": "critical",
141
- "description": "Command execution via subprocess/os",
142
- },
143
- "template_injection": {
144
- "patterns": [
145
- r"render_template_string\s*\(",
146
- r"Template\s*\([^)]*request",
147
- r"Environment\s*\(\s*loader\s*=",
148
- r"jinja2\.Environment\s*\(",
149
- r"markupsafe\.Markup\s*\(",
150
- r"Markup\s*\(",
151
- ],
152
- "cwe": "CWE-79",
153
- "severity": "high",
154
- "description": "Server-side template injection (SSTI)",
155
- },
156
- "breakpoint": {
157
- "patterns": [
158
- r"breakpoint\s*\(",
159
- r"pdb\.set_trace\s*\(",
160
- r"ipdb\.set_trace\s*\(",
161
- r"debugpy\.breakpoint\s*\(",
162
- ],
163
- "cwe": "CWE-489",
164
- "severity": "medium",
165
- "description": "Debug breakpoint in production code",
166
- },
167
- }
168
-
169
-
170
- class SandboxVisitor(ast.NodeVisitor):
171
- def __init__(self, filename: str, source_lines: List[str]):
172
- self.filename = filename
173
- self.source_lines = source_lines
174
- self.findings: List[Finding] = []
175
- self.seen_findings: Set[Tuple[str, int]] = set()
176
- self.compiled_patterns = {
177
- name: [re.compile(p, re.IGNORECASE) for p in data["patterns"]]
178
- for name, data in ESCAPE_PATTERNS.items()
179
- }
180
-
181
- def get_snippet(self, line_no: int, context: int = 2) -> str:
182
- start = max(0, line_no - 1 - context)
183
- end = min(len(self.source_lines), line_no + context)
184
- return "\n".join(self.source_lines[start:end])
185
-
186
- def check_node(self, node: ast.AST):
187
- line_no = getattr(node, 'lineno', 0)
188
- if not line_no or line_no > len(self.source_lines):
189
- return
190
-
191
- line_text = self.source_lines[line_no - 1]
192
- for category, patterns in self.compiled_patterns.items():
193
- if (category, line_no) in self.seen_findings:
194
- continue
195
- for pattern in patterns:
196
- if pattern.search(line_text):
197
- self.seen_findings.add((category, line_no))
198
- data = ESCAPE_PATTERNS[category]
199
- confidence = 0.9 if category in ("class_mro", "ctypes", "subprocess") else 0.85
200
-
201
- self.findings.append(Finding(
202
- engine="sandbox_escape",
203
- severity=data["severity"],
204
- title=f"Sandbox escape: {data['description']}",
205
- description=f"Detected {category}: {data['description']}",
206
- location=f"{self.filename}:{line_no}",
207
- metadata={
208
- "snippet": self.get_snippet(line_no),
209
- "cwe": data["cwe"],
210
- "confidence": confidence,
211
- "category": category,
212
- "pattern": pattern.pattern,
213
- }
214
- ))
215
- break
216
-
217
- def visit_Call(self, node: ast.Call):
218
- self.check_node(node)
219
- self.generic_visit(node)
220
-
221
- def visit_Attribute(self, node: ast.Attribute):
222
- self.check_node(node)
223
- self.generic_visit(node)
224
-
225
- def visit_Import(self, node: ast.Import):
226
- self.check_node(node)
227
- self.generic_visit(node)
228
-
229
- def visit_ImportFrom(self, node: ast.ImportFrom):
230
- self.check_node(node)
231
- self.generic_visit(node)
232
-
233
-
234
- class Engine:
235
- name = "sandbox_escape"
236
- version = "1.0.0"
237
-
238
- async def check_availability(self) -> bool:
239
- return True
240
-
241
- def resource_limits(self) -> ResourceLimits:
242
- return ResourceLimits(max_cpu_seconds=60, max_memory_mb=512, max_files=50000)
243
-
244
- async def run(self, ctx, config: EngineConfig) -> EngineResult:
245
- # Handle both single IR and list of IRs
246
- if not isinstance(ctx, list):
247
- ctx = [ctx]
248
- all_findings = []
249
- errors = []
250
- files_processed = 0
251
-
252
- exclude_patterns = config.toggles.get("exclude", []) if config.toggles else []
253
-
254
- for ir in ctx:
255
- try:
256
- if not ir.metadata.get("language") == "python":
257
- continue
258
-
259
- rel_path = ir.metadata.get("path", "unknown.py")
260
- if any(pattern in rel_path for pattern in exclude_patterns):
261
- continue
262
-
263
- content = ir.content.decode("utf-8", errors="replace")
264
- source_lines = content.splitlines()
265
- tree = ast.parse(content, filename=rel_path)
266
-
267
- visitor = SandboxVisitor(rel_path, source_lines)
268
- visitor.visit(tree)
269
-
270
- all_findings.extend(visitor.findings)
271
- files_processed += 1
272
-
273
- except SyntaxError as e:
274
- errors.append(f"Syntax error in {ir.metadata.get('path', 'unknown')}: {e}")
275
- except Exception as e:
276
- errors.append(f"Error processing {ir.metadata.get('path', 'unknown')}: {e}")
277
-
278
- return EngineResult(
279
- engine=self.name,
280
- findings=all_findings,
281
- errors=errors,
282
- metrics={"files_processed": files_processed, "findings_count": len(all_findings)},
283
- ok=len(errors) == 0
284
- )
@@ -1,217 +0,0 @@
1
- """Full SAST engine - primary security engine with comprehensive pattern detection."""
2
- import re
3
- import ast
4
- from pathlib import Path
5
- from typing import List, Optional
6
- from dataclasses import dataclass
7
-
8
- from orion.engine.config import EngineConfig
9
- from orion.engine.ir import OrionIR
10
- from orion.engine.results import EngineResult, Finding
11
- from orion.engine.sast.patterns import (
12
- PATTERNS,
13
- get_patterns_for_language,
14
- run_ast_patterns,
15
- Severity,
16
- Pattern,
17
- )
18
-
19
-
20
- @dataclass
21
- class SASTFinding:
22
- pattern: Pattern
23
- line: int
24
- snippet: str
25
- match: str
26
- filepath: str
27
-
28
-
29
- class SASTEngine:
30
- name = "sast"
31
- version = "2.0.0"
32
-
33
- EXCLUDED_DIRS = {
34
- "__pycache__", ".git", ".venv", "venv", "env", "node_modules",
35
- "dist", "build", ".pytest_cache", ".mypy_cache", ".tox",
36
- "migrations", "tests", "test", "scripts", "examples", "docs",
37
- }
38
-
39
- EXCLUDED_FILES = {
40
- "conftest.py", "manage.py", "wsgi.py", "asgi.py", "celery.py",
41
- "gunicorn.conf.py", "pytest.ini", "setup.py", "setup.cfg",
42
- }
43
-
44
- async def check_availability(self) -> bool:
45
- return True
46
-
47
- def resource_limits(self):
48
- from orion.engine.config import ResourceLimits
49
- return ResourceLimits(max_cpu_seconds=120, max_memory_mb=1024)
50
-
51
- def _should_exclude(self, filepath: str, config: EngineConfig) -> bool:
52
- path = Path(filepath)
53
- # Check excluded directories
54
- for part in path.parts:
55
- if part in self.EXCLUDED_DIRS:
56
- return True
57
- # Check excluded files
58
- if path.name in self.EXCLUDED_FILES:
59
- return True
60
- # Check config exclude patterns
61
- if config.toggles.get("exclude"):
62
- for pattern in config.toggles["exclude"]:
63
- if path.match(pattern) or any(p.match(pattern) for p in path.parents):
64
- return True
65
- return False
66
-
67
- def _detect_language(self, filepath: str, content: str) -> str:
68
- ext = Path(filepath).suffix.lower()
69
- lang_map = {
70
- ".py": "python",
71
- ".js": "javascript",
72
- ".ts": "typescript",
73
- ".jsx": "javascript",
74
- ".tsx": "typescript",
75
- ".java": "java",
76
- ".go": "go",
77
- ".rs": "rust",
78
- ".php": "php",
79
- ".rb": "ruby",
80
- ".cs": "csharp",
81
- ".cpp": "cpp",
82
- ".cc": "cpp",
83
- ".c": "c",
84
- ".h": "c",
85
- ".hpp": "cpp",
86
- ".sh": "bash",
87
- ".yaml": "yaml",
88
- ".yml": "yaml",
89
- ".json": "json",
90
- ".toml": "toml",
91
- ".xml": "xml",
92
- ".html": "html",
93
- ".css": "css",
94
- ".sql": "sql",
95
- }
96
- return lang_map.get(ext, "unknown")
97
-
98
- def _find_matches(self, content: str, patterns: List[Pattern], filepath: str) -> List[SASTFinding]:
99
- findings = []
100
- lines = content.splitlines()
101
- for pattern in patterns:
102
- for match in pattern.pattern.finditer(content):
103
- line_num = content[:match.start()].count('\n') + 1
104
- start = max(0, line_num - 3)
105
- end = min(len(lines), line_num + 2)
106
- snippet = '\n'.join(lines[start:end])
107
- findings.append(SASTFinding(
108
- pattern=pattern,
109
- line=line_num,
110
- snippet=snippet,
111
- match=match.group()[:100],
112
- filepath=filepath,
113
- ))
114
- return findings
115
-
116
- def _run_ast_analysis(self, content: str, filepath: str, language: str) -> List[SASTFinding]:
117
- if language != "python":
118
- return []
119
- ast_findings = run_ast_patterns(content, filepath)
120
- results = []
121
- for af in ast_findings:
122
- # Convert to pattern-like object
123
- class ASTPattern:
124
- def __init__(self, finding):
125
- self.name = finding['pattern']
126
- self.severity = finding['severity']
127
- self.confidence = finding['confidence']
128
- self.cwe = finding['cwe']
129
- self.cvss = finding['cvss']
130
- self.description = finding['description']
131
- pattern_obj = ASTPattern(af)
132
- results.append(SASTFinding(
133
- pattern=pattern_obj,
134
- line=af['line'],
135
- snippet=af['snippet'],
136
- match=af['pattern'],
137
- filepath=filepath,
138
- ))
139
- return results
140
-
141
- def _aggregate_findings(self, findings: List[SASTFinding]) -> List[Finding]:
142
- """Aggregate findings, deduplicate similar ones, and create EngineResult findings."""
143
- aggregated = []
144
- seen = set()
145
-
146
- for f in findings:
147
- key = (f.filepath, f.line, f.pattern.name)
148
- if key in seen:
149
- continue
150
- seen.add(key)
151
-
152
- if f.pattern.confidence < 0.75:
153
- continue
154
-
155
- severity = f.pattern.severity.value if hasattr(f.pattern.severity, 'value') else str(f.pattern.severity)
156
-
157
- aggregated.append(Finding(
158
- engine=self.name,
159
- severity=severity,
160
- title=f"{f.pattern.name}: {f.pattern.description}",
161
- description=f"{f.pattern.description} (CWE: {f.pattern.cwe}, CVSS: {f.pattern.cvss})",
162
- location=f"{f.filepath}:{f.line}",
163
- metadata={
164
- "pattern": f.pattern.name,
165
- "cwe": f.pattern.cwe,
166
- "cvss": f.pattern.cvss,
167
- "confidence": f.pattern.confidence,
168
- "match": f.match,
169
- "snippet": f.snippet,
170
- "file": f.filepath,
171
- "line": f.line,
172
- },
173
- ))
174
- return aggregated
175
-
176
- async def run(self, ir: OrionIR, config: EngineConfig) -> EngineResult:
177
- try:
178
- content = ir.content.decode("utf-8", errors="replace")
179
- except Exception:
180
- content = str(ir.content)
181
-
182
- filepath = ir.metadata.get("path", "unknown")
183
- project_root = ir.metadata.get("project_root", ".")
184
- language = ir.metadata.get("language") or self._detect_language(filepath, content)
185
-
186
- if self._should_exclude(filepath, config):
187
- return EngineResult(engine=self.name, ok=True, metrics={"skipped": True})
188
-
189
- all_findings = []
190
-
191
- # Run regex patterns
192
- patterns = get_patterns_for_language(language)
193
- regex_findings = self._find_matches(content, patterns, filepath)
194
- all_findings.extend(regex_findings)
195
-
196
- # Run AST patterns for Python
197
- ast_findings = self._run_ast_analysis(content, filepath, language)
198
- all_findings.extend(ast_findings)
199
-
200
- # Aggregate and filter
201
- findings = self._aggregate_findings(all_findings)
202
-
203
- return EngineResult(
204
- engine=self.name,
205
- findings=findings,
206
- ok=True,
207
- metrics={
208
- "files_scanned": 1,
209
- "patterns_run": len(patterns),
210
- "findings_count": len(findings),
211
- "language": language,
212
- },
213
- )
214
-
215
-
216
- # Backward compatibility
217
- Engine = SASTEngine