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.
- osscodeiq/__init__.py +0 -0
- osscodeiq/analyzer.py +467 -0
- osscodeiq/cache/__init__.py +0 -0
- osscodeiq/cache/hasher.py +23 -0
- osscodeiq/cache/store.py +300 -0
- osscodeiq/classifiers/__init__.py +0 -0
- osscodeiq/classifiers/layer_classifier.py +69 -0
- osscodeiq/cli.py +721 -0
- osscodeiq/config.py +113 -0
- osscodeiq/detectors/__init__.py +0 -0
- osscodeiq/detectors/auth/__init__.py +0 -0
- osscodeiq/detectors/auth/certificate_auth.py +139 -0
- osscodeiq/detectors/auth/ldap_auth.py +89 -0
- osscodeiq/detectors/auth/session_header_auth.py +120 -0
- osscodeiq/detectors/base.py +41 -0
- osscodeiq/detectors/config/__init__.py +0 -0
- osscodeiq/detectors/config/batch_structure.py +128 -0
- osscodeiq/detectors/config/cloudformation.py +183 -0
- osscodeiq/detectors/config/docker_compose.py +179 -0
- osscodeiq/detectors/config/github_actions.py +150 -0
- osscodeiq/detectors/config/gitlab_ci.py +216 -0
- osscodeiq/detectors/config/helm_chart.py +187 -0
- osscodeiq/detectors/config/ini_structure.py +101 -0
- osscodeiq/detectors/config/json_structure.py +72 -0
- osscodeiq/detectors/config/kubernetes.py +305 -0
- osscodeiq/detectors/config/kubernetes_rbac.py +212 -0
- osscodeiq/detectors/config/openapi.py +194 -0
- osscodeiq/detectors/config/package_json.py +99 -0
- osscodeiq/detectors/config/properties_detector.py +108 -0
- osscodeiq/detectors/config/pyproject_toml.py +169 -0
- osscodeiq/detectors/config/sql_structure.py +155 -0
- osscodeiq/detectors/config/toml_structure.py +93 -0
- osscodeiq/detectors/config/tsconfig_json.py +105 -0
- osscodeiq/detectors/config/yaml_structure.py +82 -0
- osscodeiq/detectors/cpp/__init__.py +0 -0
- osscodeiq/detectors/cpp/cpp_structures.py +192 -0
- osscodeiq/detectors/csharp/__init__.py +0 -0
- osscodeiq/detectors/csharp/csharp_efcore.py +184 -0
- osscodeiq/detectors/csharp/csharp_minimal_apis.py +156 -0
- osscodeiq/detectors/csharp/csharp_structures.py +317 -0
- osscodeiq/detectors/docs/__init__.py +0 -0
- osscodeiq/detectors/docs/markdown_structure.py +117 -0
- osscodeiq/detectors/frontend/__init__.py +0 -0
- osscodeiq/detectors/frontend/angular_components.py +177 -0
- osscodeiq/detectors/frontend/frontend_routes.py +259 -0
- osscodeiq/detectors/frontend/react_components.py +148 -0
- osscodeiq/detectors/frontend/svelte_components.py +84 -0
- osscodeiq/detectors/frontend/vue_components.py +150 -0
- osscodeiq/detectors/generic/__init__.py +1 -0
- osscodeiq/detectors/generic/imports_detector.py +413 -0
- osscodeiq/detectors/go/__init__.py +0 -0
- osscodeiq/detectors/go/go_orm.py +202 -0
- osscodeiq/detectors/go/go_structures.py +162 -0
- osscodeiq/detectors/go/go_web.py +157 -0
- osscodeiq/detectors/iac/__init__.py +0 -0
- osscodeiq/detectors/iac/bicep.py +135 -0
- osscodeiq/detectors/iac/dockerfile.py +182 -0
- osscodeiq/detectors/iac/terraform.py +188 -0
- osscodeiq/detectors/java/__init__.py +0 -0
- osscodeiq/detectors/java/azure_functions.py +424 -0
- osscodeiq/detectors/java/azure_messaging.py +350 -0
- osscodeiq/detectors/java/class_hierarchy.py +349 -0
- osscodeiq/detectors/java/config_def.py +82 -0
- osscodeiq/detectors/java/cosmos_db.py +105 -0
- osscodeiq/detectors/java/graphql_resolver.py +188 -0
- osscodeiq/detectors/java/grpc_service.py +142 -0
- osscodeiq/detectors/java/ibm_mq.py +178 -0
- osscodeiq/detectors/java/jaxrs.py +160 -0
- osscodeiq/detectors/java/jdbc.py +196 -0
- osscodeiq/detectors/java/jms.py +116 -0
- osscodeiq/detectors/java/jpa_entity.py +143 -0
- osscodeiq/detectors/java/kafka.py +113 -0
- osscodeiq/detectors/java/kafka_protocol.py +70 -0
- osscodeiq/detectors/java/micronaut.py +248 -0
- osscodeiq/detectors/java/module_deps.py +191 -0
- osscodeiq/detectors/java/public_api.py +206 -0
- osscodeiq/detectors/java/quarkus.py +176 -0
- osscodeiq/detectors/java/rabbitmq.py +150 -0
- osscodeiq/detectors/java/raw_sql.py +136 -0
- osscodeiq/detectors/java/repository.py +131 -0
- osscodeiq/detectors/java/rmi.py +129 -0
- osscodeiq/detectors/java/spring_events.py +117 -0
- osscodeiq/detectors/java/spring_rest.py +168 -0
- osscodeiq/detectors/java/spring_security.py +212 -0
- osscodeiq/detectors/java/tibco_ems.py +193 -0
- osscodeiq/detectors/java/websocket.py +188 -0
- osscodeiq/detectors/kotlin/__init__.py +0 -0
- osscodeiq/detectors/kotlin/kotlin_structures.py +124 -0
- osscodeiq/detectors/kotlin/ktor_routes.py +163 -0
- osscodeiq/detectors/proto/__init__.py +0 -0
- osscodeiq/detectors/proto/proto_structure.py +153 -0
- osscodeiq/detectors/python/__init__.py +0 -0
- osscodeiq/detectors/python/celery_tasks.py +88 -0
- osscodeiq/detectors/python/django_auth.py +132 -0
- osscodeiq/detectors/python/django_models.py +157 -0
- osscodeiq/detectors/python/django_views.py +74 -0
- osscodeiq/detectors/python/fastapi_auth.py +143 -0
- osscodeiq/detectors/python/fastapi_routes.py +68 -0
- osscodeiq/detectors/python/flask_routes.py +67 -0
- osscodeiq/detectors/python/kafka_python.py +175 -0
- osscodeiq/detectors/python/pydantic_models.py +115 -0
- osscodeiq/detectors/python/python_structures.py +234 -0
- osscodeiq/detectors/python/sqlalchemy_models.py +82 -0
- osscodeiq/detectors/registry.py +100 -0
- osscodeiq/detectors/rust/__init__.py +0 -0
- osscodeiq/detectors/rust/actix_web.py +234 -0
- osscodeiq/detectors/rust/rust_structures.py +174 -0
- osscodeiq/detectors/scala/__init__.py +0 -0
- osscodeiq/detectors/scala/scala_structures.py +128 -0
- osscodeiq/detectors/shell/__init__.py +0 -0
- osscodeiq/detectors/shell/bash_detector.py +127 -0
- osscodeiq/detectors/shell/powershell_detector.py +118 -0
- osscodeiq/detectors/typescript/__init__.py +0 -0
- osscodeiq/detectors/typescript/express_routes.py +55 -0
- osscodeiq/detectors/typescript/fastify_routes.py +156 -0
- osscodeiq/detectors/typescript/graphql_resolvers.py +100 -0
- osscodeiq/detectors/typescript/kafka_js.py +164 -0
- osscodeiq/detectors/typescript/mongoose_orm.py +151 -0
- osscodeiq/detectors/typescript/nestjs_controllers.py +99 -0
- osscodeiq/detectors/typescript/nestjs_guards.py +138 -0
- osscodeiq/detectors/typescript/passport_jwt.py +133 -0
- osscodeiq/detectors/typescript/prisma_orm.py +96 -0
- osscodeiq/detectors/typescript/remix_routes.py +160 -0
- osscodeiq/detectors/typescript/sequelize_orm.py +136 -0
- osscodeiq/detectors/typescript/typeorm_entities.py +86 -0
- osscodeiq/detectors/typescript/typescript_structures.py +185 -0
- osscodeiq/detectors/utils.py +49 -0
- osscodeiq/discovery/__init__.py +11 -0
- osscodeiq/discovery/change_detector.py +97 -0
- osscodeiq/discovery/file_discovery.py +342 -0
- osscodeiq/flow/__init__.py +0 -0
- osscodeiq/flow/engine.py +78 -0
- osscodeiq/flow/models.py +72 -0
- osscodeiq/flow/renderer.py +127 -0
- osscodeiq/flow/templates/interactive.html +252 -0
- osscodeiq/flow/vendor/cytoscape-dagre.min.js +8 -0
- osscodeiq/flow/vendor/cytoscape.min.js +32 -0
- osscodeiq/flow/vendor/dagre.min.js +3809 -0
- osscodeiq/flow/views.py +357 -0
- osscodeiq/graph/__init__.py +0 -0
- osscodeiq/graph/backend.py +52 -0
- osscodeiq/graph/backends/__init__.py +23 -0
- osscodeiq/graph/backends/kuzu.py +576 -0
- osscodeiq/graph/backends/networkx.py +135 -0
- osscodeiq/graph/backends/sqlite_backend.py +406 -0
- osscodeiq/graph/builder.py +297 -0
- osscodeiq/graph/query.py +228 -0
- osscodeiq/graph/store.py +183 -0
- osscodeiq/graph/views.py +231 -0
- osscodeiq/models/__init__.py +17 -0
- osscodeiq/models/graph.py +116 -0
- osscodeiq/output/__init__.py +0 -0
- osscodeiq/output/dot.py +171 -0
- osscodeiq/output/mermaid.py +160 -0
- osscodeiq/output/safety.py +58 -0
- osscodeiq/output/serializers.py +42 -0
- osscodeiq/parsing/__init__.py +5 -0
- osscodeiq/parsing/languages/__init__.py +0 -0
- osscodeiq/parsing/languages/base.py +23 -0
- osscodeiq/parsing/languages/java.py +68 -0
- osscodeiq/parsing/languages/python.py +57 -0
- osscodeiq/parsing/languages/typescript.py +95 -0
- osscodeiq/parsing/parser_manager.py +125 -0
- osscodeiq/parsing/structured/__init__.py +0 -0
- osscodeiq/parsing/structured/gradle_parser.py +78 -0
- osscodeiq/parsing/structured/json_parser.py +24 -0
- osscodeiq/parsing/structured/properties_parser.py +56 -0
- osscodeiq/parsing/structured/sql_parser.py +54 -0
- osscodeiq/parsing/structured/xml_parser.py +148 -0
- osscodeiq/parsing/structured/yaml_parser.py +38 -0
- osscodeiq/server/__init__.py +7 -0
- osscodeiq/server/app.py +53 -0
- osscodeiq/server/mcp_server.py +174 -0
- osscodeiq/server/middleware.py +16 -0
- osscodeiq/server/routes.py +184 -0
- osscodeiq/server/service.py +445 -0
- osscodeiq/server/templates/welcome.html +56 -0
- osscodeiq-0.0.0.dist-info/METADATA +30 -0
- osscodeiq-0.0.0.dist-info/RECORD +183 -0
- osscodeiq-0.0.0.dist-info/WHEEL +5 -0
- osscodeiq-0.0.0.dist-info/entry_points.txt +2 -0
- osscodeiq-0.0.0.dist-info/licenses/LICENSE +21 -0
- 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
|