Skip to content

Commit 287f185

Browse files
aksOpsclaude
andcommitted
Add 14 new detectors: 9 auth + 5 frontend + layer classifier integration
Auth detectors: - Spring Security (@secured, @PreAuthorize, SecurityFilterChain) - Django Auth (@login_required, @permission_required, mixins) - FastAPI Auth (Depends, Security, OAuth2PasswordBearer) - NestJS Guards (@UseGuards, @roles, canActivate) - Passport/JWT (passport.use, jwt.verify, express-jwt) - Kubernetes RBAC (Role, ClusterRole, RoleBinding) - LDAP Auth (ldap3, LdapContextSource, DirectoryServices) - TLS/Certificate/Azure AD (mTLS, X.509, MSAL, cert-based) - Session/Header Auth (cookies, API keys, CSRF) Frontend detectors: - React Components (function/class components, hooks, JSX) - Vue Components (defineComponent, script setup, composables) - Angular Components (@component, @Injectable, @directive) - Svelte Components (export let, $: reactive) - Frontend Routes (React Router, Vue Router, Next.js, Angular) Infrastructure: register all 14 in registry, add .vue/.svelte support 72 detectors total, 35 languages, 361 tests passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c72241a commit 287f185

41 files changed

Lines changed: 5428 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/code_intelligence/analyzer.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"bash", "powershell", "batch", "ruby", "rust", "kotlin",
3535
"scala", "swift", "r", "perl", "lua", "dart",
3636
"dockerfile", "toml", "ini", "dotenv", "csv",
37+
"vue", "svelte",
3738
}
3839

3940

@@ -98,6 +99,10 @@ def _parse_structured(language: str, content: bytes, file_path: str) -> Any:
9899
elif language == "proto":
99100
# Return raw text for regex-based detection
100101
return {"type": "proto", "file": file_path, "data": content.decode("utf-8", errors="replace")}
102+
elif language == "vue":
103+
return {"type": "vue", "file": file_path, "data": content.decode("utf-8", errors="replace")}
104+
elif language == "svelte":
105+
return {"type": "svelte", "file": file_path, "data": content.decode("utf-8", errors="replace")}
101106
return None
102107

103108

