codepathfinder 1.2.0__py3-none-manylinux_2_17_aarch64.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.
- codepathfinder/__init__.py +48 -0
- codepathfinder/bin/pathfinder +0 -0
- codepathfinder/cli/__init__.py +204 -0
- codepathfinder/config.py +92 -0
- codepathfinder/dataflow.py +193 -0
- codepathfinder/decorators.py +158 -0
- codepathfinder/ir.py +107 -0
- codepathfinder/logic.py +101 -0
- codepathfinder/matchers.py +243 -0
- codepathfinder/presets.py +135 -0
- codepathfinder/propagation.py +250 -0
- codepathfinder-1.2.0.dist-info/METADATA +111 -0
- codepathfinder-1.2.0.dist-info/RECORD +33 -0
- codepathfinder-1.2.0.dist-info/WHEEL +5 -0
- codepathfinder-1.2.0.dist-info/entry_points.txt +2 -0
- codepathfinder-1.2.0.dist-info/licenses/LICENSE +661 -0
- codepathfinder-1.2.0.dist-info/top_level.txt +2 -0
- rules/__init__.py +36 -0
- rules/container_combinators.py +209 -0
- rules/container_decorators.py +223 -0
- rules/container_ir.py +104 -0
- rules/container_matchers.py +230 -0
- rules/container_programmatic.py +115 -0
- rules/python/__init__.py +0 -0
- rules/python/deserialization/__init__.py +0 -0
- rules/python/deserialization/pickle_loads.py +479 -0
- rules/python/django/__init__.py +0 -0
- rules/python/django/sql_injection.py +355 -0
- rules/python/flask/__init__.py +0 -0
- rules/python/flask/debug_mode.py +374 -0
- rules/python/injection/__init__.py +0 -0
- rules/python_decorators.py +177 -0
- rules/python_ir.py +80 -0
rules/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Container security rules DSL for Dockerfile and docker-compose."""
|
|
2
|
+
|
|
3
|
+
from .container_decorators import dockerfile_rule, compose_rule
|
|
4
|
+
from .container_matchers import instruction, missing, service_has, service_missing
|
|
5
|
+
from .container_ir import compile_all_rules, compile_to_json
|
|
6
|
+
from .container_combinators import (
|
|
7
|
+
all_of,
|
|
8
|
+
any_of,
|
|
9
|
+
none_of,
|
|
10
|
+
instruction_after,
|
|
11
|
+
instruction_before,
|
|
12
|
+
stage,
|
|
13
|
+
final_stage_has,
|
|
14
|
+
)
|
|
15
|
+
from .container_programmatic import custom_check, DockerfileAccess, ComposeAccess
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"dockerfile_rule",
|
|
19
|
+
"compose_rule",
|
|
20
|
+
"instruction",
|
|
21
|
+
"missing",
|
|
22
|
+
"service_has",
|
|
23
|
+
"service_missing",
|
|
24
|
+
"compile_all_rules",
|
|
25
|
+
"compile_to_json",
|
|
26
|
+
"all_of",
|
|
27
|
+
"any_of",
|
|
28
|
+
"none_of",
|
|
29
|
+
"instruction_after",
|
|
30
|
+
"instruction_before",
|
|
31
|
+
"stage",
|
|
32
|
+
"final_stage_has",
|
|
33
|
+
"custom_check",
|
|
34
|
+
"DockerfileAccess",
|
|
35
|
+
"ComposeAccess",
|
|
36
|
+
]
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logic combinators for container rules.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Dict, Any, Union, Callable
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from .container_matchers import Matcher
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class CombinatorMatcher:
|
|
12
|
+
"""Represents a logic combinator (AND, OR, NOT)."""
|
|
13
|
+
|
|
14
|
+
combinator_type: str # "all_of", "any_of", "none_of"
|
|
15
|
+
conditions: List[Union[Matcher, "CombinatorMatcher", Dict, Callable]]
|
|
16
|
+
|
|
17
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
18
|
+
"""Convert to JSON IR."""
|
|
19
|
+
serialized_conditions = []
|
|
20
|
+
for cond in self.conditions:
|
|
21
|
+
if hasattr(cond, "to_dict"):
|
|
22
|
+
serialized_conditions.append(cond.to_dict())
|
|
23
|
+
elif isinstance(cond, dict):
|
|
24
|
+
serialized_conditions.append(cond)
|
|
25
|
+
elif callable(cond):
|
|
26
|
+
serialized_conditions.append(
|
|
27
|
+
{"type": "custom_function", "has_callable": True}
|
|
28
|
+
)
|
|
29
|
+
else:
|
|
30
|
+
serialized_conditions.append(cond)
|
|
31
|
+
|
|
32
|
+
return {"type": self.combinator_type, "conditions": serialized_conditions}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def all_of(*conditions: Union[Matcher, Dict, Callable]) -> CombinatorMatcher:
|
|
36
|
+
"""
|
|
37
|
+
Combine matchers with AND logic.
|
|
38
|
+
All conditions must match for the rule to trigger.
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
all_of(
|
|
42
|
+
instruction(type="FROM", image_tag="latest"),
|
|
43
|
+
missing(instruction="USER"),
|
|
44
|
+
instruction(type="RUN", contains="sudo")
|
|
45
|
+
)
|
|
46
|
+
"""
|
|
47
|
+
return CombinatorMatcher(combinator_type="all_of", conditions=list(conditions))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def any_of(*conditions: Union[Matcher, Dict, Callable]) -> CombinatorMatcher:
|
|
51
|
+
"""
|
|
52
|
+
Combine matchers with OR logic.
|
|
53
|
+
Any condition can match for the rule to trigger.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
any_of(
|
|
57
|
+
instruction(type="USER", user_name="root"),
|
|
58
|
+
missing(instruction="USER"),
|
|
59
|
+
instruction(type="FROM", base_image="scratch")
|
|
60
|
+
)
|
|
61
|
+
"""
|
|
62
|
+
return CombinatorMatcher(combinator_type="any_of", conditions=list(conditions))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def none_of(*conditions: Union[Matcher, Dict, Callable]) -> CombinatorMatcher:
|
|
66
|
+
"""
|
|
67
|
+
Combine matchers with NOT logic.
|
|
68
|
+
None of the conditions should match for the rule to pass.
|
|
69
|
+
(Inverse: if any matches, rule triggers as violation)
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
none_of(
|
|
73
|
+
instruction(type="HEALTHCHECK"),
|
|
74
|
+
instruction(type="USER", user_name_not="root")
|
|
75
|
+
)
|
|
76
|
+
"""
|
|
77
|
+
return CombinatorMatcher(combinator_type="none_of", conditions=list(conditions))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class SequenceMatcher:
|
|
82
|
+
"""Represents instruction sequence validation."""
|
|
83
|
+
|
|
84
|
+
sequence_type: str # "after" or "before"
|
|
85
|
+
instruction: Union[str, Matcher, Dict]
|
|
86
|
+
reference: Union[str, Matcher, Dict]
|
|
87
|
+
not_followed_by: bool = False
|
|
88
|
+
|
|
89
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
90
|
+
"""Convert to JSON IR."""
|
|
91
|
+
|
|
92
|
+
def serialize_ref(ref):
|
|
93
|
+
if isinstance(ref, str):
|
|
94
|
+
return {"instruction": ref}
|
|
95
|
+
elif hasattr(ref, "to_dict"):
|
|
96
|
+
return ref.to_dict()
|
|
97
|
+
elif isinstance(ref, dict):
|
|
98
|
+
return ref
|
|
99
|
+
return ref
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
"type": f"instruction_{self.sequence_type}",
|
|
103
|
+
"instruction": serialize_ref(self.instruction),
|
|
104
|
+
"reference": serialize_ref(self.reference),
|
|
105
|
+
"not_followed_by": self.not_followed_by,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def instruction_after(
|
|
110
|
+
instruction: Union[str, Matcher],
|
|
111
|
+
after: Union[str, Matcher],
|
|
112
|
+
not_followed_by: bool = False,
|
|
113
|
+
) -> SequenceMatcher:
|
|
114
|
+
"""
|
|
115
|
+
Check that an instruction appears after another.
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
# Ensure CMD comes after USER
|
|
119
|
+
instruction_after(instruction="CMD", after="USER")
|
|
120
|
+
|
|
121
|
+
# Ensure apt-get install follows apt-get update
|
|
122
|
+
instruction_after(
|
|
123
|
+
instruction=instruction(type="RUN", contains="apt-get install"),
|
|
124
|
+
after=instruction(type="RUN", contains="apt-get update")
|
|
125
|
+
)
|
|
126
|
+
"""
|
|
127
|
+
return SequenceMatcher(
|
|
128
|
+
sequence_type="after",
|
|
129
|
+
instruction=instruction,
|
|
130
|
+
reference=after,
|
|
131
|
+
not_followed_by=not_followed_by,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def instruction_before(
|
|
136
|
+
instruction: Union[str, Matcher],
|
|
137
|
+
before: Union[str, Matcher],
|
|
138
|
+
not_followed_by: bool = False,
|
|
139
|
+
) -> SequenceMatcher:
|
|
140
|
+
"""
|
|
141
|
+
Check that an instruction appears before another.
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
instruction_before(instruction="USER", before="CMD")
|
|
145
|
+
"""
|
|
146
|
+
return SequenceMatcher(
|
|
147
|
+
sequence_type="before",
|
|
148
|
+
instruction=instruction,
|
|
149
|
+
reference=before,
|
|
150
|
+
not_followed_by=not_followed_by,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class StageMatcher:
|
|
156
|
+
"""Matcher for multi-stage build stage queries."""
|
|
157
|
+
|
|
158
|
+
stage_type: str
|
|
159
|
+
params: Dict[str, Any] = field(default_factory=dict)
|
|
160
|
+
|
|
161
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
162
|
+
return {"type": f"stage_{self.stage_type}", **self.params}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def stage(
|
|
166
|
+
alias: str = None,
|
|
167
|
+
base_image: str = None,
|
|
168
|
+
is_final: bool = None,
|
|
169
|
+
) -> StageMatcher:
|
|
170
|
+
"""
|
|
171
|
+
Query a specific build stage.
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
stage(alias="builder")
|
|
175
|
+
stage(is_final=True)
|
|
176
|
+
stage(base_image="alpine")
|
|
177
|
+
"""
|
|
178
|
+
params = {}
|
|
179
|
+
if alias is not None:
|
|
180
|
+
params["alias"] = alias
|
|
181
|
+
if base_image is not None:
|
|
182
|
+
params["base_image"] = base_image
|
|
183
|
+
if is_final is not None:
|
|
184
|
+
params["is_final"] = is_final
|
|
185
|
+
|
|
186
|
+
return StageMatcher(stage_type="query", params=params)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def final_stage_has(
|
|
190
|
+
instruction: Union[str, Matcher] = None,
|
|
191
|
+
missing_instruction: str = None,
|
|
192
|
+
) -> StageMatcher:
|
|
193
|
+
"""
|
|
194
|
+
Check properties of the final build stage.
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
final_stage_has(missing_instruction="USER")
|
|
198
|
+
final_stage_has(instruction=instruction(type="USER", user_name="root"))
|
|
199
|
+
"""
|
|
200
|
+
params = {}
|
|
201
|
+
if instruction is not None:
|
|
202
|
+
if isinstance(instruction, str):
|
|
203
|
+
params["instruction"] = instruction
|
|
204
|
+
elif hasattr(instruction, "to_dict"):
|
|
205
|
+
params["instruction"] = instruction.to_dict()
|
|
206
|
+
if missing_instruction is not None:
|
|
207
|
+
params["missing_instruction"] = missing_instruction
|
|
208
|
+
|
|
209
|
+
return StageMatcher(stage_type="final_has", params=params)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decorators for Dockerfile and docker-compose rules.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import atexit
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Callable, Dict, Any, List
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class RuleMetadata:
|
|
14
|
+
"""Metadata for a container security rule."""
|
|
15
|
+
|
|
16
|
+
id: str
|
|
17
|
+
name: str = ""
|
|
18
|
+
severity: str = "MEDIUM"
|
|
19
|
+
category: str = "security"
|
|
20
|
+
cwe: str = ""
|
|
21
|
+
cve: str = ""
|
|
22
|
+
tags: str = ""
|
|
23
|
+
message: str = ""
|
|
24
|
+
file_pattern: str = ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class DockerfileRuleDefinition:
|
|
29
|
+
"""Complete definition of a Dockerfile rule."""
|
|
30
|
+
|
|
31
|
+
metadata: RuleMetadata
|
|
32
|
+
matcher: Dict[str, Any]
|
|
33
|
+
rule_function: Callable
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ComposeRuleDefinition:
|
|
38
|
+
"""Complete definition of a docker-compose rule."""
|
|
39
|
+
|
|
40
|
+
metadata: RuleMetadata
|
|
41
|
+
matcher: Dict[str, Any]
|
|
42
|
+
rule_function: Callable
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Global registries
|
|
46
|
+
_dockerfile_rules: List[DockerfileRuleDefinition] = []
|
|
47
|
+
_compose_rules: List[ComposeRuleDefinition] = []
|
|
48
|
+
_auto_execute_enabled = False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _enable_auto_execute() -> None:
|
|
52
|
+
"""
|
|
53
|
+
Enable automatic rule compilation and output when script ends.
|
|
54
|
+
|
|
55
|
+
This provides consistent behavior with code analysis rules -
|
|
56
|
+
no __main__ block needed.
|
|
57
|
+
"""
|
|
58
|
+
global _auto_execute_enabled
|
|
59
|
+
if _auto_execute_enabled:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
_auto_execute_enabled = True
|
|
63
|
+
|
|
64
|
+
def _output_rules():
|
|
65
|
+
"""Output all container rules as JSON when script ends."""
|
|
66
|
+
if not _dockerfile_rules and not _compose_rules:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
# Compile rules to JSON IR format
|
|
70
|
+
from . import container_ir
|
|
71
|
+
|
|
72
|
+
compiled = container_ir.compile_all_rules()
|
|
73
|
+
|
|
74
|
+
# Output to stdout for Go loader to capture
|
|
75
|
+
print(json.dumps(compiled))
|
|
76
|
+
|
|
77
|
+
# Register cleanup handler
|
|
78
|
+
atexit.register(_output_rules)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _register_rule() -> None:
|
|
82
|
+
"""
|
|
83
|
+
Check if auto-execution should be enabled when a rule is registered.
|
|
84
|
+
|
|
85
|
+
Enables auto-execution if the module is being executed directly (not imported).
|
|
86
|
+
"""
|
|
87
|
+
# Check if module is being executed directly
|
|
88
|
+
frame = sys._getframe(2) # Get caller's frame (the module defining the rule)
|
|
89
|
+
if frame.f_globals.get("__name__") == "__main__":
|
|
90
|
+
_enable_auto_execute()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def dockerfile_rule(
|
|
94
|
+
id: str,
|
|
95
|
+
name: str = "",
|
|
96
|
+
severity: str = "MEDIUM",
|
|
97
|
+
category: str = "security",
|
|
98
|
+
cwe: str = "",
|
|
99
|
+
cve: str = "",
|
|
100
|
+
tags: str = "",
|
|
101
|
+
message: str = "",
|
|
102
|
+
) -> Callable:
|
|
103
|
+
"""
|
|
104
|
+
Decorator for Dockerfile security rules.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
@dockerfile_rule(id="DOCKER-001", severity="HIGH", cwe="CWE-250",
|
|
108
|
+
tags="security,docker,privilege-escalation")
|
|
109
|
+
def container_runs_as_root():
|
|
110
|
+
return missing(instruction="USER")
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def decorator(func: Callable) -> Callable:
|
|
114
|
+
# Get matcher from function
|
|
115
|
+
matcher_result = func()
|
|
116
|
+
|
|
117
|
+
# Convert to dict if it's a Matcher object
|
|
118
|
+
if hasattr(matcher_result, "to_dict"):
|
|
119
|
+
matcher_dict = matcher_result.to_dict()
|
|
120
|
+
elif isinstance(matcher_result, dict):
|
|
121
|
+
matcher_dict = matcher_result
|
|
122
|
+
else:
|
|
123
|
+
raise ValueError(f"Rule {id} must return a matcher or dict")
|
|
124
|
+
|
|
125
|
+
# Create rule definition
|
|
126
|
+
metadata = RuleMetadata(
|
|
127
|
+
id=id,
|
|
128
|
+
name=name or func.__name__.replace("_", " ").title(),
|
|
129
|
+
severity=severity,
|
|
130
|
+
category=category,
|
|
131
|
+
cwe=cwe,
|
|
132
|
+
cve=cve,
|
|
133
|
+
tags=tags,
|
|
134
|
+
message=message or f"Security issue detected by {id}",
|
|
135
|
+
file_pattern="Dockerfile*",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
rule_def = DockerfileRuleDefinition(
|
|
139
|
+
metadata=metadata,
|
|
140
|
+
matcher=matcher_dict,
|
|
141
|
+
rule_function=func,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
_dockerfile_rules.append(rule_def)
|
|
145
|
+
_register_rule() # Enable auto-execution if running as script
|
|
146
|
+
|
|
147
|
+
# Return original function (can be called for testing)
|
|
148
|
+
return func
|
|
149
|
+
|
|
150
|
+
return decorator
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def compose_rule(
|
|
154
|
+
id: str,
|
|
155
|
+
name: str = "",
|
|
156
|
+
severity: str = "MEDIUM",
|
|
157
|
+
category: str = "security",
|
|
158
|
+
cwe: str = "",
|
|
159
|
+
cve: str = "",
|
|
160
|
+
tags: str = "",
|
|
161
|
+
message: str = "",
|
|
162
|
+
) -> Callable:
|
|
163
|
+
"""
|
|
164
|
+
Decorator for docker-compose security rules.
|
|
165
|
+
|
|
166
|
+
Example:
|
|
167
|
+
@compose_rule(id="COMPOSE-001", severity="HIGH", cwe="CWE-250",
|
|
168
|
+
tags="security,docker-compose,privilege-escalation")
|
|
169
|
+
def privileged_service():
|
|
170
|
+
return service_has(key="privileged", equals=True)
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def decorator(func: Callable) -> Callable:
|
|
174
|
+
matcher_result = func()
|
|
175
|
+
|
|
176
|
+
if hasattr(matcher_result, "to_dict"):
|
|
177
|
+
matcher_dict = matcher_result.to_dict()
|
|
178
|
+
elif isinstance(matcher_result, dict):
|
|
179
|
+
matcher_dict = matcher_result
|
|
180
|
+
else:
|
|
181
|
+
raise ValueError(f"Rule {id} must return a matcher or dict")
|
|
182
|
+
|
|
183
|
+
metadata = RuleMetadata(
|
|
184
|
+
id=id,
|
|
185
|
+
name=name or func.__name__.replace("_", " ").title(),
|
|
186
|
+
severity=severity,
|
|
187
|
+
category=category,
|
|
188
|
+
cwe=cwe,
|
|
189
|
+
cve=cve,
|
|
190
|
+
tags=tags,
|
|
191
|
+
message=message or f"Security issue detected by {id}",
|
|
192
|
+
file_pattern="**/docker-compose*.yml",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
rule_def = ComposeRuleDefinition(
|
|
196
|
+
metadata=metadata,
|
|
197
|
+
matcher=matcher_dict,
|
|
198
|
+
rule_function=func,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
_compose_rules.append(rule_def)
|
|
202
|
+
_register_rule() # Enable auto-execution if running as script
|
|
203
|
+
|
|
204
|
+
return func
|
|
205
|
+
|
|
206
|
+
return decorator
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_dockerfile_rules() -> List[DockerfileRuleDefinition]:
|
|
210
|
+
"""Get all registered Dockerfile rules."""
|
|
211
|
+
return _dockerfile_rules.copy()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_compose_rules() -> List[ComposeRuleDefinition]:
|
|
215
|
+
"""Get all registered docker-compose rules."""
|
|
216
|
+
return _compose_rules.copy()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def clear_rules():
|
|
220
|
+
"""Clear all registered rules (for testing)."""
|
|
221
|
+
global _dockerfile_rules, _compose_rules
|
|
222
|
+
_dockerfile_rules = []
|
|
223
|
+
_compose_rules = []
|
rules/container_ir.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JSON IR (Intermediate Representation) compiler for container rules.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import List, Dict, Any
|
|
7
|
+
|
|
8
|
+
from .container_decorators import (
|
|
9
|
+
get_dockerfile_rules,
|
|
10
|
+
get_compose_rules,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def compile_dockerfile_rules() -> List[Dict[str, Any]]:
|
|
15
|
+
"""
|
|
16
|
+
Compile all Dockerfile rules to JSON IR.
|
|
17
|
+
|
|
18
|
+
Returns list of rule definitions ready for Go executor.
|
|
19
|
+
"""
|
|
20
|
+
rules = get_dockerfile_rules()
|
|
21
|
+
compiled = []
|
|
22
|
+
|
|
23
|
+
for rule in rules:
|
|
24
|
+
ir = {
|
|
25
|
+
"id": rule.metadata.id,
|
|
26
|
+
"name": rule.metadata.name,
|
|
27
|
+
"severity": rule.metadata.severity,
|
|
28
|
+
"category": rule.metadata.category,
|
|
29
|
+
"cwe": rule.metadata.cwe,
|
|
30
|
+
"message": rule.metadata.message,
|
|
31
|
+
"file_pattern": rule.metadata.file_pattern,
|
|
32
|
+
"rule_type": "dockerfile",
|
|
33
|
+
"matcher": rule.matcher,
|
|
34
|
+
}
|
|
35
|
+
compiled.append(ir)
|
|
36
|
+
|
|
37
|
+
return compiled
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def compile_compose_rules() -> List[Dict[str, Any]]:
|
|
41
|
+
"""
|
|
42
|
+
Compile all docker-compose rules to JSON IR.
|
|
43
|
+
|
|
44
|
+
Returns list of rule definitions ready for Go executor.
|
|
45
|
+
"""
|
|
46
|
+
rules = get_compose_rules()
|
|
47
|
+
compiled = []
|
|
48
|
+
|
|
49
|
+
for rule in rules:
|
|
50
|
+
ir = {
|
|
51
|
+
"id": rule.metadata.id,
|
|
52
|
+
"name": rule.metadata.name,
|
|
53
|
+
"severity": rule.metadata.severity,
|
|
54
|
+
"category": rule.metadata.category,
|
|
55
|
+
"cwe": rule.metadata.cwe,
|
|
56
|
+
"message": rule.metadata.message,
|
|
57
|
+
"file_pattern": rule.metadata.file_pattern,
|
|
58
|
+
"rule_type": "compose",
|
|
59
|
+
"matcher": rule.matcher,
|
|
60
|
+
}
|
|
61
|
+
compiled.append(ir)
|
|
62
|
+
|
|
63
|
+
return compiled
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def compile_all_rules() -> Dict[str, List[Dict[str, Any]]]:
|
|
67
|
+
"""
|
|
68
|
+
Compile all container rules to JSON IR.
|
|
69
|
+
|
|
70
|
+
Returns dict with 'dockerfile' and 'compose' rule lists.
|
|
71
|
+
"""
|
|
72
|
+
return {
|
|
73
|
+
"dockerfile": compile_dockerfile_rules(),
|
|
74
|
+
"compose": compile_compose_rules(),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def compile_to_json(pretty: bool = True) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Compile all rules to JSON string.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
pretty: If True, format with indentation.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
JSON string of all compiled rules.
|
|
87
|
+
"""
|
|
88
|
+
compiled = compile_all_rules()
|
|
89
|
+
if pretty:
|
|
90
|
+
return json.dumps(compiled, indent=2)
|
|
91
|
+
return json.dumps(compiled)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def write_ir_file(filepath: str, pretty: bool = True):
|
|
95
|
+
"""
|
|
96
|
+
Write compiled rules to JSON file.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
filepath: Output file path.
|
|
100
|
+
pretty: If True, format with indentation.
|
|
101
|
+
"""
|
|
102
|
+
json_str = compile_to_json(pretty=pretty)
|
|
103
|
+
with open(filepath, "w") as f:
|
|
104
|
+
f.write(json_str)
|