@orionscanner/cli 0.1.0 → 0.1.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 +135 -248
  2. package/bin/orion.js +30 -29
  3. package/dist/orion.exe +0 -0
  4. package/package.json +8 -10
  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,463 +0,0 @@
1
- """Symbol table analyzer for detecting shadowed builtins, undefined names, unused imports, and late binding closures."""
2
- import ast
3
- from typing import Dict, List, Set, Optional, Any, Tuple
4
- from dataclasses import dataclass, field
5
- from collections import defaultdict
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
- import builtins
12
-
13
- BUILTINS = set(dir(builtins)) | {
14
- "__file__", "__name__", "__doc__", "__package__", "__loader__",
15
- "__spec__", "__annotations__", "__builtins__", "__all__", "__path__",
16
- "__class__", "self", "cls", "True", "False", "None", "Ellipsis", "NotImplemented"
17
- }
18
-
19
-
20
- @dataclass
21
- class Scope:
22
- name: str
23
- parent: Optional['Scope'] = None
24
- variables: Dict[str, 'Symbol'] = field(default_factory=dict)
25
- imports: Dict[str, str] = field(default_factory=dict)
26
- used_names: Set[str] = field(default_factory=set)
27
- defined_names: Set[str] = field(default_factory=set)
28
- is_function: bool = False
29
- is_class: bool = False
30
- is_module: bool = False
31
-
32
-
33
- @dataclass
34
- class Symbol:
35
- name: str
36
- scope: Scope
37
- node: ast.AST
38
- is_import: bool = False
39
- is_parameter: bool = False
40
- is_function: bool = False
41
- is_class: bool = False
42
- is_global: bool = False
43
- is_nonlocal: bool = False
44
- is_used: bool = False
45
- is_assigned: bool = False
46
- line: int = 0
47
-
48
-
49
- class SymbolVisitor(ast.NodeVisitor):
50
- def __init__(self, filename: str, source_lines: List[str]):
51
- self.filename = filename
52
- self.source_lines = source_lines
53
- self.findings: List[Finding] = []
54
- self.current_scope: Optional[Scope] = None
55
- self.module_scope: Optional[Scope] = None
56
- self.symbols: Dict[int, Symbol] = {}
57
- self.closure_vars: Dict[str, List[Tuple[str, int]]] = defaultdict(list)
58
- self.late_binding_vars: List[Tuple[str, int]] = []
59
-
60
- def get_snippet(self, node: ast.AST, context: int = 2) -> str:
61
- start = max(0, node.lineno - 1 - context)
62
- end = min(len(self.source_lines),
63
- getattr(node, 'end_lineno', node.lineno) + context if hasattr(node, 'end_lineno') else node.lineno + context)
64
- return "\n".join(self.source_lines[start:end])
65
-
66
- def enter_scope(self, name: str, is_function: bool = False, is_class: bool = False, is_module: bool = False):
67
- new_scope = Scope(
68
- name=name,
69
- parent=self.current_scope,
70
- is_function=is_function,
71
- is_class=is_class,
72
- is_module=is_module
73
- )
74
- if self.current_scope is None:
75
- self.module_scope = new_scope
76
- self.current_scope = new_scope
77
-
78
- def exit_scope(self):
79
- if self.current_scope and self.current_scope.parent:
80
- self.current_scope = self.current_scope.parent
81
- else:
82
- self.current_scope = None
83
-
84
- def define(self, name: str, node: ast.AST, is_import: bool = False, is_parameter: bool = False,
85
- is_function: bool = False, is_class: bool = False, is_global: bool = False,
86
- is_nonlocal: bool = False):
87
- if not self.current_scope:
88
- return
89
-
90
- symbol = Symbol(
91
- name=name,
92
- scope=self.current_scope,
93
- node=node,
94
- is_import=is_import,
95
- is_parameter=is_parameter,
96
- is_function=is_function,
97
- is_class=is_class,
98
- is_global=is_global,
99
- is_nonlocal=is_nonlocal,
100
- line=node.lineno
101
- )
102
- self.current_scope.variables[name] = symbol
103
- self.current_scope.defined_names.add(name)
104
- self.symbols[id(node)] = symbol
105
-
106
- def use(self, name: str, node: ast.AST):
107
- if not self.current_scope:
108
- return
109
-
110
- scope = self.current_scope
111
- while scope:
112
- if name in scope.variables:
113
- scope.variables[name].is_used = True
114
- scope.used_names.add(name)
115
- return
116
- scope = scope.parent
117
-
118
- if name not in BUILTINS and not self._is_future_import(name):
119
- self.findings.append(Finding(
120
- engine="symbol",
121
- severity="medium",
122
- title="Undefined name",
123
- description=f"Name '{name}' is used but not defined",
124
- location=f"{self.filename}:{node.lineno}",
125
- metadata={
126
- "snippet": self.get_snippet(node),
127
- "cwe": "CWE-476",
128
- "confidence": 0.85,
129
- "check": "undefined_name",
130
- "name": name,
131
- }
132
- ))
133
-
134
- def _is_future_import(self, name: str) -> bool:
135
- if self.module_scope:
136
- return name in self.module_scope.imports
137
- return False
138
-
139
- def visit_Module(self, node: ast.Module):
140
- self.enter_scope("<module>", is_module=True)
141
- self.generic_visit(node)
142
- self._check_unused_imports()
143
- self._check_shadowed_builtins()
144
- self.exit_scope()
145
-
146
- def visit_Import(self, node: ast.Import):
147
- for alias in node.names:
148
- name = alias.asname or alias.name
149
- self.define(name, node, is_import=True)
150
- if self.current_scope:
151
- self.current_scope.imports[name] = alias.name
152
- self.generic_visit(node)
153
-
154
- def visit_ImportFrom(self, node: ast.ImportFrom):
155
- if node.module:
156
- for alias in node.names:
157
- name = alias.asname or alias.name
158
- self.define(name, node, is_import=True)
159
- if self.current_scope:
160
- self.current_scope.imports[name] = f"{node.module}.{alias.name}"
161
- self.generic_visit(node)
162
-
163
- def visit_FunctionDef(self, node: ast.FunctionDef):
164
- name = node.name
165
- if self.current_scope and self.current_scope.is_class:
166
- qual_name = f"{self.current_scope.name}.{name}"
167
- else:
168
- qual_name = name
169
-
170
- self.define(name, node, is_function=True)
171
-
172
- self.enter_scope(qual_name, is_function=True)
173
-
174
- for arg in node.args.args:
175
- self.define(arg.arg, arg, is_parameter=True)
176
-
177
- if node.args.vararg:
178
- self.define(node.args.vararg.arg, node.args.vararg, is_parameter=True)
179
- if node.args.kwarg:
180
- self.define(node.args.kwarg.arg, node.args.kwarg, is_parameter=True)
181
-
182
- for default in node.args.defaults:
183
- self._check_late_binding(default, node)
184
-
185
- self.generic_visit(node)
186
-
187
- self._check_unused_params()
188
- self.exit_scope()
189
-
190
- def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
191
- self.visit_FunctionDef(node)
192
-
193
- def _check_late_binding(self, default: ast.AST, func_node: ast.FunctionDef):
194
- if isinstance(default, (ast.List, ast.Dict, ast.Set, ast.Call)):
195
- for name_node in ast.walk(default):
196
- if isinstance(name_node, ast.Name) and isinstance(name_node.ctx, ast.Load):
197
- self.late_binding_vars.append((name_node.id, default.lineno))
198
-
199
- def visit_ClassDef(self, node: ast.ClassDef):
200
- name = node.name
201
- self.define(name, node, is_class=True)
202
-
203
- self.enter_scope(name, is_class=True)
204
- self.generic_visit(node)
205
- self.exit_scope()
206
-
207
- def visit_Assign(self, node: ast.Assign):
208
- for target in node.targets:
209
- self._handle_assignment_target(target, node)
210
- self.generic_visit(node)
211
-
212
- def visit_AnnAssign(self, node: ast.AnnAssign):
213
- if node.target:
214
- self._handle_assignment_target(node.target, node)
215
- if node.value:
216
- self.generic_visit(node.value)
217
-
218
- def visit_AugAssign(self, node: ast.AugAssign):
219
- self._handle_assignment_target(node.target, node)
220
- self.generic_visit(node)
221
-
222
- def _handle_assignment_target(self, target: ast.AST, value_node: ast.AST):
223
- if isinstance(target, ast.Name):
224
- name = target.id
225
- scope = self.current_scope
226
- while scope:
227
- if name in scope.variables:
228
- sym = scope.variables[name]
229
- sym.is_assigned = True
230
- if sym.is_parameter and not scope.is_function:
231
- self.findings.append(Finding(
232
- engine="symbol",
233
- severity="low",
234
- title="Parameter reassignment",
235
- description=f"Function parameter '{name}' is reassigned",
236
- location=f"{self.filename}:{target.lineno}",
237
- metadata={
238
- "snippet": self.get_snippet(target),
239
- "cwe": "CWE-563",
240
- "confidence": 0.75,
241
- "check": "param_reassignment",
242
- "name": name,
243
- }
244
- ))
245
- break
246
- scope = scope.parent
247
- else:
248
- self.define(name, target)
249
- elif isinstance(target, ast.Attribute):
250
- pass
251
- elif isinstance(target, ast.Subscript):
252
- pass
253
- elif isinstance(target, (ast.Tuple, ast.List)):
254
- for elt in target.elts:
255
- self._handle_assignment_target(elt, value_node)
256
-
257
- def visit_Global(self, node: ast.Global):
258
- for name in node.names:
259
- if self.module_scope and name in self.module_scope.variables:
260
- self.module_scope.variables[name].is_global = True
261
- if self.current_scope:
262
- self.current_scope.variables[name] = Symbol(
263
- name=name, scope=self.module_scope or self.current_scope,
264
- node=node, is_global=True, line=node.lineno
265
- )
266
-
267
- def visit_Nonlocal(self, node: ast.Nonlocal):
268
- for name in node.names:
269
- scope = self.current_scope.parent if self.current_scope else None
270
- while scope and not scope.is_function:
271
- scope = scope.parent
272
- if scope and name in scope.variables:
273
- scope.variables[name].is_nonlocal = True
274
-
275
- def visit_Name(self, node: ast.Name):
276
- if isinstance(node.ctx, ast.Load):
277
- self.use(node.id, node)
278
- elif isinstance(node.ctx, ast.Store):
279
- pass
280
- elif isinstance(node.ctx, ast.Del):
281
- pass
282
- self.generic_visit(node)
283
-
284
- def visit_Lambda(self, node: ast.Lambda):
285
- self.enter_scope("<lambda>", is_function=True)
286
- for arg in node.args.args:
287
- self.define(arg.arg, arg, is_parameter=True)
288
- self.generic_visit(node)
289
- self.exit_scope()
290
-
291
- def visit_Comprehension(self, node: ast.comprehension):
292
- self.define(node.target.id if isinstance(node.target, ast.Name) else "_", node, is_parameter=True)
293
- for if_clause in node.ifs:
294
- self.generic_visit(if_clause)
295
- self.generic_visit(node.iter)
296
-
297
- def visit_GeneratorExp(self, node: ast.GeneratorExp):
298
- self.enter_scope("<genexpr>", is_function=True)
299
- for gen in node.generators:
300
- self.visit_Comprehension(gen)
301
- self.generic_visit(node.elt)
302
- self.exit_scope()
303
-
304
- def visit_ListComp(self, node: ast.ListComp):
305
- self.visit_GeneratorExp(node)
306
-
307
- def visit_SetComp(self, node: ast.SetComp):
308
- self.visit_GeneratorExp(node)
309
-
310
- def visit_DictComp(self, node: ast.DictComp):
311
- self.enter_scope("<dictexpr>", is_function=True)
312
- for gen in node.generators:
313
- self.visit_Comprehension(gen)
314
- self.generic_visit(node.key)
315
- self.generic_visit(node.value)
316
- self.exit_scope()
317
-
318
- def _check_unused_params(self):
319
- if not self.current_scope or not self.current_scope.is_function:
320
- return
321
- for name, sym in self.current_scope.variables.items():
322
- if sym.is_parameter and not sym.is_used and name != "_":
323
- self.findings.append(Finding(
324
- engine="symbol",
325
- severity="low",
326
- title="Unused function parameter",
327
- description=f"Parameter '{name}' is defined but never used",
328
- location=f"{self.filename}:{sym.line}",
329
- metadata={
330
- "snippet": self.get_snippet(sym.node),
331
- "cwe": "CWE-563",
332
- "confidence": 0.8,
333
- "check": "unused_parameter",
334
- "name": name,
335
- }
336
- ))
337
-
338
- def _check_unused_imports(self):
339
- if not self.module_scope:
340
- return
341
- for name, sym in self.module_scope.variables.items():
342
- if sym.is_import and not sym.is_used:
343
- if name.startswith("_"):
344
- continue
345
- if self.module_scope.imports.get(name, "").startswith("__future__"):
346
- continue
347
- self.findings.append(Finding(
348
- engine="symbol",
349
- severity="low",
350
- title="Unused import",
351
- description=f"Import '{name}' is never used",
352
- location=f"{self.filename}:{sym.line}",
353
- metadata={
354
- "snippet": self.get_snippet(sym.node),
355
- "cwe": "CWE-1041",
356
- "confidence": 0.85,
357
- "check": "unused_import",
358
- "imported": name,
359
- "module": self.module_scope.imports.get(name, ""),
360
- }
361
- ))
362
-
363
- def _check_shadowed_builtins(self):
364
- if not self.module_scope:
365
- return
366
- for name in self.module_scope.variables:
367
- if name in BUILTINS and not name.startswith("_"):
368
- sym = self.module_scope.variables[name]
369
- if sym.is_import or sym.is_function or sym.is_class:
370
- self.findings.append(Finding(
371
- engine="symbol",
372
- severity="medium",
373
- title="Shadowed builtin",
374
- description=f"Builtin '{name}' is shadowed by local definition",
375
- location=f"{self.filename}:{sym.line}",
376
- metadata={
377
- "snippet": self.get_snippet(sym.node),
378
- "cwe": "CWE-1228",
379
- "confidence": 0.9,
380
- "check": "shadowed_builtin",
381
- "name": name,
382
- }
383
- ))
384
-
385
- for scope_name, scope in self._collect_scopes(self.module_scope):
386
- for name, sym in scope.variables.items():
387
- if name in BUILTINS and not name.startswith("_"):
388
- if sym.is_parameter:
389
- self.findings.append(Finding(
390
- engine="symbol",
391
- severity="medium",
392
- title="Shadowed builtin parameter",
393
- description=f"Function parameter '{name}' shadows builtin",
394
- location=f"{self.filename}:{sym.line}",
395
- metadata={
396
- "snippet": self.get_snippet(sym.node),
397
- "cwe": "CWE-1228",
398
- "confidence": 0.85,
399
- "check": "shadowed_builtin_param",
400
- "name": name,
401
- "function": scope.name,
402
- }
403
- ))
404
-
405
- def _collect_scopes(self, scope: Scope) -> List[Tuple[str, Scope]]:
406
- result = [(scope.name, scope)]
407
- for var in scope.variables.values():
408
- if var.is_function or var.is_class:
409
- pass
410
- return result
411
-
412
-
413
- class Engine:
414
- name = "symbol"
415
- version = "1.0.0"
416
-
417
- async def check_availability(self) -> bool:
418
- return True
419
-
420
- def resource_limits(self) -> ResourceLimits:
421
- return ResourceLimits(max_cpu_seconds=60, max_memory_mb=512, max_files=50000)
422
-
423
- async def run(self, ctx, config: EngineConfig) -> EngineResult:
424
- # Handle both single IR and list of IRs
425
- if not isinstance(ctx, list):
426
- ctx = [ctx]
427
- all_findings = []
428
- errors = []
429
- files_processed = 0
430
-
431
- exclude_patterns = config.toggles.get("exclude", []) if config.toggles else []
432
-
433
- for ir in ctx:
434
- try:
435
- if not ir.metadata.get("language") == "python":
436
- continue
437
-
438
- rel_path = ir.metadata.get("path", "unknown.py")
439
- if any(pattern in rel_path for pattern in exclude_patterns):
440
- continue
441
-
442
- content = ir.content.decode("utf-8", errors="replace")
443
- source_lines = content.splitlines()
444
- tree = ast.parse(content, filename=rel_path)
445
-
446
- visitor = SymbolVisitor(rel_path, source_lines)
447
- visitor.visit(tree)
448
-
449
- all_findings.extend(visitor.findings)
450
- files_processed += 1
451
-
452
- except SyntaxError as e:
453
- errors.append(f"Syntax error in {ir.metadata.get('path', 'unknown')}: {e}")
454
- except Exception as e:
455
- errors.append(f"Error processing {ir.metadata.get('path', 'unknown')}: {e}")
456
-
457
- return EngineResult(
458
- engine=self.name,
459
- findings=all_findings,
460
- errors=errors,
461
- metrics={"files_processed": files_processed, "findings_count": len(all_findings)},
462
- ok=len(errors) == 0
463
- )