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,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
C#-specific LanguageServices factory.
|
|
3
|
+
|
|
4
|
+
Implements the LanguageServices abstract class for C#/.NET, wiring together:
|
|
5
|
+
- CSharpTypeResolver (cross-file class/interface/record resolution with
|
|
6
|
+
transitive inheritance — see type_resolver.py)
|
|
7
|
+
- CSharpConstantResolver (appsettings.json / environment variables)
|
|
8
|
+
- CSharpPathResolver (string interpolation — stub for now)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
from ..services import (
|
|
19
|
+
AnalysisContext,
|
|
20
|
+
LanguageServices,
|
|
21
|
+
ResolvedConstant,
|
|
22
|
+
ResolvedPath,
|
|
23
|
+
TypeResolver,
|
|
24
|
+
)
|
|
25
|
+
from ..services import (
|
|
26
|
+
ConstantResolver as ConstantResolverProtocol,
|
|
27
|
+
)
|
|
28
|
+
from ..services import (
|
|
29
|
+
PathResolver as PathResolverProtocol,
|
|
30
|
+
)
|
|
31
|
+
from .type_resolver import CSharpTypeResolver
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from ..base import ParsedFile
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Constant resolver — reads appsettings.json / environment variables
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CSharpConstantResolver:
|
|
45
|
+
"""
|
|
46
|
+
Resolves constants from appsettings.json and environment variable patterns.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
parsed_files: list[ParsedFile],
|
|
52
|
+
project_root: Path | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
self._constants: dict[str, str] = {}
|
|
55
|
+
self._load_appsettings(project_root)
|
|
56
|
+
|
|
57
|
+
def _load_appsettings(self, project_root: Path | None) -> None:
|
|
58
|
+
if project_root is None:
|
|
59
|
+
return
|
|
60
|
+
for candidate in [
|
|
61
|
+
project_root / "appsettings.json",
|
|
62
|
+
project_root / "appsettings.Development.json",
|
|
63
|
+
]:
|
|
64
|
+
if not candidate.exists():
|
|
65
|
+
continue
|
|
66
|
+
try:
|
|
67
|
+
data = json.loads(candidate.read_text(encoding="utf-8"))
|
|
68
|
+
self._flatten_json(data, "")
|
|
69
|
+
except Exception as exc:
|
|
70
|
+
logger.debug("Could not load %s: %s", candidate, exc)
|
|
71
|
+
|
|
72
|
+
def _flatten_json(self, data: Any, prefix: str) -> None:
|
|
73
|
+
if isinstance(data, dict):
|
|
74
|
+
for k, v in data.items():
|
|
75
|
+
full_key = f"{prefix}:{k}" if prefix else k
|
|
76
|
+
self._flatten_json(v, full_key)
|
|
77
|
+
elif isinstance(data, (str, int, float, bool)):
|
|
78
|
+
self._constants[prefix] = str(data)
|
|
79
|
+
|
|
80
|
+
# ConstantResolver protocol
|
|
81
|
+
def resolve(
|
|
82
|
+
self,
|
|
83
|
+
expression: str,
|
|
84
|
+
in_file: Path | None = None,
|
|
85
|
+
) -> ResolvedConstant | None:
|
|
86
|
+
value = self._constants.get(expression)
|
|
87
|
+
if value is not None:
|
|
88
|
+
return ResolvedConstant(
|
|
89
|
+
name=expression,
|
|
90
|
+
value=value,
|
|
91
|
+
source_type="appsettings",
|
|
92
|
+
is_default=True,
|
|
93
|
+
)
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def resolve_value(
|
|
97
|
+
self,
|
|
98
|
+
expression: str,
|
|
99
|
+
in_file: Path | None = None,
|
|
100
|
+
) -> str | None:
|
|
101
|
+
result = self.resolve(expression, in_file)
|
|
102
|
+
return result.value if result else None
|
|
103
|
+
|
|
104
|
+
def get_all_constants(self) -> dict[str, str]:
|
|
105
|
+
return dict(self._constants)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# =============================================================================
|
|
109
|
+
# Path resolver
|
|
110
|
+
# =============================================================================
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class CSharpPathResolver:
|
|
114
|
+
"""
|
|
115
|
+
Resolves computed route paths from string interpolation / concatenation.
|
|
116
|
+
|
|
117
|
+
Lightweight stub — passes through already-clean paths.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def resolve(
|
|
121
|
+
self,
|
|
122
|
+
path_expression: str,
|
|
123
|
+
file_path: Path,
|
|
124
|
+
additional_context: dict[str, str] | None = None,
|
|
125
|
+
) -> ResolvedPath:
|
|
126
|
+
if path_expression.startswith("/") or path_expression.startswith("api/"):
|
|
127
|
+
return ResolvedPath(
|
|
128
|
+
path=path_expression,
|
|
129
|
+
confidence=1.0,
|
|
130
|
+
is_fully_resolved=True,
|
|
131
|
+
)
|
|
132
|
+
return ResolvedPath(
|
|
133
|
+
path=path_expression,
|
|
134
|
+
confidence=0.5,
|
|
135
|
+
is_fully_resolved=False,
|
|
136
|
+
unresolved_vars=[path_expression],
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def get_all_constants(self) -> dict[str, str]:
|
|
140
|
+
return {}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# =============================================================================
|
|
144
|
+
# CSharpLanguageServices factory
|
|
145
|
+
# =============================================================================
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class CSharpLanguageServices(LanguageServices):
|
|
149
|
+
"""
|
|
150
|
+
Factory for C# analysis services.
|
|
151
|
+
|
|
152
|
+
Returns a CSharpTypeResolver, optional CSharpConstantResolver,
|
|
153
|
+
and a lightweight CSharpPathResolver.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def build_type_resolver(
|
|
157
|
+
self,
|
|
158
|
+
parsed_files: list[ParsedFile],
|
|
159
|
+
project_root: Path | None = None,
|
|
160
|
+
) -> TypeResolver:
|
|
161
|
+
return CSharpTypeResolver(parsed_files)
|
|
162
|
+
|
|
163
|
+
def build_constant_resolver(
|
|
164
|
+
self,
|
|
165
|
+
parsed_files: list[ParsedFile],
|
|
166
|
+
project_root: Path | None = None,
|
|
167
|
+
) -> ConstantResolverProtocol | None:
|
|
168
|
+
return CSharpConstantResolver(parsed_files, project_root)
|
|
169
|
+
|
|
170
|
+
def build_path_resolver(
|
|
171
|
+
self,
|
|
172
|
+
parsed_files: list[ParsedFile],
|
|
173
|
+
project_root: Path | None = None,
|
|
174
|
+
) -> PathResolverProtocol | None:
|
|
175
|
+
return CSharpPathResolver()
|
|
176
|
+
|
|
177
|
+
def build_framework_services(
|
|
178
|
+
self,
|
|
179
|
+
framework: str,
|
|
180
|
+
parsed_files: list[ParsedFile],
|
|
181
|
+
project_root: Path | None = None,
|
|
182
|
+
) -> dict[str, Any]:
|
|
183
|
+
return {}
|
|
184
|
+
|
|
185
|
+
def build_context(
|
|
186
|
+
self,
|
|
187
|
+
parsed_files: list[ParsedFile],
|
|
188
|
+
project_root: Path | None = None,
|
|
189
|
+
framework: str | None = None,
|
|
190
|
+
) -> AnalysisContext:
|
|
191
|
+
type_resolver = self.build_type_resolver(parsed_files, project_root)
|
|
192
|
+
constant_resolver = self.build_constant_resolver(parsed_files, project_root)
|
|
193
|
+
path_resolver = self.build_path_resolver(parsed_files, project_root)
|
|
194
|
+
|
|
195
|
+
return AnalysisContext(
|
|
196
|
+
type_resolver=type_resolver,
|
|
197
|
+
constant_resolver=constant_resolver,
|
|
198
|
+
path_resolver=path_resolver,
|
|
199
|
+
router_registry=None,
|
|
200
|
+
project_root=project_root,
|
|
201
|
+
all_parsed_files=parsed_files,
|
|
202
|
+
language_services={},
|
|
203
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""C# string-literal parsing utilities.
|
|
2
|
+
|
|
3
|
+
Used by route / endpoint extractors that need to read a URL template
|
|
4
|
+
or address string out of source code without tokenising the whole
|
|
5
|
+
file. Handles the two C# literal forms that show up in real route
|
|
6
|
+
declarations:
|
|
7
|
+
|
|
8
|
+
- Plain: `"api/users"` — `\\` escapes the next character.
|
|
9
|
+
- Verbatim: `@"api/users"` — `\\` is literal; `""` is the escape
|
|
10
|
+
for an embedded `"`, decoded back to a single `"` in
|
|
11
|
+
the returned content.
|
|
12
|
+
|
|
13
|
+
Interpolated `$"..."` and the verbatim-interpolated `$@"..."` /
|
|
14
|
+
`@$"..."` are NOT handled here. Callers that want to defer those
|
|
15
|
+
should bail when they see a `$` at the start of the literal — and
|
|
16
|
+
typically do, since interpolated route templates can't be matched
|
|
17
|
+
against resolved-value ground truth without a constant-resolution
|
|
18
|
+
pass first.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_csharp_string_literal(
|
|
25
|
+
text: str,
|
|
26
|
+
start: int,
|
|
27
|
+
) -> tuple[str, int, int] | None:
|
|
28
|
+
"""
|
|
29
|
+
Parse a C# string literal at `text[start:]`, skipping leading whitespace.
|
|
30
|
+
|
|
31
|
+
Returns `(content, open_quote_pos, after_close_quote_pos)` — both
|
|
32
|
+
positions absolute in `text` — or None when no literal starts at
|
|
33
|
+
the position (after the optional whitespace prefix).
|
|
34
|
+
"""
|
|
35
|
+
j = start
|
|
36
|
+
while j < len(text) and text[j] in " \t\r\n":
|
|
37
|
+
j += 1
|
|
38
|
+
is_verbatim = False
|
|
39
|
+
if j < len(text) and text[j] == "@":
|
|
40
|
+
is_verbatim = True
|
|
41
|
+
j += 1
|
|
42
|
+
if j >= len(text) or text[j] != '"':
|
|
43
|
+
return None
|
|
44
|
+
open_pos = j
|
|
45
|
+
k = j + 1
|
|
46
|
+
if is_verbatim:
|
|
47
|
+
pieces: list[str] = []
|
|
48
|
+
content_start = k
|
|
49
|
+
while k < len(text):
|
|
50
|
+
if text[k] == '"':
|
|
51
|
+
if k + 1 < len(text) and text[k + 1] == '"':
|
|
52
|
+
# `""` → literal quote, continue scanning.
|
|
53
|
+
pieces.append(text[content_start:k])
|
|
54
|
+
pieces.append('"')
|
|
55
|
+
k += 2
|
|
56
|
+
content_start = k
|
|
57
|
+
continue
|
|
58
|
+
# Lone `"` ends the verbatim string.
|
|
59
|
+
pieces.append(text[content_start:k])
|
|
60
|
+
return "".join(pieces), open_pos, k + 1
|
|
61
|
+
k += 1
|
|
62
|
+
return None
|
|
63
|
+
# Plain literal: `\` escapes the next char.
|
|
64
|
+
content_start = k
|
|
65
|
+
while k < len(text):
|
|
66
|
+
if text[k] == "\\":
|
|
67
|
+
k += 2
|
|
68
|
+
continue
|
|
69
|
+
if text[k] == '"':
|
|
70
|
+
return text[content_start:k], open_pos, k + 1
|
|
71
|
+
k += 1
|
|
72
|
+
return None
|