lucidscan 0.5.12__py3-none-any.whl

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 (91) hide show
  1. lucidscan/__init__.py +12 -0
  2. lucidscan/bootstrap/__init__.py +26 -0
  3. lucidscan/bootstrap/paths.py +160 -0
  4. lucidscan/bootstrap/platform.py +111 -0
  5. lucidscan/bootstrap/validation.py +76 -0
  6. lucidscan/bootstrap/versions.py +119 -0
  7. lucidscan/cli/__init__.py +50 -0
  8. lucidscan/cli/__main__.py +8 -0
  9. lucidscan/cli/arguments.py +405 -0
  10. lucidscan/cli/commands/__init__.py +64 -0
  11. lucidscan/cli/commands/autoconfigure.py +294 -0
  12. lucidscan/cli/commands/help.py +69 -0
  13. lucidscan/cli/commands/init.py +656 -0
  14. lucidscan/cli/commands/list_scanners.py +59 -0
  15. lucidscan/cli/commands/scan.py +307 -0
  16. lucidscan/cli/commands/serve.py +142 -0
  17. lucidscan/cli/commands/status.py +84 -0
  18. lucidscan/cli/commands/validate.py +105 -0
  19. lucidscan/cli/config_bridge.py +152 -0
  20. lucidscan/cli/exit_codes.py +17 -0
  21. lucidscan/cli/runner.py +284 -0
  22. lucidscan/config/__init__.py +29 -0
  23. lucidscan/config/ignore.py +178 -0
  24. lucidscan/config/loader.py +431 -0
  25. lucidscan/config/models.py +316 -0
  26. lucidscan/config/validation.py +645 -0
  27. lucidscan/core/__init__.py +3 -0
  28. lucidscan/core/domain_runner.py +463 -0
  29. lucidscan/core/git.py +174 -0
  30. lucidscan/core/logging.py +34 -0
  31. lucidscan/core/models.py +207 -0
  32. lucidscan/core/streaming.py +340 -0
  33. lucidscan/core/subprocess_runner.py +164 -0
  34. lucidscan/detection/__init__.py +21 -0
  35. lucidscan/detection/detector.py +154 -0
  36. lucidscan/detection/frameworks.py +270 -0
  37. lucidscan/detection/languages.py +328 -0
  38. lucidscan/detection/tools.py +229 -0
  39. lucidscan/generation/__init__.py +15 -0
  40. lucidscan/generation/config_generator.py +275 -0
  41. lucidscan/generation/package_installer.py +330 -0
  42. lucidscan/mcp/__init__.py +20 -0
  43. lucidscan/mcp/formatter.py +510 -0
  44. lucidscan/mcp/server.py +297 -0
  45. lucidscan/mcp/tools.py +1049 -0
  46. lucidscan/mcp/watcher.py +237 -0
  47. lucidscan/pipeline/__init__.py +17 -0
  48. lucidscan/pipeline/executor.py +187 -0
  49. lucidscan/pipeline/parallel.py +181 -0
  50. lucidscan/plugins/__init__.py +40 -0
  51. lucidscan/plugins/coverage/__init__.py +28 -0
  52. lucidscan/plugins/coverage/base.py +160 -0
  53. lucidscan/plugins/coverage/coverage_py.py +454 -0
  54. lucidscan/plugins/coverage/istanbul.py +411 -0
  55. lucidscan/plugins/discovery.py +107 -0
  56. lucidscan/plugins/enrichers/__init__.py +61 -0
  57. lucidscan/plugins/enrichers/base.py +63 -0
  58. lucidscan/plugins/linters/__init__.py +26 -0
  59. lucidscan/plugins/linters/base.py +125 -0
  60. lucidscan/plugins/linters/biome.py +448 -0
  61. lucidscan/plugins/linters/checkstyle.py +393 -0
  62. lucidscan/plugins/linters/eslint.py +368 -0
  63. lucidscan/plugins/linters/ruff.py +498 -0
  64. lucidscan/plugins/reporters/__init__.py +45 -0
  65. lucidscan/plugins/reporters/base.py +30 -0
  66. lucidscan/plugins/reporters/json_reporter.py +79 -0
  67. lucidscan/plugins/reporters/sarif_reporter.py +303 -0
  68. lucidscan/plugins/reporters/summary_reporter.py +61 -0
  69. lucidscan/plugins/reporters/table_reporter.py +81 -0
  70. lucidscan/plugins/scanners/__init__.py +57 -0
  71. lucidscan/plugins/scanners/base.py +60 -0
  72. lucidscan/plugins/scanners/checkov.py +484 -0
  73. lucidscan/plugins/scanners/opengrep.py +464 -0
  74. lucidscan/plugins/scanners/trivy.py +492 -0
  75. lucidscan/plugins/test_runners/__init__.py +27 -0
  76. lucidscan/plugins/test_runners/base.py +111 -0
  77. lucidscan/plugins/test_runners/jest.py +381 -0
  78. lucidscan/plugins/test_runners/karma.py +481 -0
  79. lucidscan/plugins/test_runners/playwright.py +434 -0
  80. lucidscan/plugins/test_runners/pytest.py +598 -0
  81. lucidscan/plugins/type_checkers/__init__.py +27 -0
  82. lucidscan/plugins/type_checkers/base.py +106 -0
  83. lucidscan/plugins/type_checkers/mypy.py +355 -0
  84. lucidscan/plugins/type_checkers/pyright.py +313 -0
  85. lucidscan/plugins/type_checkers/typescript.py +280 -0
  86. lucidscan-0.5.12.dist-info/METADATA +242 -0
  87. lucidscan-0.5.12.dist-info/RECORD +91 -0
  88. lucidscan-0.5.12.dist-info/WHEEL +5 -0
  89. lucidscan-0.5.12.dist-info/entry_points.txt +34 -0
  90. lucidscan-0.5.12.dist-info/licenses/LICENSE +201 -0
  91. lucidscan-0.5.12.dist-info/top_level.txt +1 -0
