@datahogo/core 0.1.0

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 (153) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +17 -0
  3. package/dist/engines/config.d.ts +3 -0
  4. package/dist/engines/config.d.ts.map +1 -0
  5. package/dist/engines/config.js +433 -0
  6. package/dist/engines/config.js.map +1 -0
  7. package/dist/engines/dast.d.ts +3 -0
  8. package/dist/engines/dast.d.ts.map +1 -0
  9. package/dist/engines/dast.js +167 -0
  10. package/dist/engines/dast.js.map +1 -0
  11. package/dist/engines/db-rules.d.ts +3 -0
  12. package/dist/engines/db-rules.d.ts.map +1 -0
  13. package/dist/engines/db-rules.js +166 -0
  14. package/dist/engines/db-rules.js.map +1 -0
  15. package/dist/engines/dependencies.d.ts +3 -0
  16. package/dist/engines/dependencies.d.ts.map +1 -0
  17. package/dist/engines/dependencies.js +167 -0
  18. package/dist/engines/dependencies.js.map +1 -0
  19. package/dist/engines/github-actions.d.ts +3 -0
  20. package/dist/engines/github-actions.d.ts.map +1 -0
  21. package/dist/engines/github-actions.js +215 -0
  22. package/dist/engines/github-actions.js.map +1 -0
  23. package/dist/engines/gitleaks.d.ts +3 -0
  24. package/dist/engines/gitleaks.d.ts.map +1 -0
  25. package/dist/engines/gitleaks.js +107 -0
  26. package/dist/engines/gitleaks.js.map +1 -0
  27. package/dist/engines/npm-audit.d.ts +3 -0
  28. package/dist/engines/npm-audit.d.ts.map +1 -0
  29. package/dist/engines/npm-audit.js +113 -0
  30. package/dist/engines/npm-audit.js.map +1 -0
  31. package/dist/engines/patterns.d.ts +3 -0
  32. package/dist/engines/patterns.d.ts.map +1 -0
  33. package/dist/engines/patterns.js +1330 -0
  34. package/dist/engines/patterns.js.map +1 -0
  35. package/dist/engines/secrets.d.ts +4 -0
  36. package/dist/engines/secrets.d.ts.map +1 -0
  37. package/dist/engines/secrets.js +260 -0
  38. package/dist/engines/secrets.js.map +1 -0
  39. package/dist/engines/semgrep.d.ts +3 -0
  40. package/dist/engines/semgrep.d.ts.map +1 -0
  41. package/dist/engines/semgrep.js +173 -0
  42. package/dist/engines/semgrep.js.map +1 -0
  43. package/dist/engines/types.d.ts +30 -0
  44. package/dist/engines/types.d.ts.map +1 -0
  45. package/dist/engines/types.js +3 -0
  46. package/dist/engines/types.js.map +1 -0
  47. package/dist/engines/url-scanner.d.ts +3 -0
  48. package/dist/engines/url-scanner.d.ts.map +1 -0
  49. package/dist/engines/url-scanner.js +469 -0
  50. package/dist/engines/url-scanner.js.map +1 -0
  51. package/dist/index.d.ts +8 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +8 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/orchestrator.d.ts +32 -0
  56. package/dist/orchestrator.d.ts.map +1 -0
  57. package/dist/orchestrator.js +237 -0
  58. package/dist/orchestrator.js.map +1 -0
  59. package/dist/rules/.gitkeep +0 -0
  60. package/dist/rules/ai-llm-security.yaml +199 -0
  61. package/dist/rules/api-security.yaml +489 -0
  62. package/dist/rules/auth-patterns.yaml +406 -0
  63. package/dist/rules/crypto-patterns.yaml +227 -0
  64. package/dist/rules/database-cloud-security.yaml +169 -0
  65. package/dist/rules/general-security.yaml +588 -0
  66. package/dist/rules/injection-patterns.yaml +318 -0
  67. package/dist/rules/nextjs-security.yaml +532 -0
  68. package/dist/rules/node-security.yaml +380 -0
  69. package/dist/rules/rules/.gitkeep +0 -0
  70. package/dist/rules/rules/ai-llm-security.yaml +199 -0
  71. package/dist/rules/rules/api-security.yaml +489 -0
  72. package/dist/rules/rules/auth-patterns.yaml +406 -0
  73. package/dist/rules/rules/crypto-patterns.yaml +227 -0
  74. package/dist/rules/rules/database-cloud-security.yaml +169 -0
  75. package/dist/rules/rules/general-security.yaml +588 -0
  76. package/dist/rules/rules/injection-patterns.yaml +318 -0
  77. package/dist/rules/rules/nextjs-security.yaml +532 -0
  78. package/dist/rules/rules/node-security.yaml +380 -0
  79. package/dist/rules/rules/supabase-security.yaml +387 -0
  80. package/dist/rules/supabase-security.yaml +387 -0
  81. package/dist/scanning/agents/csharp-agent.d.ts +12 -0
  82. package/dist/scanning/agents/csharp-agent.d.ts.map +1 -0
  83. package/dist/scanning/agents/csharp-agent.js +592 -0
  84. package/dist/scanning/agents/csharp-agent.js.map +1 -0
  85. package/dist/scanning/agents/go-agent.d.ts +14 -0
  86. package/dist/scanning/agents/go-agent.d.ts.map +1 -0
  87. package/dist/scanning/agents/go-agent.js +641 -0
  88. package/dist/scanning/agents/go-agent.js.map +1 -0
  89. package/dist/scanning/agents/java-agent.d.ts +8 -0
  90. package/dist/scanning/agents/java-agent.d.ts.map +1 -0
  91. package/dist/scanning/agents/java-agent.js +807 -0
  92. package/dist/scanning/agents/java-agent.js.map +1 -0
  93. package/dist/scanning/agents/javascript-agent.d.ts +8 -0
  94. package/dist/scanning/agents/javascript-agent.d.ts.map +1 -0
  95. package/dist/scanning/agents/javascript-agent.js +77 -0
  96. package/dist/scanning/agents/javascript-agent.js.map +1 -0
  97. package/dist/scanning/agents/mobile-agent.d.ts +8 -0
  98. package/dist/scanning/agents/mobile-agent.d.ts.map +1 -0
  99. package/dist/scanning/agents/mobile-agent.js +916 -0
  100. package/dist/scanning/agents/mobile-agent.js.map +1 -0
  101. package/dist/scanning/agents/php-agent.d.ts +8 -0
  102. package/dist/scanning/agents/php-agent.d.ts.map +1 -0
  103. package/dist/scanning/agents/php-agent.js +679 -0
  104. package/dist/scanning/agents/php-agent.js.map +1 -0
  105. package/dist/scanning/agents/python-agent.d.ts +8 -0
  106. package/dist/scanning/agents/python-agent.d.ts.map +1 -0
  107. package/dist/scanning/agents/python-agent.js +701 -0
  108. package/dist/scanning/agents/python-agent.js.map +1 -0
  109. package/dist/scanning/agents/supabase-agent.d.ts +8 -0
  110. package/dist/scanning/agents/supabase-agent.d.ts.map +1 -0
  111. package/dist/scanning/agents/supabase-agent.js +484 -0
  112. package/dist/scanning/agents/supabase-agent.js.map +1 -0
  113. package/dist/scanning/orchestrator.d.ts +29 -0
  114. package/dist/scanning/orchestrator.d.ts.map +1 -0
  115. package/dist/scanning/orchestrator.js +186 -0
  116. package/dist/scanning/orchestrator.js.map +1 -0
  117. package/dist/scanning/tech-detector.d.ts +12 -0
  118. package/dist/scanning/tech-detector.d.ts.map +1 -0
  119. package/dist/scanning/tech-detector.js +252 -0
  120. package/dist/scanning/tech-detector.js.map +1 -0
  121. package/dist/scanning/types.d.ts +87 -0
  122. package/dist/scanning/types.d.ts.map +1 -0
  123. package/dist/scanning/types.js +5 -0
  124. package/dist/scanning/types.js.map +1 -0
  125. package/dist/utils/child-process.d.ts +2 -0
  126. package/dist/utils/child-process.d.ts.map +1 -0
  127. package/dist/utils/child-process.js +4 -0
  128. package/dist/utils/child-process.js.map +1 -0
  129. package/dist/utils/context-classifier.d.ts +11 -0
  130. package/dist/utils/context-classifier.d.ts.map +1 -0
  131. package/dist/utils/context-classifier.js +79 -0
  132. package/dist/utils/context-classifier.js.map +1 -0
  133. package/dist/utils/exec.d.ts +18 -0
  134. package/dist/utils/exec.d.ts.map +1 -0
  135. package/dist/utils/exec.js +51 -0
  136. package/dist/utils/exec.js.map +1 -0
  137. package/dist/utils/file-filter.d.ts +4 -0
  138. package/dist/utils/file-filter.d.ts.map +1 -0
  139. package/dist/utils/file-filter.js +68 -0
  140. package/dist/utils/file-filter.js.map +1 -0
  141. package/dist/utils/fs.d.ts +2 -0
  142. package/dist/utils/fs.d.ts.map +1 -0
  143. package/dist/utils/fs.js +5 -0
  144. package/dist/utils/fs.js.map +1 -0
  145. package/dist/utils/logger.d.ts +12 -0
  146. package/dist/utils/logger.d.ts.map +1 -0
  147. package/dist/utils/logger.js +27 -0
  148. package/dist/utils/logger.js.map +1 -0
  149. package/dist/utils/post-processor.d.ts +10 -0
  150. package/dist/utils/post-processor.d.ts.map +1 -0
  151. package/dist/utils/post-processor.js +183 -0
  152. package/dist/utils/post-processor.js.map +1 -0
  153. package/package.json +44 -0
