apisec-code-bolt 0.1.0__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 (111) hide show
  1. apisec_code_bolt/__init__.py +42 -0
  2. apisec_code_bolt/__main__.py +11 -0
  3. apisec_code_bolt/analysis/__init__.py +96 -0
  4. apisec_code_bolt/analysis/analyzer.py +2309 -0
  5. apisec_code_bolt/analysis/binding_tracker.py +341 -0
  6. apisec_code_bolt/analysis/call_graph.py +1197 -0
  7. apisec_code_bolt/analysis/call_graph_types.py +332 -0
  8. apisec_code_bolt/analysis/call_resolver.py +988 -0
  9. apisec_code_bolt/analysis/capability_tagger.py +322 -0
  10. apisec_code_bolt/analysis/config_scanner.py +197 -0
  11. apisec_code_bolt/analysis/data_flow.py +1883 -0
  12. apisec_code_bolt/analysis/dependency_extractor.py +959 -0
  13. apisec_code_bolt/analysis/flow_analysis.py +1406 -0
  14. apisec_code_bolt/analysis/hof_catalog.py +61 -0
  15. apisec_code_bolt/analysis/integration_detector.py +1399 -0
  16. apisec_code_bolt/analysis/literal_scanner.py +300 -0
  17. apisec_code_bolt/analysis/path_normalizer.py +55 -0
  18. apisec_code_bolt/analysis/read_site_detector.py +310 -0
  19. apisec_code_bolt/analysis/request_patterns.py +162 -0
  20. apisec_code_bolt/analysis/sensitivity_classifier.py +224 -0
  21. apisec_code_bolt/analysis/sink_evidence.py +333 -0
  22. apisec_code_bolt/analysis/url_prefix_resolver.py +338 -0
  23. apisec_code_bolt/cli/__init__.py +5 -0
  24. apisec_code_bolt/cli/exit_codes.py +17 -0
  25. apisec_code_bolt/cli/main.py +1069 -0
  26. apisec_code_bolt/cloud/__init__.py +1 -0
  27. apisec_code_bolt/cloud/apisec_client.py +118 -0
  28. apisec_code_bolt/cloud/client.py +255 -0
  29. apisec_code_bolt/core/__init__.py +75 -0
  30. apisec_code_bolt/core/config.py +528 -0
  31. apisec_code_bolt/core/credentials.py +65 -0
  32. apisec_code_bolt/core/discovery.py +433 -0
  33. apisec_code_bolt/core/log_format.py +115 -0
  34. apisec_code_bolt/core/manifest.py +1009 -0
  35. apisec_code_bolt/core/repo.py +280 -0
  36. apisec_code_bolt/core/state.py +59 -0
  37. apisec_code_bolt/core/telemetry.py +451 -0
  38. apisec_code_bolt/core/types.py +587 -0
  39. apisec_code_bolt/fingerprinting/__init__.py +1 -0
  40. apisec_code_bolt/frameworks/__init__.py +29 -0
  41. apisec_code_bolt/frameworks/_jwt_common.py +50 -0
  42. apisec_code_bolt/frameworks/auth_helpers.py +437 -0
  43. apisec_code_bolt/frameworks/base.py +608 -0
  44. apisec_code_bolt/frameworks/dotnet/__init__.py +17 -0
  45. apisec_code_bolt/frameworks/dotnet/_path_helpers.py +43 -0
  46. apisec_code_bolt/frameworks/dotnet/aspnet_plugin.py +2546 -0
  47. apisec_code_bolt/frameworks/dotnet/grpc_plugin.py +559 -0
  48. apisec_code_bolt/frameworks/dotnet/jwt_config_extractor.py +545 -0
  49. apisec_code_bolt/frameworks/dotnet/legacy_aspnet_plugin.py +732 -0
  50. apisec_code_bolt/frameworks/dotnet/refit_plugin.py +374 -0
  51. apisec_code_bolt/frameworks/dotnet/wcf_plugin.py +1239 -0
  52. apisec_code_bolt/frameworks/java/__init__.py +6 -0
  53. apisec_code_bolt/frameworks/java/_annotations.py +167 -0
  54. apisec_code_bolt/frameworks/java/_constraints.py +128 -0
  55. apisec_code_bolt/frameworks/java/graphql_plugin.py +287 -0
  56. apisec_code_bolt/frameworks/java/jaxrs_plugin.py +748 -0
  57. apisec_code_bolt/frameworks/java/jwt_config_extractor.py +361 -0
  58. apisec_code_bolt/frameworks/java/micronaut_plugin.py +1059 -0
  59. apisec_code_bolt/frameworks/java/spring_plugin.py +1293 -0
  60. apisec_code_bolt/frameworks/js/__init__.py +8 -0
  61. apisec_code_bolt/frameworks/js/express_plugin.py +391 -0
  62. apisec_code_bolt/frameworks/js/fastify_plugin.py +381 -0
  63. apisec_code_bolt/frameworks/js/graphql_plugin.py +198 -0
  64. apisec_code_bolt/frameworks/js/nestjs_plugin.py +423 -0
  65. apisec_code_bolt/frameworks/python/__init__.py +19 -0
  66. apisec_code_bolt/frameworks/python/celery_plugin.py +393 -0
  67. apisec_code_bolt/frameworks/python/click_plugin.py +427 -0
  68. apisec_code_bolt/frameworks/python/django_plugin.py +867 -0
  69. apisec_code_bolt/frameworks/python/fastapi/__init__.py +28 -0
  70. apisec_code_bolt/frameworks/python/fastapi/plugin.py +1390 -0
  71. apisec_code_bolt/frameworks/python/flask_plugin.py +205 -0
  72. apisec_code_bolt/frameworks/python/graphql_plugin.py +274 -0
  73. apisec_code_bolt/frameworks/python/prefect_plugin.py +251 -0
  74. apisec_code_bolt/frameworks/python/webhook_plugin.py +255 -0
  75. apisec_code_bolt/parsing/__init__.py +62 -0
  76. apisec_code_bolt/parsing/base.py +554 -0
  77. apisec_code_bolt/parsing/csharp/__init__.py +5 -0
  78. apisec_code_bolt/parsing/csharp/language_services.py +203 -0
  79. apisec_code_bolt/parsing/csharp/literals.py +72 -0
  80. apisec_code_bolt/parsing/csharp/parser.py +1158 -0
  81. apisec_code_bolt/parsing/csharp/type_resolver.py +568 -0
  82. apisec_code_bolt/parsing/js/__init__.py +5 -0
  83. apisec_code_bolt/parsing/js/language_services.py +118 -0
  84. apisec_code_bolt/parsing/js/parser.py +622 -0
  85. apisec_code_bolt/parsing/jvm/__init__.py +7 -0
  86. apisec_code_bolt/parsing/jvm/language_services.py +270 -0
  87. apisec_code_bolt/parsing/jvm/parser.py +774 -0
  88. apisec_code_bolt/parsing/jvm/type_resolver.py +422 -0
  89. apisec_code_bolt/parsing/python/__init__.py +150 -0
  90. apisec_code_bolt/parsing/python/cbv_extractor.py +606 -0
  91. apisec_code_bolt/parsing/python/constant_resolver.py +500 -0
  92. apisec_code_bolt/parsing/python/cross_file_resolver.py +1054 -0
  93. apisec_code_bolt/parsing/python/dynamic_route_detector.py +532 -0
  94. apisec_code_bolt/parsing/python/expression_utils.py +221 -0
  95. apisec_code_bolt/parsing/python/extraction_types.py +271 -0
  96. apisec_code_bolt/parsing/python/language_services.py +487 -0
  97. apisec_code_bolt/parsing/python/parameter_analyzer.py +789 -0
  98. apisec_code_bolt/parsing/python/parser.py +719 -0
  99. apisec_code_bolt/parsing/python/path_resolver.py +576 -0
  100. apisec_code_bolt/parsing/python/router_registry.py +806 -0
  101. apisec_code_bolt/parsing/python/type_resolver.py +730 -0
  102. apisec_code_bolt/parsing/python/visitors.py +1544 -0
  103. apisec_code_bolt/parsing/services.py +544 -0
  104. apisec_code_bolt/query/__init__.py +1 -0
  105. apisec_code_bolt/query/ast_cache.py +182 -0
  106. apisec_code_bolt/query/executor.py +283 -0
  107. apisec_code_bolt/query/handlers.py +832 -0
  108. apisec_code_bolt-0.1.0.dist-info/METADATA +230 -0
  109. apisec_code_bolt-0.1.0.dist-info/RECORD +111 -0
  110. apisec_code_bolt-0.1.0.dist-info/WHEEL +4 -0
  111. apisec_code_bolt-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,341 @@
