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
osscodeiq/config.py ADDED
@@ -0,0 +1,113 @@
1
+ """Configuration loading and defaults for code-intelligence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import yaml
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class DiscoveryConfig(BaseModel):
13
+ """File discovery configuration."""
14
+
15
+ include_extensions: list[str] = Field(default_factory=lambda: [
16
+ ".java", ".py", ".ts", ".tsx", ".js", ".jsx",
17
+ ".xml", ".yaml", ".yml", ".json", ".properties",
18
+ ".gradle", ".gradle.kts", ".sql", ".graphql", ".gql",
19
+ ".proto", ".md", ".markdown",
20
+ ".cs", ".go", ".tf", ".tfvars", ".hcl",
21
+ ".cpp", ".cc", ".cxx", ".hpp", ".c", ".h",
22
+ ".sh", ".bash", ".zsh", ".ps1", ".psm1", ".psd1",
23
+ ".bat", ".cmd", ".bicep",
24
+ ".rb", ".rs", ".kt", ".kts", ".scala", ".swift",
25
+ ".r", ".R", ".pl", ".pm", ".lua", ".dart",
26
+ ".toml", ".ini", ".cfg", ".conf",
27
+ ".env", ".csv", ".dockerfile",
28
+ ".vue", ".svelte",
29
+ ".html", ".htm", ".css", ".scss", ".less",
30
+ ".mjs", ".cjs", ".mts", ".cts", ".jsonc",
31
+ ".groovy", ".pyi", ".razor", ".cshtml", ".adoc",
32
+ ])
33
+ exclude_patterns: list[str] = Field(default_factory=lambda: [
34
+ "**/node_modules/**",
35
+ "**/build/**",
36
+ "**/target/**",
37
+ "**/dist/**",
38
+ "**/.git/**",
39
+ "**/generated/**",
40
+ "**/__pycache__/**",
41
+ "**/venv/**",
42
+ "**/.venv/**",
43
+ ])
44
+ max_file_size_bytes: int = 1_048_576 # 1MB
45
+
46
+
47
+ class CacheConfig(BaseModel):
48
+ """Cache configuration."""
49
+
50
+ enabled: bool = True
51
+ directory: str = ".code-intelligence"
52
+ db_name: str = "cache.db"
53
+
54
+
55
+ class AnalysisConfig(BaseModel):
56
+ """Analysis configuration."""
57
+
58
+ parallelism: int = 8
59
+ incremental: bool = True
60
+
61
+
62
+ class OutputConfig(BaseModel):
63
+ """Output configuration."""
64
+
65
+ max_nodes: int = 500
66
+ default_format: str = "json"
67
+ default_view: str = "developer"
68
+
69
+
70
+ class DomainMapping(BaseModel):
71
+ """Domain grouping for architect view."""
72
+
73
+ name: str
74
+ modules: list[str]
75
+
76
+
77
+ class GraphConfig(BaseModel):
78
+ """Graph storage backend configuration."""
79
+
80
+ backend: str = "networkx" # networkx | kuzu | sqlite
81
+ path: str | None = None # For file-based backends (kuzu, sqlite)
82
+
83
+
84
+ class Config(BaseModel):
85
+ """Root configuration for code-intelligence."""
86
+
87
+ discovery: DiscoveryConfig = Field(default_factory=DiscoveryConfig)
88
+ cache: CacheConfig = Field(default_factory=CacheConfig)
89
+ analysis: AnalysisConfig = Field(default_factory=AnalysisConfig)
90
+ output: OutputConfig = Field(default_factory=OutputConfig)
91
+ graph: GraphConfig = Field(default_factory=GraphConfig)
92
+ domains: list[DomainMapping] = Field(default_factory=list)
93
+
94
+ @classmethod
95
+ def load(cls, config_path: Path | None = None, project_path: Path | None = None) -> Config:
96
+ """Load configuration from YAML file, falling back to defaults."""
97
+ if config_path and config_path.exists():
98
+ with open(config_path) as f:
99
+ data: dict[str, Any] = yaml.safe_load(f) or {}
100
+ return cls.model_validate(data)
101
+ # Check for default config file in project root
102
+ search_dir = project_path or Path.cwd()
103
+ for name in (".code-intelligence.yml", ".code-intelligence.yaml"):
104
+ default = search_dir / name
105
+ if default.exists():
106
+ with open(default) as f:
107
+ data = yaml.safe_load(f) or {}
108
+ return cls.model_validate(data)
109
+ return cls()
110
+
111
+ @property
112
+ def cache_path(self) -> Path:
113
+ return Path(self.cache.directory) / self.cache.db_name
File without changes
File without changes
@@ -0,0 +1,139 @@
1
+ """Certificate-based authentication detector (mTLS, X.509, TLS config, Azure AD)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+
8
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
9
+ from osscodeiq.detectors.utils import decode_text
10
+ from osscodeiq.models.graph import GraphNode, NodeKind, SourceLocation
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class _PatternDef:
15
+ """A pattern definition with its auth_type and optional property extractor."""
16
+
17
+ regex: re.Pattern[str]
18
+ auth_type: str
19
+ prop_key: str | None = None
20
+
21
+
22
+ # -- mTLS patterns --
23
+ _MTLS_PATTERNS: list[_PatternDef] = [
24
+ _PatternDef(re.compile(r"\bssl_verify_client\b"), "mtls"),
25
+ _PatternDef(re.compile(r"\brequestCert\s*:\s*true\b"), "mtls"),
26
+ _PatternDef(re.compile(r'\bclientAuth\s*=\s*"true"'), "mtls"),
27
+ _PatternDef(re.compile(r"\bX509AuthenticationFilter\b"), "mtls"),
28
+ _PatternDef(re.compile(r"\bAddCertificateForwarding\b"), "mtls"),
29
+ ]
30
+
31
+ # -- X.509 patterns --
32
+ _X509_PATTERNS: list[_PatternDef] = [
33
+ _PatternDef(re.compile(r"\bX509AuthenticationFilter\b"), "x509"),
34
+ _PatternDef(re.compile(r"\bCertificateAuthenticationDefaults\b"), "x509"),
35
+ _PatternDef(re.compile(r"\.x509\s*\("), "x509"),
36
+ ]
37
+
38
+ # -- TLS config patterns --
39
+ _TLS_CONFIG_PATTERNS: list[_PatternDef] = [
40
+ _PatternDef(re.compile(r"\bjavax\.net\.ssl\.keyStore\b"), "tls_config"),
41
+ _PatternDef(re.compile(r"\bssl\.SSLContext\b"), "tls_config"),
42
+ _PatternDef(re.compile(r"\btls\.createServer\b"), "tls_config"),
43
+ _PatternDef(
44
+ re.compile(r"""(?:cert|key|ca)\s*[=:]\s*(?:fs\.readFileSync\s*\(|['"][\w/.\\-]+\.(?:pem|crt|key|cert)['"])"""),
45
+ "tls_config",
46
+ "cert_path",
47
+ ),
48
+ _PatternDef(re.compile(r"\btrustStore\b"), "tls_config"),
49
+ ]
50
+
51
+ # -- Azure AD patterns --
52
+ _AZURE_AD_PATTERNS: list[_PatternDef] = [
53
+ _PatternDef(re.compile(r"\bAzureAd\b"), "azure_ad"),
54
+ _PatternDef(re.compile(r"\bAZURE_TENANT_ID\b"), "azure_ad", "tenant_id"),
55
+ _PatternDef(re.compile(r"\bAZURE_CLIENT_ID\b"), "azure_ad"),
56
+ _PatternDef(re.compile(r"""\bmsal\b"""), "azure_ad"),
57
+ _PatternDef(re.compile(r"""['"]@azure/msal-browser['"]"""), "azure_ad"),
58
+ _PatternDef(re.compile(r"\bAddMicrosoftIdentityWebApi\b"), "azure_ad"),
59
+ _PatternDef(re.compile(r"\bClientCertificateCredential\b"), "azure_ad"),
60
+ ]
61
+
62
+ _ALL_PATTERNS: list[_PatternDef] = (
63
+ _MTLS_PATTERNS + _X509_PATTERNS + _TLS_CONFIG_PATTERNS + _AZURE_AD_PATTERNS
64
+ )
65
+
66
+ # Dedup: when the same line matches both mTLS and x509 via X509AuthenticationFilter,
67
+ # prefer the more specific auth_type already recorded.
68
+
69
+ _CERT_PATH_RE = re.compile(
70
+ r"""['"]([^'"]*\.(?:pem|crt|key|cert|pfx|p12))['"]"""
71
+ )
72
+ _TENANT_ID_RE = re.compile(
73
+ r"""AZURE_TENANT_ID\s*[=:]\s*['"]?([a-f0-9-]+)['"]?"""
74
+ )
75
+
76
+
77
+ class CertificateAuthDetector:
78
+ """Detects certificate-based authentication patterns across multiple languages."""
79
+
80
+ name: str = "certificate_auth"
81
+ supported_languages: tuple[str, ...] = (
82
+ "java", "python", "typescript", "csharp", "json", "yaml",
83
+ )
84
+
85
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
86
+ result = DetectorResult()
87
+ text = decode_text(ctx)
88
+ lines = text.split("\n")
89
+
90
+ # Track which lines already produced a node (first match wins per line).
91
+ seen_lines: set[int] = set()
92
+
93
+ for line_idx, line in enumerate(lines):
94
+ for pdef in _ALL_PATTERNS:
95
+ if line_idx in seen_lines:
96
+ break
97
+ if pdef.regex.search(line):
98
+ seen_lines.add(line_idx)
99
+ line_num = line_idx + 1
100
+ matched_text = line.strip()
101
+
102
+ properties: dict[str, str] = {
103
+ "auth_type": pdef.auth_type,
104
+ "language": ctx.language,
105
+ "pattern": matched_text[:120],
106
+ }
107
+
108
+ # Extract cert_path if present
109
+ cert_m = _CERT_PATH_RE.search(line)
110
+ if cert_m:
111
+ properties["cert_path"] = cert_m.group(1)
112
+
113
+ # Extract tenant_id if present
114
+ tenant_m = _TENANT_ID_RE.search(line)
115
+ if tenant_m:
116
+ properties["tenant_id"] = tenant_m.group(1)
117
+
118
+ # Detect auth_flow for Azure AD
119
+ if pdef.auth_type == "azure_ad":
120
+ if "ClientCertificateCredential" in line:
121
+ properties["auth_flow"] = "client_certificate"
122
+ elif "msal" in line.lower():
123
+ properties["auth_flow"] = "msal"
124
+
125
+ node = GraphNode(
126
+ id=f"auth:{ctx.file_path}:cert:{line_num}",
127
+ kind=NodeKind.GUARD,
128
+ label=f"Certificate auth ({pdef.auth_type}): {matched_text[:60]}",
129
+ module=ctx.module_name,
130
+ location=SourceLocation(
131
+ file_path=ctx.file_path,
132
+ line_start=line_num,
133
+ line_end=line_num,
134
+ ),
135
+ properties=properties,
136
+ )
137
+ result.nodes.append(node)
138
+
139
+ return result
@@ -0,0 +1,89 @@
1
+ """LDAP authentication detector for Java, Python, TypeScript, and C# source files."""
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
+ # -- Java patterns --
12
+ _JAVA_PATTERNS: list[re.Pattern[str]] = [
13
+ re.compile(r"\bLdapContextSource\b"),
14
+ re.compile(r"\bLdapTemplate\b"),
15
+ re.compile(r"\bActiveDirectoryLdapAuthenticationProvider\b"),
16
+ re.compile(r"@EnableLdapRepositories\b"),
17
+ ]
18
+
19
+ # -- Python patterns --
20
+ _PYTHON_PATTERNS: list[re.Pattern[str]] = [
21
+ re.compile(r"\bldap3\.Connection\b"),
22
+ re.compile(r"\bldap3\.Server\b"),
23
+ re.compile(r"\bAUTH_LDAP_SERVER_URI\b"),
24
+ re.compile(r"\bAUTH_LDAP_BIND_DN\b"),
25
+ ]
26
+
27
+ # -- TypeScript patterns --
28
+ _TS_PATTERNS: list[re.Pattern[str]] = [
29
+ re.compile(r"""require\s*\(\s*['"]ldapjs['"]\s*\)"""),
30
+ re.compile(r"""(?:import\s+.*\s+from\s+['"]ldapjs['"]|import\s+ldapjs\b)"""),
31
+ re.compile(r"""['"]passport-ldapauth['"]"""),
32
+ ]
33
+
34
+ # -- C# patterns --
35
+ _CSHARP_PATTERNS: list[re.Pattern[str]] = [
36
+ re.compile(r"\bSystem\.DirectoryServices\b"),
37
+ re.compile(r"\bLdapConnection\b"),
38
+ re.compile(r"\bDirectoryEntry\b"),
39
+ ]
40
+
41
+ _LANGUAGE_PATTERNS: dict[str, list[re.Pattern[str]]] = {
42
+ "java": _JAVA_PATTERNS,
43
+ "python": _PYTHON_PATTERNS,
44
+ "typescript": _TS_PATTERNS,
45
+ "csharp": _CSHARP_PATTERNS,
46
+ }
47
+
48
+
49
+ class LdapAuthDetector:
50
+ """Detects LDAP authentication patterns across multiple languages."""
51
+
52
+ name: str = "ldap_auth"
53
+ supported_languages: tuple[str, ...] = ("java", "python", "typescript", "csharp")
54
+
55
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
56
+ result = DetectorResult()
57
+ if ctx.language not in _LANGUAGE_PATTERNS:
58
+ return result
59
+
60
+ text = decode_text(ctx)
61
+ lines = text.split("\n")
62
+ patterns = _LANGUAGE_PATTERNS[ctx.language]
63
+ seen_lines: set[int] = set()
64
+
65
+ for line_idx, line in enumerate(lines):
66
+ for pattern in patterns:
67
+ if pattern.search(line) and line_idx not in seen_lines:
68
+ seen_lines.add(line_idx)
69
+ line_num = line_idx + 1
70
+ matched_text = line.strip()
71
+ node = GraphNode(
72
+ id=f"auth:{ctx.file_path}:ldap:{line_num}",
73
+ kind=NodeKind.GUARD,
74
+ label=f"LDAP auth: {matched_text[:80]}",
75
+ module=ctx.module_name,
76
+ location=SourceLocation(
77
+ file_path=ctx.file_path,
78
+ line_start=line_num,
79
+ line_end=line_num,
80
+ ),
81
+ properties={
82
+ "auth_type": "ldap",
83
+ "language": ctx.language,
84
+ "pattern": matched_text[:120],
85
+ },
86
+ )
87
+ result.nodes.append(node)
88
+
89
+ return result
@@ -0,0 +1,120 @@
1
+ """Session, header, API key, and CSRF authentication detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+
8
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
9
+ from osscodeiq.detectors.utils import decode_text
10
+ from osscodeiq.models.graph import GraphNode, NodeKind, SourceLocation
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class _PatternDef:
15
+ regex: re.Pattern[str]
16
+ auth_type: str
17
+ node_kind: NodeKind
18
+
19
+
20
+ # -- Session patterns --
21
+ _SESSION_PATTERNS: list[_PatternDef] = [
22
+ _PatternDef(re.compile(r"""['"]express-session['"]"""), "session", NodeKind.MIDDLEWARE),
23
+ _PatternDef(re.compile(r"""['"]cookie-session['"]"""), "session", NodeKind.MIDDLEWARE),
24
+ _PatternDef(re.compile(r"@SessionAttributes\b"), "session", NodeKind.GUARD),
25
+ _PatternDef(re.compile(r"\bSessionMiddleware\b"), "session", NodeKind.MIDDLEWARE),
26
+ _PatternDef(re.compile(r"\bHttpSession\b"), "session", NodeKind.GUARD),
27
+ _PatternDef(re.compile(r"\bSESSION_ENGINE\b"), "session", NodeKind.GUARD),
28
+ ]
29
+
30
+ # -- Header patterns --
31
+ _HEADER_PATTERNS: list[_PatternDef] = [
32
+ _PatternDef(re.compile(r"""['"]X-API-Key['"]""", re.IGNORECASE), "header", NodeKind.GUARD),
33
+ _PatternDef(
34
+ re.compile(r"""(?:req|request|ctx)\.headers?\s*\[\s*['"]authorization['"]\s*\]""", re.IGNORECASE),
35
+ "header",
36
+ NodeKind.GUARD,
37
+ ),
38
+ _PatternDef(
39
+ re.compile(r"""getHeader\s*\(\s*['"]Authorization['"]""", re.IGNORECASE),
40
+ "header",
41
+ NodeKind.GUARD,
42
+ ),
43
+ ]
44
+
45
+ # -- API key patterns --
46
+ _API_KEY_PATTERNS: list[_PatternDef] = [
47
+ _PatternDef(
48
+ re.compile(r"""(?:req|request)\.headers?\s*\[\s*['"]x-api-key['"]\s*\]""", re.IGNORECASE),
49
+ "api_key",
50
+ NodeKind.GUARD,
51
+ ),
52
+ _PatternDef(re.compile(r"\bapi[_-]?key\s*[=:]\s*", re.IGNORECASE), "api_key", NodeKind.GUARD),
53
+ _PatternDef(re.compile(r"\bvalidate_?api_?key\b", re.IGNORECASE), "api_key", NodeKind.GUARD),
54
+ ]
55
+
56
+ # -- CSRF patterns --
57
+ _CSRF_PATTERNS: list[_PatternDef] = [
58
+ _PatternDef(re.compile(r"@csrf_protect\b"), "csrf", NodeKind.GUARD),
59
+ _PatternDef(re.compile(r"\bcsrf_exempt\b"), "csrf", NodeKind.GUARD),
60
+ _PatternDef(re.compile(r"\bCsrfViewMiddleware\b"), "csrf", NodeKind.MIDDLEWARE),
61
+ _PatternDef(re.compile(r"""['"]csurf['"]"""), "csrf", NodeKind.MIDDLEWARE),
62
+ ]
63
+
64
+ _ALL_PATTERNS: list[_PatternDef] = (
65
+ _SESSION_PATTERNS + _HEADER_PATTERNS + _API_KEY_PATTERNS + _CSRF_PATTERNS
66
+ )
67
+
68
+ # Map auth_type to the ID tag used in node IDs.
69
+ _ID_TAG: dict[str, str] = {
70
+ "session": "session",
71
+ "header": "header",
72
+ "api_key": "apikey",
73
+ "csrf": "csrf",
74
+ }
75
+
76
+
77
+ class SessionHeaderAuthDetector:
78
+ """Detects session, header, API-key, and CSRF auth patterns."""
79
+
80
+ name: str = "session_header_auth"
81
+ supported_languages: tuple[str, ...] = ("java", "python", "typescript")
82
+
83
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
84
+ result = DetectorResult()
85
+ if ctx.language not in self.supported_languages:
86
+ return result
87
+
88
+ text = decode_text(ctx)
89
+ lines = text.split("\n")
90
+ seen_lines: set[int] = set()
91
+
92
+ for line_idx, line in enumerate(lines):
93
+ for pdef in _ALL_PATTERNS:
94
+ if line_idx in seen_lines:
95
+ break
96
+ if pdef.regex.search(line):
97
+ seen_lines.add(line_idx)
98
+ line_num = line_idx + 1
99
+ matched_text = line.strip()
100
+ tag = _ID_TAG[pdef.auth_type]
101
+
102
+ node = GraphNode(
103
+ id=f"auth:{ctx.file_path}:{tag}:{line_num}",
104
+ kind=pdef.node_kind,
105
+ label=f"{pdef.auth_type} auth: {matched_text[:70]}",
106
+ module=ctx.module_name,
107
+ location=SourceLocation(
108
+ file_path=ctx.file_path,
109
+ line_start=line_num,
110
+ line_end=line_num,
111
+ ),
112
+ properties={
113
+ "auth_type": pdef.auth_type,
114
+ "language": ctx.language,
115
+ "pattern": matched_text[:120],
116
+ },
117
+ )
118
+ result.nodes.append(node)
119
+
120
+ return result
@@ -0,0 +1,41 @@
1
+ """Detector protocol and context for OSSCodeIQ analysis."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any, Protocol, runtime_checkable
8
+
9
+ import tree_sitter
10
+
11
+ from osscodeiq.models.graph import GraphNode, GraphEdge, NodeKind, EdgeKind, SourceLocation
12
+
13
+
14
+ @dataclass
15
+ class DetectorResult:
16
+ """Result of running a detector on a file."""
17
+
18
+ nodes: list[GraphNode] = field(default_factory=list)
19
+ edges: list[GraphEdge] = field(default_factory=list)
20
+
21
+
22
+ @dataclass
23
+ class DetectorContext:
24
+ """Context passed to each detector for analysis."""
25
+
26
+ file_path: str # Relative to repo root
27
+ language: str
28
+ content: bytes
29
+ tree: tree_sitter.Tree | None = None
30
+ parsed_data: Any = None # For structured files (dict, ElementTree)
31
+ module_name: str | None = None # Owning module name
32
+
33
+
34
+ @runtime_checkable
35
+ class Detector(Protocol):
36
+ """Protocol that all detectors must implement."""
37
+
38
+ name: str
39
+ supported_languages: tuple[str, ...]
40
+
41
+ def detect(self, ctx: DetectorContext) -> DetectorResult: ...
File without changes
@@ -0,0 +1,128 @@
1
+ """Batch script (.bat/.cmd) structure detector for labels, calls, and variables."""
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 (
10
+ EdgeKind,
11
+ GraphEdge,
12
+ GraphNode,
13
+ NodeKind,
14
+ SourceLocation,
15
+ )
16
+
17
+ _LABEL_RE = re.compile(r'^:(\w+)', re.MULTILINE)
18
+ _CALL_RE = re.compile(r'CALL\s+:?(\S+)', re.IGNORECASE)
19
+ _SET_RE = re.compile(r'SET\s+(\w+)=', re.IGNORECASE)
20
+
21
+
22
+ class BatchStructureDetector:
23
+ """Detects Batch script structures: labels, CALL commands, and SET variables."""
24
+
25
+ name: str = "batch_structure"
26
+ supported_languages: tuple[str, ...] = ("batch",)
27
+
28
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
29
+ result = DetectorResult()
30
+
31
+ try:
32
+ text = decode_text(ctx)
33
+ except Exception:
34
+ return result
35
+
36
+ filepath = ctx.file_path
37
+ lines = text.split("\n")
38
+ module_id = f"bat:{filepath}"
39
+
40
+ # MODULE node for the script
41
+ result.nodes.append(GraphNode(
42
+ id=module_id,
43
+ kind=NodeKind.MODULE,
44
+ label=filepath,
45
+ fqn=filepath,
46
+ module=ctx.module_name,
47
+ location=SourceLocation(
48
+ file_path=filepath,
49
+ line_start=1,
50
+ ),
51
+ ))
52
+
53
+ for i, line in enumerate(lines):
54
+ line_num = i + 1
55
+ stripped = line.strip()
56
+
57
+ # Skip comments and echo off
58
+ if not stripped:
59
+ continue
60
+ upper = stripped.upper()
61
+ if upper.startswith("@ECHO OFF"):
62
+ continue
63
+ if upper.startswith("REM ") or upper == "REM":
64
+ continue
65
+ if stripped.startswith("::"):
66
+ continue
67
+
68
+ # Labels
69
+ m = _LABEL_RE.match(stripped)
70
+ if m:
71
+ label_name = m.group(1)
72
+ result.nodes.append(GraphNode(
73
+ id=f"bat:{filepath}:label:{label_name}",
74
+ kind=NodeKind.METHOD,
75
+ label=f":{label_name}",
76
+ fqn=f"{filepath}:{label_name}",
77
+ module=ctx.module_name,
78
+ location=SourceLocation(
79
+ file_path=filepath,
80
+ line_start=line_num,
81
+ ),
82
+ ))
83
+ result.edges.append(GraphEdge(
84
+ source=module_id,
85
+ target=f"bat:{filepath}:label:{label_name}",
86
+ kind=EdgeKind.CONTAINS,
87
+ label=f"{filepath} contains :{label_name}",
88
+ ))
89
+ continue
90
+
91
+ # CALL commands
92
+ m = _CALL_RE.search(stripped)
93
+ if m:
94
+ call_target = m.group(1)
95
+ # Determine if calling an internal label or external script
96
+ if call_target.startswith(":"):
97
+ target_id = f"bat:{filepath}:label:{call_target[1:]}"
98
+ elif "." in call_target:
99
+ # External script call
100
+ target_id = call_target
101
+ else:
102
+ target_id = f"bat:{filepath}:label:{call_target}"
103
+
104
+ result.edges.append(GraphEdge(
105
+ source=module_id,
106
+ target=target_id,
107
+ kind=EdgeKind.CALLS,
108
+ label=f"{filepath} calls {call_target}",
109
+ ))
110
+
111
+ # SET variables
112
+ m = _SET_RE.search(stripped)
113
+ if m:
114
+ var_name = m.group(1)
115
+ result.nodes.append(GraphNode(
116
+ id=f"bat:{filepath}:set:{var_name}",
117
+ kind=NodeKind.CONFIG_DEFINITION,
118
+ label=f"SET {var_name}",
119
+ fqn=f"{filepath}:{var_name}",
120
+ module=ctx.module_name,
121
+ location=SourceLocation(
122
+ file_path=filepath,
123
+ line_start=line_num,
124
+ ),
125
+ properties={"variable": var_name},
126
+ ))
127
+
128
+ return result