@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,454 @@
1
+ """Universal language support for Orion Engine."""
2
+
3
+ from enum import Enum
4
+ from dataclasses import dataclass
5
+ from typing import Dict, List, Set, Optional
6
+ import os
7
+
8
+
9
+ class Language(Enum):
10
+ """Supported programming languages."""
11
+ PYTHON = "python"
12
+ JAVASCRIPT = "javascript"
13
+ TYPESCRIPT = "typescript"
14
+ JAVA = "java"
15
+ GO = "go"
16
+ RUST = "rust"
17
+ CPP = "cpp"
18
+ C = "c"
19
+ CSHARP = "csharp"
20
+ PHP = "php"
21
+ RUBY = "ruby"
22
+ SWIFT = "swift"
23
+ KOTLIN = "kotlin"
24
+ SCALA = "scala"
25
+ HTML = "html"
26
+ CSS = "css"
27
+ SQL = "sql"
28
+ YAML = "yaml"
29
+ JSON = "json"
30
+ TOML = "toml"
31
+ XML = "xml"
32
+ DOCKERFILE = "dockerfile"
33
+ MARKDOWN = "markdown"
34
+ SHELL = "shell"
35
+ POWERSHELL = "powershell"
36
+ UNKNOWN = "unknown"
37
+
38
+
39
+ # File extension to language mapping
40
+ EXTENSION_MAP: Dict[str, Language] = {
41
+ # Python
42
+ ".py": Language.PYTHON,
43
+ ".pyw": Language.PYTHON,
44
+ ".pyi": Language.PYTHON,
45
+
46
+ # JavaScript/TypeScript
47
+ ".js": Language.JAVASCRIPT,
48
+ ".jsx": Language.JAVASCRIPT,
49
+ ".mjs": Language.JAVASCRIPT,
50
+ ".cjs": Language.JAVASCRIPT,
51
+ ".ts": Language.TYPESCRIPT,
52
+ ".tsx": Language.TYPESCRIPT,
53
+ ".mts": Language.TYPESCRIPT,
54
+ ".cts": Language.TYPESCRIPT,
55
+
56
+ # Java
57
+ ".java": Language.JAVA,
58
+
59
+ # Go
60
+ ".go": Language.GO,
61
+
62
+ # Rust
63
+ ".rs": Language.RUST,
64
+
65
+ # C/C++
66
+ ".c": Language.C,
67
+ ".h": Language.C,
68
+ ".cpp": Language.CPP,
69
+ ".cc": Language.CPP,
70
+ ".cxx": Language.CPP,
71
+ ".hpp": Language.CPP,
72
+ ".hxx": Language.CPP,
73
+
74
+ # C#
75
+ ".cs": Language.CSHARP,
76
+
77
+ # PHP
78
+ ".php": Language.PHP,
79
+ ".phtml": Language.PHP,
80
+
81
+ # Ruby
82
+ ".rb": Language.RUBY,
83
+ ".erb": Language.RUBY,
84
+
85
+ # Swift
86
+ ".swift": Language.SWIFT,
87
+
88
+ # Kotlin
89
+ ".kt": Language.KOTLIN,
90
+ ".kts": Language.KOTLIN,
91
+
92
+ # Scala
93
+ ".scala": Language.SCALA,
94
+
95
+ # Web
96
+ ".html": Language.HTML,
97
+ ".htm": Language.HTML,
98
+ ".xhtml": Language.HTML,
99
+ ".css": Language.CSS,
100
+ ".scss": Language.CSS,
101
+ ".sass": Language.CSS,
102
+ ".less": Language.CSS,
103
+
104
+ # SQL
105
+ ".sql": Language.SQL,
106
+
107
+ # Config
108
+ ".yaml": Language.YAML,
109
+ ".yml": Language.YAML,
110
+ ".json": Language.JSON,
111
+ ".toml": Language.TOML,
112
+ ".xml": Language.XML,
113
+ ".ini": Language.YAML,
114
+ ".cfg": Language.YAML,
115
+ ".conf": Language.YAML,
116
+ ".properties": Language.YAML,
117
+
118
+ # Docker/Container
119
+ "Dockerfile": Language.DOCKERFILE,
120
+ "dockerfile": Language.DOCKERFILE,
121
+ ".dockerfile": Language.DOCKERFILE,
122
+ "docker-compose.yml": Language.DOCKERFILE,
123
+ "docker-compose.yaml": Language.DOCKERFILE,
124
+
125
+ # Shell
126
+ ".sh": Language.SHELL,
127
+ ".bash": Language.SHELL,
128
+ ".zsh": Language.SHELL,
129
+ ".fish": Language.SHELL,
130
+
131
+ # PowerShell
132
+ ".ps1": Language.POWERSHELL,
133
+ ".psm1": Language.POWERSHELL,
134
+ ".psd1": Language.POWERSHELL,
135
+
136
+ # Documentation
137
+ ".md": Language.MARKDOWN,
138
+ ".markdown": Language.MARKDOWN,
139
+ ".rst": Language.MARKDOWN,
140
+
141
+ # Config files without extensions
142
+ "Makefile": Language.SHELL,
143
+ "makefile": Language.SHELL,
144
+ "CMakeLists.txt": Language.SHELL,
145
+ ".gitignore": Language.YAML,
146
+ ".gitattributes": Language.YAML,
147
+ ".editorconfig": Language.YAML,
148
+ ".env": Language.YAML,
149
+ ".env.example": Language.YAML,
150
+ ".env.local": Language.YAML,
151
+ "package.json": Language.JSON,
152
+ "package-lock.json": Language.JSON,
153
+ "yarn.lock": Language.YAML,
154
+ "pnpm-lock.yaml": Language.YAML,
155
+ "requirements.txt": Language.YAML,
156
+ "requirements-dev.txt": Language.YAML,
157
+ "Pipfile": Language.TOML,
158
+ "Pipfile.lock": Language.JSON,
159
+ "pyproject.toml": Language.TOML,
160
+ "poetry.lock": Language.JSON,
161
+ "Cargo.toml": Language.TOML,
162
+ "Cargo.lock": Language.JSON,
163
+ "go.mod": Language.YAML,
164
+ "go.sum": Language.YAML,
165
+ "pom.xml": Language.XML,
166
+ "build.gradle": Language.SHELL,
167
+ "build.gradle.kts": Language.SHELL,
168
+ "settings.gradle": Language.SHELL,
169
+ "composer.json": Language.JSON,
170
+ "composer.lock": Language.JSON,
171
+ "Gemfile": Language.YAML,
172
+ "Gemfile.lock": Language.YAML,
173
+ "mix.exs": Language.SHELL,
174
+ "mix.lock": Language.YAML,
175
+ "pubspec.yaml": Language.YAML,
176
+ "pubspec.lock": Language.YAML,
177
+ }
178
+
179
+
180
+ # Filename patterns for special files
181
+ FILENAME_PATTERNS: Dict[str, Language] = {
182
+ "Dockerfile": Language.DOCKERFILE,
183
+ "dockerfile": Language.DOCKERFILE,
184
+ "docker-compose.yml": Language.DOCKERFILE,
185
+ "docker-compose.yaml": Language.DOCKERFILE,
186
+ "docker-compose.override.yml": Language.DOCKERFILE,
187
+ "docker-compose.override.yaml": Language.DOCKERFILE,
188
+ "Makefile": Language.SHELL,
189
+ "makefile": Language.SHELL,
190
+ "CMakeLists.txt": Language.SHELL,
191
+ "CMakeLists.txt": Language.SHELL,
192
+ }
193
+
194
+
195
+ def detect_language(filepath: str) -> Language:
196
+ """Detect language from file path."""
197
+ filename = os.path.basename(filepath)
198
+
199
+ # Check exact filename matches first
200
+ if filename in FILENAME_PATTERNS:
201
+ return FILENAME_PATTERNS[filename]
202
+
203
+ # Check extension
204
+ _, ext = os.path.splitext(filename)
205
+ ext = ext.lower()
206
+ if ext in EXTENSION_MAP:
207
+ return EXTENSION_MAP[ext]
208
+
209
+ # Check for double extensions (e.g., .dockerfile, .min.js)
210
+ if '.' in filename:
211
+ parts = filename.split('.')
212
+ if len(parts) > 2:
213
+ double_ext = '.' + '.'.join(parts[-2:]).lower()
214
+ if double_ext in EXTENSION_MAP:
215
+ return EXTENSION_MAP[double_ext]
216
+
217
+ return Language.UNKNOWN
218
+
219
+
220
+ def is_binary_file(filepath: str) -> bool:
221
+ """Quick check if file is likely binary."""
222
+ try:
223
+ with open(filepath, 'rb') as f:
224
+ chunk = f.read(8192)
225
+ return b'\x00' in chunk
226
+ except Exception:
227
+ return True
228
+
229
+
230
+ # Language groups for engine targeting
231
+ LANGUAGE_GROUPS: Dict[str, Set[Language]] = {
232
+ "pythonic": {Language.PYTHON},
233
+ "javascript": {Language.JAVASCRIPT, Language.TYPESCRIPT},
234
+ "jvm": {Language.JAVA, Language.KOTLIN, Language.SCALA},
235
+ "native": {Language.C, Language.CPP, Language.RUST, Language.GO, Language.CSHARP},
236
+ "web": {Language.HTML, Language.CSS, Language.JAVASCRIPT, Language.TYPESCRIPT},
237
+ "scripting": {Language.PYTHON, Language.JAVASCRIPT, Language.TYPESCRIPT, Language.PHP, Language.RUBY, Language.SHELL, Language.POWERSHELL},
238
+ "compiled": {Language.C, Language.CPP, Language.RUST, Language.GO, Language.JAVA, Language.CSHARP, Language.SWIFT, Language.KOTLIN, Language.SCALA},
239
+ "interpreted": {Language.PYTHON, Language.JAVASCRIPT, Language.TYPESCRIPT, Language.PHP, Language.RUBY, Language.SHELL},
240
+ "config": {Language.YAML, Language.JSON, Language.TOML, Language.XML},
241
+ "container": {Language.DOCKERFILE},
242
+ "data": {Language.SQL, Language.JSON, Language.YAML, Language.XML},
243
+ "web_static": {Language.HTML, Language.CSS},
244
+ "documentation": {Language.MARKDOWN},
245
+ }
246
+
247
+
248
+ def get_language_group(language: Language) -> List[str]:
249
+ """Get groups a language belongs to."""
250
+ groups = []
251
+ for group_name, langs in LANGUAGE_GROUPS.items():
252
+ if language in langs:
253
+ groups.append(group_name)
254
+ return groups
255
+
256
+
257
+ @dataclass
258
+ class LanguageCapabilities:
259
+ """What analysis capabilities are available for a language."""
260
+ language: Language
261
+ has_ast_parser: bool = False
262
+ has_tree_sitter: bool = False
263
+ sast_patterns: int = 0
264
+ secret_patterns: int = 0
265
+ dep_parsers: List[str] = None
266
+ taint_sources: List[str] = None
267
+ taint_sinks: List[str] = None
268
+
269
+ def __post_init__(self):
270
+ if self.dep_parsers is None:
271
+ self.dep_parsers = []
272
+ if self.taint_sources is None:
273
+ self.taint_sources = []
274
+ if self.taint_sinks is None:
275
+ self.taint_sinks = []
276
+
277
+
278
+ # Language capability registry
279
+ LANGUAGE_CAPABILITIES: Dict[Language, LanguageCapabilities] = {
280
+ Language.PYTHON: LanguageCapabilities(
281
+ language=Language.PYTHON,
282
+ has_ast_parser=True,
283
+ has_tree_sitter=True,
284
+ sast_patterns=28,
285
+ secret_patterns=12,
286
+ dep_parsers=["requirements.txt", "pyproject.toml", "Pipfile", "poetry.lock", "setup.py"],
287
+ taint_sources=["request.", "input(", "sys.argv", "os.environ", "request.GET", "request.POST"],
288
+ taint_sinks=["execute", "eval", "exec", "system", "popen", "subprocess", "render_template_string"],
289
+ ),
290
+ Language.JAVASCRIPT: LanguageCapabilities(
291
+ language=Language.JAVASCRIPT,
292
+ has_ast_parser=True,
293
+ has_tree_sitter=True,
294
+ sast_patterns=6,
295
+ secret_patterns=8,
296
+ dep_parsers=["package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"],
297
+ taint_sources=["req.body", "req.query", "req.params", "req.headers", "process.env"],
298
+ taint_sinks=["eval", "exec", "Function", "innerHTML", "outerHTML", "document.write"],
299
+ ),
300
+ Language.TYPESCRIPT: LanguageCapabilities(
301
+ language=Language.TYPESCRIPT,
302
+ has_ast_parser=True,
303
+ has_tree_sitter=True,
304
+ sast_patterns=6,
305
+ secret_patterns=8,
306
+ dep_parsers=["package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"],
307
+ taint_sources=["req.body", "req.query", "req.params", "req.headers", "process.env"],
308
+ taint_sinks=["eval", "exec", "Function", "innerHTML", "outerHTML", "document.write"],
309
+ ),
310
+ Language.JAVA: LanguageCapabilities(
311
+ language=Language.JAVA,
312
+ has_ast_parser=True,
313
+ has_tree_sitter=True,
314
+ sast_patterns=0,
315
+ secret_patterns=8,
316
+ dep_parsers=["pom.xml", "build.gradle", "build.gradle.kts"],
317
+ taint_sources=["request.getParameter", "request.getHeader", "System.getenv"],
318
+ taint_sinks=["executeQuery", "executeUpdate", "Runtime.exec", "ProcessBuilder"],
319
+ ),
320
+ Language.GO: LanguageCapabilities(
321
+ language=Language.GO,
322
+ has_ast_parser=True,
323
+ has_tree_sitter=True,
324
+ sast_patterns=0,
325
+ secret_patterns=8,
326
+ dep_parsers=["go.mod", "go.sum"],
327
+ taint_sources=["r.FormValue", "r.URL.Query", "os.Getenv"],
328
+ taint_sinks=["Exec", "Query", "ExecContext", "os/exec.Command"],
329
+ ),
330
+ Language.RUST: LanguageCapabilities(
331
+ language=Language.RUST,
332
+ has_ast_parser=True,
333
+ has_tree_sitter=True,
334
+ sast_patterns=0,
335
+ secret_patterns=8,
336
+ dep_parsers=["Cargo.toml", "Cargo.lock"],
337
+ taint_sources=["std::env::var", "req.body", "req.query"],
338
+ taint_sinks=["std::process::Command", "sqlx::query", "diesel::RunQueryDsl"],
339
+ ),
340
+ Language.CSHARP: LanguageCapabilities(
341
+ language=Language.CSHARP,
342
+ has_ast_parser=True,
343
+ has_tree_sitter=True,
344
+ sast_patterns=0,
345
+ secret_patterns=8,
346
+ dep_parsers=["*.csproj", "packages.config", "paket.dependencies"],
347
+ taint_sources=["Request.QueryString", "Request.Form", "Environment.GetEnvironmentVariable"],
348
+ taint_sinks=["SqlCommand.ExecuteReader", "Process.Start", "SqlCommand.ExecuteNonQuery"],
349
+ ),
350
+ Language.PHP: LanguageCapabilities(
351
+ language=Language.PHP,
352
+ has_ast_parser=True,
353
+ has_tree_sitter=True,
354
+ sast_patterns=0,
355
+ secret_patterns=8,
356
+ dep_parsers=["composer.json", "composer.lock"],
357
+ taint_sources=["$_GET", "$_POST", "$_REQUEST", "$_COOKIE", "$_SERVER", "$_ENV"],
358
+ taint_sinks=["exec", "system", "shell_exec", "passthru", "proc_open", "eval", "mysqli_query"],
359
+ ),
360
+ Language.RUBY: LanguageCapabilities(
361
+ language=Language.RUBY,
362
+ has_ast_parser=True,
363
+ has_tree_sitter=True,
364
+ sast_patterns=0,
365
+ secret_patterns=8,
366
+ dep_parsers=["Gemfile", "Gemfile.lock"],
367
+ taint_sources=["params", "request.env", "ENV"],
368
+ taint_sinks=["system", "exec", "`", "eval", "ActiveRecord::Base.connection.execute"],
369
+ ),
370
+ Language.HTML: LanguageCapabilities(
371
+ language=Language.HTML,
372
+ has_ast_parser=False,
373
+ has_tree_sitter=True,
374
+ sast_patterns=0,
375
+ secret_patterns=4,
376
+ dep_parsers=[],
377
+ taint_sources=[],
378
+ taint_sinks=["innerHTML", "outerHTML", "document.write", "eval"],
379
+ ),
380
+ Language.CSS: LanguageCapabilities(
381
+ language=Language.CSS,
382
+ has_ast_parser=False,
383
+ has_tree_sitter=True,
384
+ sast_patterns=0,
385
+ secret_patterns=0,
386
+ dep_parsers=[],
387
+ taint_sources=[],
388
+ taint_sinks=[],
389
+ ),
390
+ Language.YAML: LanguageCapabilities(
391
+ language=Language.YAML,
392
+ has_ast_parser=False,
393
+ has_tree_sitter=False,
394
+ sast_patterns=0,
395
+ secret_patterns=8,
396
+ dep_parsers=[],
397
+ taint_sources=[],
398
+ taint_sinks=[],
399
+ ),
400
+ Language.JSON: LanguageCapabilities(
401
+ language=Language.JSON,
402
+ has_ast_parser=False,
403
+ has_tree_sitter=False,
404
+ sast_patterns=0,
405
+ secret_patterns=4,
406
+ dep_parsers=["package.json", "composer.json", "composer.lock", "poetry.lock"],
407
+ taint_sources=[],
408
+ taint_sinks=[],
409
+ ),
410
+ Language.TOML: LanguageCapabilities(
411
+ language=Language.TOML,
412
+ has_ast_parser=False,
413
+ has_tree_sitter=False,
414
+ sast_patterns=0,
415
+ secret_patterns=4,
416
+ dep_parsers=["pyproject.toml", "Cargo.toml"],
417
+ taint_sources=[],
418
+ taint_sinks=[],
419
+ ),
420
+ Language.DOCKERFILE: LanguageCapabilities(
421
+ language=Language.DOCKERFILE,
422
+ has_ast_parser=False,
423
+ has_tree_sitter=False,
424
+ sast_patterns=0,
425
+ secret_patterns=8,
426
+ dep_parsers=["Dockerfile", "docker-compose.yml"],
427
+ taint_sources=[],
428
+ taint_sinks=["ADD http", "ADD https", "RUN curl", "RUN wget"],
429
+ ),
430
+ }
431
+
432
+
433
+ def get_capabilities(language: Language) -> LanguageCapabilities:
434
+ """Get capabilities for a language."""
435
+ return LANGUAGE_CAPABILITIES.get(language, LanguageCapabilities(language=language))
436
+
437
+
438
+ def get_supported_languages() -> List[Language]:
439
+ """Get all supported languages."""
440
+ return list(Language)[:-1] # Exclude UNKNOWN
441
+
442
+
443
+ __all__ = [
444
+ "Language",
445
+ "detect_language",
446
+ "is_binary_file",
447
+ "LanguageCapabilities",
448
+ "get_capabilities",
449
+ "get_supported_languages",
450
+ "LANGUAGE_GROUPS",
451
+ "get_language_group",
452
+ "EXTENSION_MAP",
453
+ "FILENAME_PATTERNS",
454
+ ]
@@ -0,0 +1,172 @@
1
+ """License header + GPL contamination detection engine."""
2
+ import re
3
+ import os
4
+ from pathlib import Path
5
+ from orion.engine.config import EngineConfig
6
+ from orion.engine.ir import OrionIR
7
+ from orion.engine.results import EngineResult, Finding
8
+
9
+
10
+ class Engine:
11
+ name = "license"
12
+ version = "1.0.0"
13
+
14
+ SPDX_PATTERN = re.compile(r"SPDX-License-Identifier:\s*([A-Za-z0-9\.\-\+]+)")
15
+ LICENSE_HEADER_PATTERNS = [
16
+ re.compile(r"Copyright\s+\(c\)\s+\d{4}"),
17
+ re.compile(r"Licensed under the\s+([A-Za-z0-9\.\-\+]+)"),
18
+ re.compile(r"License:\s*([A-Za-z0-9\.\-\+]+)"),
19
+ ]
20
+
21
+ GPL_LICENSES = {
22
+ "GPL-2.0", "GPL-3.0", "AGPL-3.0", "LGPL-2.1", "LGPL-3.0",
23
+ "GPL-2.0-only", "GPL-2.0-or-later", "GPL-3.0-only", "GPL-3.0-or-later",
24
+ "AGPL-3.0-only", "AGPL-3.0-or-later", "LGPL-2.1-only", "LGPL-2.1-or-later",
25
+ "LGPL-3.0-only", "LGPL-3.0-or-later",
26
+ }
27
+
28
+ PERMISSIVE_LICENSES = {
29
+ "MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC", "MPL-2.0",
30
+ "Unlicense", "0BSD", "Zlib", "Python-2.0",
31
+ }
32
+
33
+ TEST_PATHS = {"test", "tests", "testing", "spec", "specs", "__pycache__", "vendor", "third_party", "generated"}
34
+ GENERATED_MARKERS = ["generated", "auto-generated", "do not edit", "codegen"]
35
+
36
+ async def check_availability(self) -> bool:
37
+ return True
38
+
39
+ def resource_limits(self):
40
+ from orion.engine.config import ResourceLimits
41
+ return ResourceLimits(max_cpu_seconds=30, max_memory_mb=128)
42
+
43
+ async def run(self, ir: OrionIR, config: EngineConfig) -> EngineResult:
44
+ try:
45
+ text = ir.content.decode("utf-8", errors="replace")
46
+ except Exception:
47
+ text = str(ir.content)
48
+
49
+ path = ir.metadata.get("path", "unknown")
50
+ findings = []
51
+
52
+ if self._is_test_or_generated(path, text):
53
+ return EngineResult(engine=self.name, findings=findings, ok=True)
54
+
55
+ has_spdx, spdx_license = self._check_spdx_header(text)
56
+ has_license_hdr, hdr_license = self._check_license_header(text)
57
+ detected_license = spdx_license or hdr_license
58
+
59
+ # Focus on GPL/Copyleft contamination in proprietary projects
60
+ if detected_license and self._is_gpl_contaminated(detected_license):
61
+ findings.append(Finding(
62
+ engine=self.name,
63
+ severity="high",
64
+ title=f"GPL/Copyleft Contamination Risk: {detected_license}",
65
+ description=f"Strong copyleft license '{detected_license}' detected, which may impose open-source distribution requirements",
66
+ location=path,
67
+ metadata={
68
+ "license": detected_license,
69
+ "risk": "gpl_copyleft",
70
+ "cwe": "CWE-1104",
71
+ "confidence": 0.95,
72
+ },
73
+ ))
74
+
75
+ return EngineResult(engine=self.name, findings=findings, ok=True)
76
+
77
+ def _is_test_or_generated(self, path: str, text: str) -> bool:
78
+ path_lower = path.lower()
79
+ for test_dir in self.TEST_PATHS:
80
+ if f"/{test_dir}/" in path_lower or path_lower.startswith(f"{test_dir}/"):
81
+ return True
82
+ for marker in self.GENERATED_MARKERS:
83
+ if marker.lower() in text[:500].lower():
84
+ return True
85
+ return False
86
+
87
+ def _check_spdx_header(self, text: str):
88
+ lines = text.splitlines()[:30]
89
+ header_text = "\n".join(lines)
90
+ match = self.SPDX_PATTERN.search(header_text)
91
+ if match:
92
+ return True, match.group(1).strip()
93
+ for pattern in self.LICENSE_HEADER_PATTERNS:
94
+ match = pattern.search(header_text)
95
+ if match:
96
+ return True, match.group(1).strip() if match.groups() else "Unknown"
97
+ return False, None
98
+
99
+ def _check_root_license_files(self, path: str):
100
+ findings = []
101
+ p = Path(path)
102
+ project_root = None
103
+ for parent in p.parents:
104
+ if (parent / ".git").exists() or (parent / "pyproject.toml").exists() or (parent / "package.json").exists():
105
+ project_root = parent
106
+ break
107
+
108
+ if project_root:
109
+ license_files = list(project_root.glob("LICENSE*")) + list(project_root.glob("COPYING*"))
110
+ if not license_files:
111
+ findings.append(Finding(
112
+ engine=self.name,
113
+ severity="low",
114
+ title="Missing license file at project root",
115
+ description="No LICENSE or COPYING file found in project root",
116
+ location=str(project_root),
117
+ metadata={
118
+ "confidence": 0.8,
119
+ "missing_license_file": True,
120
+ "project_root": str(project_root),
121
+ },
122
+ ))
123
+ else:
124
+ for lf in license_files:
125
+ try:
126
+ content = lf.read_text(encoding="utf-8", errors="replace")
127
+ for gpl in self.GPL_LICENSES:
128
+ if gpl in content:
129
+ findings.append(Finding(
130
+ engine=self.name,
131
+ severity="high",
132
+ title=f"GPL/AGPL license file detected: {gpl}",
133
+ description=f"Project root license file contains {gpl}",
134
+ location=str(lf),
135
+ metadata={
136
+ "confidence": 0.95,
137
+ "license": gpl,
138
+ "file": str(lf),
139
+ "cwe": "CWE-1104",
140
+ },
141
+ ))
142
+ except Exception:
143
+ pass
144
+ return findings
145
+
146
+ def _check_dep_gpl_conflicts(self, ir: OrionIR, file_license: str):
147
+ findings = []
148
+ if file_license not in self.PERMISSIVE_LICENSES:
149
+ return findings
150
+
151
+ metrics = ir.metadata.get("metrics", {})
152
+ deps = metrics.get("deps", {})
153
+ if isinstance(deps, dict):
154
+ for dep_name, dep_info in deps.items():
155
+ dep_license = dep_info.get("license") if isinstance(dep_info, dict) else None
156
+ if dep_license and dep_license in self.GPL_LICENSES:
157
+ findings.append(Finding(
158
+ engine=self.name,
159
+ severity="high",
160
+ title=f"GPL/AGPL dependency in permissive project: {dep_name}",
161
+ description=f"Dependency '{dep_name}' uses {dep_license} but project uses {file_license or 'permissive'} license",
162
+ location=ir.metadata.get("path", "unknown"),
163
+ metadata={
164
+ "confidence": 0.85,
165
+ "gpl_dep": dep_name,
166
+ "license": dep_license,
167
+ "project_license": file_license,
168
+ "cwe": "CWE-1104",
169
+ "cvss": 7.0,
170
+ },
171
+ ))
172
+ return findings