security-use 0.1.1__py3-none-any.whl → 0.2.9__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 (45) hide show
  1. security_use/__init__.py +9 -1
  2. security_use/auth/__init__.py +16 -0
  3. security_use/auth/client.py +223 -0
  4. security_use/auth/config.py +177 -0
  5. security_use/auth/oauth.py +317 -0
  6. security_use/cli.py +699 -34
  7. security_use/compliance/__init__.py +10 -0
  8. security_use/compliance/mapper.py +275 -0
  9. security_use/compliance/models.py +50 -0
  10. security_use/dependency_scanner.py +76 -30
  11. security_use/fixers/iac_fixer.py +173 -95
  12. security_use/iac/rules/azure.py +246 -0
  13. security_use/iac/rules/gcp.py +255 -0
  14. security_use/iac/rules/kubernetes.py +429 -0
  15. security_use/iac/rules/registry.py +56 -0
  16. security_use/parsers/__init__.py +18 -0
  17. security_use/parsers/base.py +2 -0
  18. security_use/parsers/composer.py +101 -0
  19. security_use/parsers/conda.py +97 -0
  20. security_use/parsers/dotnet.py +89 -0
  21. security_use/parsers/gradle.py +90 -0
  22. security_use/parsers/maven.py +108 -0
  23. security_use/parsers/npm.py +196 -0
  24. security_use/parsers/yarn.py +108 -0
  25. security_use/reporter.py +29 -1
  26. security_use/sbom/__init__.py +10 -0
  27. security_use/sbom/generator.py +340 -0
  28. security_use/sbom/models.py +40 -0
  29. security_use/scanner.py +15 -2
  30. security_use/sensor/__init__.py +125 -0
  31. security_use/sensor/alert_queue.py +207 -0
  32. security_use/sensor/config.py +217 -0
  33. security_use/sensor/dashboard_alerter.py +246 -0
  34. security_use/sensor/detector.py +415 -0
  35. security_use/sensor/endpoint_analyzer.py +339 -0
  36. security_use/sensor/middleware.py +521 -0
  37. security_use/sensor/models.py +140 -0
  38. security_use/sensor/webhook.py +227 -0
  39. security_use-0.2.9.dist-info/METADATA +531 -0
  40. security_use-0.2.9.dist-info/RECORD +60 -0
  41. security_use-0.2.9.dist-info/licenses/LICENSE +21 -0
  42. security_use-0.1.1.dist-info/METADATA +0 -92
  43. security_use-0.1.1.dist-info/RECORD +0 -30
  44. {security_use-0.1.1.dist-info → security_use-0.2.9.dist-info}/WHEEL +0 -0
  45. {security_use-0.1.1.dist-info → security_use-0.2.9.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,10 @@
1
+ """Compliance framework mapping module."""
2
+
3
+ from security_use.compliance.mapper import ComplianceMapper
4
+ from security_use.compliance.models import ComplianceFramework, ComplianceMapping
5
+
6
+ __all__ = [
7
+ "ComplianceMapper",
8
+ "ComplianceFramework",
9
+ "ComplianceMapping",
10
+ ]
@@ -0,0 +1,275 @@
1
+ """Compliance framework mapper for security rules."""
2
+
3
+ from typing import Optional
4
+
5
+ from security_use.compliance.models import (
6
+ ComplianceControl,
7
+ ComplianceFinding,
8
+ ComplianceFramework,
9
+ ComplianceMapping,
10
+ )
11
+ from security_use.models import IaCFinding
12
+
13
+
14
+ class ComplianceMapper:
15
+ """Maps security findings to compliance framework controls."""
16
+
17
+ # Mapping of rule IDs to compliance controls
18
+ MAPPINGS: dict[str, list[tuple[ComplianceFramework, str, str, str]]] = {
19
+ # AWS S3 Encryption
20
+ "CKV_AWS_19": [
21
+ (ComplianceFramework.SOC2, "CC6.1", "Encryption of Data at Rest",
22
+ "Logical and physical access controls"),
23
+ (ComplianceFramework.HIPAA, "164.312(a)(2)(iv)", "Encryption",
24
+ "Technical safeguards for data encryption"),
25
+ (ComplianceFramework.PCI_DSS, "3.4", "Render PAN unreadable",
26
+ "Protect stored cardholder data"),
27
+ (ComplianceFramework.NIST_800_53, "SC-28", "Protection of Information at Rest",
28
+ "Protect data at rest with encryption"),
29
+ (ComplianceFramework.CIS_AWS, "2.1.1", "Ensure S3 Bucket Encryption",
30
+ "S3 buckets should have encryption enabled"),
31
+ (ComplianceFramework.ISO_27001, "A.10.1.1", "Cryptographic Controls",
32
+ "Policy on use of cryptographic controls"),
33
+ ],
34
+ # AWS S3 Public Access
35
+ "CKV_AWS_20": [
36
+ (ComplianceFramework.SOC2, "CC6.6", "Logical Access Controls",
37
+ "Restrict access to authorized users"),
38
+ (ComplianceFramework.HIPAA, "164.312(e)(1)", "Transmission Security",
39
+ "Technical safeguards for access control"),
40
+ (ComplianceFramework.PCI_DSS, "7.1", "Limit access to system components",
41
+ "Restrict access to need-to-know basis"),
42
+ (ComplianceFramework.NIST_800_53, "AC-3", "Access Enforcement",
43
+ "Enforce approved authorizations"),
44
+ (ComplianceFramework.CIS_AWS, "2.1.5", "Ensure S3 Block Public Access",
45
+ "Block public access to S3 buckets"),
46
+ ],
47
+ # AWS Security Group
48
+ "CKV_AWS_23": [
49
+ (ComplianceFramework.SOC2, "CC6.6", "Logical Access Controls",
50
+ "Network access restrictions"),
51
+ (ComplianceFramework.PCI_DSS, "1.3", "Prohibit direct public access",
52
+ "Restrict inbound and outbound traffic"),
53
+ (ComplianceFramework.NIST_800_53, "SC-7", "Boundary Protection",
54
+ "Monitor and control communications at boundaries"),
55
+ (ComplianceFramework.CIS_AWS, "5.2", "Ensure VPC Security Groups",
56
+ "Restrict unrestricted ingress"),
57
+ ],
58
+ # AWS RDS Encryption
59
+ "CKV_AWS_16": [
60
+ (ComplianceFramework.SOC2, "CC6.1", "Encryption of Data at Rest",
61
+ "Database encryption"),
62
+ (ComplianceFramework.HIPAA, "164.312(a)(2)(iv)", "Encryption",
63
+ "Encrypt PHI at rest"),
64
+ (ComplianceFramework.PCI_DSS, "3.4", "Render PAN unreadable",
65
+ "Encrypt stored cardholder data"),
66
+ (ComplianceFramework.NIST_800_53, "SC-28", "Protection of Information at Rest",
67
+ "Database encryption at rest"),
68
+ (ComplianceFramework.CIS_AWS, "2.3.1", "Ensure RDS Encryption",
69
+ "RDS instances should be encrypted"),
70
+ ],
71
+ # AWS EBS Encryption
72
+ "CKV_AWS_3": [
73
+ (ComplianceFramework.SOC2, "CC6.1", "Encryption of Data at Rest",
74
+ "Volume encryption"),
75
+ (ComplianceFramework.HIPAA, "164.312(a)(2)(iv)", "Encryption",
76
+ "Encrypt PHI on storage volumes"),
77
+ (ComplianceFramework.PCI_DSS, "3.4", "Render PAN unreadable",
78
+ "Encrypt stored data on volumes"),
79
+ (ComplianceFramework.CIS_AWS, "2.2.1", "Ensure EBS Encryption",
80
+ "EBS volumes should be encrypted"),
81
+ ],
82
+ # AWS IAM MFA
83
+ "CKV_AWS_14": [
84
+ (ComplianceFramework.SOC2, "CC6.1", "Authentication Controls",
85
+ "Multi-factor authentication"),
86
+ (ComplianceFramework.HIPAA, "164.312(d)", "Person or Entity Authentication",
87
+ "Verify user identity"),
88
+ (ComplianceFramework.PCI_DSS, "8.3", "Secure all administrative access",
89
+ "Use MFA for all access"),
90
+ (ComplianceFramework.NIST_800_53, "IA-2", "Identification and Authentication",
91
+ "Multi-factor authentication"),
92
+ (ComplianceFramework.CIS_AWS, "1.10", "Ensure MFA is enabled",
93
+ "Enable MFA for IAM users"),
94
+ ],
95
+ # AWS CloudTrail
96
+ "CKV_AWS_35": [
97
+ (ComplianceFramework.SOC2, "CC7.2", "System Monitoring",
98
+ "Audit logging and monitoring"),
99
+ (ComplianceFramework.HIPAA, "164.312(b)", "Audit Controls",
100
+ "Record and examine activity"),
101
+ (ComplianceFramework.PCI_DSS, "10.1", "Implement audit trails",
102
+ "Link all access to individual users"),
103
+ (ComplianceFramework.NIST_800_53, "AU-2", "Audit Events",
104
+ "Generate audit records"),
105
+ (ComplianceFramework.CIS_AWS, "3.1", "Ensure CloudTrail is enabled",
106
+ "Enable CloudTrail in all regions"),
107
+ ],
108
+ # AWS VPC Flow Logs
109
+ "CKV_AWS_12": [
110
+ (ComplianceFramework.SOC2, "CC7.2", "System Monitoring",
111
+ "Network traffic monitoring"),
112
+ (ComplianceFramework.PCI_DSS, "10.6", "Review logs",
113
+ "Monitor network traffic"),
114
+ (ComplianceFramework.NIST_800_53, "AU-12", "Audit Generation",
115
+ "Generate audit records for network traffic"),
116
+ (ComplianceFramework.CIS_AWS, "3.9", "Ensure VPC Flow Logs",
117
+ "Enable VPC Flow Logs"),
118
+ ],
119
+ # Azure Storage Public Access
120
+ "CKV_AZURE_19": [
121
+ (ComplianceFramework.SOC2, "CC6.6", "Logical Access Controls",
122
+ "Storage access controls"),
123
+ (ComplianceFramework.CIS_AZURE, "3.7", "Ensure Storage Account Access",
124
+ "Disable public blob access"),
125
+ ],
126
+ # Azure NSG
127
+ "CKV_AZURE_9": [
128
+ (ComplianceFramework.SOC2, "CC6.6", "Network Access Controls",
129
+ "NSG configuration"),
130
+ (ComplianceFramework.CIS_AZURE, "6.1", "Ensure NSG Rules",
131
+ "Restrict unrestricted access"),
132
+ ],
133
+ # GCP Storage Public Access
134
+ "CKV_GCP_5": [
135
+ (ComplianceFramework.SOC2, "CC6.6", "Logical Access Controls",
136
+ "Cloud Storage access controls"),
137
+ (ComplianceFramework.CIS_GCP, "5.1", "Ensure Cloud Storage Bucket Access",
138
+ "Remove public access"),
139
+ ],
140
+ # GCP Firewall
141
+ "CKV_GCP_2": [
142
+ (ComplianceFramework.SOC2, "CC6.6", "Network Access Controls",
143
+ "Firewall configuration"),
144
+ (ComplianceFramework.CIS_GCP, "3.6", "Ensure Firewall Rules",
145
+ "Restrict unrestricted access"),
146
+ ],
147
+ # Kubernetes Privileged Container
148
+ "CKV_K8S_1": [
149
+ (ComplianceFramework.SOC2, "CC6.1", "Access Controls",
150
+ "Container privilege restrictions"),
151
+ (ComplianceFramework.CIS_K8S, "5.2.1", "Minimize privileged containers",
152
+ "Do not run privileged containers"),
153
+ ],
154
+ # Kubernetes Run as Root
155
+ "CKV_K8S_6": [
156
+ (ComplianceFramework.SOC2, "CC6.1", "Access Controls",
157
+ "Container user restrictions"),
158
+ (ComplianceFramework.CIS_K8S, "5.2.6", "Minimize root containers",
159
+ "Do not run containers as root"),
160
+ ],
161
+ # Kubernetes Resource Limits
162
+ "CKV_K8S_11": [
163
+ (ComplianceFramework.SOC2, "CC6.8", "System Operations",
164
+ "Resource management"),
165
+ (ComplianceFramework.CIS_K8S, "5.4.1", "Ensure resource limits",
166
+ "Set CPU and memory limits"),
167
+ ],
168
+ }
169
+
170
+ def get_mapping(self, rule_id: str) -> ComplianceMapping:
171
+ """Get compliance mapping for a rule.
172
+
173
+ Args:
174
+ rule_id: The security rule ID.
175
+
176
+ Returns:
177
+ ComplianceMapping with associated controls.
178
+ """
179
+ mapping = ComplianceMapping(rule_id=rule_id)
180
+
181
+ raw_mappings = self.MAPPINGS.get(rule_id, [])
182
+ for framework, control_id, title, description in raw_mappings:
183
+ control = ComplianceControl(
184
+ framework=framework,
185
+ control_id=control_id,
186
+ title=title,
187
+ description=description,
188
+ )
189
+ mapping.controls.append(control)
190
+
191
+ return mapping
192
+
193
+ def enrich_finding(self, finding: IaCFinding) -> ComplianceFinding:
194
+ """Enrich an IaC finding with compliance information.
195
+
196
+ Args:
197
+ finding: The original IaC finding.
198
+
199
+ Returns:
200
+ ComplianceFinding with compliance controls.
201
+ """
202
+ mapping = self.get_mapping(finding.rule_id)
203
+
204
+ return ComplianceFinding(
205
+ rule_id=finding.rule_id,
206
+ title=finding.title,
207
+ severity=finding.severity.value,
208
+ file_path=finding.file_path,
209
+ line_number=finding.line_number,
210
+ controls=mapping.controls,
211
+ )
212
+
213
+ def get_findings_by_framework(
214
+ self,
215
+ findings: list[IaCFinding],
216
+ framework: ComplianceFramework,
217
+ ) -> list[ComplianceFinding]:
218
+ """Filter findings by compliance framework.
219
+
220
+ Args:
221
+ findings: List of IaC findings.
222
+ framework: The compliance framework to filter by.
223
+
224
+ Returns:
225
+ List of ComplianceFindings relevant to the framework.
226
+ """
227
+ result = []
228
+
229
+ for finding in findings:
230
+ enriched = self.enrich_finding(finding)
231
+ # Filter controls to only the requested framework
232
+ framework_controls = [
233
+ c for c in enriched.controls
234
+ if c.framework == framework
235
+ ]
236
+
237
+ if framework_controls:
238
+ enriched.controls = framework_controls
239
+ result.append(enriched)
240
+
241
+ return result
242
+
243
+ def get_supported_frameworks(self) -> list[ComplianceFramework]:
244
+ """Get list of supported compliance frameworks.
245
+
246
+ Returns:
247
+ List of supported ComplianceFramework values.
248
+ """
249
+ return list(ComplianceFramework)
250
+
251
+ def get_framework_summary(
252
+ self,
253
+ findings: list[IaCFinding],
254
+ framework: ComplianceFramework,
255
+ ) -> dict[str, list[ComplianceFinding]]:
256
+ """Group findings by control within a framework.
257
+
258
+ Args:
259
+ findings: List of IaC findings.
260
+ framework: The compliance framework.
261
+
262
+ Returns:
263
+ Dict mapping control_id to list of findings.
264
+ """
265
+ by_control: dict[str, list[ComplianceFinding]] = {}
266
+
267
+ framework_findings = self.get_findings_by_framework(findings, framework)
268
+
269
+ for finding in framework_findings:
270
+ for control in finding.controls:
271
+ if control.control_id not in by_control:
272
+ by_control[control.control_id] = []
273
+ by_control[control.control_id].append(finding)
274
+
275
+ return by_control
@@ -0,0 +1,50 @@
1
+ """Data models for compliance mapping."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ from typing import Optional
6
+
7
+
8
+ class ComplianceFramework(Enum):
9
+ """Supported compliance frameworks."""
10
+
11
+ SOC2 = "soc2"
12
+ HIPAA = "hipaa"
13
+ PCI_DSS = "pci-dss"
14
+ NIST_800_53 = "nist-800-53"
15
+ CIS_AWS = "cis-aws"
16
+ CIS_AZURE = "cis-azure"
17
+ CIS_GCP = "cis-gcp"
18
+ CIS_K8S = "cis-kubernetes"
19
+ ISO_27001 = "iso-27001"
20
+
21
+
22
+ @dataclass
23
+ class ComplianceControl:
24
+ """Represents a compliance control/requirement."""
25
+
26
+ framework: ComplianceFramework
27
+ control_id: str
28
+ title: str
29
+ description: str
30
+ category: Optional[str] = None
31
+
32
+
33
+ @dataclass
34
+ class ComplianceMapping:
35
+ """Maps a security rule to compliance controls."""
36
+
37
+ rule_id: str
38
+ controls: list[ComplianceControl] = field(default_factory=list)
39
+
40
+
41
+ @dataclass
42
+ class ComplianceFinding:
43
+ """A finding with compliance context."""
44
+
45
+ rule_id: str
46
+ title: str
47
+ severity: str
48
+ file_path: str
49
+ line_number: int
50
+ controls: list[ComplianceControl] = field(default_factory=list)
@@ -7,10 +7,21 @@ from security_use.models import ScanResult, Vulnerability
7
7
  from security_use.parsers import (
8
8
  Dependency,
9
9
  DependencyParser,
10
+ MavenParser,
11
+ NpmLockParser,
12
+ NpmParser,
10
13
  PipfileParser,
11
14
  PoetryLockParser,
12
15
  PyProjectParser,
13
16
  RequirementsParser,
17
+ GradleParser,
18
+ YarnLockParser,
19
+ PnpmLockParser,
20
+ CsprojParser,
21
+ PackagesConfigParser,
22
+ CondaEnvironmentParser,
23
+ ComposerParser,
24
+ ComposerLockParser,
14
25
  )
15
26
  from security_use.parsers.pipfile import PipfileLockParser
16
27
 
@@ -24,6 +35,17 @@ class DependencyScanner:
24
35
  PipfileParser,
25
36
  PipfileLockParser,
26
37
  PoetryLockParser,
38
+ MavenParser,
39
+ NpmParser,
40
+ NpmLockParser,
41
+ GradleParser,
42
+ YarnLockParser,
43
+ PnpmLockParser,
44
+ CsprojParser,
45
+ PackagesConfigParser,
46
+ CondaEnvironmentParser,
47
+ ComposerParser,
48
+ ComposerLockParser,
27
49
  ]
28
50
 
29
51
  DEPENDENCY_FILES = [
@@ -36,6 +58,18 @@ class DependencyScanner:
36
58
  "Pipfile.lock",
37
59
  "poetry.lock",
38
60
  "setup.py",
61
+ "pom.xml",
62
+ "package.json",
63
+ "package-lock.json",
64
+ "build.gradle",
65
+ "build.gradle.kts",
66
+ "yarn.lock",
67
+ "pnpm-lock.yaml",
68
+ "packages.config",
69
+ "environment.yml",
70
+ "environment.yaml",
71
+ "composer.json",
72
+ "composer.lock",
39
73
  ]
40
74
 
41
75
  def __init__(self) -> None:
@@ -129,53 +163,65 @@ class DependencyScanner:
129
163
  """
130
164
  vulnerabilities = []
131
165
 
132
- # Build package list for batch query
133
- packages = [
134
- (dep.name, dep.version)
135
- for dep in dependencies
136
- if dep.version is not None
137
- ]
138
-
139
- if not packages:
140
- return vulnerabilities
141
-
142
- # Query OSV for vulnerabilities
143
- vuln_results = self.osv_client.query_batch(packages)
144
-
166
+ # Group dependencies by ecosystem
167
+ by_ecosystem: dict[str, list[Dependency]] = {}
145
168
  for dep in dependencies:
146
169
  if dep.version is None:
147
170
  continue
171
+ ecosystem = getattr(dep, "ecosystem", "PyPI") or "PyPI"
172
+ if ecosystem not in by_ecosystem:
173
+ by_ecosystem[ecosystem] = []
174
+ by_ecosystem[ecosystem].append(dep)
148
175
 
149
- key = (dep.normalized_name, dep.version)
150
- if key in vuln_results:
151
- vulnerabilities.extend(vuln_results[key])
176
+ # Query each ecosystem separately
177
+ for ecosystem, deps in by_ecosystem.items():
178
+ packages = [(dep.name, dep.version) for dep in deps]
179
+
180
+ if not packages:
181
+ continue
182
+
183
+ # Query OSV for vulnerabilities
184
+ vuln_results = self.osv_client.query_batch(packages, ecosystem=ecosystem)
185
+
186
+ for dep in deps:
187
+ key = (dep.normalized_name, dep.version)
188
+ if key in vuln_results:
189
+ vulnerabilities.extend(vuln_results[key])
152
190
 
153
191
  return vulnerabilities
154
192
 
155
- def _find_dependency_files(self, directory: Path) -> list[Path]:
156
- """Find all dependency files in a directory.
193
+ def _find_dependency_files(self, directory: Path, max_depth: int = 4) -> list[Path]:
194
+ """Find all dependency files in a directory recursively.
157
195
 
158
196
  Args:
159
197
  directory: Directory to search.
198
+ max_depth: Maximum depth to search (default: 4).
160
199
 
161
200
  Returns:
162
201
  List of dependency file paths.
163
202
  """
164
203
  files = []
204
+ # Directories to skip (contain many nested dependencies we don't want to scan)
205
+ skip_dirs = {
206
+ "node_modules", ".git", ".venv", "venv", "__pycache__",
207
+ ".tox", ".pytest_cache", "dist", "build", ".eggs",
208
+ "target", ".gradle", ".m2"
209
+ }
165
210
 
166
- for filename in self.DEPENDENCY_FILES:
167
- # Check root directory
168
- file_path = directory / filename
169
- if file_path.exists():
170
- files.append(file_path)
171
-
172
- # Check subdirectories (one level deep)
173
- for subdir in directory.iterdir():
174
- if subdir.is_dir() and not subdir.name.startswith("."):
175
- sub_file = subdir / filename
176
- if sub_file.exists():
177
- files.append(sub_file)
211
+ def search_dir(path: Path, depth: int) -> None:
212
+ if depth > max_depth:
213
+ return
178
214
 
215
+ try:
216
+ for item in path.iterdir():
217
+ if item.is_file() and item.name in self.DEPENDENCY_FILES:
218
+ files.append(item)
219
+ elif item.is_dir() and not item.name.startswith(".") and item.name not in skip_dirs:
220
+ search_dir(item, depth + 1)
221
+ except PermissionError:
222
+ pass
223
+
224
+ search_dir(directory, 0)
179
225
  return files
180
226
 
181
227
  def _get_parser(self, file_type: str) -> Optional[DependencyParser]: