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,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