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,587 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core type definitions for apisec-code-bolt.
|
|
3
|
+
|
|
4
|
+
This module defines all enums, protocols, and base types used throughout the codebase.
|
|
5
|
+
We use strong typing everywhere - no magic strings where enums should be used.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum, auto
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Iterator, Sequence
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# =============================================================================
|
|
20
|
+
# Language and Framework Enums
|
|
21
|
+
# =============================================================================
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Language(Enum):
|
|
25
|
+
"""
|
|
26
|
+
Programming languages supported by the analyzer.
|
|
27
|
+
|
|
28
|
+
Only add languages here when parser support is implemented.
|
|
29
|
+
This enum drives parser selection and framework detection.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
PYTHON = auto()
|
|
33
|
+
JAVA = auto()
|
|
34
|
+
CSHARP = auto()
|
|
35
|
+
JAVASCRIPT = auto()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Framework(Enum):
|
|
39
|
+
"""
|
|
40
|
+
Frameworks we detect and provide specialized analysis for.
|
|
41
|
+
|
|
42
|
+
Only add frameworks here when extraction logic is implemented.
|
|
43
|
+
Includes web frameworks, CLI frameworks, and task/worker frameworks.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Python web frameworks
|
|
47
|
+
FASTAPI = auto()
|
|
48
|
+
FLASK = auto()
|
|
49
|
+
DJANGO = auto()
|
|
50
|
+
|
|
51
|
+
# Python CLI frameworks
|
|
52
|
+
CLICK = auto()
|
|
53
|
+
TYPER = auto()
|
|
54
|
+
|
|
55
|
+
# Python task / worker frameworks
|
|
56
|
+
CELERY = auto()
|
|
57
|
+
DRAMATIQ = auto()
|
|
58
|
+
PREFECT = auto()
|
|
59
|
+
|
|
60
|
+
# Java frameworks
|
|
61
|
+
SPRING_BOOT = auto()
|
|
62
|
+
MICRONAUT = auto()
|
|
63
|
+
JAXRS = auto() # Jersey, Quarkus REST, RESTEasy — javax.ws.rs / jakarta.ws.rs
|
|
64
|
+
|
|
65
|
+
# .NET / C# frameworks
|
|
66
|
+
ASPNET_CORE = auto()
|
|
67
|
+
ASPNET_LEGACY = auto() # System.Web.Mvc / System.Web.Http (pre-.NET Core)
|
|
68
|
+
REFIT = auto() # Refit declarative HTTP client interfaces
|
|
69
|
+
GRPC = auto() # Grpc.Core / Grpc.AspNetCore.Server (server-side)
|
|
70
|
+
WCF = auto() # Windows Communication Foundation / CoreWCF
|
|
71
|
+
|
|
72
|
+
# JavaScript / TypeScript frameworks
|
|
73
|
+
EXPRESS = auto()
|
|
74
|
+
NESTJS = auto()
|
|
75
|
+
FASTIFY = auto()
|
|
76
|
+
|
|
77
|
+
# GraphQL (cross-language)
|
|
78
|
+
GRAPHQL = auto()
|
|
79
|
+
|
|
80
|
+
# Generic fallback
|
|
81
|
+
GENERIC = auto()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# =============================================================================
|
|
85
|
+
# Data Origin Types (Generic - not vulnerability-specific)
|
|
86
|
+
# =============================================================================
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class OriginType(Enum):
|
|
90
|
+
"""
|
|
91
|
+
Types of data origins - where data enters the application.
|
|
92
|
+
|
|
93
|
+
These are OBSERVABLE FACTS about data sources, not security judgments.
|
|
94
|
+
The Cloud applies vulnerability rules to interpret these.
|
|
95
|
+
|
|
96
|
+
Member names are the canonical serialization strings written into
|
|
97
|
+
manifests and matched by the reasoning engine's _ORIGIN_CATEGORY_MAP.
|
|
98
|
+
Do NOT rename members without updating the engine map.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
# HTTP request data
|
|
102
|
+
HTTP_PATH_PARAM = auto() # /users/{user_id}
|
|
103
|
+
HTTP_QUERY_PARAM = auto() # ?filter=value
|
|
104
|
+
HTTP_BODY = auto() # POST/PUT body
|
|
105
|
+
HTTP_HEADER = auto() # Request headers
|
|
106
|
+
HTTP_COOKIE = auto() # Cookie values
|
|
107
|
+
HTTP_FORM = auto() # Form data
|
|
108
|
+
HTTP_FILE = auto() # Uploaded files
|
|
109
|
+
|
|
110
|
+
# Environment and configuration
|
|
111
|
+
ENVIRONMENT_VAR = auto() # os.environ, System.getenv
|
|
112
|
+
CONFIG_VALUE = auto() # Loaded from config files
|
|
113
|
+
|
|
114
|
+
# External data sources
|
|
115
|
+
FILE_CONTENT = auto() # Read from filesystem
|
|
116
|
+
DATABASE_RESULT = auto() # Query result from DB
|
|
117
|
+
EXTERNAL_API = auto() # Response from HTTP call
|
|
118
|
+
MESSAGE_QUEUE = auto() # Message from queue/broker
|
|
119
|
+
|
|
120
|
+
# Code-level origins
|
|
121
|
+
FUNCTION_PARAMETER = auto() # Parameter passed to function
|
|
122
|
+
FUNCTION_RETURN = auto() # Return value from function
|
|
123
|
+
CLASS_ATTRIBUTE = auto() # Instance/class attribute
|
|
124
|
+
LITERAL = auto() # Hardcoded literal value
|
|
125
|
+
|
|
126
|
+
# Generic
|
|
127
|
+
USER_INPUT = auto() # Generic user input
|
|
128
|
+
|
|
129
|
+
# Unknown/unresolved
|
|
130
|
+
UNKNOWN = auto()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TransformationType(Enum):
|
|
134
|
+
"""
|
|
135
|
+
Types of transformations applied to data as it flows.
|
|
136
|
+
|
|
137
|
+
This helps the Cloud understand what happened to data.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# String operations
|
|
141
|
+
STRING_CONCATENATION = auto() # "prefix" + value
|
|
142
|
+
STRING_FORMAT = auto() # f"...{value}..." or .format()
|
|
143
|
+
STRING_INTERPOLATION = auto() # Template strings
|
|
144
|
+
|
|
145
|
+
# Encoding/decoding
|
|
146
|
+
URL_ENCODE = auto()
|
|
147
|
+
URL_DECODE = auto()
|
|
148
|
+
BASE64_ENCODE = auto()
|
|
149
|
+
BASE64_DECODE = auto()
|
|
150
|
+
JSON_SERIALIZE = auto()
|
|
151
|
+
JSON_DESERIALIZE = auto()
|
|
152
|
+
|
|
153
|
+
# Type conversions
|
|
154
|
+
TYPE_CAST = auto() # int(value), str(value)
|
|
155
|
+
TYPE_COERCION = auto() # Implicit conversion
|
|
156
|
+
|
|
157
|
+
# Collection operations
|
|
158
|
+
LIST_INDEX = auto() # value[0]
|
|
159
|
+
DICT_ACCESS = auto() # value["key"]
|
|
160
|
+
ATTRIBUTE_ACCESS = auto() # value.attr
|
|
161
|
+
|
|
162
|
+
# Function calls
|
|
163
|
+
FUNCTION_CALL = auto() # some_function(value)
|
|
164
|
+
METHOD_CALL = auto() # obj.method(value)
|
|
165
|
+
|
|
166
|
+
# Assignments
|
|
167
|
+
VARIABLE_ASSIGNMENT = auto() # x = value
|
|
168
|
+
DESTRUCTURING = auto() # a, b = value
|
|
169
|
+
|
|
170
|
+
# Unknown
|
|
171
|
+
UNKNOWN = auto()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# =============================================================================
|
|
175
|
+
# Authentication Types
|
|
176
|
+
# =============================================================================
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class AuthSchemeType(Enum):
|
|
180
|
+
"""
|
|
181
|
+
Types of authentication schemes we detect.
|
|
182
|
+
|
|
183
|
+
These represent observable authentication patterns, not security judgments.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
# OAuth2 variants
|
|
187
|
+
OAUTH2_PASSWORD = auto() # Resource Owner Password Grant
|
|
188
|
+
OAUTH2_AUTHORIZATION_CODE = auto() # Authorization Code Grant
|
|
189
|
+
OAUTH2_CLIENT_CREDENTIALS = auto() # Client Credentials Grant
|
|
190
|
+
OAUTH2_IMPLICIT = auto() # Implicit Grant (deprecated but exists)
|
|
191
|
+
|
|
192
|
+
# Token-based
|
|
193
|
+
JWT_BEARER = auto() # JWT in Authorization: Bearer header
|
|
194
|
+
API_KEY_HEADER = auto() # API key in custom header
|
|
195
|
+
API_KEY_QUERY = auto() # API key in query string
|
|
196
|
+
API_KEY_COOKIE = auto() # API key in cookie
|
|
197
|
+
|
|
198
|
+
# Session-based
|
|
199
|
+
SESSION_COOKIE = auto() # Server-side session with cookie
|
|
200
|
+
|
|
201
|
+
# Basic authentication
|
|
202
|
+
HTTP_BASIC = auto() # Basic auth header
|
|
203
|
+
HTTP_DIGEST = auto() # Digest auth header
|
|
204
|
+
|
|
205
|
+
# Framework-specific
|
|
206
|
+
SPRING_SECURITY = auto() # Spring Security configuration
|
|
207
|
+
|
|
208
|
+
# Unknown/custom
|
|
209
|
+
CUSTOM = auto()
|
|
210
|
+
UNKNOWN = auto()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class AuthDependencyType(Enum):
|
|
214
|
+
"""Types of authentication dependencies/guards."""
|
|
215
|
+
|
|
216
|
+
FUNCTION = auto() # Dependency is a function
|
|
217
|
+
CLASS = auto() # Dependency is a class
|
|
218
|
+
DECORATOR = auto() # Dependency is a decorator
|
|
219
|
+
ANNOTATION = auto() # Dependency is an annotation (Java)
|
|
220
|
+
MIDDLEWARE = auto() # Authentication in middleware
|
|
221
|
+
FILTER = auto() # Authentication in filter (Java)
|
|
222
|
+
INTERCEPTOR = auto() # Authentication in interceptor (Java)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# =============================================================================
|
|
226
|
+
# HTTP Types
|
|
227
|
+
# =============================================================================
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class HttpMethod(Enum):
|
|
231
|
+
"""HTTP methods for route definitions."""
|
|
232
|
+
|
|
233
|
+
GET = auto()
|
|
234
|
+
POST = auto()
|
|
235
|
+
PUT = auto()
|
|
236
|
+
PATCH = auto()
|
|
237
|
+
DELETE = auto()
|
|
238
|
+
HEAD = auto()
|
|
239
|
+
OPTIONS = auto()
|
|
240
|
+
TRACE = auto()
|
|
241
|
+
|
|
242
|
+
# WebSocket (special case)
|
|
243
|
+
WEBSOCKET = auto()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class ParameterLocation(Enum):
|
|
247
|
+
"""Where a parameter comes from in a request or entry point invocation."""
|
|
248
|
+
|
|
249
|
+
# HTTP parameter locations
|
|
250
|
+
PATH = auto()
|
|
251
|
+
QUERY = auto()
|
|
252
|
+
HEADER = auto()
|
|
253
|
+
COOKIE = auto()
|
|
254
|
+
BODY = auto()
|
|
255
|
+
FORM = auto()
|
|
256
|
+
|
|
257
|
+
# CLI parameter locations
|
|
258
|
+
CLI_ARGUMENT = auto()
|
|
259
|
+
CLI_OPTION = auto()
|
|
260
|
+
|
|
261
|
+
# Task / message consumer parameter locations
|
|
262
|
+
TASK_ARGUMENT = auto()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# =============================================================================
|
|
266
|
+
# Code Location Types
|
|
267
|
+
# =============================================================================
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@dataclass(frozen=True, slots=True)
|
|
271
|
+
class CodeLocation:
|
|
272
|
+
"""
|
|
273
|
+
Precise location in source code.
|
|
274
|
+
|
|
275
|
+
Immutable and hashable for use as dict keys and in sets.
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
file: Path
|
|
279
|
+
line: int
|
|
280
|
+
column: int | None = None
|
|
281
|
+
end_line: int | None = None
|
|
282
|
+
end_column: int | None = None
|
|
283
|
+
|
|
284
|
+
def __str__(self) -> str:
|
|
285
|
+
if self.column is not None:
|
|
286
|
+
return f"{self.file}:{self.line}:{self.column}"
|
|
287
|
+
return f"{self.file}:{self.line}"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@dataclass(frozen=True, slots=True)
|
|
291
|
+
class QualifiedName:
|
|
292
|
+
"""
|
|
293
|
+
Fully qualified name for a symbol (function, class, method).
|
|
294
|
+
|
|
295
|
+
Examples:
|
|
296
|
+
- "app.routes.users.get_user"
|
|
297
|
+
- "com.example.UserController.getUser"
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
module: str # "app.routes.users" or "com.example"
|
|
301
|
+
name: str # "get_user" or "UserController.getUser"
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def full(self) -> str:
|
|
305
|
+
"""Full qualified name."""
|
|
306
|
+
if self.module:
|
|
307
|
+
return f"{self.module}.{self.name}"
|
|
308
|
+
return self.name
|
|
309
|
+
|
|
310
|
+
def __str__(self) -> str:
|
|
311
|
+
return self.full
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# =============================================================================
|
|
315
|
+
# Analysis Context Types
|
|
316
|
+
# =============================================================================
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@dataclass(slots=True)
|
|
320
|
+
class CallContext:
|
|
321
|
+
"""
|
|
322
|
+
Context about where a function call occurs.
|
|
323
|
+
|
|
324
|
+
This provides valuable information for security analysis:
|
|
325
|
+
- Is the call inside a try block? (affects error handling)
|
|
326
|
+
- Is it inside a conditional? (affects reachability)
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
in_try_block: bool = False
|
|
330
|
+
exception_handlers: list[str] = field(default_factory=list)
|
|
331
|
+
exception_swallowed: bool = False # except: pass
|
|
332
|
+
|
|
333
|
+
in_conditional: bool = False
|
|
334
|
+
conditional_expressions: list[str] = field(default_factory=list)
|
|
335
|
+
guards_this_call: bool = False # Is the conditional a guard for this call?
|
|
336
|
+
|
|
337
|
+
in_loop: bool = False
|
|
338
|
+
loop_type: str | None = None # "for", "while"
|
|
339
|
+
|
|
340
|
+
in_async_context: bool = False
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@dataclass(slots=True)
|
|
344
|
+
class FlowContext:
|
|
345
|
+
"""
|
|
346
|
+
Context about a data flow's execution environment.
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
entry_point_id: str | None = None
|
|
350
|
+
in_try_block: bool = False
|
|
351
|
+
exception_handlers: list[str] = field(default_factory=list)
|
|
352
|
+
guarded_by_conditionals: list[str] = field(default_factory=list)
|
|
353
|
+
in_loop: bool = False
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# =============================================================================
|
|
357
|
+
# Confidence and Analysis Metadata
|
|
358
|
+
# =============================================================================
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class Confidence(Enum):
|
|
362
|
+
"""
|
|
363
|
+
Confidence level in an analysis result.
|
|
364
|
+
|
|
365
|
+
Used to indicate how certain we are about detected patterns.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
HIGH = auto() # Definitely correct
|
|
369
|
+
MEDIUM = auto() # Likely correct, may need verification
|
|
370
|
+
LOW = auto() # Uncertain, needs Cloud verification
|
|
371
|
+
UNKNOWN = auto() # Could not determine
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@dataclass(slots=True)
|
|
375
|
+
class AnalysisNote:
|
|
376
|
+
"""
|
|
377
|
+
Note or warning about analysis quality for a specific item.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
level: str # "info", "warning", "error"
|
|
381
|
+
message: str
|
|
382
|
+
location: CodeLocation | None = None
|
|
383
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
# =============================================================================
|
|
387
|
+
# Protocol Definitions (Interfaces)
|
|
388
|
+
# =============================================================================
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@runtime_checkable
|
|
392
|
+
class Visitable(Protocol):
|
|
393
|
+
"""Protocol for AST nodes that can be visited."""
|
|
394
|
+
|
|
395
|
+
def accept(self, visitor: Any) -> Any:
|
|
396
|
+
"""Accept a visitor."""
|
|
397
|
+
...
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@runtime_checkable
|
|
401
|
+
class Parser(Protocol):
|
|
402
|
+
"""
|
|
403
|
+
Protocol for language-specific parsers.
|
|
404
|
+
|
|
405
|
+
Each language (Python, Java, etc.) implements this protocol
|
|
406
|
+
to provide parsing capabilities.
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def language(self) -> Language:
|
|
411
|
+
"""The language this parser handles."""
|
|
412
|
+
...
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def supported_extensions(self) -> frozenset[str]:
|
|
416
|
+
"""File extensions this parser handles (e.g., {'.py'})."""
|
|
417
|
+
...
|
|
418
|
+
|
|
419
|
+
def can_parse(self, file_path: Path) -> bool:
|
|
420
|
+
"""Check if this parser can handle the given file."""
|
|
421
|
+
...
|
|
422
|
+
|
|
423
|
+
def parse_file(self, file_path: Path) -> ParsedFile:
|
|
424
|
+
"""
|
|
425
|
+
Parse a single file and return structured data.
|
|
426
|
+
|
|
427
|
+
Raises:
|
|
428
|
+
ParseError: If the file cannot be parsed.
|
|
429
|
+
"""
|
|
430
|
+
...
|
|
431
|
+
|
|
432
|
+
def parse_files(self, file_paths: Sequence[Path]) -> Iterator[ParsedFile]:
|
|
433
|
+
"""
|
|
434
|
+
Parse multiple files, yielding results.
|
|
435
|
+
|
|
436
|
+
This allows for streaming/incremental processing.
|
|
437
|
+
"""
|
|
438
|
+
...
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@runtime_checkable
|
|
442
|
+
class FrameworkPlugin(Protocol):
|
|
443
|
+
"""
|
|
444
|
+
Protocol for framework-specific extraction plugins.
|
|
445
|
+
|
|
446
|
+
Each framework (FastAPI, Spring Boot, etc.) implements this
|
|
447
|
+
to provide specialized route and dependency extraction.
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
@property
|
|
451
|
+
def framework(self) -> Framework:
|
|
452
|
+
"""The framework this plugin handles."""
|
|
453
|
+
...
|
|
454
|
+
|
|
455
|
+
@property
|
|
456
|
+
def language(self) -> Language:
|
|
457
|
+
"""The language this framework is for."""
|
|
458
|
+
...
|
|
459
|
+
|
|
460
|
+
def detect(self, parsed_file: ParsedFile) -> bool:
|
|
461
|
+
"""
|
|
462
|
+
Detect if this framework is used in the parsed file.
|
|
463
|
+
|
|
464
|
+
Returns True if framework-specific patterns are found.
|
|
465
|
+
"""
|
|
466
|
+
...
|
|
467
|
+
|
|
468
|
+
def extract_routes(self, parsed_file: ParsedFile) -> list[RouteDefinition]:
|
|
469
|
+
"""Extract HTTP route definitions from the file."""
|
|
470
|
+
...
|
|
471
|
+
|
|
472
|
+
def extract_dependencies(self, parsed_file: ParsedFile) -> list[DependencyDefinition]:
|
|
473
|
+
"""Extract dependency injection definitions."""
|
|
474
|
+
...
|
|
475
|
+
|
|
476
|
+
def extract_auth(self, parsed_file: ParsedFile) -> list[AuthDefinition]:
|
|
477
|
+
"""Extract authentication-related definitions."""
|
|
478
|
+
...
|
|
479
|
+
|
|
480
|
+
def extract_middleware(self, parsed_file: ParsedFile) -> list[MiddlewareDefinition]:
|
|
481
|
+
"""Extract middleware/interceptor definitions."""
|
|
482
|
+
...
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# =============================================================================
|
|
486
|
+
# Forward Declarations (to be fully defined in manifest.py)
|
|
487
|
+
# =============================================================================
|
|
488
|
+
|
|
489
|
+
# These are declared here so they can be referenced in protocols above.
|
|
490
|
+
# Full definitions with all fields are in manifest.py
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@dataclass
|
|
494
|
+
class ParsedFile:
|
|
495
|
+
"""Represents a parsed source file."""
|
|
496
|
+
|
|
497
|
+
path: Path
|
|
498
|
+
language: Language
|
|
499
|
+
# Full definition in manifest.py
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@dataclass
|
|
503
|
+
class RouteDefinition:
|
|
504
|
+
"""Represents an HTTP route definition."""
|
|
505
|
+
|
|
506
|
+
id: str
|
|
507
|
+
method: HttpMethod
|
|
508
|
+
path: str
|
|
509
|
+
# Full definition in manifest.py
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
@dataclass
|
|
513
|
+
class DependencyDefinition:
|
|
514
|
+
"""Represents a dependency injection definition."""
|
|
515
|
+
|
|
516
|
+
id: str
|
|
517
|
+
name: str
|
|
518
|
+
# Full definition in manifest.py
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
@dataclass
|
|
522
|
+
class AuthDefinition:
|
|
523
|
+
"""Represents an authentication definition."""
|
|
524
|
+
|
|
525
|
+
id: str
|
|
526
|
+
scheme_type: AuthSchemeType
|
|
527
|
+
# Full definition in manifest.py
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@dataclass
|
|
531
|
+
class MiddlewareDefinition:
|
|
532
|
+
"""Represents a middleware/interceptor definition."""
|
|
533
|
+
|
|
534
|
+
id: str
|
|
535
|
+
name: str
|
|
536
|
+
# Full definition in manifest.py
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
# =============================================================================
|
|
540
|
+
# Exception Types
|
|
541
|
+
# =============================================================================
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
class CodeBoltError(Exception):
|
|
545
|
+
"""Base exception for all apisec-code-bolt errors."""
|
|
546
|
+
|
|
547
|
+
pass
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class ParseError(CodeBoltError):
|
|
551
|
+
"""Error during parsing."""
|
|
552
|
+
|
|
553
|
+
def __init__(
|
|
554
|
+
self,
|
|
555
|
+
message: str,
|
|
556
|
+
file_path: Path | None = None,
|
|
557
|
+
line: int | None = None,
|
|
558
|
+
column: int | None = None,
|
|
559
|
+
):
|
|
560
|
+
super().__init__(message)
|
|
561
|
+
self.file_path = file_path
|
|
562
|
+
self.line = line
|
|
563
|
+
self.column = column
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class ConfigurationError(CodeBoltError):
|
|
567
|
+
"""Error in configuration."""
|
|
568
|
+
|
|
569
|
+
pass
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class AnalysisError(CodeBoltError):
|
|
573
|
+
"""Error during analysis."""
|
|
574
|
+
|
|
575
|
+
pass
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class CloudCommunicationError(CodeBoltError):
|
|
579
|
+
"""Error communicating with cloud services."""
|
|
580
|
+
|
|
581
|
+
pass
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
class TimeoutError(CodeBoltError):
|
|
585
|
+
"""Analysis timeout."""
|
|
586
|
+
|
|
587
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Integration detection and capability fingerprinting."""
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Framework-specific extraction plugins."""
|
|
2
|
+
|
|
3
|
+
from .base import (
|
|
4
|
+
BaseFrameworkPlugin,
|
|
5
|
+
ExtractedAuthDependency,
|
|
6
|
+
ExtractedAuthScheme,
|
|
7
|
+
ExtractedBody,
|
|
8
|
+
ExtractedDependency,
|
|
9
|
+
ExtractedJwtConfig,
|
|
10
|
+
ExtractedMiddleware,
|
|
11
|
+
ExtractedParameter,
|
|
12
|
+
ExtractedResponse,
|
|
13
|
+
ExtractedRoute,
|
|
14
|
+
FrameworkPluginRegistry,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"BaseFrameworkPlugin",
|
|
19
|
+
"FrameworkPluginRegistry",
|
|
20
|
+
"ExtractedRoute",
|
|
21
|
+
"ExtractedParameter",
|
|
22
|
+
"ExtractedBody",
|
|
23
|
+
"ExtractedResponse",
|
|
24
|
+
"ExtractedDependency",
|
|
25
|
+
"ExtractedAuthScheme",
|
|
26
|
+
"ExtractedAuthDependency",
|
|
27
|
+
"ExtractedJwtConfig",
|
|
28
|
+
"ExtractedMiddleware",
|
|
29
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared JWT algorithm helpers.
|
|
3
|
+
|
|
4
|
+
The canonical JWA algorithm set is identical across every JWT library
|
|
5
|
+
(HS256/RS256/ES256/PS256 families). The normalization and append-to-config
|
|
6
|
+
helpers used by `JavaJwtConfigExtractor` and `DotNetJwtConfigExtractor`
|
|
7
|
+
were near-byte-identical — promoted here so each extractor only carries
|
|
8
|
+
the framework-specific aliases.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from .base import ExtractedJwtConfig
|
|
14
|
+
|
|
15
|
+
# Canonical JWA algorithm names (RFC 7518). Both Java and .NET extractors
|
|
16
|
+
# share this set; framework-specific aliases live alongside each extractor.
|
|
17
|
+
ALGORITHM_NAMES: frozenset[str] = frozenset(
|
|
18
|
+
{
|
|
19
|
+
"HS256",
|
|
20
|
+
"HS384",
|
|
21
|
+
"HS512",
|
|
22
|
+
"RS256",
|
|
23
|
+
"RS384",
|
|
24
|
+
"RS512",
|
|
25
|
+
"ES256",
|
|
26
|
+
"ES384",
|
|
27
|
+
"ES512",
|
|
28
|
+
"PS256",
|
|
29
|
+
"PS384",
|
|
30
|
+
"PS512",
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def normalize_algorithm(raw: str, aliases: dict[str, str]) -> str | None:
|
|
36
|
+
"""Return a canonical JWA algorithm name, or None if unrecognised.
|
|
37
|
+
|
|
38
|
+
Upper-cases and strips `-` / `_` so framework-specific forms like
|
|
39
|
+
`HMAC-SHA256` or `RSA_SHA256` collapse to the alias-lookup key.
|
|
40
|
+
"""
|
|
41
|
+
upper = raw.upper().replace("-", "").replace("_", "")
|
|
42
|
+
if upper in ALGORITHM_NAMES:
|
|
43
|
+
return upper
|
|
44
|
+
return aliases.get(upper)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def add_algorithm(algo: str | None, jwt_config: ExtractedJwtConfig) -> None:
|
|
48
|
+
"""Append an algorithm to the config if non-empty and not already present."""
|
|
49
|
+
if algo and algo not in jwt_config.algorithms:
|
|
50
|
+
jwt_config.algorithms.append(algo)
|