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.
@@ -0,0 +1,230 @@
1
+ """
2
+ Matcher functions for Dockerfile and docker-compose rules.
3
+ """
4
+
5
+ from typing import Optional, Any, List, Dict
6
+ from dataclasses import dataclass, field
7
+
8
+
9
+ @dataclass
10
+ class Matcher:
11
+ """Base class for all matchers."""
12
+
13
+ type: str
14
+ params: Dict[str, Any] = field(default_factory=dict)
15
+
16
+ def to_dict(self) -> Dict[str, Any]:
17
+ """Convert matcher to dictionary for JSON IR."""
18
+ return {"type": self.type, **self.params}
19
+
20
+
21
+ # --- Dockerfile Matchers ---
22
+
23
+
24
+ def instruction(
25
+ type: str,
26
+ # FROM instruction
27
+ base_image: Optional[str] = None,
28
+ image_tag: Optional[str] = None,
29
+ image_tag_regex: Optional[str] = None,
30
+ missing_digest: Optional[bool] = None,
31
+ # USER instruction
32
+ user_name: Optional[str] = None,
33
+ user_name_regex: Optional[str] = None,
34
+ # EXPOSE instruction
35
+ port: Optional[int] = None,
36
+ port_less_than: Optional[int] = None,
37
+ port_greater_than: Optional[int] = None,
38
+ protocol: Optional[str] = None,
39
+ # ARG instruction
40
+ arg_name: Optional[str] = None,
41
+ arg_name_regex: Optional[str] = None,
42
+ # COPY/ADD instruction
43
+ copy_from: Optional[str] = None,
44
+ chown: Optional[str] = None,
45
+ missing_flag: Optional[str] = None,
46
+ # HEALTHCHECK instruction
47
+ healthcheck_interval_less_than: Optional[str] = None,
48
+ healthcheck_timeout_greater_than: Optional[str] = None,
49
+ healthcheck_retries_greater_than: Optional[int] = None,
50
+ # LABEL instruction
51
+ label_key: Optional[str] = None,
52
+ label_value_regex: Optional[str] = None,
53
+ # CMD/ENTRYPOINT
54
+ command_form: Optional[str] = None,
55
+ # WORKDIR
56
+ workdir_not_absolute: Optional[bool] = None,
57
+ # STOPSIGNAL
58
+ signal_not_in: Optional[List[str]] = None,
59
+ # Generic matchers
60
+ contains: Optional[str] = None,
61
+ not_contains: Optional[str] = None,
62
+ regex: Optional[str] = None,
63
+ not_regex: Optional[str] = None,
64
+ # Custom validation
65
+ validate: Optional[callable] = None,
66
+ ) -> Matcher:
67
+ """
68
+ Match a Dockerfile instruction by type and properties.
69
+
70
+ Examples:
71
+ instruction(type="FROM", image_tag="latest")
72
+ instruction(type="USER", user_name="root")
73
+ instruction(type="ARG", arg_name_regex=r"(?i).*password.*")
74
+ """
75
+ params = {"instruction": type}
76
+
77
+ # Add non-None parameters
78
+ if base_image is not None:
79
+ params["base_image"] = base_image
80
+ if image_tag is not None:
81
+ params["image_tag"] = image_tag
82
+ if image_tag_regex is not None:
83
+ params["image_tag_regex"] = image_tag_regex
84
+ if missing_digest is not None:
85
+ params["missing_digest"] = missing_digest
86
+ if user_name is not None:
87
+ params["user_name"] = user_name
88
+ if user_name_regex is not None:
89
+ params["user_name_regex"] = user_name_regex
90
+ if port is not None:
91
+ params["port"] = port
92
+ if port_less_than is not None:
93
+ params["port_less_than"] = port_less_than
94
+ if port_greater_than is not None:
95
+ params["port_greater_than"] = port_greater_than
96
+ if protocol is not None:
97
+ params["protocol"] = protocol
98
+ if arg_name is not None:
99
+ params["arg_name"] = arg_name
100
+ if arg_name_regex is not None:
101
+ params["arg_name_regex"] = arg_name_regex
102
+ if copy_from is not None:
103
+ params["copy_from"] = copy_from
104
+ if chown is not None:
105
+ params["chown"] = chown
106
+ if missing_flag is not None:
107
+ params["missing_flag"] = missing_flag
108
+ if healthcheck_interval_less_than is not None:
109
+ params["healthcheck_interval_less_than"] = healthcheck_interval_less_than
110
+ if healthcheck_timeout_greater_than is not None:
111
+ params["healthcheck_timeout_greater_than"] = healthcheck_timeout_greater_than
112
+ if healthcheck_retries_greater_than is not None:
113
+ params["healthcheck_retries_greater_than"] = healthcheck_retries_greater_than
114
+ if label_key is not None:
115
+ params["label_key"] = label_key
116
+ if label_value_regex is not None:
117
+ params["label_value_regex"] = label_value_regex
118
+ if command_form is not None:
119
+ params["command_form"] = command_form
120
+ if workdir_not_absolute is not None:
121
+ params["workdir_not_absolute"] = workdir_not_absolute
122
+ if signal_not_in is not None:
123
+ params["signal_not_in"] = signal_not_in
124
+ if contains is not None:
125
+ params["contains"] = contains
126
+ if not_contains is not None:
127
+ params["not_contains"] = not_contains
128
+ if regex is not None:
129
+ params["regex"] = regex
130
+ if not_regex is not None:
131
+ params["not_regex"] = not_regex
132
+ if validate is not None:
133
+ # Custom validation stored separately
134
+ params["has_custom_validate"] = True
135
+
136
+ return Matcher(type="instruction", params=params)
137
+
138
+
139
+ def missing(
140
+ instruction: str,
141
+ label_key: Optional[str] = None,
142
+ ) -> Matcher:
143
+ """
144
+ Match when a Dockerfile instruction is missing.
145
+
146
+ Examples:
147
+ missing(instruction="USER")
148
+ missing(instruction="HEALTHCHECK")
149
+ missing(instruction="LABEL", label_key="maintainer")
150
+ """
151
+ params = {"instruction": instruction}
152
+ if label_key is not None:
153
+ params["label_key"] = label_key
154
+
155
+ return Matcher(type="missing_instruction", params=params)
156
+
157
+
158
+ # --- docker-compose Matchers ---
159
+
160
+
161
+ def service_has(
162
+ key: str,
163
+ equals: Optional[Any] = None,
164
+ not_equals: Optional[Any] = None,
165
+ contains: Optional[str] = None,
166
+ not_contains: Optional[str] = None,
167
+ contains_any: Optional[List[str]] = None,
168
+ regex: Optional[str] = None,
169
+ env_name_regex: Optional[str] = None,
170
+ env_value_regex: Optional[str] = None,
171
+ volume_type: Optional[str] = None,
172
+ source_regex: Optional[str] = None,
173
+ target_regex: Optional[str] = None,
174
+ published_port_less_than: Optional[int] = None,
175
+ ) -> Matcher:
176
+ """
177
+ Match docker-compose services with specific properties.
178
+
179
+ Examples:
180
+ service_has(key="privileged", equals=True)
181
+ service_has(key="volumes", contains="/var/run/docker.sock")
182
+ service_has(key="network_mode", equals="host")
183
+ """
184
+ params = {"key": key}
185
+
186
+ if equals is not None:
187
+ params["equals"] = equals
188
+ if not_equals is not None:
189
+ params["not_equals"] = not_equals
190
+ if contains is not None:
191
+ params["contains"] = contains
192
+ if not_contains is not None:
193
+ params["not_contains"] = not_contains
194
+ if contains_any is not None:
195
+ params["contains_any"] = contains_any
196
+ if regex is not None:
197
+ params["regex"] = regex
198
+ if env_name_regex is not None:
199
+ params["env_name_regex"] = env_name_regex
200
+ if env_value_regex is not None:
201
+ params["env_value_regex"] = env_value_regex
202
+ if volume_type is not None:
203
+ params["volume_type"] = volume_type
204
+ if source_regex is not None:
205
+ params["source_regex"] = source_regex
206
+ if target_regex is not None:
207
+ params["target_regex"] = target_regex
208
+ if published_port_less_than is not None:
209
+ params["published_port_less_than"] = published_port_less_than
210
+
211
+ return Matcher(type="service_has", params=params)
212
+
213
+
214
+ def service_missing(
215
+ key: str,
216
+ value_contains: Optional[str] = None,
217
+ ) -> Matcher:
218
+ """
219
+ Match docker-compose services missing a property.
220
+
221
+ Examples:
222
+ service_missing(key="read_only")
223
+ service_missing(key="security_opt")
224
+ service_missing(key="security_opt", value_contains="no-new-privileges")
225
+ """
226
+ params = {"key": key}
227
+ if value_contains is not None:
228
+ params["value_contains"] = value_contains
229
+
230
+ return Matcher(type="service_missing", params=params)
@@ -0,0 +1,115 @@
1
+ """
2
+ Programmatic access to Dockerfile and docker-compose objects.
3
+ """
4
+
5
+ from typing import Callable, Dict, Any
6
+ from dataclasses import dataclass
7
+
8
+
9
+ @dataclass
10
+ class ProgrammaticMatcher:
11
+ """Wraps a custom validation function."""
12
+
13
+ check_function: Callable
14
+ description: str = ""
15
+
16
+ def to_dict(self) -> Dict[str, Any]:
17
+ return {
18
+ "type": "programmatic",
19
+ "has_callable": True,
20
+ "description": self.description,
21
+ }
22
+
23
+
24
+ def custom_check(check: Callable, description: str = "") -> ProgrammaticMatcher:
25
+ """
26
+ Create a custom validation function.
27
+
28
+ The check function receives the parsed dockerfile or compose object
29
+ and should return True if the rule matches (vulnerability found).
30
+
31
+ Example:
32
+ @dockerfile_rule(id="DOCKER-CUSTOM-001")
33
+ def last_user_is_root():
34
+ def check(dockerfile):
35
+ final_user = dockerfile.get_final_user()
36
+ return final_user is None or final_user.user_name == "root"
37
+ return custom_check(check, "Check if last USER is root")
38
+ """
39
+ return ProgrammaticMatcher(check_function=check, description=description)
40
+
41
+
42
+ class DockerfileAccess:
43
+ """
44
+ Provides programmatic access to Dockerfile structure.
45
+ Used in custom validation functions.
46
+ """
47
+
48
+ def __init__(self, dockerfile_graph):
49
+ self._graph = dockerfile_graph
50
+
51
+ def get_instructions(self, instruction_type: str):
52
+ """Get all instructions of a type."""
53
+ return self._graph.GetInstructions(instruction_type)
54
+
55
+ def has_instruction(self, instruction_type: str) -> bool:
56
+ """Check if instruction type exists."""
57
+ return self._graph.HasInstruction(instruction_type)
58
+
59
+ def get_final_user(self):
60
+ """Get the last USER instruction."""
61
+ return self._graph.GetFinalUser()
62
+
63
+ def is_running_as_root(self) -> bool:
64
+ """Check if container runs as root."""
65
+ return self._graph.IsRunningAsRoot()
66
+
67
+ def get_stages(self):
68
+ """Get all build stages."""
69
+ return self._graph.GetStages()
70
+
71
+ def is_multi_stage(self) -> bool:
72
+ """Check if Dockerfile uses multi-stage build."""
73
+ return self._graph.IsMultiStage()
74
+
75
+ def get_stage_by_alias(self, alias: str):
76
+ """Get a stage by its AS alias."""
77
+ return self._graph.GetStageByAlias(alias)
78
+
79
+ def get_final_stage(self):
80
+ """Get the final build stage."""
81
+ return self._graph.GetFinalStage()
82
+
83
+
84
+ class ComposeAccess:
85
+ """
86
+ Provides programmatic access to docker-compose structure.
87
+ Used in custom validation functions.
88
+ """
89
+
90
+ def __init__(self, compose_graph):
91
+ self._graph = compose_graph
92
+
93
+ def get_services(self):
94
+ """Get all service names."""
95
+ return self._graph.GetServices()
96
+
97
+ def service_has(self, service_name: str, key: str, value) -> bool:
98
+ """Check if service has property with value."""
99
+ return self._graph.ServiceHas(service_name, key, value)
100
+
101
+ def service_get(self, service_name: str, key: str):
102
+ """Get service property value."""
103
+ return self._graph.ServiceGet(service_name, key)
104
+
105
+ def get_privileged_services(self):
106
+ """Get services with privileged: true."""
107
+ return self._graph.GetPrivilegedServices()
108
+
109
+ def services_with_docker_socket(self):
110
+ """Get services mounting Docker socket."""
111
+ return self._graph.ServicesWithDockerSocket()
112
+
113
+ def services_with_host_network(self):
114
+ """Get services using host network mode."""
115
+ return self._graph.ServicesWithHostNetwork()
File without changes
File without changes