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,270 @@
1
+ """
2
+ Java-specific LanguageServices factory.
3
+
4
+ Implements the LanguageServices abstract class for Java, wiring together:
5
+ - JavaTypeResolver (cross-file symbol + type resolution)
6
+ - JavaConstantResolver (application.properties / @Value)
7
+ - JavaPathResolver (String.format / concatenation — stub for now)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from pathlib import Path
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ from ..services import (
17
+ AnalysisContext,
18
+ LanguageServices,
19
+ ResolvedConstant,
20
+ ResolvedPath,
21
+ TypeResolver,
22
+ )
23
+ from ..services import (
24
+ ConstantResolver as ConstantResolverProtocol,
25
+ )
26
+ from ..services import (
27
+ PathResolver as PathResolverProtocol,
28
+ )
29
+ from .type_resolver import JavaTypeResolver
30
+
31
+ if TYPE_CHECKING:
32
+ from ..base import ParsedFile
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ # =============================================================================
38
+ # Constant resolver — reads application.properties / application.yml
39
+ # =============================================================================
40
+
41
+
42
+ class JavaConstantResolver:
43
+ """
44
+ Resolves constants from Spring application properties and @Value annotations.
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ parsed_files: list[ParsedFile],
50
+ project_root: Path | None = None,
51
+ ) -> None:
52
+ self._constants: dict[str, str] = {}
53
+ self._load_properties(project_root)
54
+ self._scan_value_annotations(parsed_files)
55
+
56
+ def _load_properties(self, project_root: Path | None) -> None:
57
+ if project_root is None:
58
+ return
59
+ for candidate in [
60
+ project_root / "src" / "main" / "resources" / "application.properties",
61
+ project_root / "src" / "main" / "resources" / "application.yml",
62
+ project_root / "application.properties",
63
+ project_root / "application.yml",
64
+ ]:
65
+ if not candidate.exists():
66
+ continue
67
+ try:
68
+ if candidate.suffix == ".properties":
69
+ self._parse_properties_file(candidate)
70
+ elif candidate.suffix in (".yml", ".yaml"):
71
+ self._parse_yaml_file(candidate)
72
+ except Exception as e:
73
+ logger.debug("Could not load %s: %s", candidate, e)
74
+
75
+ def _parse_properties_file(self, path: Path) -> None:
76
+ for line in path.read_text(encoding="utf-8").splitlines():
77
+ line = line.strip()
78
+ if not line or line.startswith("#"):
79
+ continue
80
+ if "=" in line:
81
+ key, _, value = line.partition("=")
82
+ self._constants[key.strip()] = value.strip()
83
+
84
+ def _parse_yaml_file(self, path: Path) -> None:
85
+ try:
86
+ import yaml # type: ignore
87
+
88
+ data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
89
+ self._flatten_yaml(data, "")
90
+ except ImportError:
91
+ pass # PyYAML not available, skip
92
+
93
+ def _flatten_yaml(self, data: Any, prefix: str) -> None:
94
+ if isinstance(data, dict):
95
+ for k, v in data.items():
96
+ full_key = f"{prefix}.{k}" if prefix else k
97
+ self._flatten_yaml(v, full_key)
98
+ elif isinstance(data, (str, int, float, bool)):
99
+ self._constants[prefix] = str(data)
100
+
101
+ def _scan_value_annotations(self, parsed_files: list[ParsedFile]) -> None:
102
+ """Extract @Value("${key}") and @Value("#{key}") from parsed Java files.
103
+
104
+ Spring's @Value annotation injects property values at runtime.
105
+ We capture the property key so the constant resolver can answer
106
+ lookups for those keys against application.properties / .yml.
107
+ """
108
+ import re
109
+
110
+ _value_re = re.compile(r"[$#]\{([^}]+)\}")
111
+
112
+ for pf in parsed_files:
113
+ if not pf.success:
114
+ continue
115
+ for cls in pf.classes:
116
+ for field in cls.fields:
117
+ # ParsedField decorators carry annotation args as positional
118
+ # or named values captured by the Java parser.
119
+ for dec in getattr(field, "decorators", []) or []:
120
+ if dec.name != "Value":
121
+ continue
122
+ # Positional arg 0 or keyword "value"
123
+ raw: str | None = None
124
+ if dec.positional_args:
125
+ raw = str(dec.positional_args[0])
126
+ if raw is None and dec.arguments:
127
+ raw = str(dec.arguments.get("value", "")) or None
128
+ if not raw:
129
+ continue
130
+ m = _value_re.search(raw)
131
+ if m:
132
+ key = m.group(1)
133
+ # If already in properties, keep that value;
134
+ # otherwise register the key with an empty sentinel
135
+ # so callers know the field is externalised.
136
+ if key not in self._constants:
137
+ self._constants[key] = ""
138
+
139
+ # ConstantResolver protocol
140
+ def resolve(
141
+ self,
142
+ expression: str,
143
+ in_file: Path | None = None,
144
+ ) -> ResolvedConstant | None:
145
+ # Handle ${key} and #{key} patterns
146
+ key = expression
147
+ if key.startswith("${") and key.endswith("}") or key.startswith("#{") and key.endswith("}"):
148
+ key = key[2:-1]
149
+
150
+ value = self._constants.get(key)
151
+ if value is not None:
152
+ return ResolvedConstant(
153
+ name=key,
154
+ value=value,
155
+ source_type="properties",
156
+ is_default=True,
157
+ )
158
+ return None
159
+
160
+ def resolve_value(
161
+ self,
162
+ expression: str,
163
+ in_file: Path | None = None,
164
+ ) -> str | None:
165
+ result = self.resolve(expression, in_file)
166
+ return result.value if result else None
167
+
168
+ def get_all_constants(self) -> dict[str, str]:
169
+ return dict(self._constants)
170
+
171
+
172
+ # =============================================================================
173
+ # Path resolver — handles Spring path concatenation
174
+ # =============================================================================
175
+
176
+
177
+ class JavaPathResolver:
178
+ """
179
+ Resolves computed route paths from String concatenation / String.format().
180
+
181
+ Currently a lightweight stub — passes through paths it cannot resolve.
182
+ """
183
+
184
+ def resolve(
185
+ self,
186
+ path_expression: str,
187
+ file_path: Path,
188
+ additional_context: dict[str, str] | None = None,
189
+ ) -> ResolvedPath:
190
+ # If it's already a clean path literal, pass through
191
+ if path_expression.startswith("/"):
192
+ return ResolvedPath(
193
+ path=path_expression,
194
+ confidence=1.0,
195
+ is_fully_resolved=True,
196
+ )
197
+ return ResolvedPath(
198
+ path=path_expression,
199
+ confidence=0.5,
200
+ is_fully_resolved=False,
201
+ unresolved_vars=[path_expression],
202
+ )
203
+
204
+ def get_all_constants(self) -> dict[str, str]:
205
+ return {}
206
+
207
+
208
+ # =============================================================================
209
+ # JavaLanguageServices factory
210
+ # =============================================================================
211
+
212
+
213
+ class JavaLanguageServices(LanguageServices):
214
+ """
215
+ Factory for Java analysis services.
216
+
217
+ Returns a JavaTypeResolver, optional JavaConstantResolver,
218
+ and a lightweight JavaPathResolver.
219
+ """
220
+
221
+ def build_type_resolver(
222
+ self,
223
+ parsed_files: list[ParsedFile],
224
+ project_root: Path | None = None,
225
+ ) -> TypeResolver:
226
+ return JavaTypeResolver(parsed_files)
227
+
228
+ def build_constant_resolver(
229
+ self,
230
+ parsed_files: list[ParsedFile],
231
+ project_root: Path | None = None,
232
+ ) -> ConstantResolverProtocol | None:
233
+ return JavaConstantResolver(parsed_files, project_root)
234
+
235
+ def build_path_resolver(
236
+ self,
237
+ parsed_files: list[ParsedFile],
238
+ project_root: Path | None = None,
239
+ ) -> PathResolverProtocol | None:
240
+ return JavaPathResolver()
241
+
242
+ def build_framework_services(
243
+ self,
244
+ framework: str,
245
+ parsed_files: list[ParsedFile],
246
+ project_root: Path | None = None,
247
+ ) -> dict[str, Any]:
248
+ # Spring doesn't need a RouterRegistry equivalent —
249
+ # class-level @RequestMapping is handled in the plugin directly.
250
+ return {}
251
+
252
+ def build_context(
253
+ self,
254
+ parsed_files: list[ParsedFile],
255
+ project_root: Path | None = None,
256
+ framework: str | None = None,
257
+ ) -> AnalysisContext:
258
+ type_resolver = self.build_type_resolver(parsed_files, project_root)
259
+ constant_resolver = self.build_constant_resolver(parsed_files, project_root)
260
+ path_resolver = self.build_path_resolver(parsed_files, project_root)
261
+
262
+ return AnalysisContext(
263
+ type_resolver=type_resolver,
264
+ constant_resolver=constant_resolver,
265
+ path_resolver=path_resolver,
266
+ router_registry=None,
267
+ project_root=project_root,
268
+ all_parsed_files=parsed_files,
269
+ language_services={},
270
+ )