1313# ---------------------------------------------------------------------------
1414
1515HARDCODED_SECRET_PATTERNS = [
16- (re .compile (r"password\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE ), "Hardcoded password detected." ),
17- (re .compile (r"api_key\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE ), "Hardcoded API key detected." ),
18- (re .compile (r"secret\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE ), "Hardcoded secret detected." ),
19- (re .compile (r"token\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE ), "Hardcoded token detected." ),
20- (re .compile (r"aws_secret_access_key\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE ), "AWS secret access key hardcoded." ),
16+ # Only match at start of line (variable assignments), not inside strings
17+ (re .compile (r"^\s*password\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE | re .MULTILINE ), "Hardcoded password detected." ),
18+ (re .compile (r"^\s*api_key\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE | re .MULTILINE ), "Hardcoded API key detected." ),
19+ (re .compile (r"^\s*secret\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE | re .MULTILINE ), "Hardcoded secret detected." ),
20+ (re .compile (r"^\s*token\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE | re .MULTILINE ), "Hardcoded token detected." ),
21+ (re .compile (r"^\s*aws_secret_access_key\s*=\s*[\"'][^\"']+[\"']" , re .IGNORECASE | re .MULTILINE ), "AWS secret access key hardcoded." ),
2122 (re .compile (r"-----BEGIN (?:RSA |EC )?PRIVATE KEY-----" ), "Exposed private key in source code." ),
2223]
2324
4344# JavaScript / TypeScript patterns
4445# ---------------------------------------------------------------------------
4546
47+ # ---------------------------------------------------------------------------
48+ # Go patterns
49+ # ---------------------------------------------------------------------------
50+
51+ GO_SECURITY_PATTERNS = [
52+ (re .compile (r"\bos\.exec\s*\(" ), "high" , "os/exec usage — verify no shell injection." ),
53+ (re .compile (r"\bexec\.Command\s*\(" ), "medium" , "exec.Command usage — validate arguments carefully." ),
54+ (re .compile (r"\bhttp\.ListenAndServe\s*\(" ), "medium" , "HTTP server without TLS — traffic sent in plaintext." ),
55+ (re .compile (r"\bsql\.Open\s*\(" ), "medium" , "SQL database connection — use parameterized queries." ),
56+ ]
57+
58+ # ---------------------------------------------------------------------------
59+ # Rust patterns
60+ # ---------------------------------------------------------------------------
61+
62+ RUST_SECURITY_PATTERNS = [
63+ (re .compile (r"\bunsafe\s*\{" ), "high" , "Unsafe block detected — verify memory safety invariants." ),
64+ (re .compile (r"\bunsafe\s+fn\b" ), "high" , "Unsafe function definition — verify memory safety invariants." ),
65+ (re .compile (r"\bstd::process::Command\b" ), "medium" , "Process command execution — validate arguments for injection." ),
66+ (re .compile (r"\braw pointer\b" ), "high" , "Raw pointer usage — potential memory safety issue." ),
67+ ]
68+
69+ # ---------------------------------------------------------------------------
70+ # Java patterns
71+ # ---------------------------------------------------------------------------
72+
73+ JAVA_SECURITY_PATTERNS = [
74+ (re .compile (r"Runtime\.getRuntime\(\)\.exec\s*\(" ), "high" , "Runtime.exec() — shell injection risk." ),
75+ (re .compile (r"\bProcessBuilder\b" ), "medium" , "ProcessBuilder — validate command arguments." ),
76+ (re .compile (r"\bClass\.forName\s*\(" ), "medium" , "Dynamic class loading via reflection." ),
77+ (re .compile (r"\bObjectInputStream\b" ), "high" , "Java deserialization — can execute arbitrary code." ),
78+ (re .compile (r"\bScriptEngine\b" ), "high" , "Script engine — allows arbitrary code execution." ),
79+ (re .compile (r"\binnerHTML\b" ), "medium" , "innerHTML usage in Java templates — potential XSS." ),
80+ ]
81+
82+ # ---------------------------------------------------------------------------
83+ # Ruby patterns
84+ # ---------------------------------------------------------------------------
85+
86+ RUBY_SECURITY_PATTERNS = [
87+ (re .compile (r"\beval\s*[(']" ), "high" , "eval() in Ruby — allows arbitrary code execution." ),
88+ (re .compile (r"\bexec\s*[(']" ), "high" , "exec() in Ruby — shell injection risk." ),
89+ (re .compile (r"\bsystem\s*[(']" ), "high" , "system() in Ruby — shell injection risk." ),
90+ (re .compile (r"\bKernel\.system\s*\(" ), "high" , "Kernel.system() — shell injection risk." ),
91+ (re .compile (r"\bMarshal\.load\s*\(" ), "high" , "Marshal.load() — insecure deserialization." ),
92+ (re .compile (r"\bYAML\.load\s*\(" ), "medium" , "YAML.load() without safe_load — code execution risk." ),
93+ (re .compile (r"\bsend\s+" ), "medium" , "Dynamic method dispatch — verify no injection vector." ),
94+ ]
95+
96+ # ---------------------------------------------------------------------------
97+ # JavaScript / TypeScript patterns
98+ # ---------------------------------------------------------------------------
99+
46100JS_SECURITY_PATTERNS = [
47101 (re .compile (r"\beval\s*\(" ), "high" , "Use of eval() allows arbitrary code execution." ),
48102 (re .compile (r"\.innerHTML\s*=" ), "high" , "Assignment to innerHTML enables XSS attacks." ),
@@ -61,6 +115,7 @@ def _detect_python_security_ast(
61115 file : FileResult ,
62116 issues : list [Issue ],
63117 tree : ast .AST ,
118+ line_offset : int = 0 ,
64119) -> None :
65120 """Detect security issues in Python files using AST analysis."""
66121 for node in ast .walk (tree ):
@@ -75,6 +130,7 @@ def _detect_python_security_ast(
75130 SECURITY ,
76131 "high" ,
77132 _DANGEROUS_PYTHON_CALLS [func_name ],
133+ line_offset = line_offset ,
78134 )
79135
80136 # subprocess with shell=True
@@ -87,6 +143,7 @@ def _detect_python_security_ast(
87143 SECURITY ,
88144 "high" ,
89145 "subprocess called with shell=True — shell injection risk." ,
146+ line_offset = line_offset ,
90147 )
91148
92149 # os.system()
@@ -100,6 +157,7 @@ def _detect_python_security_ast(
100157 SECURITY ,
101158 "high" ,
102159 "os.system() — use subprocess without shell=True instead." ,
160+ line_offset = line_offset ,
103161 )
104162
105163 # yaml.load() without Loader
@@ -113,6 +171,7 @@ def _detect_python_security_ast(
113171 SECURITY ,
114172 "medium" ,
115173 "yaml.load() without Loader — use yaml.safe_load() instead." ,
174+ line_offset = line_offset ,
116175 )
117176
118177 # tempfile.mktemp()
@@ -125,6 +184,7 @@ def _detect_python_security_ast(
125184 SECURITY ,
126185 "medium" ,
127186 "tempfile.mktemp() is insecure — use tempfile.mkstemp() instead." ,
187+ line_offset = line_offset ,
128188 )
129189
130190 # pickle.loads / pickle.dumps (deserialization)
@@ -139,6 +199,7 @@ def _detect_python_security_ast(
139199 SECURITY ,
140200 "high" ,
141201 "Insecure deserialization via pickle/shelve — can execute arbitrary code." ,
202+ line_offset = line_offset ,
142203 )
143204
144205 # assert used for validation in non-test files
@@ -151,12 +212,14 @@ def _detect_python_security_ast(
151212 SECURITY ,
152213 "medium" ,
153214 "assert used for validation — asserts are stripped in optimized mode (-O)." ,
215+ line_offset = line_offset ,
154216 )
155217
156218
157219def _detect_python_security_regex (
158220 file : FileResult ,
159221 issues : list [Issue ],
222+ line_offset : int = 0 ,
160223) -> None :
161224 """Detect security issues in Python files using regex (line-based)."""
162225 lines = file .content .splitlines ()
@@ -170,12 +233,14 @@ def _detect_python_security_regex(
170233 SECURITY ,
171234 "high" ,
172235 "Possible SQL injection — string interpolation in query." ,
236+ line_offset = line_offset ,
173237 )
174238
175239
176240def _detect_js_security (
177241 file : FileResult ,
178242 issues : list [Issue ],
243+ line_offset : int = 0 ,
179244) -> None :
180245 """Detect security issues in JS/TS files."""
181246 lines = file .content .splitlines ()
@@ -189,12 +254,14 @@ def _detect_js_security(
189254 SECURITY ,
190255 severity ,
191256 description ,
257+ line_offset = line_offset ,
192258 )
193259
194260
195261def _detect_generic_security (
196262 file : FileResult ,
197263 issues : list [Issue ],
264+ line_offset : int = 0 ,
198265) -> None :
199266 """Detect hardcoded secrets and keys (language-agnostic)."""
200267 lines = file .content .splitlines ()
@@ -208,6 +275,7 @@ def _detect_generic_security(
208275 SECURITY ,
209276 "high" ,
210277 description ,
278+ line_offset = line_offset ,
211279 )
212280
213281
@@ -258,17 +326,90 @@ def _has_loader_argument(node: ast.Call) -> bool:
258326# ---------------------------------------------------------------------------
259327
260328
329+ def _detect_go_security (
330+ file : FileResult ,
331+ issues : list [Issue ],
332+ line_offset : int = 0 ,
333+ ) -> None :
334+ """Detect security issues in Go files."""
335+ lines = file .content .splitlines ()
336+ for idx , line in enumerate (lines , start = 1 ):
337+ for pattern , severity , description in GO_SECURITY_PATTERNS :
338+ if pattern .search (line ):
339+ _add_issue (
340+ issues , file .path , idx , SECURITY , severity , description ,
341+ line_offset = line_offset ,
342+ )
343+
344+
345+ def _detect_rust_security (
346+ file : FileResult ,
347+ issues : list [Issue ],
348+ line_offset : int = 0 ,
349+ ) -> None :
350+ """Detect security issues in Rust files."""
351+ lines = file .content .splitlines ()
352+ for idx , line in enumerate (lines , start = 1 ):
353+ for pattern , severity , description in RUST_SECURITY_PATTERNS :
354+ if pattern .search (line ):
355+ _add_issue (
356+ issues , file .path , idx , SECURITY , severity , description ,
357+ line_offset = line_offset ,
358+ )
359+
360+
361+ def _detect_java_security (
362+ file : FileResult ,
363+ issues : list [Issue ],
364+ line_offset : int = 0 ,
365+ ) -> None :
366+ """Detect security issues in Java files."""
367+ lines = file .content .splitlines ()
368+ for idx , line in enumerate (lines , start = 1 ):
369+ for pattern , severity , description in JAVA_SECURITY_PATTERNS :
370+ if pattern .search (line ):
371+ _add_issue (
372+ issues , file .path , idx , SECURITY , severity , description ,
373+ line_offset = line_offset ,
374+ )
375+
376+
377+ def _detect_ruby_security (
378+ file : FileResult ,
379+ issues : list [Issue ],
380+ line_offset : int = 0 ,
381+ ) -> None :
382+ """Detect security issues in Ruby files."""
383+ lines = file .content .splitlines ()
384+ for idx , line in enumerate (lines , start = 1 ):
385+ for pattern , severity , description in RUBY_SECURITY_PATTERNS :
386+ if pattern .search (line ):
387+ _add_issue (
388+ issues , file .path , idx , SECURITY , severity , description ,
389+ line_offset = line_offset ,
390+ )
391+
392+
261393def detect_security_issues (
262394 file : FileResult ,
263395 issues : list [Issue ],
264396 tree : ast .AST | None ,
397+ line_offset : int = 0 ,
265398) -> None :
266399 """Run all security detection checks on a file."""
267- _detect_generic_security (file , issues )
400+ _detect_generic_security (file , issues , line_offset = line_offset )
268401
269402 if file .language == "python" :
270- _detect_python_security_regex (file , issues )
403+ _detect_python_security_regex (file , issues , line_offset = line_offset )
271404 if tree is not None :
272- _detect_python_security_ast (file , issues , tree )
405+ _detect_python_security_ast (file , issues , tree , line_offset = line_offset )
273406 elif file .language in {"javascript" , "typescript" }:
274- _detect_js_security (file , issues )
407+ _detect_js_security (file , issues , line_offset = line_offset )
408+ elif file .language == "go" :
409+ _detect_go_security (file , issues , line_offset = line_offset )
410+ elif file .language == "rust" :
411+ _detect_rust_security (file , issues , line_offset = line_offset )
412+ elif file .language == "java" :
413+ _detect_java_security (file , issues , line_offset = line_offset )
414+ elif file .language == "ruby" :
415+ _detect_ruby_security (file , issues , line_offset = line_offset )
0 commit comments