@@ -0,0 +1,807 @@
1
+ // Java ecosystem security scanner agent.
2
+ // Detects Java/Kotlin projects (Maven, Gradle) and scans for Spring
3
+ // misconfigurations, deserialization issues, injection vectors,
4
+ // weak cryptography, and common Spring Security pitfalls.
5
+ export class JavaScanAgent {
6
+ async detect(files) {
7
+ for (const filePath of files.keys()) {
8
+ if (filePath === "pom.xml" ||
9
+ filePath === "build.gradle" ||
10
+ filePath === "build.gradle.kts" ||
11
+ filePath.endsWith("/pom.xml") ||
12
+ filePath.endsWith("/build.gradle") ||
13
+ filePath.endsWith("/build.gradle.kts")) {
14
+ return true;
15
+ }
16
+ }
17
+ return false;
18
+ }
19
+ async scan(files) {
20
+ const results = [];
21
+ // Detect project-level security infrastructure once and pass to checks
22
+ // that may want to downgrade confidence based on it.
23
+ const projectSecurity = detectProjectLevelSecurity(files);
24
+ results.push(...checkActuatorExposed(files));
25
+ results.push(...checkLog4Shell(files));
26
+ results.push(...checkUnsafeDeserialization(files));
27
+ results.push(...checkSqlInjectionQuery(files));
28
+ results.push(...checkCredentialsInConfig(files));
29
+ results.push(...checkCsrfDisabled(files));
30
+ results.push(...checkXxeVulnerability(files));
31
+ results.push(...checkRequestMappingWithoutMethod(files));
32
+ results.push(...checkPermitAllOnSensitiveRoutes(files));
33
+ results.push(...checkWeakHashAlgorithm(files));
34
+ results.push(...checkCommandInjection(files));
35
+ results.push(...checkCorsWildcard(files));
36
+ results.push(...checkHardcodedJwtSecret(files));
37
+ results.push(...checkStackTraceInResponse(files));
38
+ results.push(...checkSpringSecurityDebug(files));
39
+ results.push(...checkInsecureRandom(files));
40
+ results.push(...checkMassAssignment(files));
41
+ // If project-wide Spring Security is active, downgrade auth-absence
42
+ // findings to "low" confidence (the framework may handle auth globally).
43
+ if (projectSecurity.hasSpringSecurityConfig) {
44
+ for (const result of results) {
45
+ if (result.checkId === "java:permit-all-sensitive" ||
46
+ result.checkId === "java:request-mapping-no-method") {
47
+ result.confidence = "low";
48
+ }
49
+ }
50
+ }
51
+ // If project has rate-limiting libraries, suppress rate-limiting findings
52
+ // (bucket4j / resilience4j / Spring @RateLimiter).
53
+ // (No explicit rate-limit check yet, but leaving the hook for future checks.)
54
+ return results;
55
+ }
56
+ getMetadata() {
57
+ return {
58
+ name: "java-agent",
59
+ version: "1.0.0",
60
+ technologies: ["java", "spring", "kotlin"],
61
+ };
62
+ }
63
+ getChecks() {
64
+ return [
65
+ { id: "java:actuator-exposed", name: "Spring Actuator endpoints exposed", severity: "HIGH" },
66
+ { id: "java:log4shell", name: "Log4Shell JNDI injection", severity: "CRITICAL" },
67
+ { id: "java:unsafe-deserialization", name: "Unsafe Java deserialization", severity: "CRITICAL" },
68
+ { id: "java:sql-injection-query", name: "SQL injection via @Query", severity: "CRITICAL" },
69
+ { id: "java:credentials-in-config", name: "Hardcoded credentials in config", severity: "CRITICAL" },
70
+ { id: "java:csrf-disabled", name: "CSRF protection disabled", severity: "HIGH" },
71
+ { id: "java:xxe", name: "XML External Entity (XXE) vulnerability", severity: "HIGH" },
72
+ { id: "java:request-mapping-no-method", name: "@RequestMapping without HTTP method restriction", severity: "MEDIUM" },
73
+ { id: "java:permit-all-sensitive", name: "Sensitive route accessible without authentication", severity: "HIGH" },
74
+ { id: "java:weak-hash", name: "Weak hash algorithm (MD5/SHA-1)", severity: "HIGH" },
75
+ { id: "java:command-injection", name: "Command injection via Runtime.exec()", severity: "CRITICAL" },
76
+ { id: "java:cors-wildcard", name: "CORS wildcard allows all origins", severity: "MEDIUM" },
77
+ { id: "java:hardcoded-jwt-secret", name: "Hardcoded JWT secret", severity: "CRITICAL" },
78
+ { id: "java:stack-trace-exposure", name: "Stack trace exposed via printStackTrace()", severity: "MEDIUM" },
79
+ { id: "java:spring-security-debug", name: "Spring Security debug mode enabled", severity: "HIGH" },
80
+ { id: "java:insecure-random", name: "java.util.Random used for security-sensitive value", severity: "HIGH" },
81
+ { id: "java:mass-assignment", name: "Mass assignment via unvalidated model binding", severity: "MEDIUM" },
82
+ ];
83
+ }
84
+ }
85
+ // --- Helpers ---
86
+ function isJavaFile(filePath) {
87
+ return filePath.endsWith(".java");
88
+ }
89
+ function isConfigFile(filePath) {
90
+ return (filePath.endsWith(".properties") ||
91
+ filePath.endsWith(".yml") ||
92
+ filePath.endsWith(".yaml"));
93
+ }
94
+ function detectProjectLevelSecurity(files) {
95
+ let hasSpringSecurityConfig = false;
96
+ let hasRateLimiting = false;
97
+ for (const [filePath, content] of files) {
98
+ if (!isJavaFile(filePath))
99
+ continue;
100
+ if (/@EnableWebSecurity/.test(content) ||
101
+ /@EnableGlobalMethodSecurity/.test(content) ||
102
+ /@EnableMethodSecurity/.test(content)) {
103
+ hasSpringSecurityConfig = true;
104
+ }
105
+ if (/@RateLimiter/.test(content) ||
106
+ /bucket4j/.test(content) ||
107
+ /resilience4j/.test(content)) {
108
+ hasRateLimiting = true;
109
+ }
110
+ if (hasSpringSecurityConfig && hasRateLimiting)
111
+ break;
112
+ }
113
+ return { hasSpringSecurityConfig, hasRateLimiting };
114
+ }
115
+ // --- Check 1: Spring Actuator exposed ---
116
+ // HIGH, CWE-200
117
+ // Detects management.endpoints.web.exposure.include with dangerous values.
118
+ function checkActuatorExposed(files) {
119
+ const results = [];
120
+ for (const [filePath, content] of files) {
121
+ if (!isConfigFile(filePath))
122
+ continue;
123
+ const lines = content.split("\n");
124
+ for (let i = 0; i < lines.length; i++) {
125
+ const line = lines[i];
126
+ // --- Properties file format: single line contains full dotted key ---
127
+ if (/management\.endpoints\.web\.exposure\.include/.test(line)) {
128
+ // Handle both properties format (=*) and inline YAML (: * or : "*")
129
+ if (/[=:]\s*["']?\*["']?/.test(line) || /[=:]\s*.*(?:env|configprops|heapdump)/.test(line)) {
130
+ results.push(buildActuatorFinding(filePath, i + 1));
131
+ }
132
+ continue;
133
+ }
134
+ // --- YAML multi-line format: look for `include:` key with dangerous value
135
+ // when `exposure:` appeared in the preceding few lines ---
136
+ if (/^\s+include\s*:/.test(line)) {
137
+ const value = line.split(":").slice(1).join(":").trim().replace(/["']/g, "");
138
+ const isDangerous = value === "*" ||
139
+ /(?:env|configprops|heapdump)/.test(value);
140
+ if (isDangerous) {
141
+ // Confirm we are in an actuator exposure context by scanning back
142
+ const lookBehind = lines.slice(Math.max(0, i - 5), i).join("\n");
143
+ if (/exposure\s*:/.test(lookBehind) && /endpoints\s*:/.test(lookBehind)) {
144
+ results.push(buildActuatorFinding(filePath, i + 1));
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ return results;
151
+ }
152
+ function buildActuatorFinding(filePath, line) {
153
+ return {
154
+ checkId: "java:actuator-exposed",
155
+ title: "Spring Actuator endpoints exposed",
156
+ severity: "HIGH",
157
+ confidence: "high",
158
+ file: filePath,
159
+ line,
160
+ description: "Actuator endpoints are configured to expose sensitive information (*, env, configprops, or heapdump). " +
161
+ "These endpoints can leak environment variables, configuration properties, memory heap dumps, and other " +
162
+ "sensitive runtime data to anyone who can reach them.",
163
+ fix: "Restrict actuator endpoints: management.endpoints.web.exposure.include=health,info",
164
+ cwe: "CWE-200",
165
+ };
166
+ }
167
+ // --- Check 2: Log4j / Log4Shell ---
168
+ // CRITICAL, CWE-917
169
+ // Detects the literal JNDI lookup string in Java source files.
170
+ function checkLog4Shell(files) {
171
+ const results = [];
172
+ for (const [filePath, content] of files) {
173
+ if (!isJavaFile(filePath))
174
+ continue;
175
+ const lines = content.split("\n");
176
+ for (let i = 0; i < lines.length; i++) {
177
+ const line = lines[i];
178
+ if (line.includes("${jndi:")) {
179
+ results.push({
180
+ checkId: "java:log4shell",
181
+ title: "Log4Shell JNDI lookup string detected",
182
+ severity: "CRITICAL",
183
+ confidence: "high",
184
+ file: filePath,
185
+ line: i + 1,
186
+ description: "A JNDI lookup string '${jndi:' was found in the source code. If this string is logged via Log4j 2.x " +
187
+ "before version 2.17.1, it triggers the Log4Shell vulnerability (CVE-2021-44228), enabling remote code " +
188
+ "execution by an attacker who can control any logged value.",
189
+ fix: "Update Log4j to 2.17.1+. Set log4j2.formatMsgNoLookups=true.",
190
+ cwe: "CWE-917",
191
+ });
192
+ }
193
+ }
194
+ }
195
+ return results;
196
+ }
197
+ // --- Check 3: Unsafe deserialization ---
198
+ // CRITICAL, CWE-502
199
+ // Detects ObjectInputStream instantiation in Java source.
200
+ function checkUnsafeDeserialization(files) {
201
+ const results = [];
202
+ for (const [filePath, content] of files) {
203
+ if (!isJavaFile(filePath))
204
+ continue;
205
+ const lines = content.split("\n");
206
+ for (let i = 0; i < lines.length; i++) {
207
+ const line = lines[i];
208
+ const trimmed = line.trim();
209
+ // Skip comments
210
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
211
+ continue;
212
+ if (/new\s+ObjectInputStream\s*\(/.test(line)) {
213
+ results.push({
214
+ checkId: "java:unsafe-deserialization",
215
+ title: "Unsafe Java deserialization via ObjectInputStream",
216
+ severity: "CRITICAL",
217
+ confidence: "medium",
218
+ file: filePath,
219
+ line: i + 1,
220
+ description: "ObjectInputStream.readObject() can execute arbitrary code when deserializing untrusted data. " +
221
+ "Attackers who control the serialized input can trigger gadget chains leading to remote code execution, " +
222
+ "arbitrary file write, or SSRF.",
223
+ fix: "Use JSON/Protocol Buffers instead. If ObjectInputStream is needed, use a whitelist filter.",
224
+ cwe: "CWE-502",
225
+ });
226
+ }
227
+ }
228
+ }
229
+ return results;
230
+ }
231
+ // --- Check 4: SQL injection via @Query with string concatenation ---
232
+ // CRITICAL, CWE-89
233
+ // Detects @Query annotations that build queries via string concatenation
234
+ // but DO NOT use positional (?1, ?2) or named (:paramName) parameter markers.
235
+ function checkSqlInjectionQuery(files) {
236
+ const results = [];
237
+ for (const [filePath, content] of files) {
238
+ if (!isJavaFile(filePath))
239
+ continue;
240
+ const lines = content.split("\n");
241
+ for (let i = 0; i < lines.length; i++) {
242
+ const line = lines[i];
243
+ if (!/@Query\s*\(/.test(line))
244
+ continue;
245
+ // Look at the current line and the next few lines for string concatenation
246
+ const snippet = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
247
+ // Only flag actual string concatenation (the + operator after a closing quote)
248
+ if (!/"\s*\+/.test(snippet))
249
+ continue;
250
+ // Do NOT flag if the snippet contains named (:param) or positional (?1) markers.
251
+ // These indicate the query is parameterized even though there is a + in the
252
+ // annotation string (e.g., multi-line string with a compile-time constant).
253
+ if (/\?[0-9]+/.test(snippet) || /:[a-zA-Z][a-zA-Z0-9]*/.test(snippet))
254
+ continue;
255
+ results.push({
256
+ checkId: "java:sql-injection-query",
257
+ title: "SQL injection via @Query with string concatenation",
258
+ severity: "CRITICAL",
259
+ confidence: "medium",
260
+ file: filePath,
261
+ line: i + 1,
262
+ description: "The @Query annotation uses string concatenation (+) to build the query. " +
263
+ "Concatenating user-controlled values into JPQL or native SQL queries allows attackers " +
264
+ "to manipulate the query structure and access or modify unauthorized data.",
265
+ fix: 'Use named parameters: @Query("SELECT u FROM User u WHERE u.name = :name")',
266
+ cwe: "CWE-89",
267
+ });
268
+ }
269
+ }
270
+ return results;
271
+ }
272
+ // --- Check 5: Credentials in config files ---
273
+ // CRITICAL, CWE-798
274
+ // Detects hardcoded passwords, secrets, keys, or tokens in .properties/.yml files.
275
+ function checkCredentialsInConfig(files) {
276
+ const results = [];
277
+ // Matches: password=secret123, spring.datasource.password: mypassword, etc.
278
+ // Skips: values that are property references like ${DB_PASSWORD}
279
+ const credentialPattern = /(?:password|secret|key|token)\s*[=:]\s*([^\s${}]{4,})/i;
280
+ for (const [filePath, content] of files) {
281
+ if (!isConfigFile(filePath))
282
+ continue;
283
+ const lines = content.split("\n");
284
+ for (let i = 0; i < lines.length; i++) {
285
+ const line = lines[i];
286
+ const trimmed = line.trim();
287
+ // Skip blank lines and comments
288
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//"))
289
+ continue;
290
+ // Skip lines that are YAML keys with no value (e.g., "password:")
291
+ // Skip lines referencing env vars (${...})
292
+ if (trimmed.includes("${"))
293
+ continue;
294
+ const match = credentialPattern.exec(line);
295
+ if (match) {
296
+ const value = match[1];
297
+ // Skip obvious placeholders
298
+ if (/^(?:your[_-]|change[_-]me|placeholder|example|here|todo|xxx|n\/a|null|true|false|\d+)$/i.test(value)) {
299
+ continue;
300
+ }
301
+ results.push({
302
+ checkId: "java:credentials-in-config",
303
+ title: "Hardcoded credential in configuration file",
304
+ severity: "CRITICAL",
305
+ confidence: "high",
306
+ file: filePath,
307
+ line: i + 1,
308
+ description: "A password, secret, key, or token appears to be hardcoded directly in a configuration file. " +
309
+ "Committing credentials to source control exposes them to anyone with repository access " +
310
+ "and they persist in git history even after removal.",
311
+ fix: "Use environment variables or a vault: spring.datasource.password=${DB_PASSWORD}",
312
+ cwe: "CWE-798",
313
+ });
314
+ }
315
+ }
316
+ }
317
+ return results;
318
+ }
319
+ // --- Check 6: CSRF disabled ---
320
+ // HIGH, CWE-352
321
+ // Detects Spring Security configuration that explicitly disables CSRF protection.
322
+ function checkCsrfDisabled(files) {
323
+ const results = [];
324
+ // Patterns for different Spring Security versions
325
+ const csrfDisablePatterns = [
326
+ /\.csrf\s*\(\s*\)\s*\.disable\s*\(\s*\)/,
327
+ /csrf\s*\(\s*csrf\s*->\s*csrf\.disable\s*\(\s*\)/,
328
+ /\.csrf\s*\(\s*AbstractHttpConfigurer\s*::\s*disable\s*\)/,
329
+ ];
330
+ for (const [filePath, content] of files) {
331
+ if (!isJavaFile(filePath))
332
+ continue;
333
+ const lines = content.split("\n");
334
+ for (let i = 0; i < lines.length; i++) {
335
+ const line = lines[i];
336
+ const trimmed = line.trim();
337
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
338
+ continue;
339
+ if (csrfDisablePatterns.some((pattern) => pattern.test(line))) {
340
+ results.push({
341
+ checkId: "java:csrf-disabled",
342
+ title: "CSRF protection disabled",
343
+ severity: "HIGH",
344
+ confidence: "high",
345
+ file: filePath,
346
+ line: i + 1,
347
+ description: "Spring Security's CSRF protection has been explicitly disabled. " +
348
+ "CSRF allows attackers to trick authenticated users into performing unintended actions " +
349
+ "(state-changing requests) on your application.",
350
+ fix: "Enable CSRF protection for browser-facing endpoints.",
351
+ cwe: "CWE-352",
352
+ });
353
+ }
354
+ }
355
+ }
356
+ return results;
357
+ }
358
+ // --- Check 7: XXE vulnerability ---
359
+ // HIGH, CWE-611
360
+ // Detects DocumentBuilderFactory usage without disabling DTD features.
361
+ // Uses a 10-line lookahead (extended from 5) to reduce false positives on
362
+ // multi-statement builder setup patterns.
363
+ function checkXxeVulnerability(files) {
364
+ const results = [];
365
+ for (const [filePath, content] of files) {
366
+ if (!isJavaFile(filePath))
367
+ continue;
368
+ const lines = content.split("\n");
369
+ for (let i = 0; i < lines.length; i++) {
370
+ const line = lines[i];
371
+ if (!/DocumentBuilderFactory\.newInstance\s*\(\s*\)/.test(line))
372
+ continue;
373
+ // Look within 10 lines after this call for .setFeature (extended from 5)
374
+ const lookAhead = lines.slice(i, Math.min(i + 11, lines.length)).join("\n");
375
+ if (!lookAhead.includes(".setFeature")) {
376
+ results.push({
377
+ checkId: "java:xxe",
378
+ title: "XML External Entity (XXE) vulnerability",
379
+ severity: "HIGH",
380
+ confidence: "medium",
381
+ file: filePath,
382
+ line: i + 1,
383
+ description: "DocumentBuilderFactory is instantiated without disabling DTD processing. " +
384
+ "An attacker who can control XML input can exploit this to read local files, " +
385
+ "perform SSRF attacks, or cause denial of service via entity expansion.",
386
+ fix: 'Disable DTDs: factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)',
387
+ cwe: "CWE-611",
388
+ });
389
+ }
390
+ }
391
+ }
392
+ return results;
393
+ }
394
+ // --- Check 8: @RequestMapping without HTTP method restriction ---
395
+ // MEDIUM, CWE-749
396
+ // Detects @RequestMapping annotations that omit a method= attribute.
397
+ // NOT flagged when the file has a class-level @Secured or @PreAuthorize
398
+ // combined with @RestController, which indicates the class is already
399
+ // protected via method security — making the HTTP method ambiguity lower risk.
400
+ function checkRequestMappingWithoutMethod(files) {
401
+ const results = [];
402
+ for (const [filePath, content] of files) {
403
+ if (!isJavaFile(filePath))
404
+ continue;
405
+ // Check file-wide for class-level security annotations alongside @RestController.
406
+ // If both are present, the developer is using method-security; skip this file.
407
+ const hasRestController = /@RestController/.test(content);
408
+ const hasClassLevelSecurity = /@Secured\s*\(/.test(content) || /@PreAuthorize\s*\(/.test(content);
409
+ if (hasRestController && hasClassLevelSecurity)
410
+ continue;
411
+ const lines = content.split("\n");
412
+ for (let i = 0; i < lines.length; i++) {
413
+ const line = lines[i];
414
+ // Match @RequestMapping with a path string but no method= attribute
415
+ if (!/@RequestMapping\s*\(/.test(line))
416
+ continue;
417
+ if (/method\s*=/.test(line))
418
+ continue;
419
+ // Only flag if it has a value/path (not bare @RequestMapping on a class without value)
420
+ if (!/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']/.test(line))
421
+ continue;
422
+ results.push({
423
+ checkId: "java:request-mapping-no-method",
424
+ title: "@RequestMapping without HTTP method restriction",
425
+ severity: "MEDIUM",
426
+ confidence: "low",
427
+ file: filePath,
428
+ line: i + 1,
429
+ description: "@RequestMapping without a method= attribute accepts all HTTP methods (GET, POST, PUT, DELETE, PATCH, etc.). " +
430
+ "This widens the attack surface unnecessarily. A CSRF-unprotected endpoint might inadvertently accept POST " +
431
+ "requests, or a read-only endpoint might accept state-changing methods.",
432
+ fix: "Specify HTTP method: @GetMapping, @PostMapping, or @RequestMapping(method = RequestMethod.GET)",
433
+ cwe: "CWE-749",
434
+ });
435
+ }
436
+ }
437
+ return results;
438
+ }
439
+ // --- Check 9: permitAll on sensitive routes ---
440
+ // HIGH, CWE-306
441
+ // Heuristic: only flags `permitAll()` when it appears on the SAME LINE as a
442
+ // sensitive path pattern. We do NOT attempt to parse the Spring Security DSL
443
+ // across multiple lines because the DSL ordering matters (rules are evaluated
444
+ // in declaration order) and multi-line regex is too error-prone.
445
+ // Confidence is "low" for the same reason: a later `.authenticated()` rule
446
+ // could override this for some paths.
447
+ function checkPermitAllOnSensitiveRoutes(files) {
448
+ const results = [];
449
+ // Sensitive path fragments that should require authentication
450
+ const sensitivePaths = ["/admin", "/api/", "/manage", "/config", "/users", "/settings", "/account"];
451
+ for (const [filePath, content] of files) {
452
+ if (!isJavaFile(filePath))
453
+ continue;
454
+ const lines = content.split("\n");
455
+ for (let i = 0; i < lines.length; i++) {
456
+ const line = lines[i];
457
+ const trimmed = line.trim();
458
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
459
+ continue;
460
+ // Only examine lines that contain BOTH permitAll() AND a sensitive path.
461
+ // Multi-line DSL chaining is intentionally ignored — too ambiguous.
462
+ if (!line.includes("permitAll"))
463
+ continue;
464
+ const hasSensitivePath = sensitivePaths.some((path) => line.includes(path));
465
+ if (hasSensitivePath) {
466
+ results.push({
467
+ checkId: "java:permit-all-sensitive",
468
+ title: "Sensitive route accessible without authentication",
469
+ severity: "HIGH",
470
+ confidence: "low",
471
+ file: filePath,
472
+ line: i + 1,
473
+ description: "A sensitive path (/admin, /api/, /manage, /config, /users, /settings, or /account) appears on the " +
474
+ "same line as permitAll(), suggesting it may be accessible without authentication. " +
475
+ "Spring Security DSL ordering matters — verify this rule is not overridden by a later authenticated() call.",
476
+ fix: "Require authentication for sensitive endpoints.",
477
+ cwe: "CWE-306",
478
+ });
479
+ }
480
+ }
481
+ }
482
+ return results;
483
+ }
484
+ // --- Check 10: Weak hash algorithm ---
485
+ // HIGH, CWE-328
486
+ // Detects MessageDigest.getInstance calls using MD5 or SHA-1.
487
+ function checkWeakHashAlgorithm(files) {
488
+ const results = [];
489
+ const weakAlgorithmPattern = /MessageDigest\.getInstance\s*\(\s*["'](MD5|SHA-1|SHA1)["']\s*\)/;
490
+ for (const [filePath, content] of files) {
491
+ if (!isJavaFile(filePath))
492
+ continue;
493
+ const lines = content.split("\n");
494
+ for (let i = 0; i < lines.length; i++) {
495
+ const line = lines[i];
496
+ const match = weakAlgorithmPattern.exec(line);
497
+ if (match) {
498
+ results.push({
499
+ checkId: "java:weak-hash",
500
+ title: `Weak hash algorithm: ${match[1]}`,
501
+ severity: "HIGH",
502
+ confidence: "high",
503
+ file: filePath,
504
+ line: i + 1,
505
+ description: `${match[1]} is a cryptographically broken hash algorithm. MD5 and SHA-1 are vulnerable to collision ` +
506
+ "attacks and should not be used for security-sensitive purposes such as password hashing, " +
507
+ "digital signatures, or integrity verification.",
508
+ fix: 'Use SHA-256 or SHA-512: MessageDigest.getInstance("SHA-256")',
509
+ cwe: "CWE-328",
510
+ });
511
+ }
512
+ }
513
+ }
514
+ return results;
515
+ }
516
+ // --- Check 11: Command injection ---
517
+ // CRITICAL, CWE-78
518
+ // Only flags Runtime.exec() or ProcessBuilder when the command is built via
519
+ // string concatenation (e.g. exec("cmd " + userInput)).
520
+ // exec(new String[]{"cmd", arg}) passes separate arguments to the OS and
521
+ // does NOT invoke a shell, so it is NOT flagged.
522
+ function checkCommandInjection(files) {
523
+ const results = [];
524
+ for (const [filePath, content] of files) {
525
+ if (!isJavaFile(filePath))
526
+ continue;
527
+ const lines = content.split("\n");
528
+ for (let i = 0; i < lines.length; i++) {
529
+ const line = lines[i];
530
+ const trimmed = line.trim();
531
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
532
+ continue;
533
+ // Runtime.exec() with string concatenation — dangerous because a single
534
+ // concatenated string is passed to a shell and can contain metacharacters.
535
+ // exec(new String[]{...}) is safe; we skip those by checking that the
536
+ // argument is NOT an array literal (no "new String[" on the same line).
537
+ const hasRuntimeExec = /Runtime\.getRuntime\s*\(\s*\)\.exec\s*\(/.test(line);
538
+ const hasProcessBuilder = /new\s+ProcessBuilder\s*\(/.test(line);
539
+ if (!hasRuntimeExec && !hasProcessBuilder)
540
+ continue;
541
+ // Skip array-based exec calls — they do not invoke a shell.
542
+ if (/new\s+String\s*\[/.test(line))
543
+ continue;
544
+ // Only flag when string concatenation is present in the same statement.
545
+ if (line.includes("+")) {
546
+ results.push({
547
+ checkId: "java:command-injection",
548
+ title: "Command injection via Runtime.exec() with dynamic input",
549
+ severity: "CRITICAL",
550
+ confidence: "high",
551
+ file: filePath,
552
+ line: i + 1,
553
+ description: "Runtime.getRuntime().exec() or ProcessBuilder is called with a dynamically constructed command " +
554
+ "(string concatenation with +). If any part of the command string comes from user input, an attacker " +
555
+ "can inject shell metacharacters to execute arbitrary commands on the server.",
556
+ fix: "Avoid Runtime.exec(). If necessary, use ProcessBuilder with a pre-validated, fixed array of arguments.",
557
+ cwe: "CWE-78",
558
+ });
559
+ }
560
+ }
561
+ }
562
+ return results;
563
+ }
564
+ // --- Check 12: CORS wildcard ---
565
+ // MEDIUM, CWE-942
566
+ // Detects Spring @CrossOrigin with wildcard origins or allowedOrigins("*").
567
+ function checkCorsWildcard(files) {
568
+ const results = [];
569
+ const corsPatterns = [
570
+ // @CrossOrigin(origins = "*")
571
+ /@CrossOrigin\s*\([^)]*origins\s*=\s*["']\*["']/,
572
+ // @CrossOrigin with no arguments (defaults to all origins)
573
+ /@CrossOrigin\s*(?:\(\s*\))?\s*$/,
574
+ // allowedOrigins("*")
575
+ /allowedOrigins\s*\(\s*["']\*["']\s*\)/,
576
+ ];
577
+ for (const [filePath, content] of files) {
578
+ if (!isJavaFile(filePath))
579
+ continue;
580
+ const lines = content.split("\n");
581
+ for (let i = 0; i < lines.length; i++) {
582
+ const line = lines[i];
583
+ const trimmed = line.trim();
584
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
585
+ continue;
586
+ if (corsPatterns.some((pattern) => pattern.test(trimmed))) {
587
+ results.push({
588
+ checkId: "java:cors-wildcard",
589
+ title: "CORS wildcard allows all origins",
590
+ severity: "MEDIUM",
591
+ confidence: "high",
592
+ file: filePath,
593
+ line: i + 1,
594
+ description: "CORS is configured to allow requests from any origin (*). " +
595
+ "This allows any website to make authenticated cross-origin requests to your API, " +
596
+ "potentially leaking user data or enabling cross-site request forgery.",
597
+ fix: 'Restrict CORS: @CrossOrigin(origins = "https://yourdomain.com")',
598
+ cwe: "CWE-942",
599
+ });
600
+ }
601
+ }
602
+ }
603
+ return results;
604
+ }
605
+ // --- Check 13: Hardcoded JWT secret ---
606
+ // CRITICAL, CWE-798
607
+ // Detects JWT secret/signing keys hardcoded as string literals.
608
+ // Confidence is "high" only when the literal value is present on the same
609
+ // line as the jwt/JWT keyword AND is not a Spring @Value injection.
610
+ function checkHardcodedJwtSecret(files) {
611
+ const results = [];
612
+ // Matches: secret = "...", key = "...", signing = "..." in JWT-adjacent context
613
+ const jwtSecretPattern = /(?:secret|key|signing)\s*=\s*["']([^"']{8,})["']/i;
614
+ for (const [filePath, content] of files) {
615
+ if (!isJavaFile(filePath))
616
+ continue;
617
+ const lines = content.split("\n");
618
+ for (let i = 0; i < lines.length; i++) {
619
+ const line = lines[i];
620
+ const trimmed = line.trim();
621
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
622
+ continue;
623
+ // Only flag lines in JWT-relevant context
624
+ if (!/jwt/i.test(line) && !/JWT/i.test(line))
625
+ continue;
626
+ // Skip @Value-injected fields — those read from environment/config
627
+ if (/@Value\s*\(/.test(line))
628
+ continue;
629
+ const match = jwtSecretPattern.exec(line);
630
+ if (match) {
631
+ const value = match[1];
632
+ // Skip obvious placeholders
633
+ if (/^(?:your[_-]|change[_-]me|placeholder|example|here|xxx)$/i.test(value))
634
+ continue;
635
+ results.push({
636
+ checkId: "java:hardcoded-jwt-secret",
637
+ title: "Hardcoded JWT secret in source code",
638
+ severity: "CRITICAL",
639
+ confidence: "high",
640
+ file: filePath,
641
+ line: i + 1,
642
+ description: "A JWT secret or signing key is hardcoded as a string literal. " +
643
+ "Anyone with access to the source code can forge valid JWT tokens for any user, " +
644
+ "including administrator accounts.",
645
+ fix: "Store JWT secrets in environment variables or a secrets manager.",
646
+ cwe: "CWE-798",
647
+ });
648
+ }
649
+ }
650
+ }
651
+ return results;
652
+ }
653
+ // --- Check 14: Stack trace in response ---
654
+ // MEDIUM, CWE-209
655
+ // Detects printStackTrace() only when the surrounding catch block also contains
656
+ // a Response-related type or a return/throw statement. A bare printStackTrace()
657
+ // in a catch block that only logs is not flagged, because the stack trace never
658
+ // reaches the client in that case.
659
+ function checkStackTraceInResponse(files) {
660
+ const results = [];
661
+ for (const [filePath, content] of files) {
662
+ if (!isJavaFile(filePath))
663
+ continue;
664
+ // Skip test files — stack traces in tests are acceptable
665
+ if (filePath.includes("/test/") ||
666
+ filePath.includes("Test.java") ||
667
+ filePath.endsWith("Tests.java")) {
668
+ continue;
669
+ }
670
+ const lines = content.split("\n");
671
+ for (let i = 0; i < lines.length; i++) {
672
+ const line = lines[i];
673
+ const trimmed = line.trim();
674
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
675
+ continue;
676
+ if (!/\.printStackTrace\s*\(\s*\)/.test(line))
677
+ continue;
678
+ // Scan a window of ±8 lines to find the enclosing catch block context.
679
+ // If the block contains Response/return/throw, the stack trace could
680
+ // reach the client; flag it.
681
+ const windowStart = Math.max(0, i - 4);
682
+ const windowEnd = Math.min(lines.length, i + 9);
683
+ const catchWindow = lines.slice(windowStart, windowEnd).join("\n");
684
+ const couldLeakToClient = /Response/.test(catchWindow) ||
685
+ /\breturn\b/.test(catchWindow) ||
686
+ /\bthrow\b/.test(catchWindow);
687
+ if (!couldLeakToClient)
688
+ continue;
689
+ results.push({
690
+ checkId: "java:stack-trace-exposure",
691
+ title: "Stack trace exposed via printStackTrace()",
692
+ severity: "MEDIUM",
693
+ confidence: "low",
694
+ file: filePath,
695
+ line: i + 1,
696
+ description: "printStackTrace() outputs the full exception stack trace, which may be captured in HTTP " +
697
+ "responses, logs visible to users, or error pages. This leaks internal implementation details " +
698
+ "(class names, method signatures, library versions) that help attackers map the attack surface.",
699
+ fix: "Log errors server-side. Return generic error messages to clients.",
700
+ cwe: "CWE-209",
701
+ });
702
+ }
703
+ }
704
+ return results;
705
+ }
706
+ // --- Check 15: Spring Security debug mode ---
707
+ // HIGH, CWE-215
708
+ // Detects @EnableWebSecurity(debug = true).
709
+ function checkSpringSecurityDebug(files) {
710
+ const results = [];
711
+ const debugPattern = /@EnableWebSecurity\s*\([^)]*debug\s*=\s*true[^)]*\)/;
712
+ for (const [filePath, content] of files) {
713
+ if (!isJavaFile(filePath))
714
+ continue;
715
+ const lines = content.split("\n");
716
+ for (let i = 0; i < lines.length; i++) {
717
+ const line = lines[i];
718
+ const trimmed = line.trim();
719
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
720
+ continue;
721
+ if (debugPattern.test(line)) {
722
+ results.push({
723
+ checkId: "java:spring-security-debug",
724
+ title: "Spring Security debug mode enabled",
725
+ severity: "HIGH",
726
+ confidence: "high",
727
+ file: filePath,
728
+ line: i + 1,
729
+ description: "@EnableWebSecurity(debug = true) logs every HTTP request including security-relevant information " +
730
+ "such as headers, request parameters, and filter chain decisions. In production this generates " +
731
+ "verbose logs that may expose sensitive data and slow down the application.",
732
+ fix: "Set debug=false in production: @EnableWebSecurity(debug = false)",
733
+ cwe: "CWE-215",
734
+ });
735
+ }
736
+ }
737
+ }
738
+ return results;
739
+ }
740
+ // --- Check 16: Insecure randomness ---
741
+ // HIGH, CWE-330
742
+ // Detects java.util.Random used near security-sensitive variable names.
743
+ function checkInsecureRandom(files) {
744
+ const results = [];
745
+ for (const [filePath, content] of files) {
746
+ if (!isJavaFile(filePath))
747
+ continue;
748
+ const lines = content.split("\n");
749
+ for (let i = 0; i < lines.length; i++) {
750
+ const line = lines[i];
751
+ if (!/new\s+Random\s*\(|ThreadLocalRandom\.current\(\)/.test(line))
752
+ continue;
753
+ const contextStart = Math.max(0, i - 3);
754
+ const contextEnd = Math.min(lines.length, i + 3);
755
+ const context = lines.slice(contextStart, contextEnd).join("\n").toLowerCase();
756
+ if (/token|secret|key|session|nonce|salt|otp|password|csrf/.test(context)) {
757
+ results.push({
758
+ checkId: "java:insecure-random",
759
+ title: "java.util.Random used for security-sensitive value",
760
+ severity: "HIGH",
761
+ confidence: "medium",
762
+ file: filePath,
763
+ line: i + 1,
764
+ description: "java.util.Random is not cryptographically secure. For tokens, secrets, or session IDs, use java.security.SecureRandom instead.",
765
+ fix: "SecureRandom random = new SecureRandom(); byte[] bytes = new byte[32]; random.nextBytes(bytes);",
766
+ cwe: "CWE-330",
767
+ });
768
+ }
769
+ }
770
+ }
771
+ return results;
772
+ }
773
+ // --- Check 17: Mass assignment ---
774
+ // MEDIUM, CWE-915
775
+ // Detects @RequestBody without @Valid on the same method signature,
776
+ // or @ModelAttribute passed directly to repository.save().
777
+ function checkMassAssignment(files) {
778
+ const results = [];
779
+ for (const [filePath, content] of files) {
780
+ if (!isJavaFile(filePath))
781
+ continue;
782
+ const lines = content.split("\n");
783
+ for (let i = 0; i < lines.length; i++) {
784
+ const line = lines[i];
785
+ // @RequestBody without @Valid — spreads entire JSON body into object
786
+ if (/@RequestBody\s+(?!@Valid)/.test(line)) {
787
+ // Look ahead for repository.save() without field filtering
788
+ const ahead = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
789
+ if (/\.save\s*\(|\.saveAndFlush\s*\(|\.persist\s*\(/.test(ahead)) {
790
+ results.push({
791
+ checkId: "java:mass-assignment",
792
+ title: "Mass assignment — @RequestBody without @Valid to repository.save()",
793
+ severity: "MEDIUM",
794
+ confidence: "low",
795
+ file: filePath,
796
+ line: i + 1,
797
+ description: "The request body is bound directly to an entity and saved without validation. An attacker can submit extra fields (role, isAdmin, price) to modify unauthorized properties.",
798
+ fix: "Use a DTO with only the allowed fields, add @Valid for validation, and map explicitly to the entity.",
799
+ cwe: "CWE-915",
800
+ });
801
+ }
802
+ }
803
+ }
804
+ }
805
+ return results;
806
+ }
807
+ //# sourceMappingURL=java-agent.js.map