1
+ """
2
+ PyCG-style variable-to-type binding tracker with fixed-point propagation.
3
+
4
+ Tracks what types each variable could be bound to so that method calls on
5
+ dynamically-typed receivers can be resolved to concrete targets.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ from .call_graph_types import TypeBinding
13
+
14
+
15
+ class BindingTracker:
16
+ """
17
+ Tracks variable-to-type bindings for dynamic dispatch resolution.
18
+
19
+ This implements the core of the PyCG algorithm: we track what types
20
+ each variable could be bound to, so we can resolve method calls.
21
+
22
+ KEY IMPROVEMENT: Uses fixed-point iteration to propagate bindings
23
+ transitively (x = y means x has all types that y has).
24
+ """
25
+
26
+ def __init__(self):
27
+ # Per-function bindings: (func_qname, var_name) -> TypeBinding
28
+ self._local_bindings: dict[tuple[str, str], TypeBinding] = {}
29
+
30
+ # Module-level bindings: (file_path, var_name) -> TypeBinding
31
+ self._module_bindings: dict[tuple[Path, str], TypeBinding] = {}
32
+
33
+ # Class attribute bindings: (class_qname, attr_name) -> TypeBinding
34
+ self._class_bindings: dict[tuple[str, str], TypeBinding] = {}
35
+
36
+ # Return value bindings: func_qname -> possible return types/callables
37
+ self._return_bindings: dict[str, TypeBinding] = {}
38
+
39
+ # Protocol/ABC implementations: protocol_name -> [implementing_classes]
40
+ self._protocol_implementations: dict[str, set[str]] = {}
41
+
42
+ # Propagation tracking
43
+ self._propagation_complete = False
44
+
45
+ def add_assignment(
46
+ self,
47
+ variable: str,
48
+ assigned_type: str | None,
49
+ file_path: Path,
50
+ line: int,
51
+ scope_function: str | None = None,
52
+ scope_class: str | None = None,
53
+ source_variable: str | None = None,
54
+ is_callable: bool = False,
55
+ callable_target: str | None = None,
56
+ ) -> None:
57
+ """Record an assignment that binds a variable to a type."""
58
+ self._propagation_complete = False
59
+ location = (file_path, line)
60
+
61
+ if scope_function:
62
+ key = (scope_function, variable)
63
+ if key not in self._local_bindings:
64
+ self._local_bindings[key] = TypeBinding(variable=variable)
65
+ binding = self._local_bindings[key]
66
+ elif scope_class:
67
+ key = (scope_class, variable)
68
+ if key not in self._class_bindings:
69
+ self._class_bindings[key] = TypeBinding(variable=variable)
70
+ binding = self._class_bindings[key]
71
+ else:
72
+ key = (file_path, variable)
73
+ if key not in self._module_bindings:
74
+ self._module_bindings[key] = TypeBinding(variable=variable)
75
+ binding = self._module_bindings[key]
76
+
77
+ if assigned_type:
78
+ binding.possible_types.add(assigned_type)
79
+ if source_variable:
80
+ binding.source_variables.add(source_variable)
81
+ if is_callable:
82
+ binding.holds_callable = True
83
+ if callable_target:
84
+ binding.callable_targets.add(callable_target)
85
+
86
+ binding.assignment_locations.append(location)
87
+
88
+ def add_self_binding(
89
+ self,
90
+ function_qname: str,
91
+ class_qname: str,
92
+ param_name: str = "self",
93
+ ) -> None:
94
+ """Bind self/cls parameter to its enclosing class."""
95
+ self._propagation_complete = False
96
+ key = (function_qname, param_name)
97
+ binding = TypeBinding(
98
+ variable=param_name,
99
+ is_self=param_name == "self",
100
+ is_cls=param_name == "cls",
101
+ enclosing_class=class_qname,
102
+ )
103
+ binding.possible_types.add(class_qname)
104
+ self._local_bindings[key] = binding
105
+
106
+ def add_parameter(
107
+ self,
108
+ param_name: str,
109
+ function_qname: str,
110
+ type_annotation: str | None = None,
111
+ ) -> None:
112
+ """Record a function parameter (could be any compatible type)."""
113
+ self._propagation_complete = False
114
+ key = (function_qname, param_name)
115
+ binding = TypeBinding(
116
+ variable=param_name,
117
+ is_parameter=True,
118
+ parameter_type_annotation=type_annotation,
119
+ )
120
+
121
+ # If we have a type annotation, that's the possible type
122
+ if type_annotation:
123
+ binding.possible_types.add(type_annotation)
124
+ # Check if it's a protocol/ABC
125
+ if type_annotation in self._protocol_implementations:
126
+ binding.possible_types.update(self._protocol_implementations[type_annotation])
127
+
128
+ self._local_bindings[key] = binding
129
+
130
+ def add_import(
131
+ self,
132
+ local_name: str,
133
+ import_source: str,
134
+ file_path: Path,
135
+ line: int,
136
+ is_star: bool = False,
137
+ star_exports: list[str] | None = None,
138
+ ) -> None:
139
+ """Record an import binding."""
140
+ self._propagation_complete = False
141
+ key = (file_path, local_name)
142
+ binding = TypeBinding(
143
+ variable=local_name,
144
+ is_import=True,
145
+ import_source=import_source,
146
+ )
147
+ binding.assignment_locations.append((file_path, line))
148
+ self._module_bindings[key] = binding
149
+
150
+ # Handle star imports
151
+ if is_star and star_exports:
152
+ for name in star_exports:
153
+ star_key = (file_path, name)
154
+ star_binding = TypeBinding(
155
+ variable=name,
156
+ is_import=True,
157
+ import_source=f"{import_source}.{name}",
158
+ )
159
+ self._module_bindings[star_key] = star_binding
160
+
161
+ def add_return_binding(
162
+ self,
163
+ function_qname: str,
164
+ return_type: str | None = None,
165
+ returns_callable: bool = False,
166
+ callable_target: str | None = None,
167
+ ) -> None:
168
+ """Track what a function returns."""
169
+ self._propagation_complete = False
170
+ if function_qname not in self._return_bindings:
171
+ self._return_bindings[function_qname] = TypeBinding(
172
+ variable=f"<return:{function_qname}>"
173
+ )
174
+ binding = self._return_bindings[function_qname]
175
+
176
+ if return_type:
177
+ binding.possible_types.add(return_type)
178
+ if returns_callable:
179
+ binding.holds_callable = True
180
+ if callable_target:
181
+ binding.callable_targets.add(callable_target)
182
+
183
+ def add_protocol_implementation(
184
+ self,
185
+ protocol: str,
186
+ implementer: str,
187
+ ) -> None:
188
+ """Record that a class implements a protocol/ABC."""
189
+ self._propagation_complete = False
190
+ if protocol not in self._protocol_implementations:
191
+ self._protocol_implementations[protocol] = set()
192
+ self._protocol_implementations[protocol].add(implementer)
193
+
194
+ def propagate(self, max_iterations: int = 100) -> int:
195
+ """
196
+ Perform fixed-point iteration to propagate bindings transitively.
197
+
198
+ If x = y, then x should have all types that y has.
199
+
200
+ Returns: Number of iterations performed
201
+ """
202
+ if self._propagation_complete:
203
+ return 0
204
+
205
+ iterations = 0
206
+ changed = True
207
+
208
+ while changed and iterations < max_iterations:
209
+ changed = False
210
+ iterations += 1
211
+
212
+ # Propagate in local bindings
213
+ for key, binding in list(self._local_bindings.items()):
214
+ func_qname, var_name = key
215
+ for source_var in list(binding.source_variables):
216
+ source_key = (func_qname, source_var)
217
+ if source_key in self._local_bindings:
218
+ source_binding = self._local_bindings[source_key]
219
+ old_size = len(binding.possible_types)
220
+ binding.possible_types.update(source_binding.possible_types)
221
+ binding.callable_targets.update(source_binding.callable_targets)
222
+ if source_binding.holds_callable:
223
+ binding.holds_callable = True
224
+ if len(binding.possible_types) > old_size:
225
+ changed = True
226
+
227
+ # Propagate in module bindings
228
+ for key, binding in list(self._module_bindings.items()):
229
+ file_path, var_name = key
230
+ for source_var in list(binding.source_variables):
231
+ source_key = (file_path, source_var)
232
+ if source_key in self._module_bindings:
233
+ source_binding = self._module_bindings[source_key]
234
+ old_size = len(binding.possible_types)
235
+ binding.possible_types.update(source_binding.possible_types)
236
+ binding.callable_targets.update(source_binding.callable_targets)
237
+ if source_binding.holds_callable:
238
+ binding.holds_callable = True
239
+ if len(binding.possible_types) > old_size:
240
+ changed = True
241
+
242
+ # Propagate return values for call assignments
243
+ # If x = func(), x should have the return types of func
244
+ for _key, binding in list(self._local_bindings.items()):
245
+ for callable_name in list(binding.source_variables):
246
+ if callable_name in self._return_bindings:
247
+ return_binding = self._return_bindings[callable_name]
248
+ old_size = len(binding.possible_types)
249
+ binding.possible_types.update(return_binding.possible_types)
250
+ binding.callable_targets.update(return_binding.callable_targets)
251
+ if return_binding.holds_callable:
252
+ binding.holds_callable = True
253
+ if len(binding.possible_types) > old_size:
254
+ changed = True
255
+
256
+ self._propagation_complete = True
257
+ return iterations
258
+
259
+ def get_possible_types(
260
+ self,
261
+ variable: str,
262
+ file_path: Path,
263
+ scope_function: str | None = None,
264
+ ) -> set[str]:
265
+ """Get possible types for a variable in a given scope."""
266
+ if not self._propagation_complete:
267
+ self.propagate()
268
+
269
+ types: set[str] = set()
270
+
271
+ # Check local scope first
272
+ if scope_function:
273
+ key = (scope_function, variable)
274
+ if key in self._local_bindings:
275
+ types.update(self._local_bindings[key].possible_types)
276
+
277
+ # Check module scope
278
+ key = (file_path, variable)
279
+ if key in self._module_bindings:
280
+ binding = self._module_bindings[key]
281
+ types.update(binding.possible_types)
282
+ if binding.is_import and binding.import_source:
283
+ types.add(binding.import_source)
284
+
285
+ return types
286
+
287
+ def get_callable_targets(
288
+ self,
289
+ variable: str,
290
+ file_path: Path,
291
+ scope_function: str | None = None,
292
+ ) -> set[str]:
293
+ """Get callable targets for a variable that holds a function."""
294
+ if not self._propagation_complete:
295
+ self.propagate()
296
+
297
+ targets: set[str] = set()
298
+
299
+ if scope_function:
300
+ key = (scope_function, variable)
301
+ if key in self._local_bindings:
302
+ binding = self._local_bindings[key]
303
+ targets.update(binding.callable_targets)
304
+
305
+ key = (file_path, variable)
306
+ if key in self._module_bindings:
307
+ binding = self._module_bindings[key]
308
+ targets.update(binding.callable_targets)
309
+
310
+ return targets
311
+
312
+ def get_binding(
313
+ self,
314
+ variable: str,
315
+ file_path: Path,
316
+ scope_function: str | None = None,
317
+ ) -> TypeBinding | None:
318
+ """Get the binding for a variable."""
319
+ if scope_function:
320
+ key = (scope_function, variable)
321
+ if key in self._local_bindings:
322
+ return self._local_bindings[key]
323
+
324
+ key = (file_path, variable)
325
+ return self._module_bindings.get(key)
326
+
327
+ def get_return_types(self, function_qname: str) -> set[str]:
328
+ """Get the return types of a function."""
329
+ if function_qname in self._return_bindings:
330
+ return self._return_bindings[function_qname].possible_types
331
+ return set()
332
+
333
+ def get_returned_callables(self, function_qname: str) -> set[str]:
334
+ """Get callables returned by a function."""
335
+ if function_qname in self._return_bindings:
336
+ return self._return_bindings[function_qname].callable_targets
337
+ return set()
338
+
339
+ def get_protocol_implementers(self, protocol: str) -> set[str]:
340
+ """Get all classes that implement a protocol/ABC."""
341
+ return self._protocol_implementations.get(protocol, set())