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.
- apisec_code_bolt/__init__.py +42 -0
- apisec_code_bolt/__main__.py +11 -0
- apisec_code_bolt/analysis/__init__.py +96 -0
- apisec_code_bolt/analysis/analyzer.py +2309 -0
- apisec_code_bolt/analysis/binding_tracker.py +341 -0
- apisec_code_bolt/analysis/call_graph.py +1197 -0
- apisec_code_bolt/analysis/call_graph_types.py +332 -0
- apisec_code_bolt/analysis/call_resolver.py +988 -0
- apisec_code_bolt/analysis/capability_tagger.py +322 -0
- apisec_code_bolt/analysis/config_scanner.py +197 -0
- apisec_code_bolt/analysis/data_flow.py +1883 -0
- apisec_code_bolt/analysis/dependency_extractor.py +959 -0
- apisec_code_bolt/analysis/flow_analysis.py +1406 -0
- apisec_code_bolt/analysis/hof_catalog.py +61 -0
- apisec_code_bolt/analysis/integration_detector.py +1399 -0
- apisec_code_bolt/analysis/literal_scanner.py +300 -0
- apisec_code_bolt/analysis/path_normalizer.py +55 -0
- apisec_code_bolt/analysis/read_site_detector.py +310 -0
- apisec_code_bolt/analysis/request_patterns.py +162 -0
- apisec_code_bolt/analysis/sensitivity_classifier.py +224 -0
- apisec_code_bolt/analysis/sink_evidence.py +333 -0
- apisec_code_bolt/analysis/url_prefix_resolver.py +338 -0
- apisec_code_bolt/cli/__init__.py +5 -0
- apisec_code_bolt/cli/exit_codes.py +17 -0
- apisec_code_bolt/cli/main.py +1069 -0
- apisec_code_bolt/cloud/__init__.py +1 -0
- apisec_code_bolt/cloud/apisec_client.py +118 -0
- apisec_code_bolt/cloud/client.py +255 -0
- apisec_code_bolt/core/__init__.py +75 -0
- apisec_code_bolt/core/config.py +528 -0
- apisec_code_bolt/core/credentials.py +65 -0
- apisec_code_bolt/core/discovery.py +433 -0
- apisec_code_bolt/core/log_format.py +115 -0
- apisec_code_bolt/core/manifest.py +1009 -0
- apisec_code_bolt/core/repo.py +280 -0
- apisec_code_bolt/core/state.py +59 -0
- apisec_code_bolt/core/telemetry.py +451 -0
- apisec_code_bolt/core/types.py +587 -0
- apisec_code_bolt/fingerprinting/__init__.py +1 -0
- apisec_code_bolt/frameworks/__init__.py +29 -0
- apisec_code_bolt/frameworks/_jwt_common.py +50 -0
- apisec_code_bolt/frameworks/auth_helpers.py +437 -0
- apisec_code_bolt/frameworks/base.py +608 -0
- apisec_code_bolt/frameworks/dotnet/__init__.py +17 -0
- apisec_code_bolt/frameworks/dotnet/_path_helpers.py +43 -0
- apisec_code_bolt/frameworks/dotnet/aspnet_plugin.py +2546 -0
- apisec_code_bolt/frameworks/dotnet/grpc_plugin.py +559 -0
- apisec_code_bolt/frameworks/dotnet/jwt_config_extractor.py +545 -0
- apisec_code_bolt/frameworks/dotnet/legacy_aspnet_plugin.py +732 -0
- apisec_code_bolt/frameworks/dotnet/refit_plugin.py +374 -0
- apisec_code_bolt/frameworks/dotnet/wcf_plugin.py +1239 -0
- apisec_code_bolt/frameworks/java/__init__.py +6 -0
- apisec_code_bolt/frameworks/java/_annotations.py +167 -0
- apisec_code_bolt/frameworks/java/_constraints.py +128 -0
- apisec_code_bolt/frameworks/java/graphql_plugin.py +287 -0
- apisec_code_bolt/frameworks/java/jaxrs_plugin.py +748 -0
- apisec_code_bolt/frameworks/java/jwt_config_extractor.py +361 -0
- apisec_code_bolt/frameworks/java/micronaut_plugin.py +1059 -0
- apisec_code_bolt/frameworks/java/spring_plugin.py +1293 -0
- apisec_code_bolt/frameworks/js/__init__.py +8 -0
- apisec_code_bolt/frameworks/js/express_plugin.py +391 -0
- apisec_code_bolt/frameworks/js/fastify_plugin.py +381 -0
- apisec_code_bolt/frameworks/js/graphql_plugin.py +198 -0
- apisec_code_bolt/frameworks/js/nestjs_plugin.py +423 -0
- apisec_code_bolt/frameworks/python/__init__.py +19 -0
- apisec_code_bolt/frameworks/python/celery_plugin.py +393 -0
- apisec_code_bolt/frameworks/python/click_plugin.py +427 -0
- apisec_code_bolt/frameworks/python/django_plugin.py +867 -0
- apisec_code_bolt/frameworks/python/fastapi/__init__.py +28 -0
- apisec_code_bolt/frameworks/python/fastapi/plugin.py +1390 -0
- apisec_code_bolt/frameworks/python/flask_plugin.py +205 -0
- apisec_code_bolt/frameworks/python/graphql_plugin.py +274 -0
- apisec_code_bolt/frameworks/python/prefect_plugin.py +251 -0
- apisec_code_bolt/frameworks/python/webhook_plugin.py +255 -0
- apisec_code_bolt/parsing/__init__.py +62 -0
- apisec_code_bolt/parsing/base.py +554 -0
- apisec_code_bolt/parsing/csharp/__init__.py +5 -0
- apisec_code_bolt/parsing/csharp/language_services.py +203 -0
- apisec_code_bolt/parsing/csharp/literals.py +72 -0
- apisec_code_bolt/parsing/csharp/parser.py +1158 -0
- apisec_code_bolt/parsing/csharp/type_resolver.py +568 -0
- apisec_code_bolt/parsing/js/__init__.py +5 -0
- apisec_code_bolt/parsing/js/language_services.py +118 -0
- apisec_code_bolt/parsing/js/parser.py +622 -0
- apisec_code_bolt/parsing/jvm/__init__.py +7 -0
- apisec_code_bolt/parsing/jvm/language_services.py +270 -0
- apisec_code_bolt/parsing/jvm/parser.py +774 -0
- apisec_code_bolt/parsing/jvm/type_resolver.py +422 -0
- apisec_code_bolt/parsing/python/__init__.py +150 -0
- apisec_code_bolt/parsing/python/cbv_extractor.py +606 -0
- apisec_code_bolt/parsing/python/constant_resolver.py +500 -0
- apisec_code_bolt/parsing/python/cross_file_resolver.py +1054 -0
- apisec_code_bolt/parsing/python/dynamic_route_detector.py +532 -0
- apisec_code_bolt/parsing/python/expression_utils.py +221 -0
- apisec_code_bolt/parsing/python/extraction_types.py +271 -0
- apisec_code_bolt/parsing/python/language_services.py +487 -0
- apisec_code_bolt/parsing/python/parameter_analyzer.py +789 -0
- apisec_code_bolt/parsing/python/parser.py +719 -0
- apisec_code_bolt/parsing/python/path_resolver.py +576 -0
- apisec_code_bolt/parsing/python/router_registry.py +806 -0
- apisec_code_bolt/parsing/python/type_resolver.py +730 -0
- apisec_code_bolt/parsing/python/visitors.py +1544 -0
- apisec_code_bolt/parsing/services.py +544 -0
- apisec_code_bolt/query/__init__.py +1 -0
- apisec_code_bolt/query/ast_cache.py +182 -0
- apisec_code_bolt/query/executor.py +283 -0
- apisec_code_bolt/query/handlers.py +832 -0
- apisec_code_bolt-0.1.0.dist-info/METADATA +230 -0
- apisec_code_bolt-0.1.0.dist-info/RECORD +111 -0
- apisec_code_bolt-0.1.0.dist-info/WHEEL +4 -0
- 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
|
+
)
|