@@ -0,0 +1,316 @@
1
+ """Configuration data models for lucidscan.
2
+
3
+ Defines typed configuration classes that represent .lucidscan.yml structure.
4
+ Core fields are validated, while plugin-specific options are passed through.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Any, Dict, List, Optional, Union
11
+
12
+
13
+ # Default plugins per domain (used when not specified in config)
14
+ DEFAULT_PLUGINS: Dict[str, str] = {
15
+ "sca": "trivy",
16
+ "container": "trivy",
17
+ "sast": "opengrep",
18
+ "iac": "checkov",
19
+ }
20
+
21
+ # Valid severity values for fail_on
22
+ VALID_SEVERITIES = {"critical", "high", "medium", "low", "info"}
23
+
24
+ # Valid domain keys for fail_on dict format
25
+ VALID_FAIL_ON_DOMAINS = {"linting", "type_checking", "security", "testing", "coverage"}
26
+
27
+ # Special fail_on values (not severities)
28
+ SPECIAL_FAIL_ON_VALUES = {"error", "any", "none"}
29
+
30
+
31
+ @dataclass
32
+ class FailOnConfig:
33
+ """Failure threshold configuration.
34
+
35
+ Supports per-domain thresholds for different scan types.
36
+ Values can be severity levels (critical, high, medium, low, info)
37
+ or special values (error, any, none).
38
+ """
39
+
40
+ linting: Optional[str] = None # error, none
41
+ type_checking: Optional[str] = None # error, none
42
+ security: Optional[str] = None # critical, high, medium, low, info, none
43
+ testing: Optional[str] = None # any, none
44
+ coverage: Optional[str] = None # any, none
45
+
46
+ def get_threshold(self, domain: str) -> Optional[str]:
47
+ """Get threshold for a specific domain.
48
+
49
+ Args:
50
+ domain: Domain name (linting, type_checking, security, testing, coverage).
51
+
52
+ Returns:
53
+ Threshold value or None if not set.
54
+ """
55
+ return getattr(self, domain, None)
56
+
57
+
58
+ @dataclass
59
+ class OutputConfig:
60
+ """Output formatting configuration."""
61
+
62
+ format: str = "json"
63
+
64
+
65
+ @dataclass
66
+ class ToolConfig:
67
+ """Configuration for a single tool."""
68
+
69
+ name: str
70
+ config: Optional[str] = None # Path to tool-specific config
71
+ strict: bool = False # For type checkers
72
+ domains: List[str] = field(default_factory=list) # For security scanners
73
+ options: Dict[str, Any] = field(default_factory=dict) # Tool-specific options
74
+
75
+
76
+ @dataclass
77
+ class DomainPipelineConfig:
78
+ """Configuration for a pipeline domain (linting, type_checking, etc.)."""
79
+
80
+ enabled: bool = True
81
+ tools: List[ToolConfig] = field(default_factory=list)
82
+
83
+
84
+ @dataclass
85
+ class CoveragePipelineConfig:
86
+ """Coverage-specific pipeline configuration."""
87
+
88
+ enabled: bool = False
89
+ threshold: int = 80
90
+ tools: List[ToolConfig] = field(default_factory=list)
91
+
92
+
93
+ @dataclass
94
+ class PipelineConfig:
95
+ """Pipeline execution configuration.
96
+
97
+ Controls how the scan pipeline executes, including enricher
98
+ ordering and parallelism settings.
99
+ """
100
+
101
+ # List of enricher names in execution order
102
+ enrichers: List[str] = field(default_factory=list)
103
+
104
+ # Maximum parallel scanner workers (used when not in sequential mode)
105
+ max_workers: int = 4
106
+
107
+ # Domain-specific configurations
108
+ linting: Optional[DomainPipelineConfig] = None
109
+ type_checking: Optional[DomainPipelineConfig] = None
110
+ testing: Optional[DomainPipelineConfig] = None
111
+ coverage: Optional[CoveragePipelineConfig] = None
112
+ security: Optional[DomainPipelineConfig] = None
113
+
114
+ def get_enabled_tool_names(self, domain: str) -> List[str]:
115
+ """Get list of enabled tool names for a domain.
116
+
117
+ Args:
118
+ domain: Domain name (linting, type_checking, testing, security).
119
+
120
+ Returns:
121
+ List of tool names, or empty list if domain not configured.
122
+ """
123
+ domain_config = getattr(self, domain, None)
124
+ if domain_config is None or not domain_config.enabled:
125
+ return []
126
+ return [tool.name for tool in domain_config.tools]
127
+
128
+ def get_enabled_security_domains(self) -> List[str]:
129
+ """Get list of security domains enabled via pipeline.security.tools.
130
+
131
+ Extracts domains from each tool's domains list in pipeline.security.tools.
132
+
133
+ Returns:
134
+ List of unique domain names (sca, sast, iac, container).
135
+ """
136
+ if self.security is None or not self.security.enabled:
137
+ return []
138
+ domains: List[str] = []
139
+ for tool in self.security.tools:
140
+ for domain in tool.domains:
141
+ if domain not in domains:
142
+ domains.append(domain)
143
+ return domains
144
+
145
+ def get_security_plugin_for_domain(self, domain: str) -> Optional[str]:
146
+ """Get the plugin name configured for a security domain.
147
+
148
+ Looks up which tool in pipeline.security.tools handles the given domain.
149
+
150
+ Args:
151
+ domain: Security domain name (sca, sast, iac, container).
152
+
153
+ Returns:
154
+ Plugin name if configured, None otherwise.
155
+ """
156
+ if self.security is None or not self.security.enabled:
157
+ return None
158
+ for tool in self.security.tools:
159
+ if domain in tool.domains:
160
+ return tool.name
161
+ return None
162
+
163
+
164
+ @dataclass
165
+ class ScannerDomainConfig:
166
+ """Configuration for a scanner domain (sca, sast, iac, container).
167
+
168
+ The `enabled` and `plugin` fields are handled by the framework.
169
+ All other fields in `options` are passed through to the plugin.
170
+ """
171
+
172
+ enabled: bool = True
173
+ plugin: str = "" # Plugin name, e.g., "trivy", "snyk". Empty = use default.
174
+ options: Dict[str, Any] = field(default_factory=dict) # Plugin-specific options
175
+
176
+
177
+ @dataclass
178
+ class ProjectConfig:
179
+ """Project metadata configuration."""
180
+
181
+ name: str = ""
182
+ languages: List[str] = field(default_factory=list)
183
+
184
+
185
+ @dataclass
186
+ class LucidScanConfig:
187
+ """Complete lucidscan configuration.
188
+
189
+ Core fields are validated by the framework. Plugin-specific options
190
+ under `scanners.*` are passed through without validation.
191
+
192
+ Example .lucidscan.yml:
193
+ fail_on: high
194
+ ignore:
195
+ - "tests/**"
196
+ scanners:
197
+ sca:
198
+ enabled: true
199
+ plugin: trivy
200
+ ignore_unfixed: true # Plugin-specific, passed through
201
+ """
202
+
203
+ # Project metadata
204
+ project: ProjectConfig = field(default_factory=ProjectConfig)
205
+
206
+ # Core config (validated)
207
+ # fail_on can be a string (legacy) or FailOnConfig (per-domain thresholds)
208
+ fail_on: Optional[Union[str, FailOnConfig]] = None
209
+ ignore: List[str] = field(default_factory=list)
210
+ output: OutputConfig = field(default_factory=OutputConfig)
211
+
212
+ # Scanner configs per domain (plugin-specific options passed through)
213
+ scanners: Dict[str, ScannerDomainConfig] = field(default_factory=dict)
214
+
215
+ # Enricher configs (plugin-specific options passed through)
216
+ enrichers: Dict[str, Dict[str, Any]] = field(default_factory=dict)
217
+
218
+ # Pipeline configuration (enricher ordering, parallelism)
219
+ pipeline: PipelineConfig = field(default_factory=PipelineConfig)
220
+
221
+ # Metadata (not from YAML, set by loader)
222
+ _config_sources: List[str] = field(default_factory=list, repr=False)
223
+
224
+ def get_scanner_config(self, domain: str) -> ScannerDomainConfig:
225
+ """Get configuration for a domain, with defaults.
226
+
227
+ Args:
228
+ domain: Domain name (sca, sast, iac, container).
229
+
230
+ Returns:
231
+ ScannerDomainConfig for the domain, or a default if not configured.
232
+ """
233
+ return self.scanners.get(domain, ScannerDomainConfig())
234
+
235
+ def get_enabled_domains(self) -> List[str]:
236
+ """Get list of enabled security domain names.
237
+
238
+ Checks both:
239
+ 1. Legacy scanners.{domain} config
240
+ 2. New pipeline.security.tools[*].domains config
241
+
242
+ Returns:
243
+ List of domain names that are enabled in config.
244
+ """
245
+ # Check legacy scanners config
246
+ domains = [domain for domain, cfg in self.scanners.items() if cfg.enabled]
247
+
248
+ # Also check pipeline.security.tools for domains
249
+ pipeline_domains = self.pipeline.get_enabled_security_domains()
250
+ for domain in pipeline_domains:
251
+ if domain not in domains:
252
+ domains.append(domain)
253
+
254
+ return domains
255
+
256
+ def get_plugin_for_domain(self, domain: str) -> str:
257
+ """Get which plugin serves a domain.
258
+
259
+ Checks both:
260
+ 1. Legacy scanners.{domain}.plugin config
261
+ 2. New pipeline.security.tools[*].domains config
262
+
263
+ Args:
264
+ domain: Domain name (sca, sast, iac, container).
265
+
266
+ Returns:
267
+ Plugin name, falling back to default if not specified.
268
+ """
269
+ # Check legacy scanners config first
270
+ domain_config = self.get_scanner_config(domain)
271
+ if domain_config.plugin:
272
+ return domain_config.plugin
273
+
274
+ # Check pipeline.security.tools
275
+ pipeline_plugin = self.pipeline.get_security_plugin_for_domain(domain)
276
+ if pipeline_plugin:
277
+ return pipeline_plugin
278
+
279
+ # Fall back to defaults only if no config exists
280
+ return DEFAULT_PLUGINS.get(domain, "")
281
+
282
+ def get_fail_on_threshold(self, domain: str = "security") -> Optional[str]:
283
+ """Get fail_on threshold for a specific domain.
284
+
285
+ Handles both string (legacy) and FailOnConfig (per-domain) formats.
286
+
287
+ Args:
288
+ domain: Domain name (security, linting, type_checking, testing, coverage).
289
+ Defaults to "security" for backwards compatibility.
290
+
291
+ Returns:
292
+ Threshold value or None if not set.
293
+ """
294
+ if self.fail_on is None:
295
+ return None
296
+ if isinstance(self.fail_on, str):
297
+ # Legacy string format applies to security domain only
298
+ return self.fail_on if domain == "security" else None
299
+ if isinstance(self.fail_on, FailOnConfig):
300
+ return self.fail_on.get_threshold(domain)
301
+ return None
302
+
303
+ def get_scanner_options(self, domain: str) -> Dict[str, Any]:
304
+ """Get plugin-specific options for a domain.
305
+
306
+ These are all the options configured under scanners.<domain>
307
+ except for `enabled` and `plugin`.
308
+
309
+ Args:
310
+ domain: Domain name (sca, sast, iac, container).
311
+
312
+ Returns:
313
+ Dictionary of plugin-specific options.
314
+ """
315
+ domain_config = self.get_scanner_config(domain)
316
+ return domain_config.options