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.
- lucidscan/__init__.py +12 -0
- lucidscan/bootstrap/__init__.py +26 -0
- lucidscan/bootstrap/paths.py +160 -0
- lucidscan/bootstrap/platform.py +111 -0
- lucidscan/bootstrap/validation.py +76 -0
- lucidscan/bootstrap/versions.py +119 -0
- lucidscan/cli/__init__.py +50 -0
- lucidscan/cli/__main__.py +8 -0
- lucidscan/cli/arguments.py +405 -0
- lucidscan/cli/commands/__init__.py +64 -0
- lucidscan/cli/commands/autoconfigure.py +294 -0
- lucidscan/cli/commands/help.py +69 -0
- lucidscan/cli/commands/init.py +656 -0
- lucidscan/cli/commands/list_scanners.py +59 -0
- lucidscan/cli/commands/scan.py +307 -0
- lucidscan/cli/commands/serve.py +142 -0
- lucidscan/cli/commands/status.py +84 -0
- lucidscan/cli/commands/validate.py +105 -0
- lucidscan/cli/config_bridge.py +152 -0
- lucidscan/cli/exit_codes.py +17 -0
- lucidscan/cli/runner.py +284 -0
- lucidscan/config/__init__.py +29 -0
- lucidscan/config/ignore.py +178 -0
- lucidscan/config/loader.py +431 -0
- lucidscan/config/models.py +316 -0
- lucidscan/config/validation.py +645 -0
- lucidscan/core/__init__.py +3 -0
- lucidscan/core/domain_runner.py +463 -0
- lucidscan/core/git.py +174 -0
- lucidscan/core/logging.py +34 -0
- lucidscan/core/models.py +207 -0
- lucidscan/core/streaming.py +340 -0
- lucidscan/core/subprocess_runner.py +164 -0
- lucidscan/detection/__init__.py +21 -0
- lucidscan/detection/detector.py +154 -0
- lucidscan/detection/frameworks.py +270 -0
- lucidscan/detection/languages.py +328 -0
- lucidscan/detection/tools.py +229 -0
- lucidscan/generation/__init__.py +15 -0
- lucidscan/generation/config_generator.py +275 -0
- lucidscan/generation/package_installer.py +330 -0
- lucidscan/mcp/__init__.py +20 -0
- lucidscan/mcp/formatter.py +510 -0
- lucidscan/mcp/server.py +297 -0
- lucidscan/mcp/tools.py +1049 -0
- lucidscan/mcp/watcher.py +237 -0
- lucidscan/pipeline/__init__.py +17 -0
- lucidscan/pipeline/executor.py +187 -0
- lucidscan/pipeline/parallel.py +181 -0
- lucidscan/plugins/__init__.py +40 -0
- lucidscan/plugins/coverage/__init__.py +28 -0
- lucidscan/plugins/coverage/base.py +160 -0
- lucidscan/plugins/coverage/coverage_py.py +454 -0
- lucidscan/plugins/coverage/istanbul.py +411 -0
- lucidscan/plugins/discovery.py +107 -0
- lucidscan/plugins/enrichers/__init__.py +61 -0
- lucidscan/plugins/enrichers/base.py +63 -0
- lucidscan/plugins/linters/__init__.py +26 -0
- lucidscan/plugins/linters/base.py +125 -0
- lucidscan/plugins/linters/biome.py +448 -0
- lucidscan/plugins/linters/checkstyle.py +393 -0
- lucidscan/plugins/linters/eslint.py +368 -0
- lucidscan/plugins/linters/ruff.py +498 -0
- lucidscan/plugins/reporters/__init__.py +45 -0
- lucidscan/plugins/reporters/base.py +30 -0
- lucidscan/plugins/reporters/json_reporter.py +79 -0
- lucidscan/plugins/reporters/sarif_reporter.py +303 -0
- lucidscan/plugins/reporters/summary_reporter.py +61 -0
- lucidscan/plugins/reporters/table_reporter.py +81 -0
- lucidscan/plugins/scanners/__init__.py +57 -0
- lucidscan/plugins/scanners/base.py +60 -0
- lucidscan/plugins/scanners/checkov.py +484 -0
- lucidscan/plugins/scanners/opengrep.py +464 -0
- lucidscan/plugins/scanners/trivy.py +492 -0
- lucidscan/plugins/test_runners/__init__.py +27 -0
- lucidscan/plugins/test_runners/base.py +111 -0
- lucidscan/plugins/test_runners/jest.py +381 -0
- lucidscan/plugins/test_runners/karma.py +481 -0
- lucidscan/plugins/test_runners/playwright.py +434 -0
- lucidscan/plugins/test_runners/pytest.py +598 -0
- lucidscan/plugins/type_checkers/__init__.py +27 -0
- lucidscan/plugins/type_checkers/base.py +106 -0
- lucidscan/plugins/type_checkers/mypy.py +355 -0
- lucidscan/plugins/type_checkers/pyright.py +313 -0
- lucidscan/plugins/type_checkers/typescript.py +280 -0
- lucidscan-0.5.12.dist-info/METADATA +242 -0
- lucidscan-0.5.12.dist-info/RECORD +91 -0
- lucidscan-0.5.12.dist-info/WHEEL +5 -0
- lucidscan-0.5.12.dist-info/entry_points.txt +34 -0
- lucidscan-0.5.12.dist-info/licenses/LICENSE +201 -0
- 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
|