osscodeiq 0.0.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 (183) hide show
  1. osscodeiq/__init__.py +0 -0
  2. osscodeiq/analyzer.py +467 -0
  3. osscodeiq/cache/__init__.py +0 -0
  4. osscodeiq/cache/hasher.py +23 -0
  5. osscodeiq/cache/store.py +300 -0
  6. osscodeiq/classifiers/__init__.py +0 -0
  7. osscodeiq/classifiers/layer_classifier.py +69 -0
  8. osscodeiq/cli.py +721 -0
  9. osscodeiq/config.py +113 -0
  10. osscodeiq/detectors/__init__.py +0 -0
  11. osscodeiq/detectors/auth/__init__.py +0 -0
  12. osscodeiq/detectors/auth/certificate_auth.py +139 -0
  13. osscodeiq/detectors/auth/ldap_auth.py +89 -0
  14. osscodeiq/detectors/auth/session_header_auth.py +120 -0
  15. osscodeiq/detectors/base.py +41 -0
  16. osscodeiq/detectors/config/__init__.py +0 -0
  17. osscodeiq/detectors/config/batch_structure.py +128 -0
  18. osscodeiq/detectors/config/cloudformation.py +183 -0
  19. osscodeiq/detectors/config/docker_compose.py +179 -0
  20. osscodeiq/detectors/config/github_actions.py +150 -0
  21. osscodeiq/detectors/config/gitlab_ci.py +216 -0
  22. osscodeiq/detectors/config/helm_chart.py +187 -0
  23. osscodeiq/detectors/config/ini_structure.py +101 -0
  24. osscodeiq/detectors/config/json_structure.py +72 -0
  25. osscodeiq/detectors/config/kubernetes.py +305 -0
  26. osscodeiq/detectors/config/kubernetes_rbac.py +212 -0
  27. osscodeiq/detectors/config/openapi.py +194 -0
  28. osscodeiq/detectors/config/package_json.py +99 -0
  29. osscodeiq/detectors/config/properties_detector.py +108 -0
  30. osscodeiq/detectors/config/pyproject_toml.py +169 -0
  31. osscodeiq/detectors/config/sql_structure.py +155 -0
  32. osscodeiq/detectors/config/toml_structure.py +93 -0
  33. osscodeiq/detectors/config/tsconfig_json.py +105 -0
  34. osscodeiq/detectors/config/yaml_structure.py +82 -0
  35. osscodeiq/detectors/cpp/__init__.py +0 -0
  36. osscodeiq/detectors/cpp/cpp_structures.py +192 -0
  37. osscodeiq/detectors/csharp/__init__.py +0 -0
  38. osscodeiq/detectors/csharp/csharp_efcore.py +184 -0
  39. osscodeiq/detectors/csharp/csharp_minimal_apis.py +156 -0
  40. osscodeiq/detectors/csharp/csharp_structures.py +317 -0
  41. osscodeiq/detectors/docs/__init__.py +0 -0
  42. osscodeiq/detectors/docs/markdown_structure.py +117 -0
  43. osscodeiq/detectors/frontend/__init__.py +0 -0
  44. osscodeiq/detectors/frontend/angular_components.py +177 -0
  45. osscodeiq/detectors/frontend/frontend_routes.py +259 -0
  46. osscodeiq/detectors/frontend/react_components.py +148 -0
  47. osscodeiq/detectors/frontend/svelte_components.py +84 -0
  48. osscodeiq/detectors/frontend/vue_components.py +150 -0
  49. osscodeiq/detectors/generic/__init__.py +1 -0
  50. osscodeiq/detectors/generic/imports_detector.py +413 -0
  51. osscodeiq/detectors/go/__init__.py +0 -0
  52. osscodeiq/detectors/go/go_orm.py +202 -0
  53. osscodeiq/detectors/go/go_structures.py +162 -0
  54. osscodeiq/detectors/go/go_web.py +157 -0
  55. osscodeiq/detectors/iac/__init__.py +0 -0
  56. osscodeiq/detectors/iac/bicep.py +135 -0
  57. osscodeiq/detectors/iac/dockerfile.py +182 -0
  58. osscodeiq/detectors/iac/terraform.py +188 -0
  59. osscodeiq/detectors/java/__init__.py +0 -0
  60. osscodeiq/detectors/java/azure_functions.py +424 -0
  61. osscodeiq/detectors/java/azure_messaging.py +350 -0
  62. osscodeiq/detectors/java/class_hierarchy.py +349 -0
  63. osscodeiq/detectors/java/config_def.py +82 -0
  64. osscodeiq/detectors/java/cosmos_db.py +105 -0
  65. osscodeiq/detectors/java/graphql_resolver.py +188 -0
  66. osscodeiq/detectors/java/grpc_service.py +142 -0
  67. osscodeiq/detectors/java/ibm_mq.py +178 -0
  68. osscodeiq/detectors/java/jaxrs.py +160 -0
  69. osscodeiq/detectors/java/jdbc.py +196 -0
  70. osscodeiq/detectors/java/jms.py +116 -0
  71. osscodeiq/detectors/java/jpa_entity.py +143 -0
  72. osscodeiq/detectors/java/kafka.py +113 -0
  73. osscodeiq/detectors/java/kafka_protocol.py +70 -0
  74. osscodeiq/detectors/java/micronaut.py +248 -0
  75. osscodeiq/detectors/java/module_deps.py +191 -0
  76. osscodeiq/detectors/java/public_api.py +206 -0
  77. osscodeiq/detectors/java/quarkus.py +176 -0
  78. osscodeiq/detectors/java/rabbitmq.py +150 -0
  79. osscodeiq/detectors/java/raw_sql.py +136 -0
  80. osscodeiq/detectors/java/repository.py +131 -0
  81. osscodeiq/detectors/java/rmi.py +129 -0
  82. osscodeiq/detectors/java/spring_events.py +117 -0
  83. osscodeiq/detectors/java/spring_rest.py +168 -0
  84. osscodeiq/detectors/java/spring_security.py +212 -0
  85. osscodeiq/detectors/java/tibco_ems.py +193 -0
  86. osscodeiq/detectors/java/websocket.py +188 -0
  87. osscodeiq/detectors/kotlin/__init__.py +0 -0
  88. osscodeiq/detectors/kotlin/kotlin_structures.py +124 -0
  89. osscodeiq/detectors/kotlin/ktor_routes.py +163 -0
  90. osscodeiq/detectors/proto/__init__.py +0 -0
  91. osscodeiq/detectors/proto/proto_structure.py +153 -0
  92. osscodeiq/detectors/python/__init__.py +0 -0
  93. osscodeiq/detectors/python/celery_tasks.py +88 -0
  94. osscodeiq/detectors/python/django_auth.py +132 -0
  95. osscodeiq/detectors/python/django_models.py +157 -0
  96. osscodeiq/detectors/python/django_views.py +74 -0
  97. osscodeiq/detectors/python/fastapi_auth.py +143 -0
  98. osscodeiq/detectors/python/fastapi_routes.py +68 -0
  99. osscodeiq/detectors/python/flask_routes.py +67 -0
  100. osscodeiq/detectors/python/kafka_python.py +175 -0
  101. osscodeiq/detectors/python/pydantic_models.py +115 -0
  102. osscodeiq/detectors/python/python_structures.py +234 -0
  103. osscodeiq/detectors/python/sqlalchemy_models.py +82 -0
  104. osscodeiq/detectors/registry.py +100 -0
  105. osscodeiq/detectors/rust/__init__.py +0 -0
  106. osscodeiq/detectors/rust/actix_web.py +234 -0
  107. osscodeiq/detectors/rust/rust_structures.py +174 -0
  108. osscodeiq/detectors/scala/__init__.py +0 -0
  109. osscodeiq/detectors/scala/scala_structures.py +128 -0
  110. osscodeiq/detectors/shell/__init__.py +0 -0
  111. osscodeiq/detectors/shell/bash_detector.py +127 -0
  112. osscodeiq/detectors/shell/powershell_detector.py +118 -0
  113. osscodeiq/detectors/typescript/__init__.py +0 -0
  114. osscodeiq/detectors/typescript/express_routes.py +55 -0
  115. osscodeiq/detectors/typescript/fastify_routes.py +156 -0
  116. osscodeiq/detectors/typescript/graphql_resolvers.py +100 -0
  117. osscodeiq/detectors/typescript/kafka_js.py +164 -0
  118. osscodeiq/detectors/typescript/mongoose_orm.py +151 -0
  119. osscodeiq/detectors/typescript/nestjs_controllers.py +99 -0
  120. osscodeiq/detectors/typescript/nestjs_guards.py +138 -0
  121. osscodeiq/detectors/typescript/passport_jwt.py +133 -0
  122. osscodeiq/detectors/typescript/prisma_orm.py +96 -0
  123. osscodeiq/detectors/typescript/remix_routes.py +160 -0
  124. osscodeiq/detectors/typescript/sequelize_orm.py +136 -0
  125. osscodeiq/detectors/typescript/typeorm_entities.py +86 -0
  126. osscodeiq/detectors/typescript/typescript_structures.py +185 -0
  127. osscodeiq/detectors/utils.py +49 -0
  128. osscodeiq/discovery/__init__.py +11 -0
  129. osscodeiq/discovery/change_detector.py +97 -0
  130. osscodeiq/discovery/file_discovery.py +342 -0
  131. osscodeiq/flow/__init__.py +0 -0
  132. osscodeiq/flow/engine.py +78 -0
  133. osscodeiq/flow/models.py +72 -0
  134. osscodeiq/flow/renderer.py +127 -0
  135. osscodeiq/flow/templates/interactive.html +252 -0
  136. osscodeiq/flow/vendor/cytoscape-dagre.min.js +8 -0
  137. osscodeiq/flow/vendor/cytoscape.min.js +32 -0
  138. osscodeiq/flow/vendor/dagre.min.js +3809 -0
  139. osscodeiq/flow/views.py +357 -0
  140. osscodeiq/graph/__init__.py +0 -0
  141. osscodeiq/graph/backend.py +52 -0
  142. osscodeiq/graph/backends/__init__.py +23 -0
  143. osscodeiq/graph/backends/kuzu.py +576 -0
  144. osscodeiq/graph/backends/networkx.py +135 -0
  145. osscodeiq/graph/backends/sqlite_backend.py +406 -0
  146. osscodeiq/graph/builder.py +297 -0
  147. osscodeiq/graph/query.py +228 -0
  148. osscodeiq/graph/store.py +183 -0
  149. osscodeiq/graph/views.py +231 -0
  150. osscodeiq/models/__init__.py +17 -0
  151. osscodeiq/models/graph.py +116 -0
  152. osscodeiq/output/__init__.py +0 -0
  153. osscodeiq/output/dot.py +171 -0
  154. osscodeiq/output/mermaid.py +160 -0
  155. osscodeiq/output/safety.py +58 -0
  156. osscodeiq/output/serializers.py +42 -0
  157. osscodeiq/parsing/__init__.py +5 -0
  158. osscodeiq/parsing/languages/__init__.py +0 -0
  159. osscodeiq/parsing/languages/base.py +23 -0
  160. osscodeiq/parsing/languages/java.py +68 -0
  161. osscodeiq/parsing/languages/python.py +57 -0
  162. osscodeiq/parsing/languages/typescript.py +95 -0
  163. osscodeiq/parsing/parser_manager.py +125 -0
  164. osscodeiq/parsing/structured/__init__.py +0 -0
  165. osscodeiq/parsing/structured/gradle_parser.py +78 -0
  166. osscodeiq/parsing/structured/json_parser.py +24 -0
  167. osscodeiq/parsing/structured/properties_parser.py +56 -0
  168. osscodeiq/parsing/structured/sql_parser.py +54 -0
  169. osscodeiq/parsing/structured/xml_parser.py +148 -0
  170. osscodeiq/parsing/structured/yaml_parser.py +38 -0
  171. osscodeiq/server/__init__.py +7 -0
  172. osscodeiq/server/app.py +53 -0
  173. osscodeiq/server/mcp_server.py +174 -0
  174. osscodeiq/server/middleware.py +16 -0
  175. osscodeiq/server/routes.py +184 -0
  176. osscodeiq/server/service.py +445 -0
  177. osscodeiq/server/templates/welcome.html +56 -0
  178. osscodeiq-0.0.0.dist-info/METADATA +30 -0
  179. osscodeiq-0.0.0.dist-info/RECORD +183 -0
  180. osscodeiq-0.0.0.dist-info/WHEEL +5 -0
  181. osscodeiq-0.0.0.dist-info/entry_points.txt +2 -0
  182. osscodeiq-0.0.0.dist-info/licenses/LICENSE +21 -0
  183. osscodeiq-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,151 @@
