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,8 @@
1
+ """JavaScript/TypeScript framework plugins."""
2
+
3
+ from .express_plugin import ExpressPlugin
4
+ from .fastify_plugin import FastifyPlugin # noqa: F401 — triggers registration
5
+ from .graphql_plugin import JavaScriptGraphQLPlugin
6
+ from .nestjs_plugin import NestJSPlugin
7
+
8
+ __all__ = ["ExpressPlugin", "FastifyPlugin", "JavaScriptGraphQLPlugin", "NestJSPlugin"]
@@ -0,0 +1,391 @@
1
+ """
2
+ Express.js framework plugin.
3
+
4
+ Extracts HTTP routes registered via express.Router() and app.get/post/etc.
5
+ Handles:
6
+ - Direct method calls: app.get('/path', handler)
7
+ - Router instance: router.post('/path', handler)
8
+ - Path parameters: /users/:id → /users/{id}
9
+ - Cross-file prefix from url_prefix_map (populated by url_prefix_resolver)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import re
16
+ from typing import TYPE_CHECKING, Any
17
+
18
+ from ...core.types import (
19
+ AuthDependencyType,
20
+ AuthSchemeType,
21
+ CodeLocation,
22
+ Confidence,
23
+ Framework,
24
+ HttpMethod,
25
+ Language,
26
+ ParameterLocation,
27
+ QualifiedName,
28
+ )
29
+ from ...parsing.base import ParsedFile
30
+ from ..base import (
31
+ BaseFrameworkPlugin,
32
+ ExtractedAuthDependency,
33
+ ExtractedAuthScheme,
34
+ ExtractedDependency,
35
+ ExtractedMiddleware,
36
+ ExtractedParameter,
37
+ ExtractedRoute,
38
+ FrameworkPluginRegistry,
39
+ )
40
+
41
+ if TYPE_CHECKING:
42
+ from ...parsing.services import AnalysisContext
43
+
44
+ logger = logging.getLogger(__name__)
45
+
46
+ # HTTP methods handled by Express router
47
+ _EXPRESS_HTTP_METHODS: frozenset[str] = frozenset(
48
+ {
49
+ "get",
50
+ "post",
51
+ "put",
52
+ "patch",
53
+ "delete",
54
+ "options",
55
+ "head",
56
+ "all",
57
+ }
58
+ )
59
+
60
+ # Regex to convert Express :param to {param}
61
+ _PARAM_RE = re.compile(r":([A-Za-z_]\w*)")
62
+
63
+ # ── Auth detection constants ──────────────────────────────────────────────────
64
+
65
+ # Module names (or fragments) that indicate JWT auth
66
+ _JWT_MODULE_HINTS = (
67
+ "jsonwebtoken",
68
+ "express-jwt",
69
+ "passport-jwt",
70
+ "jwt-simple",
71
+ )
72
+
73
+ # Variable/import name substrings that indicate an auth middleware
74
+ _AUTH_MIDDLEWARE_HINTS = (
75
+ "auth",
76
+ "authentication",
77
+ "authorize",
78
+ "authorization",
79
+ "jwt",
80
+ "passport",
81
+ "token",
82
+ "apikey",
83
+ "api_key",
84
+ "bearer",
85
+ "security",
86
+ "verify",
87
+ "guard",
88
+ )
89
+
90
+ # Variable name substrings that indicate API key middleware
91
+ _API_KEY_HINTS = ("apikey", "api_key", "apikeyrepo", "header.api_key")
92
+
93
+ # Call names that indicate JWT operations
94
+ _JWT_CALL_HINTS = ("jwt.verify", "jwt.sign", "jwt.decode", "jwt.validate", "JWT.validate")
95
+
96
+
97
+ def _colon_to_curly(path: str) -> str:
98
+ """Convert Express-style :param to OpenAPI-style {param}."""
99
+ return _PARAM_RE.sub(r"{\1}", path)
100
+
101
+
102
+ def _extract_path_params(path: str) -> list[ExtractedParameter]:
103
+ """Extract path parameters from a route path string."""
104
+ return [
105
+ ExtractedParameter(name=m.group(1), location=ParameterLocation.PATH)
106
+ for m in _PARAM_RE.finditer(path)
107
+ ]
108
+
109
+
110
+ class ExpressPlugin(BaseFrameworkPlugin):
111
+ """
112
+ Framework plugin for Express.js.
113
+
114
+ Detects Express usage and extracts route definitions from
115
+ router.get/post/put/delete/patch/options/head/all() calls.
116
+ """
117
+
118
+ FRAMEWORK = Framework.EXPRESS
119
+ LANGUAGE = Language.JAVASCRIPT
120
+ DETECTION_IMPORTS: frozenset[str] = frozenset({"express"})
121
+
122
+ def detect(self, parsed_file: ParsedFile) -> bool:
123
+ """Detect Express by looking for 'express' in imports."""
124
+ for imp in parsed_file.imports:
125
+ if imp.module == "express" or imp.module.startswith("express/"):
126
+ return True
127
+ # require('express') parsed as an import with module='express'
128
+ for name in imp.names:
129
+ if name == "express":
130
+ return True
131
+ return False
132
+
133
+ def extract_routes(
134
+ self,
135
+ parsed_file: ParsedFile,
136
+ context: AnalysisContext | None = None,
137
+ ) -> list[ExtractedRoute]:
138
+ """Extract Express route registrations from call sites."""
139
+ routes: list[ExtractedRoute] = []
140
+
141
+ # Get per-file prefix from cross-file prefix map (if available)
142
+ file_prefix = ""
143
+ if context and context.language_services:
144
+ prefix_map = context.language_services.get("_url_prefix_map")
145
+ if prefix_map:
146
+ # Use resolved path so symlinks (e.g. /var→/private/var on macOS)
147
+ # don't cause mismatches with the resolver's map keys.
148
+ file_key = str(parsed_file.path.resolve())
149
+ prefixes = prefix_map.get(file_key, [])
150
+ if prefixes:
151
+ file_prefix = prefixes[0] # Use the first prefix
152
+
153
+ for call in parsed_file.call_sites:
154
+ if not call.is_method_call:
155
+ continue
156
+
157
+ method_lower = call.callee_name.lower()
158
+ if method_lower not in _EXPRESS_HTTP_METHODS:
159
+ continue
160
+
161
+ # First argument must be a string literal (the path)
162
+ if not call.arguments:
163
+ continue
164
+
165
+ path_arg = call.arguments[0]
166
+ if not path_arg.is_literal or not isinstance(path_arg.literal_value, str):
167
+ continue
168
+
169
+ raw_path = path_arg.literal_value
170
+ path = _colon_to_curly(raw_path)
171
+
172
+ # Apply file-level prefix
173
+ if file_prefix:
174
+ path = file_prefix.rstrip("/") + "/" + path.lstrip("/")
175
+
176
+ # Normalize double slashes and strip trailing slash (except root)
177
+ path = re.sub(r"/+", "/", path)
178
+ if not path.startswith("/"):
179
+ path = "/" + path
180
+ if path != "/" and path.endswith("/"):
181
+ path = path.rstrip("/")
182
+
183
+ # Get handler name from second argument (variable or arrow function)
184
+ handler_name = "<anonymous>"
185
+ if len(call.arguments) > 1:
186
+ handler_arg = call.arguments[1]
187
+ if handler_arg.is_variable and handler_arg.variable_name:
188
+ handler_name = handler_arg.variable_name
189
+ elif handler_arg.is_expression and handler_arg.expression_text:
190
+ handler_name = "<arrow>"
191
+
192
+ # HTTP method
193
+ http_method = _method_from_str(method_lower)
194
+
195
+ # Path params (from original raw path)
196
+ path_params = _extract_path_params(raw_path)
197
+
198
+ routes.append(
199
+ ExtractedRoute(
200
+ method=http_method,
201
+ path=path,
202
+ handler_function=QualifiedName(
203
+ module=parsed_file.path.stem,
204
+ name=handler_name,
205
+ ),
206
+ handler_location=call.location,
207
+ path_params=path_params,
208
+ )
209
+ )
210
+
211
+ return routes
212
+
213
+ def extract_dependencies(self, parsed_file: ParsedFile) -> list[ExtractedDependency]:
214
+ return []
215
+
216
+ def extract_auth_schemes(self, parsed_file: ParsedFile) -> list[ExtractedAuthScheme]:
217
+ """
218
+ Detect Express auth scheme definitions.
219
+
220
+ Covers:
221
+ - JWT library imports (jsonwebtoken, express-jwt, passport-jwt) → JWT_BEARER
222
+ - Local JWT class/util imports (files named 'jwt', 'JWT', 'authentication')
223
+ - API key patterns: imports of local files named 'apikey', 'api-key'
224
+ - JWT call sites (jwt.verify, JWT.validate) in the file
225
+ """
226
+ schemes: list[ExtractedAuthScheme] = []
227
+ seen: set[str] = set()
228
+ file_loc = CodeLocation(file=parsed_file.path, line=1)
229
+
230
+ def _add(name: str, scheme_type: AuthSchemeType) -> None:
231
+ if name not in seen:
232
+ seen.add(name)
233
+ schemes.append(
234
+ ExtractedAuthScheme(
235
+ scheme_type=scheme_type,
236
+ name=name,
237
+ location=file_loc,
238
+ confidence=Confidence.HIGH,
239
+ )
240
+ )
241
+
242
+ # ── Import-based detection ───────────────────────────────────────────
243
+ for imp in parsed_file.imports:
244
+ mod = imp.module.lower()
245
+ # Third-party JWT packages
246
+ if any(hint in mod for hint in _JWT_MODULE_HINTS):
247
+ _add("JwtBearer", AuthSchemeType.JWT_BEARER)
248
+ # Local module paths that look like JWT/auth utilities
249
+ elif imp.module.startswith((".", "/")):
250
+ stem = mod.rsplit("/", 1)[-1]
251
+ if any(hint in stem for hint in ("jwt", "authentication", "auth")):
252
+ _add("JwtBearer", AuthSchemeType.JWT_BEARER)
253
+ elif any(hint in stem for hint in ("apikey", "api-key", "api_key")):
254
+ _add("ApiKey", AuthSchemeType.API_KEY_HEADER)
255
+ # passport
256
+ elif mod.startswith("passport"):
257
+ _add("Passport", AuthSchemeType.JWT_BEARER)
258
+
259
+ # ── Call-site-based detection ────────────────────────────────────────
260
+ for call in parsed_file.call_sites:
261
+ callee = call.callee_name.lower()
262
+ if callee in ("verify", "validate", "decode") and call.is_method_call:
263
+ _add("JwtBearer", AuthSchemeType.JWT_BEARER)
264
+ elif callee == "authenticate":
265
+ _add("Passport", AuthSchemeType.JWT_BEARER)
266
+
267
+ return schemes
268
+
269
+ def extract_auth_dependencies(
270
+ self,
271
+ parsed_file: ParsedFile,
272
+ known_scheme_names: set[str] | None = None,
273
+ **kwargs: Any,
274
+ ) -> list[ExtractedAuthDependency]:
275
+ """
276
+ Detect Express auth middleware applied via router.use().
277
+
278
+ Covers:
279
+ - router.use(authentication, ...) — JWT auth middleware by variable name
280
+ - router.use(apikey) — API key middleware by variable name
281
+ - router.use(authentication, role(RoleCode.WRITER), authorization) — role chain
282
+ - router.use(role(RoleCode.X)) — extracts role name from role() call
283
+
284
+ Auth middleware is identified by name heuristic: variables whose names
285
+ contain 'auth', 'authentication', 'authorization', 'apikey', 'jwt', etc.
286
+ Role names are extracted from role(RoleCode.X) call arguments.
287
+ """
288
+ deps: list[ExtractedAuthDependency] = []
289
+
290
+ # Build set of locally imported names that hint at auth
291
+ auth_imports: set[str] = set()
292
+ for imp in parsed_file.imports:
293
+ mod_lower = imp.module.lower()
294
+ is_auth_module = any(h in mod_lower for h in _AUTH_MIDDLEWARE_HINTS)
295
+ for name in imp.names:
296
+ if is_auth_module or any(h in name.lower() for h in _AUTH_MIDDLEWARE_HINTS):
297
+ auth_imports.add(name)
298
+
299
+ for call in parsed_file.call_sites:
300
+ if not call.is_method_call:
301
+ continue
302
+ if call.callee_name.lower() != "use":
303
+ continue
304
+ if not call.arguments:
305
+ continue
306
+
307
+ # Collect auth middleware names and roles from args
308
+ middleware_names: list[str] = []
309
+ requires_roles: list[str] = []
310
+
311
+ for arg in call.arguments:
312
+ if arg.is_variable and arg.variable_name:
313
+ vname = arg.variable_name
314
+ if (
315
+ any(h in vname.lower() for h in _AUTH_MIDDLEWARE_HINTS)
316
+ or vname in auth_imports
317
+ ):
318
+ middleware_names.append(vname)
319
+ elif arg.is_expression and arg.expression_text:
320
+ expr = arg.expression_text
321
+ # Detect role(RoleCode.X) or role('X') call
322
+ role_match = re.match(r"role\s*\(\s*(?:RoleCode\.)?([A-Za-z_]\w*)\s*\)", expr)
323
+ if role_match:
324
+ requires_roles.append(role_match.group(1))
325
+ # Detect passport.authenticate('strategy')
326
+ elif "authenticate" in expr.lower():
327
+ strat_match = re.search(r"authenticate\s*\(\s*['\"]([^'\"]+)['\"]", expr)
328
+ if strat_match:
329
+ middleware_names.append(f"passport:{strat_match.group(1)}")
330
+ else:
331
+ middleware_names.append("passport")
332
+ # Other expression-based middleware with auth hint
333
+ elif any(h in expr.lower() for h in _AUTH_MIDDLEWARE_HINTS):
334
+ name_hint = expr.split("(")[0].strip()
335
+ middleware_names.append(name_hint)
336
+
337
+ if not middleware_names and not requires_roles:
338
+ continue
339
+
340
+ uses_schemes: list[str] = []
341
+ for name in middleware_names:
342
+ low = name.lower()
343
+ if any(h in low for h in ("jwt", "authentication", "bearer", "token")):
344
+ if "JwtBearer" not in uses_schemes:
345
+ uses_schemes.append("JwtBearer")
346
+ elif any(h in low for h in _API_KEY_HINTS):
347
+ if "ApiKey" not in uses_schemes:
348
+ uses_schemes.append("ApiKey")
349
+ elif "passport" in low:
350
+ if "Passport" not in uses_schemes:
351
+ uses_schemes.append("Passport")
352
+
353
+ loc = call.location or CodeLocation(file=parsed_file.path, line=1)
354
+ dep_name = "+".join(middleware_names) if middleware_names else "role"
355
+ deps.append(
356
+ ExtractedAuthDependency(
357
+ name=dep_name,
358
+ qualified_name=QualifiedName(
359
+ module=parsed_file.path.stem,
360
+ name=dep_name,
361
+ ),
362
+ location=loc,
363
+ dependency_type=AuthDependencyType.MIDDLEWARE,
364
+ uses_schemes=uses_schemes,
365
+ requires_roles=requires_roles,
366
+ confidence=Confidence.HIGH,
367
+ )
368
+ )
369
+
370
+ return deps
371
+
372
+ def extract_middleware(self, parsed_file: ParsedFile) -> list[ExtractedMiddleware]:
373
+ return []
374
+
375
+
376
+ def _method_from_str(method: str) -> HttpMethod:
377
+ """Convert a lowercase HTTP method string to HttpMethod enum."""
378
+ _MAP = {
379
+ "get": HttpMethod.GET,
380
+ "post": HttpMethod.POST,
381
+ "put": HttpMethod.PUT,
382
+ "patch": HttpMethod.PATCH,
383
+ "delete": HttpMethod.DELETE,
384
+ "options": HttpMethod.OPTIONS,
385
+ "head": HttpMethod.HEAD,
386
+ "all": HttpMethod.GET, # 'all' → GET as a fallback
387
+ }
388
+ return _MAP.get(method, HttpMethod.GET)
389
+
390
+
391
+ FrameworkPluginRegistry.register(ExpressPlugin())