src/code_intelligence/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class DiscoveryConfig(BaseModel):
2525
".r", ".R", ".pl", ".pm", ".lua", ".dart",
2626
".toml", ".ini", ".cfg", ".conf",
2727
".env", ".csv", ".dockerfile",
28+
".vue", ".svelte",
2829
])
2930
exclude_patterns: list[str] = Field(default_factory=lambda: [
3031
"**/node_modules/**",

src/code_intelligence/detectors/auth/__init__.py

Whitespace-only changes.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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 code_intelligence.detectors.base import DetectorContext, DetectorResult
9+
from code_intelligence.models.graph import GraphNode, NodeKind, SourceLocation
10+
11+
12+
@dataclass(frozen=True)
13+
class _PatternDef:
14+
"""A pattern definition with its auth_type and optional property extractor."""
15+
16+
regex: re.Pattern[str]
17+
auth_type: str
18+
prop_key: str | None = None
19+
20+
21+
# -- mTLS patterns --
22+
_MTLS_PATTERNS: list[_PatternDef] = [
23+
_PatternDef(re.compile(r"\bssl_verify_client\b"), "mtls"),
24+
_PatternDef(re.compile(r"\brequestCert\s*:\s*true\b"), "mtls"),
25+
_PatternDef(re.compile(r'\bclientAuth\s*=\s*"true"'), "mtls"),
26+
_PatternDef(re.compile(r"\bX509AuthenticationFilter\b"), "mtls"),
27+
_PatternDef(re.compile(r"\bAddCertificateForwarding\b"), "mtls"),
28+
]
29+
30+
# -- X.509 patterns --
31+
_X509_PATTERNS: list[_PatternDef] = [
32+
_PatternDef(re.compile(r"\bX509AuthenticationFilter\b"), "x509"),
33+
_PatternDef(re.compile(r"\bCertificateAuthenticationDefaults\b"), "x509"),
34+
_PatternDef(re.compile(r"\.x509\s*\("), "x509"),
35+
]
36+
37+
# -- TLS config patterns --
38+
_TLS_CONFIG_PATTERNS: list[_PatternDef] = [
39+
_PatternDef(re.compile(r"\bjavax\.net\.ssl\.keyStore\b"), "tls_config"),
40+
_PatternDef(re.compile(r"\bssl\.SSLContext\b"), "tls_config"),
41+
_PatternDef(re.compile(r"\btls\.createServer\b"), "tls_config"),
42+
_PatternDef(
43+
re.compile(r"""(?:cert|key|ca)\s*[=:]\s*(?:fs\.readFileSync\s*\(|['"][\w/.\\-]+\.(?:pem|crt|key|cert)['"])"""),
44+
"tls_config",
45+
"cert_path",
46+
),
47+
_PatternDef(re.compile(r"\btrustStore\b"), "tls_config"),
48+
]
49+
50+
# -- Azure AD patterns --
51+
_AZURE_AD_PATTERNS: list[_PatternDef] = [
52+
_PatternDef(re.compile(r"\bAzureAd\b"), "azure_ad"),
53+
_PatternDef(re.compile(r"\bAZURE_TENANT_ID\b"), "azure_ad", "tenant_id"),
54+
_PatternDef(re.compile(r"\bAZURE_CLIENT_ID\b"), "azure_ad"),
55+
_PatternDef(re.compile(r"""\bmsal\b"""), "azure_ad"),
56+
_PatternDef(re.compile(r"""['"]@azure/msal-browser['"]"""), "azure_ad"),
57+
_PatternDef(re.compile(r"\bAddMicrosoftIdentityWebApi\b"), "azure_ad"),
58+
_PatternDef(re.compile(r"\bClientCertificateCredential\b"), "azure_ad"),
59+
]
60+
61+
_ALL_PATTERNS: list[_PatternDef] = (
62+
_MTLS_PATTERNS + _X509_PATTERNS + _TLS_CONFIG_PATTERNS + _AZURE_AD_PATTERNS
63+
)
64+
65+
# Dedup: when the same line matches both mTLS and x509 via X509AuthenticationFilter,
66+
# prefer the more specific auth_type already recorded.
67+
68+
_CERT_PATH_RE = re.compile(
69+
r"""['"]([^'"]*\.(?:pem|crt|key|cert|pfx|p12))['"]"""
70+
)
71+
_TENANT_ID_RE = re.compile(
72+
r"""AZURE_TENANT_ID\s*[=:]\s*['"]?([a-f0-9-]+)['"]?"""
73+
)
74+
75+
76+
class CertificateAuthDetector:
77+
"""Detects certificate-based authentication patterns across multiple languages."""
78+
79+
name: str = "certificate_auth"
80+
supported_languages: tuple[str, ...] = (
81+
"java", "python", "typescript", "csharp", "json", "yaml",
82+
)
83+
84+
def detect(self, ctx: DetectorContext) -> DetectorResult:
85+
result = DetectorResult()
86+
text = ctx.content.decode("utf-8", errors="replace")
87+
lines = text.split("\n")
88+
89+
# Track which lines already produced a node (first match wins per line).
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+
101+
properties: dict[str, str] = {
102+
"auth_type": pdef.auth_type,
103+
"language": ctx.language,
104+
"pattern": matched_text[:120],
105+
}
106+
107+
# Extract cert_path if present
108+
cert_m = _CERT_PATH_RE.search(line)
109+
if cert_m:
110+
properties["cert_path"] = cert_m.group(1)
111+
112+
# Extract tenant_id if present
113+
tenant_m = _TENANT_ID_RE.search(line)
114+
if tenant_m:
115+
properties["tenant_id"] = tenant_m.group(1)
116+
117+
# Detect auth_flow for Azure AD
118+
if pdef.auth_type == "azure_ad":
119+
if "ClientCertificateCredential" in line:
120+
properties["auth_flow"] = "client_certificate"
121+
elif "msal" in line.lower():
122+
properties["auth_flow"] = "msal"
123+
124+
node = GraphNode(
125+
id=f"auth:{ctx.file_path}:cert:{line_num}",
126+
kind=NodeKind.GUARD,
127+
label=f"Certificate auth ({pdef.auth_type}): {matched_text[:60]}",
128+
module=ctx.module_name,
129+
location=SourceLocation(
130+
file_path=ctx.file_path,
131+
line_start=line_num,
132+
line_end=line_num,
133+
),
134+
properties=properties,
135+
)
136+
result.nodes.append(node)
137+
138+
return result
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 code_intelligence.detectors.base import DetectorContext, DetectorResult
8+
from code_intelligence.models.graph import GraphNode, NodeKind, SourceLocation
9+
10+
# -- Java patterns --
11+
_JAVA_PATTERNS: list[re.Pattern[str]] = [
12+
re.compile(r"\bLdapContextSource\b"),
13+
re.compile(r"\bLdapTemplate\b"),
14+
re.compile(r"\bActiveDirectoryLdapAuthenticationProvider\b"),
15+
re.compile(r"@EnableLdapRepositories\b"),
16+
]
17+
18+
# -- Python patterns --
19+
_PYTHON_PATTERNS: list[re.Pattern[str]] = [
20+
re.compile(r"\bldap3\.Connection\b"),
21+
re.compile(r"\bldap3\.Server\b"),
22+
re.compile(r"\bAUTH_LDAP_SERVER_URI\b"),
23+
re.compile(r"\bAUTH_LDAP_BIND_DN\b"),
24+
]
25+
26+
# -- TypeScript patterns --
27+
_TS_PATTERNS: list[re.Pattern[str]] = [
28+
re.compile(r"""require\s*\(\s*['"]ldapjs['"]\s*\)"""),
29+
re.compile(r"""(?:import\s+.*\s+from\s+['"]ldapjs['"]|import\s+ldapjs\b)"""),
30+
re.compile(r"""['"]passport-ldapauth['"]"""),
31+
]
32+
33+
# -- C# patterns --
34+
_CSHARP_PATTERNS: list[re.Pattern[str]] = [
35+
re.compile(r"\bSystem\.DirectoryServices\b"),
36+
re.compile(r"\bLdapConnection\b"),
37+
re.compile(r"\bDirectoryEntry\b"),
38+
]
39+
40+
_LANGUAGE_PATTERNS: dict[str, list[re.Pattern[str]]] = {
41+
"java": _JAVA_PATTERNS,
42+
"python": _PYTHON_PATTERNS,
43+
"typescript": _TS_PATTERNS,
44+
"csharp": _CSHARP_PATTERNS,
45+
}
46+
47+
48+
class LdapAuthDetector:
49+
"""Detects LDAP authentication patterns across multiple languages."""
50+
51+
name: str = "ldap_auth"
52+
supported_languages: tuple[str, ...] = ("java", "python", "typescript", "csharp")
53+
54+
def detect(self, ctx: DetectorContext) -> DetectorResult:
55+
result = DetectorResult()
56+
if ctx.language not in _LANGUAGE_PATTERNS:
57+
return result
58+
59+
text = ctx.content.decode("utf-8", errors="replace")
60+
lines = text.split("\n")
61+
patterns = _LANGUAGE_PATTERNS[ctx.language]
62+
seen_lines: set[int] = set()
63+
64+
for line_idx, line in enumerate(lines):
65+
for pattern in patterns:
66+
if pattern.search(line) and line_idx not in seen_lines:
67+
seen_lines.add(line_idx)
68+
line_num = line_idx + 1
69+
matched_text = line.strip()
70+
node = GraphNode(
71+
id=f"auth:{ctx.file_path}:ldap:{line_num}",
72+
kind=NodeKind.GUARD,
73+
label=f"LDAP auth: {matched_text[:80]}",
74+
module=ctx.module_name,
75+
location=SourceLocation(
76+
file_path=ctx.file_path,
77+
line_start=line_num,
78+
line_end=line_num,
79+
),
80+
properties={
81+
"auth_type": "ldap",
82+
"language": ctx.language,
83+
"pattern": matched_text[:120],
84+
},
85+
)
86+
result.nodes.append(node)
87+
88+
return result
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 code_intelligence.detectors.base import DetectorContext, DetectorResult
9+
from code_intelligence.models.graph import GraphNode, NodeKind, SourceLocation
10+
11+
12+
@dataclass(frozen=True)
13+
class _PatternDef:
14+
regex: re.Pattern[str]
15+
auth_type: str
16+
node_kind: NodeKind
17+
18+
19+
# -- Session patterns --
20+
_SESSION_PATTERNS: list[_PatternDef] = [
21+
_PatternDef(re.compile(r"""['"]express-session['"]"""), "session", NodeKind.MIDDLEWARE),
22+
_PatternDef(re.compile(r"""['"]cookie-session['"]"""), "session", NodeKind.MIDDLEWARE),
23+
_PatternDef(re.compile(r"@SessionAttributes\b"), "session", NodeKind.GUARD),
24+
_PatternDef(re.compile(r"\bSessionMiddleware\b"), "session", NodeKind.MIDDLEWARE),
25+
_PatternDef(re.compile(r"\bHttpSession\b"), "session", NodeKind.GUARD),
26+
_PatternDef(re.compile(r"\bSESSION_ENGINE\b"), "session", NodeKind.GUARD),
27+
]
28+
29+
# -- Header patterns --
30+
_HEADER_PATTERNS: list[_PatternDef] = [
31+
_PatternDef(re.compile(r"""['"](X-API-Key|x-api-key)['"]""", re.IGNORECASE), "header", NodeKind.GUARD),
32+
_PatternDef(
33+
re.compile(r"""(?:req|request|ctx)\.headers?\s*\[\s*['"]authorization['"]\s*\]""", re.IGNORECASE),
34+
"header",
35+
NodeKind.GUARD,
36+
),
37+
_PatternDef(
38+
re.compile(r"""getHeader\s*\(\s*['"]Authorization['"]""", re.IGNORECASE),
39+
"header",
40+
NodeKind.GUARD,
41+
),
42+
]
43+
44+
# -- API key patterns --
45+
_API_KEY_PATTERNS: list[_PatternDef] = [
46+
_PatternDef(
47+
re.compile(r"""(?:req|request)\.headers?\s*\[\s*['"]x-api-key['"]\s*\]""", re.IGNORECASE),
48+
"api_key",
49+
NodeKind.GUARD,
50+
),
51+
_PatternDef(re.compile(r"\bapi[_-]?key\s*(?:=|:)\s*", re.IGNORECASE), "api_key", NodeKind.GUARD),
52+
_PatternDef(re.compile(r"\bvalidate[_]?api[_]?key\b", re.IGNORECASE), "api_key", NodeKind.GUARD),
53+
]
54+
55+
# -- CSRF patterns --
56+
_CSRF_PATTERNS: list[_PatternDef] = [
57+
_PatternDef(re.compile(r"@csrf_protect\b"), "csrf", NodeKind.GUARD),
58+
_PatternDef(re.compile(r"\bcsrf_exempt\b"), "csrf", NodeKind.GUARD),
59+
_PatternDef(re.compile(r"\bCsrfViewMiddleware\b"), "csrf", NodeKind.MIDDLEWARE),
60+
_PatternDef(re.compile(r"""['"]csurf['"]"""), "csrf", NodeKind.MIDDLEWARE),
61+
]
62+
63+
_ALL_PATTERNS: list[_PatternDef] = (
64+
_SESSION_PATTERNS + _HEADER_PATTERNS + _API_KEY_PATTERNS + _CSRF_PATTERNS
65+
)
66+
67+
# Map auth_type to the ID tag used in node IDs.
68+
_ID_TAG: dict[str, str] = {
69+
"session": "session",
70+
"header": "header",
71+
"api_key": "apikey",
72+
"csrf": "csrf",
73+
}
74+
75+
76+
class SessionHeaderAuthDetector:
77+
"""Detects session, header, API-key, and CSRF auth patterns."""
78+
79+
name: str = "session_header_auth"
80+
supported_languages: tuple[str, ...] = ("java", "python", "typescript")
81+
82+
def detect(self, ctx: DetectorContext) -> DetectorResult:
83+
result = DetectorResult()
84+
if ctx.language not in self.supported_languages:
85+
return result
86+
87+
text = ctx.content.decode("utf-8", errors="replace")
88+
lines = text.split("\n")
89+
seen_lines: set[int] = set()
90+
91+
for line_idx, line in enumerate(lines):
92+
for pdef in _ALL_PATTERNS:
93+
if line_idx in seen_lines:
94+
break
95+
if pdef.regex.search(line):
96+
seen_lines.add(line_idx)
97+
line_num = line_idx + 1
98+
matched_text = line.strip()
99+
tag = _ID_TAG[pdef.auth_type]
100+
101+
node = GraphNode(
102+
id=f"auth:{ctx.file_path}:{tag}:{line_num}",
103+
kind=pdef.node_kind,
104+
label=f"{pdef.auth_type} auth: {matched_text[:70]}",
105+
module=ctx.module_name,
106+
location=SourceLocation(
107+
file_path=ctx.file_path,
108+
line_start=line_num,
109+
line_end=line_num,
110+
),
111+
properties={
112+
"auth_type": pdef.auth_type,
113+
"language": ctx.language,
114+
"pattern": matched_text[:120],
115+
},
116+
)
117+
result.nodes.append(node)
118+
119+
return result

0 commit comments

Comments
 (0)