1
+ """Mongoose ODM detector for TypeScript/JavaScript."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class MongooseORMDetector:
13
+ """Detects Mongoose ODM usage patterns in TypeScript/JavaScript files."""
14
+
15
+ name: str = "mongoose_orm"
16
+ supported_languages: tuple[str, ...] = ("typescript", "javascript")
17
+
18
+ # mongoose.model('Name', schema)
19
+ _MODEL_RE = re.compile(r"mongoose\.model\s*\(\s*['\"](\w+)['\"]")
20
+ # new Schema({ or new mongoose.Schema({
21
+ _SCHEMA_RE = re.compile(r"(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\.)?Schema\s*\(")
22
+ # mongoose.connect(
23
+ _CONNECT_RE = re.compile(r"mongoose\.connect\s*\(")
24
+ # Model query operations
25
+ _QUERY_RE = re.compile(
26
+ r"(\w+)\.(find|findOne|findById|findOneAndUpdate|findOneAndDelete"
27
+ r"|create|insertMany|updateOne|updateMany|deleteOne|deleteMany"
28
+ r"|countDocuments|aggregate)\s*\("
29
+ )
30
+ # schema.virtual('name')
31
+ _VIRTUAL_RE = re.compile(r"(\w+)\.virtual\s*\(\s*['\"](\w+)['\"]")
32
+ # schema.pre('save', ...) / schema.post('save', ...)
33
+ _HOOK_RE = re.compile(r"(\w+)\.(pre|post)\s*\(\s*['\"](\w+)['\"]")
34
+
35
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
36
+ result = DetectorResult()
37
+ text = decode_text(ctx)
38
+
39
+ seen_models: dict[str, str] = {}
40
+ schema_vars: set[str] = set()
41
+
42
+ # Detect mongoose.connect -> DATABASE_CONNECTION
43
+ for match in self._CONNECT_RE.finditer(text):
44
+ line = text[: match.start()].count("\n") + 1
45
+ node_id = f"mongoose:{ctx.file_path}:connection:{line}"
46
+ result.nodes.append(
47
+ GraphNode(
48
+ id=node_id,
49
+ kind=NodeKind.DATABASE_CONNECTION,
50
+ label="mongoose.connect",
51
+ fqn=f"{ctx.file_path}::mongoose.connect",
52
+ module=ctx.module_name,
53
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
54
+ properties={"framework": "mongoose"},
55
+ )
56
+ )
57
+
58
+ # Detect new Schema({ ... }) -> ENTITY for schema definition
59
+ for match in self._SCHEMA_RE.finditer(text):
60
+ var_name = match.group(1)
61
+ schema_vars.add(var_name)
62
+ line = text[: match.start()].count("\n") + 1
63
+ node_id = f"mongoose:{ctx.file_path}:schema:{var_name}"
64
+ result.nodes.append(
65
+ GraphNode(
66
+ id=node_id,
67
+ kind=NodeKind.ENTITY,
68
+ label=var_name,
69
+ fqn=f"{ctx.file_path}::{var_name}",
70
+ module=ctx.module_name,
71
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
72
+ properties={"framework": "mongoose", "definition": "schema"},
73
+ )
74
+ )
75
+
76
+ # Detect mongoose.model('Name', schema) -> ENTITY
77
+ for match in self._MODEL_RE.finditer(text):
78
+ model_name = match.group(1)
79
+ line = text[: match.start()].count("\n") + 1
80
+ model_id = f"mongoose:{ctx.file_path}:model:{model_name}"
81
+ seen_models[model_name] = model_id
82
+ result.nodes.append(
83
+ GraphNode(
84
+ id=model_id,
85
+ kind=NodeKind.ENTITY,
86
+ label=model_name,
87
+ fqn=f"{ctx.file_path}::{model_name}",
88
+ module=ctx.module_name,
89
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
90
+ properties={"framework": "mongoose", "definition": "model"},
91
+ )
92
+ )
93
+
94
+ # Detect schema.virtual('name') -> property on schema
95
+ virtuals: list[str] = []
96
+ for match in self._VIRTUAL_RE.finditer(text):
97
+ var_name = match.group(1)
98
+ virtual_name = match.group(2)
99
+ if var_name in schema_vars:
100
+ virtuals.append(virtual_name)
101
+
102
+ # Attach virtuals to schema nodes
103
+ if virtuals:
104
+ for node in result.nodes:
105
+ if node.properties.get("definition") == "schema":
106
+ node.properties["virtuals"] = virtuals
107
+
108
+ # Detect schema.pre/post hooks -> EVENT nodes
109
+ for match in self._HOOK_RE.finditer(text):
110
+ var_name = match.group(1)
111
+ hook_type = match.group(2)
112
+ event_name = match.group(3)
113
+ if var_name in schema_vars:
114
+ line = text[: match.start()].count("\n") + 1
115
+ event_id = f"mongoose:{ctx.file_path}:hook:{hook_type}:{event_name}:{line}"
116
+ result.nodes.append(
117
+ GraphNode(
118
+ id=event_id,
119
+ kind=NodeKind.EVENT,
120
+ label=f"{hook_type}:{event_name}",
121
+ fqn=f"{ctx.file_path}::{hook_type}:{event_name}",
122
+ module=ctx.module_name,
123
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
124
+ properties={
125
+ "framework": "mongoose",
126
+ "hook_type": hook_type,
127
+ "event": event_name,
128
+ },
129
+ )
130
+ )
131
+
132
+ # Detect query operations -> QUERIES edges
133
+ for match in self._QUERY_RE.finditer(text):
134
+ model_name = match.group(1)
135
+ operation = match.group(2)
136
+ line = text[: match.start()].count("\n") + 1
137
+
138
+ target_id = seen_models.get(
139
+ model_name, f"mongoose:{ctx.file_path}:model:{model_name}"
140
+ )
141
+ result.edges.append(
142
+ GraphEdge(
143
+ source=ctx.file_path,
144
+ target=target_id,
145
+ kind=EdgeKind.QUERIES,
146
+ label=f"{model_name}.{operation}",
147
+ properties={"operation": operation, "line": line},
148
+ )
149
+ )
150
+
151
+ return result
@@ -0,0 +1,99 @@
1
+ """NestJS controller detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class NestJSControllerDetector:
13
+ """Detects NestJS controller decorators (@Controller, @Get, @Post, etc.)."""
14
+
15
+ name: str = "typescript.nestjs_controllers"
16
+ supported_languages: tuple[str, ...] = ("typescript",)
17
+
18
+ # @Controller('users') or @Controller('/api/users')
19
+ _CONTROLLER_PATTERN = re.compile(
20
+ r"@Controller\(\s*['\"`]?([^'\"`)\s]*)['\"`]?\s*\)\s*\n\s*(?:export\s+)?class\s+(\w+)"
21
+ )
22
+
23
+ # @Get('/:id'), @Post(), @Put('/:id'), @Delete('/:id'), @Patch('/:id')
24
+ _ROUTE_PATTERN = re.compile(
25
+ r"@(Get|Post|Put|Delete|Patch|Options|Head)\(\s*['\"`]?([^'\"`)\s]*)['\"`]?\s*\)"
26
+ r"\s*\n\s*(?:async\s+)?(\w+)"
27
+ )
28
+
29
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
30
+ result = DetectorResult()
31
+ text = decode_text(ctx)
32
+
33
+ # Find controllers and their base paths
34
+ controllers: dict[str, str] = {}
35
+ for match in self._CONTROLLER_PATTERN.finditer(text):
36
+ base_path = match.group(1) or ""
37
+ class_name = match.group(2)
38
+ controllers[class_name] = base_path
39
+ line = text[:match.start()].count("\n") + 1
40
+
41
+ class_id = f"class:{ctx.file_path}::{class_name}"
42
+ result.nodes.append(GraphNode(
43
+ id=class_id,
44
+ kind=NodeKind.CLASS,
45
+ label=class_name,
46
+ fqn=f"{ctx.file_path}::{class_name}",
47
+ module=ctx.module_name,
48
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
49
+ annotations=["@Controller"],
50
+ properties={"framework": "nestjs", "stereotype": "controller"},
51
+ ))
52
+
53
+ # Build sorted list of (line, class_name, base_path)
54
+ controller_ranges = []
55
+ for match in self._CONTROLLER_PATTERN.finditer(text):
56
+ ctrl_line = text[:match.start()].count("\n") + 1
57
+ controller_ranges.append((ctrl_line, match.group(2), match.group(1) or ""))
58
+
59
+ # For each route, find the controller it belongs to
60
+ for match in self._ROUTE_PATTERN.finditer(text):
61
+ route_line = text[:match.start()].count("\n") + 1
62
+ # Find the last controller that starts before this route
63
+ enclosing_ctrl = ("", "") # (class_name, base_path)
64
+ for ctrl_line, ctrl_name, ctrl_path in controller_ranges:
65
+ if ctrl_line <= route_line:
66
+ enclosing_ctrl = (ctrl_name, ctrl_path)
67
+ current_class, base_path = enclosing_ctrl
68
+
69
+ method = match.group(1).upper()
70
+ path = match.group(2) or ""
71
+ func_name = match.group(3)
72
+
73
+ full_path = f"/{base_path}/{path}".replace("//", "/").rstrip("/") or "/"
74
+
75
+ node_id = f"endpoint:{ctx.module_name or ''}:{method}:{full_path}"
76
+ result.nodes.append(GraphNode(
77
+ id=node_id,
78
+ kind=NodeKind.ENDPOINT,
79
+ label=f"{method} {full_path}",
80
+ fqn=f"{ctx.file_path}::{func_name}",
81
+ module=ctx.module_name,
82
+ location=SourceLocation(file_path=ctx.file_path, line_start=route_line),
83
+ properties={
84
+ "protocol": "REST",
85
+ "http_method": method,
86
+ "path_pattern": full_path,
87
+ "framework": "nestjs",
88
+ },
89
+ ))
90
+
91
+ if current_class:
92
+ class_id = f"class:{ctx.file_path}::{current_class}"
93
+ result.edges.append(GraphEdge(
94
+ source=class_id,
95
+ target=node_id,
96
+ kind=EdgeKind.EXPOSES,
97
+ ))
98
+
99
+ return result
@@ -0,0 +1,138 @@
1
+ """NestJS guard and role-based access control detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class NestJSGuardsDetector:
13
+ """Detects NestJS guard decorators, role decorators, and canActivate implementations."""
14
+
15
+ name: str = "typescript.nestjs_guards"
16
+ supported_languages: tuple[str, ...] = ("typescript",)
17
+
18
+ # @UseGuards(JwtAuthGuard) or @UseGuards(JwtAuthGuard, RolesGuard)
19
+ _USE_GUARDS_PATTERN = re.compile(
20
+ r"@UseGuards\(\s*([^)]+)\)"
21
+ )
22
+
23
+ # @Roles('admin', 'user') or @Roles("admin", "user")
24
+ _ROLES_PATTERN = re.compile(
25
+ r"@Roles\(\s*([^)]+)\)"
26
+ )
27
+
28
+ # canActivate(context): boolean/Promise<boolean>/Observable<boolean>
29
+ _CAN_ACTIVATE_PATTERN = re.compile(
30
+ r"(?:async\s+)?canActivate\s*\("
31
+ )
32
+
33
+ # AuthGuard('jwt') or AuthGuard("local")
34
+ _AUTH_GUARD_PATTERN = re.compile(
35
+ r"AuthGuard\(\s*['\"](\w+)['\"]\s*\)"
36
+ )
37
+
38
+ @staticmethod
39
+ def _parse_roles(raw: str) -> list[str]:
40
+ """Extract role strings from a @Roles(...) argument list."""
41
+ roles: list[str] = []
42
+ for match in re.finditer(r"""['"]([\w\-]+)['"]""", raw):
43
+ roles.append(match.group(1))
44
+ return roles
45
+
46
+ @staticmethod
47
+ def _parse_guard_names(raw: str) -> list[str]:
48
+ """Extract guard class names from a @UseGuards(...) argument list."""
49
+ names: list[str] = []
50
+ for token in raw.split(","):
51
+ token = token.strip()
52
+ if token and re.match(r"^\w+$", token):
53
+ names.append(token)
54
+ return names
55
+
56
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
57
+ result = DetectorResult()
58
+ text = decode_text(ctx)
59
+
60
+ # Detect @UseGuards(...)
61
+ for match in self._USE_GUARDS_PATTERN.finditer(text):
62
+ line = text[: match.start()].count("\n") + 1
63
+ guard_names = self._parse_guard_names(match.group(1))
64
+ for guard_name in guard_names:
65
+ node_id = f"auth:{ctx.file_path}:UseGuards({guard_name}):{line}"
66
+ result.nodes.append(GraphNode(
67
+ id=node_id,
68
+ kind=NodeKind.GUARD,
69
+ label=f"UseGuards({guard_name})",
70
+ fqn=f"{ctx.file_path}::UseGuards({guard_name})",
71
+ module=ctx.module_name,
72
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
73
+ annotations=["@UseGuards"],
74
+ properties={
75
+ "auth_type": "nestjs_guard",
76
+ "guard_name": guard_name,
77
+ "roles": [],
78
+ },
79
+ ))
80
+
81
+ # Detect @Roles(...)
82
+ for match in self._ROLES_PATTERN.finditer(text):
83
+ line = text[: match.start()].count("\n") + 1
84
+ roles = self._parse_roles(match.group(1))
85
+ node_id = f"auth:{ctx.file_path}:Roles:{line}"
86
+ result.nodes.append(GraphNode(
87
+ id=node_id,
88
+ kind=NodeKind.GUARD,
89
+ label=f"Roles({', '.join(roles)})",
90
+ fqn=f"{ctx.file_path}::Roles",
91
+ module=ctx.module_name,
92
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
93
+ annotations=["@Roles"],
94
+ properties={
95
+ "auth_type": "nestjs_guard",
96
+ "roles": roles,
97
+ },
98
+ ))
99
+
100
+ # Detect canActivate() implementations
101
+ for match in self._CAN_ACTIVATE_PATTERN.finditer(text):
102
+ line = text[: match.start()].count("\n") + 1
103
+ node_id = f"auth:{ctx.file_path}:canActivate:{line}"
104
+ result.nodes.append(GraphNode(
105
+ id=node_id,
106
+ kind=NodeKind.GUARD,
107
+ label="canActivate()",
108
+ fqn=f"{ctx.file_path}::canActivate",
109
+ module=ctx.module_name,
110
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
111
+ properties={
112
+ "auth_type": "nestjs_guard",
113
+ "guard_impl": "canActivate",
114
+ "roles": [],
115
+ },
116
+ ))
117
+
118
+ # Detect AuthGuard('jwt') etc.
119
+ for match in self._AUTH_GUARD_PATTERN.finditer(text):
120
+ line = text[: match.start()].count("\n") + 1
121
+ strategy = match.group(1)
122
+ node_id = f"auth:{ctx.file_path}:AuthGuard({strategy}):{line}"
123
+ result.nodes.append(GraphNode(
124
+ id=node_id,
125
+ kind=NodeKind.GUARD,
126
+ label=f"AuthGuard('{strategy}')",
127
+ fqn=f"{ctx.file_path}::AuthGuard({strategy})",
128
+ module=ctx.module_name,
129
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
130
+ annotations=["AuthGuard"],
131
+ properties={
132
+ "auth_type": "nestjs_guard",
133
+ "strategy": strategy,
134
+ "roles": [],
135
+ },
136
+ ))
137
+
138
+ return result
@@ -0,0 +1,133 @@
1
+ """Passport.js and JWT authentication detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class PassportJwtDetector:
13
+ """Detects Passport.js strategy registrations, authenticate calls, and JWT verification."""
14
+
15
+ name: str = "typescript.passport_jwt"
16
+ supported_languages: tuple[str, ...] = ("typescript", "javascript")
17
+
18
+ # passport.use(new JwtStrategy(...)) or passport.use(new LocalStrategy(...))
19
+ _PASSPORT_USE_PATTERN = re.compile(
20
+ r"passport\.use\(\s*new\s+(\w+Strategy)\s*\("
21
+ )
22
+
23
+ # passport.authenticate('jwt') or passport.authenticate("local", ...)
24
+ _PASSPORT_AUTH_PATTERN = re.compile(
25
+ r"passport\.authenticate\(\s*['\"](\w+)['\"]"
26
+ )
27
+
28
+ # jwt.verify(token, secret) or jwt.verify(token, secret, options)
29
+ _JWT_VERIFY_PATTERN = re.compile(
30
+ r"jwt\.verify\s*\("
31
+ )
32
+
33
+ # require('express-jwt') or require("express-jwt")
34
+ _REQUIRE_EXPRESS_JWT_PATTERN = re.compile(
35
+ r"""require\(\s*['"]express-jwt['"]\s*\)"""
36
+ )
37
+
38
+ # import { expressjwt } from 'express-jwt' (and variants)
39
+ _IMPORT_EXPRESS_JWT_PATTERN = re.compile(
40
+ r"""import\s+\{[^}]*\bexpressjwt\b[^}]*\}\s+from\s+['"]express-jwt['"]"""
41
+ )
42
+
43
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
44
+ result = DetectorResult()
45
+ text = decode_text(ctx)
46
+
47
+ # Detect passport.use(new XxxStrategy(...))
48
+ for match in self._PASSPORT_USE_PATTERN.finditer(text):
49
+ line = text[: match.start()].count("\n") + 1
50
+ strategy_name = match.group(1)
51
+ node_id = f"auth:{ctx.file_path}:passport.use({strategy_name}):{line}"
52
+ result.nodes.append(GraphNode(
53
+ id=node_id,
54
+ kind=NodeKind.GUARD,
55
+ label=f"passport.use({strategy_name})",
56
+ fqn=f"{ctx.file_path}::passport.use({strategy_name})",
57
+ module=ctx.module_name,
58
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
59
+ properties={
60
+ "auth_type": "passport",
61
+ "strategy": strategy_name,
62
+ },
63
+ ))
64
+
65
+ # Detect passport.authenticate('xxx')
66
+ for match in self._PASSPORT_AUTH_PATTERN.finditer(text):
67
+ line = text[: match.start()].count("\n") + 1
68
+ strategy = match.group(1)
69
+ node_id = f"auth:{ctx.file_path}:passport.authenticate({strategy}):{line}"
70
+ result.nodes.append(GraphNode(
71
+ id=node_id,
72
+ kind=NodeKind.MIDDLEWARE,
73
+ label=f"passport.authenticate('{strategy}')",
74
+ fqn=f"{ctx.file_path}::passport.authenticate({strategy})",
75
+ module=ctx.module_name,
76
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
77
+ properties={
78
+ "auth_type": "jwt",
79
+ "strategy": strategy,
80
+ },
81
+ ))
82
+
83
+ # Detect jwt.verify(...)
84
+ for match in self._JWT_VERIFY_PATTERN.finditer(text):
85
+ line = text[: match.start()].count("\n") + 1
86
+ node_id = f"auth:{ctx.file_path}:jwt.verify:{line}"
87
+ result.nodes.append(GraphNode(
88
+ id=node_id,
89
+ kind=NodeKind.MIDDLEWARE,
90
+ label="jwt.verify()",
91
+ fqn=f"{ctx.file_path}::jwt.verify",
92
+ module=ctx.module_name,
93
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
94
+ properties={
95
+ "auth_type": "jwt",
96
+ },
97
+ ))
98
+
99
+ # Detect require('express-jwt')
100
+ for match in self._REQUIRE_EXPRESS_JWT_PATTERN.finditer(text):
101
+ line = text[: match.start()].count("\n") + 1
102
+ node_id = f"auth:{ctx.file_path}:require(express-jwt):{line}"
103
+ result.nodes.append(GraphNode(
104
+ id=node_id,
105
+ kind=NodeKind.MIDDLEWARE,
106
+ label="require('express-jwt')",
107
+ fqn=f"{ctx.file_path}::require(express-jwt)",
108
+ module=ctx.module_name,
109
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
110
+ properties={
111
+ "auth_type": "jwt",
112
+ "library": "express-jwt",
113
+ },
114
+ ))
115
+
116
+ # Detect import { expressjwt } from 'express-jwt'
117
+ for match in self._IMPORT_EXPRESS_JWT_PATTERN.finditer(text):
118
+ line = text[: match.start()].count("\n") + 1
119
+ node_id = f"auth:{ctx.file_path}:import(expressjwt):{line}"
120
+ result.nodes.append(GraphNode(
121
+ id=node_id,
122
+ kind=NodeKind.MIDDLEWARE,
123
+ label="import { expressjwt }",
124
+ fqn=f"{ctx.file_path}::import(expressjwt)",
125
+ module=ctx.module_name,
126
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
127
+ properties={
128
+ "auth_type": "jwt",
129
+ "library": "express-jwt",
130
+ },
131
+ ))
132
+
133
+ return result
@@ -0,0 +1,96 @@
1
+ """Prisma ORM detector for TypeScript/JavaScript."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class PrismaORMDetector:
13
+ """Detects Prisma ORM usage patterns in TypeScript/JavaScript files."""
14
+
15
+ name: str = "prisma_orm"
16
+ supported_languages: tuple[str, ...] = ("typescript", "javascript")
17
+
18
+ _PRISMA_OP_RE = re.compile(
19
+ r"prisma\.(\w+)\.(findMany|findFirst|findUnique|create|update|delete|upsert|count|aggregate|groupBy)\s*\("
20
+ )
21
+ _PRISMA_CLIENT_RE = re.compile(r"new\s+PrismaClient\s*\(|PrismaClient\s*\(")
22
+ _PRISMA_IMPORT_RE = re.compile(r"""(?:import|require)\s*\(?[^)]*['"]@prisma/client['"]""")
23
+ _PRISMA_TRANSACTION_RE = re.compile(r"prisma\.\$transaction\s*\(")
24
+
25
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
26
+ result = DetectorResult()
27
+ text = decode_text(ctx)
28
+
29
+ # Detect PrismaClient instantiation -> DATABASE_CONNECTION
30
+ for match in self._PRISMA_CLIENT_RE.finditer(text):
31
+ line = text[: match.start()].count("\n") + 1
32
+ node_id = f"prisma:{ctx.file_path}:client:{line}"
33
+ props: dict[str, object] = {"framework": "prisma"}
34
+ # Check for $transaction usage
35
+ if self._PRISMA_TRANSACTION_RE.search(text):
36
+ props["transaction"] = True
37
+ result.nodes.append(
38
+ GraphNode(
39
+ id=node_id,
40
+ kind=NodeKind.DATABASE_CONNECTION,
41
+ label="PrismaClient",
42
+ fqn=f"{ctx.file_path}::PrismaClient",
43
+ module=ctx.module_name,
44
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
45
+ properties=props,
46
+ )
47
+ )
48
+
49
+ # Detect @prisma/client imports -> IMPORTS edge
50
+ for match in self._PRISMA_IMPORT_RE.finditer(text):
51
+ line = text[: match.start()].count("\n") + 1
52
+ result.edges.append(
53
+ GraphEdge(
54
+ source=ctx.file_path,
55
+ target="@prisma/client",
56
+ kind=EdgeKind.IMPORTS,
57
+ label="import @prisma/client",
58
+ properties={"line": line},
59
+ )
60
+ )
61
+
62
+ # Detect prisma model operations -> ENTITY nodes + QUERIES edges
63
+ seen_models: dict[str, str] = {}
64
+ for match in self._PRISMA_OP_RE.finditer(text):
65
+ model_name = match.group(1)
66
+ operation = match.group(2)
67
+ line = text[: match.start()].count("\n") + 1
68
+
69
+ # Create ENTITY node for each unique model
70
+ if model_name not in seen_models:
71
+ model_id = f"prisma:{ctx.file_path}:model:{model_name}"
72
+ seen_models[model_name] = model_id
73
+ result.nodes.append(
74
+ GraphNode(
75
+ id=model_id,
76
+ kind=NodeKind.ENTITY,
77
+ label=model_name,
78
+ fqn=f"{ctx.file_path}::{model_name}",
79
+ module=ctx.module_name,
80
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
81
+ properties={"framework": "prisma"},
82
+ )
83
+ )
84
+
85
+ # QUERIES edge from file to model
86
+ result.edges.append(
87
+ GraphEdge(
88
+ source=ctx.file_path,
89
+ target=seen_models[model_name],
90
+ kind=EdgeKind.QUERIES,
91
+ label=f"{model_name}.{operation}",
92
+ properties={"operation": operation, "line": line},
93
+ )
94
+ )
95
+
96
+ return result