@oculum/scanner 1.0.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 (281) hide show
  1. package/dist/formatters/cli-terminal.d.ts +27 -0
  2. package/dist/formatters/cli-terminal.d.ts.map +1 -0
  3. package/dist/formatters/cli-terminal.js +412 -0
  4. package/dist/formatters/cli-terminal.js.map +1 -0
  5. package/dist/formatters/github-comment.d.ts +41 -0
  6. package/dist/formatters/github-comment.d.ts.map +1 -0
  7. package/dist/formatters/github-comment.js +306 -0
  8. package/dist/formatters/github-comment.js.map +1 -0
  9. package/dist/formatters/grouping.d.ts +52 -0
  10. package/dist/formatters/grouping.d.ts.map +1 -0
  11. package/dist/formatters/grouping.js +152 -0
  12. package/dist/formatters/grouping.js.map +1 -0
  13. package/dist/formatters/index.d.ts +9 -0
  14. package/dist/formatters/index.d.ts.map +1 -0
  15. package/dist/formatters/index.js +35 -0
  16. package/dist/formatters/index.js.map +1 -0
  17. package/dist/formatters/vscode-diagnostic.d.ts +103 -0
  18. package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
  19. package/dist/formatters/vscode-diagnostic.js +151 -0
  20. package/dist/formatters/vscode-diagnostic.js.map +1 -0
  21. package/dist/index.d.ts +52 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +648 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/layer1/comments.d.ts +8 -0
  26. package/dist/layer1/comments.d.ts.map +1 -0
  27. package/dist/layer1/comments.js +203 -0
  28. package/dist/layer1/comments.js.map +1 -0
  29. package/dist/layer1/config-audit.d.ts +8 -0
  30. package/dist/layer1/config-audit.d.ts.map +1 -0
  31. package/dist/layer1/config-audit.js +252 -0
  32. package/dist/layer1/config-audit.js.map +1 -0
  33. package/dist/layer1/entropy.d.ts +8 -0
  34. package/dist/layer1/entropy.d.ts.map +1 -0
  35. package/dist/layer1/entropy.js +500 -0
  36. package/dist/layer1/entropy.js.map +1 -0
  37. package/dist/layer1/file-flags.d.ts +7 -0
  38. package/dist/layer1/file-flags.d.ts.map +1 -0
  39. package/dist/layer1/file-flags.js +112 -0
  40. package/dist/layer1/file-flags.js.map +1 -0
  41. package/dist/layer1/index.d.ts +36 -0
  42. package/dist/layer1/index.d.ts.map +1 -0
  43. package/dist/layer1/index.js +132 -0
  44. package/dist/layer1/index.js.map +1 -0
  45. package/dist/layer1/patterns.d.ts +8 -0
  46. package/dist/layer1/patterns.d.ts.map +1 -0
  47. package/dist/layer1/patterns.js +482 -0
  48. package/dist/layer1/patterns.js.map +1 -0
  49. package/dist/layer1/urls.d.ts +8 -0
  50. package/dist/layer1/urls.d.ts.map +1 -0
  51. package/dist/layer1/urls.js +296 -0
  52. package/dist/layer1/urls.js.map +1 -0
  53. package/dist/layer1/weak-crypto.d.ts +7 -0
  54. package/dist/layer1/weak-crypto.d.ts.map +1 -0
  55. package/dist/layer1/weak-crypto.js +291 -0
  56. package/dist/layer1/weak-crypto.js.map +1 -0
  57. package/dist/layer2/ai-agent-tools.d.ts +19 -0
  58. package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
  59. package/dist/layer2/ai-agent-tools.js +528 -0
  60. package/dist/layer2/ai-agent-tools.js.map +1 -0
  61. package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
  62. package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
  63. package/dist/layer2/ai-endpoint-protection.js +332 -0
  64. package/dist/layer2/ai-endpoint-protection.js.map +1 -0
  65. package/dist/layer2/ai-execution-sinks.d.ts +18 -0
  66. package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
  67. package/dist/layer2/ai-execution-sinks.js +496 -0
  68. package/dist/layer2/ai-execution-sinks.js.map +1 -0
  69. package/dist/layer2/ai-fingerprinting.d.ts +7 -0
  70. package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
  71. package/dist/layer2/ai-fingerprinting.js +654 -0
  72. package/dist/layer2/ai-fingerprinting.js.map +1 -0
  73. package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
  74. package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
  75. package/dist/layer2/ai-prompt-hygiene.js +356 -0
  76. package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
  77. package/dist/layer2/ai-rag-safety.d.ts +21 -0
  78. package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
  79. package/dist/layer2/ai-rag-safety.js +459 -0
  80. package/dist/layer2/ai-rag-safety.js.map +1 -0
  81. package/dist/layer2/ai-schema-validation.d.ts +25 -0
  82. package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
  83. package/dist/layer2/ai-schema-validation.js +375 -0
  84. package/dist/layer2/ai-schema-validation.js.map +1 -0
  85. package/dist/layer2/auth-antipatterns.d.ts +20 -0
  86. package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
  87. package/dist/layer2/auth-antipatterns.js +333 -0
  88. package/dist/layer2/auth-antipatterns.js.map +1 -0
  89. package/dist/layer2/byok-patterns.d.ts +12 -0
  90. package/dist/layer2/byok-patterns.d.ts.map +1 -0
  91. package/dist/layer2/byok-patterns.js +299 -0
  92. package/dist/layer2/byok-patterns.js.map +1 -0
  93. package/dist/layer2/dangerous-functions.d.ts +7 -0
  94. package/dist/layer2/dangerous-functions.d.ts.map +1 -0
  95. package/dist/layer2/dangerous-functions.js +1375 -0
  96. package/dist/layer2/dangerous-functions.js.map +1 -0
  97. package/dist/layer2/data-exposure.d.ts +16 -0
  98. package/dist/layer2/data-exposure.d.ts.map +1 -0
  99. package/dist/layer2/data-exposure.js +279 -0
  100. package/dist/layer2/data-exposure.js.map +1 -0
  101. package/dist/layer2/framework-checks.d.ts +7 -0
  102. package/dist/layer2/framework-checks.d.ts.map +1 -0
  103. package/dist/layer2/framework-checks.js +388 -0
  104. package/dist/layer2/framework-checks.js.map +1 -0
  105. package/dist/layer2/index.d.ts +58 -0
  106. package/dist/layer2/index.d.ts.map +1 -0
  107. package/dist/layer2/index.js +380 -0
  108. package/dist/layer2/index.js.map +1 -0
  109. package/dist/layer2/logic-gates.d.ts +7 -0
  110. package/dist/layer2/logic-gates.d.ts.map +1 -0
  111. package/dist/layer2/logic-gates.js +182 -0
  112. package/dist/layer2/logic-gates.js.map +1 -0
  113. package/dist/layer2/risky-imports.d.ts +7 -0
  114. package/dist/layer2/risky-imports.d.ts.map +1 -0
  115. package/dist/layer2/risky-imports.js +161 -0
  116. package/dist/layer2/risky-imports.js.map +1 -0
  117. package/dist/layer2/variables.d.ts +8 -0
  118. package/dist/layer2/variables.d.ts.map +1 -0
  119. package/dist/layer2/variables.js +152 -0
  120. package/dist/layer2/variables.js.map +1 -0
  121. package/dist/layer3/anthropic.d.ts +83 -0
  122. package/dist/layer3/anthropic.d.ts.map +1 -0
  123. package/dist/layer3/anthropic.js +1745 -0
  124. package/dist/layer3/anthropic.js.map +1 -0
  125. package/dist/layer3/index.d.ts +24 -0
  126. package/dist/layer3/index.d.ts.map +1 -0
  127. package/dist/layer3/index.js +119 -0
  128. package/dist/layer3/index.js.map +1 -0
  129. package/dist/layer3/openai.d.ts +25 -0
  130. package/dist/layer3/openai.d.ts.map +1 -0
  131. package/dist/layer3/openai.js +238 -0
  132. package/dist/layer3/openai.js.map +1 -0
  133. package/dist/layer3/package-check.d.ts +63 -0
  134. package/dist/layer3/package-check.d.ts.map +1 -0
  135. package/dist/layer3/package-check.js +508 -0
  136. package/dist/layer3/package-check.js.map +1 -0
  137. package/dist/modes/incremental.d.ts +66 -0
  138. package/dist/modes/incremental.d.ts.map +1 -0
  139. package/dist/modes/incremental.js +200 -0
  140. package/dist/modes/incremental.js.map +1 -0
  141. package/dist/tiers.d.ts +125 -0
  142. package/dist/tiers.d.ts.map +1 -0
  143. package/dist/tiers.js +234 -0
  144. package/dist/tiers.js.map +1 -0
  145. package/dist/types.d.ts +175 -0
  146. package/dist/types.d.ts.map +1 -0
  147. package/dist/types.js +50 -0
  148. package/dist/types.js.map +1 -0
  149. package/dist/utils/auth-helper-detector.d.ts +56 -0
  150. package/dist/utils/auth-helper-detector.d.ts.map +1 -0
  151. package/dist/utils/auth-helper-detector.js +360 -0
  152. package/dist/utils/auth-helper-detector.js.map +1 -0
  153. package/dist/utils/context-helpers.d.ts +96 -0
  154. package/dist/utils/context-helpers.d.ts.map +1 -0
  155. package/dist/utils/context-helpers.js +493 -0
  156. package/dist/utils/context-helpers.js.map +1 -0
  157. package/dist/utils/diff-detector.d.ts +53 -0
  158. package/dist/utils/diff-detector.d.ts.map +1 -0
  159. package/dist/utils/diff-detector.js +104 -0
  160. package/dist/utils/diff-detector.js.map +1 -0
  161. package/dist/utils/diff-parser.d.ts +80 -0
  162. package/dist/utils/diff-parser.d.ts.map +1 -0
  163. package/dist/utils/diff-parser.js +202 -0
  164. package/dist/utils/diff-parser.js.map +1 -0
  165. package/dist/utils/imported-auth-detector.d.ts +37 -0
  166. package/dist/utils/imported-auth-detector.d.ts.map +1 -0
  167. package/dist/utils/imported-auth-detector.js +251 -0
  168. package/dist/utils/imported-auth-detector.js.map +1 -0
  169. package/dist/utils/middleware-detector.d.ts +55 -0
  170. package/dist/utils/middleware-detector.d.ts.map +1 -0
  171. package/dist/utils/middleware-detector.js +260 -0
  172. package/dist/utils/middleware-detector.js.map +1 -0
  173. package/dist/utils/oauth-flow-detector.d.ts +41 -0
  174. package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
  175. package/dist/utils/oauth-flow-detector.js +202 -0
  176. package/dist/utils/oauth-flow-detector.js.map +1 -0
  177. package/dist/utils/path-exclusions.d.ts +55 -0
  178. package/dist/utils/path-exclusions.d.ts.map +1 -0
  179. package/dist/utils/path-exclusions.js +222 -0
  180. package/dist/utils/path-exclusions.js.map +1 -0
  181. package/dist/utils/project-context-builder.d.ts +119 -0
  182. package/dist/utils/project-context-builder.d.ts.map +1 -0
  183. package/dist/utils/project-context-builder.js +534 -0
  184. package/dist/utils/project-context-builder.js.map +1 -0
  185. package/dist/utils/registry-clients.d.ts +93 -0
  186. package/dist/utils/registry-clients.d.ts.map +1 -0
  187. package/dist/utils/registry-clients.js +273 -0
  188. package/dist/utils/registry-clients.js.map +1 -0
  189. package/dist/utils/trpc-analyzer.d.ts +78 -0
  190. package/dist/utils/trpc-analyzer.d.ts.map +1 -0
  191. package/dist/utils/trpc-analyzer.js +297 -0
  192. package/dist/utils/trpc-analyzer.js.map +1 -0
  193. package/package.json +45 -0
  194. package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
  195. package/src/__tests__/benchmark/fixtures/index.ts +68 -0
  196. package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
  197. package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
  198. package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
  199. package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
  200. package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
  201. package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
  202. package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
  203. package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
  204. package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
  205. package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
  206. package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
  207. package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
  208. package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
  209. package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
  210. package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
  211. package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
  212. package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
  213. package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
  214. package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
  215. package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
  216. package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
  217. package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
  218. package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
  219. package/src/__tests__/benchmark/index.ts +29 -0
  220. package/src/__tests__/benchmark/run-benchmark.ts +144 -0
  221. package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
  222. package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
  223. package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
  224. package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
  225. package/src/__tests__/benchmark/types.ts +144 -0
  226. package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
  227. package/src/__tests__/regression/known-false-positives.test.ts +467 -0
  228. package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
  229. package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
  230. package/src/__tests__/validation/analyze-results.ts +542 -0
  231. package/src/__tests__/validation/extract-for-triage.ts +146 -0
  232. package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
  233. package/src/__tests__/validation/run-validation.ts +364 -0
  234. package/src/__tests__/validation/triage-template.md +132 -0
  235. package/src/formatters/cli-terminal.ts +446 -0
  236. package/src/formatters/github-comment.ts +382 -0
  237. package/src/formatters/grouping.ts +190 -0
  238. package/src/formatters/index.ts +47 -0
  239. package/src/formatters/vscode-diagnostic.ts +243 -0
  240. package/src/index.ts +823 -0
  241. package/src/layer1/comments.ts +218 -0
  242. package/src/layer1/config-audit.ts +289 -0
  243. package/src/layer1/entropy.ts +583 -0
  244. package/src/layer1/file-flags.ts +127 -0
  245. package/src/layer1/index.ts +181 -0
  246. package/src/layer1/patterns.ts +516 -0
  247. package/src/layer1/urls.ts +334 -0
  248. package/src/layer1/weak-crypto.ts +328 -0
  249. package/src/layer2/ai-agent-tools.ts +601 -0
  250. package/src/layer2/ai-endpoint-protection.ts +387 -0
  251. package/src/layer2/ai-execution-sinks.ts +580 -0
  252. package/src/layer2/ai-fingerprinting.ts +758 -0
  253. package/src/layer2/ai-prompt-hygiene.ts +411 -0
  254. package/src/layer2/ai-rag-safety.ts +511 -0
  255. package/src/layer2/ai-schema-validation.ts +421 -0
  256. package/src/layer2/auth-antipatterns.ts +394 -0
  257. package/src/layer2/byok-patterns.ts +336 -0
  258. package/src/layer2/dangerous-functions.ts +1563 -0
  259. package/src/layer2/data-exposure.ts +315 -0
  260. package/src/layer2/framework-checks.ts +433 -0
  261. package/src/layer2/index.ts +473 -0
  262. package/src/layer2/logic-gates.ts +206 -0
  263. package/src/layer2/risky-imports.ts +186 -0
  264. package/src/layer2/variables.ts +166 -0
  265. package/src/layer3/anthropic.ts +2030 -0
  266. package/src/layer3/index.ts +130 -0
  267. package/src/layer3/package-check.ts +604 -0
  268. package/src/modes/incremental.ts +293 -0
  269. package/src/tiers.ts +318 -0
  270. package/src/types.ts +284 -0
  271. package/src/utils/auth-helper-detector.ts +443 -0
  272. package/src/utils/context-helpers.ts +535 -0
  273. package/src/utils/diff-detector.ts +135 -0
  274. package/src/utils/diff-parser.ts +272 -0
  275. package/src/utils/imported-auth-detector.ts +320 -0
  276. package/src/utils/middleware-detector.ts +333 -0
  277. package/src/utils/oauth-flow-detector.ts +246 -0
  278. package/src/utils/path-exclusions.ts +266 -0
  279. package/src/utils/project-context-builder.ts +707 -0
  280. package/src/utils/registry-clients.ts +351 -0
  281. package/src/utils/trpc-analyzer.ts +382 -0
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Layer 2: AI Prompt Hygiene Detection
3
+ * Detects prompt injection vulnerabilities and secrets in LLM prompts
4
+ *
5
+ * Covers:
6
+ * - B1: Prompt & template hygiene (LLM01)
7
+ * - B3: Secrets & sensitive data in prompts (LLM06)
8
+ */
9
+
10
+ import type { Vulnerability, VulnerabilitySeverity } from '../types'
11
+ import {
12
+ isComment,
13
+ isTestOrMockFile,
14
+ isDocumentationFile,
15
+ isScannerOrFixtureFile,
16
+ } from '../utils/context-helpers'
17
+
18
+ /**
19
+ * Check if a file is in an LLM/AI context based on path and content
20
+ */
21
+ function isLLMContextFile(filePath: string, content: string): boolean {
22
+ // File path indicators of AI/LLM code
23
+ const llmPathPatterns = [
24
+ /\/(ai|llm|chat|openai|anthropic|gpt|claude)\//i,
25
+ /\/(assistants?|agents?|prompts?)\//i,
26
+ /(chat|ai|llm|prompt|assistant|agent).*\.(ts|js|tsx|jsx|py)$/i,
27
+ ]
28
+
29
+ if (llmPathPatterns.some(p => p.test(filePath))) {
30
+ return true
31
+ }
32
+
33
+ // Content patterns suggesting LLM API usage
34
+ const llmContentPatterns = [
35
+ /\.create\s*\(\s*\{[^}]*messages\s*:/i, // OpenAI/Anthropic SDK
36
+ /from\s+['"](@anthropic-ai|openai|langchain|llama[-_]?index)/i, // Imports
37
+ /\bsystem\s*:\s*['"`]/i, // System message definition
38
+ /role:\s*['"`](user|assistant|system)['"`]/i, // Message roles
39
+ /\b(systemPrompt|userPrompt|assistantPrompt)\b/i, // Prompt variables
40
+ /messages\s*:\s*\[/i, // Messages array
41
+ /\.chat\.completions?\.create/i, // OpenAI chat completion
42
+ /\.messages\.create/i, // Anthropic messages
43
+ /ChatCompletion|MessageCreate/i, // SDK types
44
+ ]
45
+
46
+ return llmContentPatterns.some(p => p.test(content))
47
+ }
48
+
49
+ /**
50
+ * Check if user input delimiter/fence patterns are present
51
+ */
52
+ function hasPromptDelimiters(lineContent: string, contextLines: string[]): boolean {
53
+ const context = [lineContent, ...contextLines].join('\n')
54
+
55
+ const delimiterPatterns = [
56
+ /```/, // Triple backticks
57
+ /<user>|<\/user>/i, // XML-style user tags
58
+ /<human>|<\/human>/i, // Human tags
59
+ /---+/, // Horizontal rules
60
+ /\[USER\]|\[\/USER\]/i, // Bracket tags
61
+ /\{\{user\}\}/i, // Template variable
62
+ /###\s*User|###\s*Input/i, // Markdown headers
63
+ /INPUT:|OUTPUT:/i, // Section markers
64
+ ]
65
+
66
+ return delimiterPatterns.some(p => p.test(context))
67
+ }
68
+
69
+ /**
70
+ * Check if content looks like proper parameterization rather than concatenation
71
+ */
72
+ function isProperlyParameterized(lineContent: string): boolean {
73
+ const safePatterns = [
74
+ /\{\{.*\}\}/, // Handlebars/mustache templates
75
+ /\{[a-zA-Z_]+\}/, // Python format strings (positional)
76
+ /\$\{.*\}.*sanitize|escape/i, // Template with sanitization
77
+ /placeholder|PLACEHOLDER/, // Explicit placeholders
78
+ ]
79
+
80
+ return safePatterns.some(p => p.test(lineContent))
81
+ }
82
+
83
+ // ============================================================================
84
+ // Pattern Definitions
85
+ // ============================================================================
86
+
87
+ interface PromptHygienePattern {
88
+ name: string
89
+ pattern: RegExp
90
+ severity: VulnerabilitySeverity
91
+ description: string
92
+ suggestedFix: string
93
+ checkDelimiters?: boolean // If true, downgrade if delimiters found
94
+ }
95
+
96
+ /**
97
+ * B1: Unsafe prompt interpolation patterns
98
+ */
99
+ const UNSAFE_INTERPOLATION_PATTERNS: PromptHygienePattern[] = [
100
+ // Template literals with user input in system prompts
101
+ {
102
+ name: 'User input in system prompt',
103
+ pattern: /system\s*[=:]\s*`[^`]*\$\{.*(?:user|input|req|request|body|query|params|data).*\}[^`]*`/gi,
104
+ severity: 'high',
105
+ description: 'User input is directly interpolated into a system prompt. This creates a prompt injection vulnerability where attackers can manipulate the AI\'s behavior.',
106
+ suggestedFix: 'Use clear delimiters (```, <user>, ---) between system instructions and user content. Consider using structured input rather than string interpolation.',
107
+ checkDelimiters: true,
108
+ },
109
+ // String concatenation in prompt building
110
+ {
111
+ name: 'Prompt string concatenation with user input',
112
+ pattern: /(?:system|prompt|instruction)\s*[=+]\s*.*\+\s*(?:user|input|req|request|body|query|params)(?:\.|Input|\[)/gi,
113
+ severity: 'high',
114
+ description: 'User input is concatenated into prompt strings. Attackers can inject malicious instructions.',
115
+ suggestedFix: 'Use delimiters to clearly separate system instructions from user content. Example: ```user input here```',
116
+ checkDelimiters: true,
117
+ },
118
+ // Messages array with dynamic user content in system role
119
+ {
120
+ name: 'Dynamic content in system message',
121
+ pattern: /role:\s*['"`]system['"`]\s*,\s*content:\s*`[^`]*\$\{/gi,
122
+ severity: 'medium',
123
+ description: 'System message content includes dynamic values. If user-controlled, this enables prompt injection.',
124
+ suggestedFix: 'Keep system messages static. Place user input in messages with role: "user" instead.',
125
+ checkDelimiters: true,
126
+ },
127
+ // f-strings in Python with user input
128
+ {
129
+ name: 'Python f-string prompt with user input',
130
+ pattern: /f['"][^'"]*\{.*(?:user|input|request|body).*\}[^'"]*['"]/gi,
131
+ severity: 'high',
132
+ description: 'User input in Python f-string prompt creates prompt injection risk.',
133
+ suggestedFix: 'Use explicit delimiters: f"System instructions...\n---\n{user_input}\n---"',
134
+ checkDelimiters: true,
135
+ },
136
+ ]
137
+
138
+ /**
139
+ * B3: Secrets in prompt context patterns
140
+ */
141
+ const SECRETS_IN_PROMPTS_PATTERNS: PromptHygienePattern[] = [
142
+ // API keys in message content
143
+ {
144
+ name: 'API key in prompt content',
145
+ pattern: /(?:messages|prompt|system|content)\s*[=:][^;]*(?:sk-[a-zA-Z0-9]{20,}|api[_-]?key\s*[:=]\s*['"][^'"]{16,}['"])/gi,
146
+ severity: 'critical',
147
+ description: 'API key appears to be hardcoded in prompt content. Keys in prompts may be logged, cached, or sent to model providers.',
148
+ suggestedFix: 'Never include API keys in prompts. Use environment variables and keep them server-side only.',
149
+ },
150
+ // AWS keys in prompts
151
+ {
152
+ name: 'AWS credentials in prompt',
153
+ pattern: /(?:messages|prompt|system|content)\s*[=:][^;]*(?:AKIA[A-Z0-9]{16}|aws[_-]?(?:secret|access)[_-]?key)/gi,
154
+ severity: 'critical',
155
+ description: 'AWS credentials detected in prompt content.',
156
+ suggestedFix: 'Remove credentials from prompts. Use IAM roles or environment variables instead.',
157
+ },
158
+ // Database URLs with credentials
159
+ {
160
+ name: 'Database credentials in prompt',
161
+ pattern: /(?:messages|prompt|system|content).*(?:mongodb|postgres|mysql|redis):\/\/[^:]+:[^@]+@/gi,
162
+ severity: 'critical',
163
+ description: 'Database connection string with credentials in prompt. This exposes database access.',
164
+ suggestedFix: 'Never include connection strings in prompts. Reference data by ID instead.',
165
+ },
166
+ // Passwords in prompt context
167
+ {
168
+ name: 'Password in prompt content',
169
+ pattern: /(?:messages|prompt|content)\s*[=:].*(?:password|passwd|pwd)\s*[:=]\s*['"`][^'"`]{8,}/gi,
170
+ severity: 'high',
171
+ description: 'Password appears in prompt content. This may be logged or exposed to model providers.',
172
+ suggestedFix: 'Remove passwords from prompts. Use authentication tokens or session references instead.',
173
+ },
174
+ // Private keys
175
+ {
176
+ name: 'Private key in prompt',
177
+ pattern: /(?:messages|prompt|content).*(?:-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----)/gi,
178
+ severity: 'critical',
179
+ description: 'Private key material detected in prompt context.',
180
+ suggestedFix: 'Never include private keys in prompts. Sign data server-side instead.',
181
+ },
182
+ // Generic token patterns
183
+ {
184
+ name: 'Access token in prompt',
185
+ pattern: /(?:messages|prompt|content)\s*[=:].*(?:access[_-]?token|auth[_-]?token|bearer)\s*[:=]\s*['"`][a-zA-Z0-9_.-]{20,}/gi,
186
+ severity: 'high',
187
+ description: 'Access token detected in prompt content. Tokens in prompts risk exposure.',
188
+ suggestedFix: 'Do not include tokens in prompts. Pass token context through secure server-side channels.',
189
+ },
190
+ ]
191
+
192
+ /**
193
+ * Missing boundary patterns - prompts without clear user/system separation
194
+ */
195
+ const MISSING_BOUNDARY_PATTERNS: PromptHygienePattern[] = [
196
+ // Direct concatenation without any markers
197
+ {
198
+ name: 'Missing prompt boundaries',
199
+ pattern: /(?:content|prompt)\s*[:=]\s*(?:systemInstructions?|instructions?)\s*\+\s*(?:userMessage|userInput|input)/gi,
200
+ severity: 'medium',
201
+ description: 'Prompt concatenates system instructions with user input without clear boundaries.',
202
+ suggestedFix: 'Add delimiters between instructions and user content: "Instructions...\n---\n" + userInput + "\n---"',
203
+ },
204
+ // Template literals building prompts without delimiters
205
+ {
206
+ name: 'Unbounded template prompt',
207
+ pattern: /`(?:You are|As an|Your task)[^`]{20,}\$\{(?!.*(?:```|<user|---|\[USER))/gi,
208
+ severity: 'medium',
209
+ description: 'Prompt template interpolates values without clear delimiter boundaries.',
210
+ suggestedFix: 'Wrap interpolated user content with delimiters: ```${userInput}```',
211
+ },
212
+ // M5: RAG-specific prompt injection patterns
213
+ {
214
+ name: 'Retrieved context in system prompt',
215
+ pattern: /role:\s*['"`]system['"`]\s*,\s*content:\s*`[^`]*\$\{.*(?:context|chunks|documents|retrieved|sources)/gi,
216
+ severity: 'high',
217
+ description: 'Retrieved documents injected into system prompt. Poisoned documents could hijack model behavior.',
218
+ suggestedFix: 'Place retrieved context in user messages with clear delimiters. Use structured prompts separating instructions from data.',
219
+ checkDelimiters: true,
220
+ },
221
+ {
222
+ name: 'Mixed user input and retrieved context',
223
+ pattern: /\$\{.*(?:userInput|query|question).*\}[^`]*\$\{.*(?:context|chunks|documents).*\}|\$\{.*(?:context|chunks|documents).*\}[^`]*\$\{.*(?:userInput|query|question).*\}/gi,
224
+ severity: 'medium',
225
+ description: 'User input and retrieved context concatenated without clear separation. Both could contain injection attempts.',
226
+ suggestedFix: 'Clearly separate user input from retrieved context using XML tags or delimiters: <user_query>...</user_query><context>...</context>',
227
+ checkDelimiters: true,
228
+ },
229
+ {
230
+ name: 'RAG context directly interpolated',
231
+ pattern: /(?:system|prompt)\s*[:=].*(?:retrievedContext|ragContext|documentContext|knowledgeBase)\s*(?:\+|,)/gi,
232
+ severity: 'medium',
233
+ description: 'RAG context directly concatenated into prompt. Could enable data poisoning attacks.',
234
+ suggestedFix: 'Use structured prompt format with clear boundaries between instructions, context, and user input.',
235
+ checkDelimiters: true,
236
+ },
237
+ ]
238
+
239
+ // ============================================================================
240
+ // Detection Functions
241
+ // ============================================================================
242
+
243
+ /**
244
+ * Get surrounding context lines for analysis
245
+ */
246
+ function getSurroundingContext(content: string, lineIndex: number, windowSize: number = 10): string[] {
247
+ const lines = content.split('\n')
248
+ const start = Math.max(0, lineIndex - windowSize)
249
+ const end = Math.min(lines.length, lineIndex + windowSize)
250
+ return lines.slice(start, end)
251
+ }
252
+
253
+ /**
254
+ * Main detection function for AI prompt hygiene issues
255
+ */
256
+ export function detectAIPromptHygiene(
257
+ content: string,
258
+ filePath: string
259
+ ): Vulnerability[] {
260
+ const vulnerabilities: Vulnerability[] = []
261
+
262
+ // Skip non-applicable files
263
+ if (isScannerOrFixtureFile(filePath)) return vulnerabilities
264
+ if (isDocumentationFile(filePath)) return vulnerabilities
265
+
266
+ // Only scan files that appear to be in LLM context
267
+ if (!isLLMContextFile(filePath, content)) {
268
+ return vulnerabilities
269
+ }
270
+
271
+ const lines = content.split('\n')
272
+ const isTestFile = isTestOrMockFile(filePath)
273
+
274
+ // Scan for unsafe interpolation patterns (B1)
275
+ for (const pattern of UNSAFE_INTERPOLATION_PATTERNS) {
276
+ const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
277
+ let match
278
+
279
+ while ((match = regex.exec(content)) !== null) {
280
+ const lineNumber = content.substring(0, match.index).split('\n').length
281
+ const lineContent = lines[lineNumber - 1]?.trim() || ''
282
+
283
+ // Skip comments
284
+ if (isComment(lineContent)) continue
285
+
286
+ // Skip if properly parameterized
287
+ if (isProperlyParameterized(lineContent)) continue
288
+
289
+ // Check for delimiters if applicable
290
+ let severity = pattern.severity
291
+ let description = pattern.description
292
+ const contextLines = getSurroundingContext(content, lineNumber - 1, 15)
293
+
294
+ if (pattern.checkDelimiters && hasPromptDelimiters(lineContent, contextLines)) {
295
+ // Delimiters present - downgrade severity
296
+ severity = 'info'
297
+ description += ' (Note: Delimiters detected in context, which mitigates this risk.)'
298
+ }
299
+
300
+ // Downgrade test files
301
+ if (isTestFile) {
302
+ severity = 'info'
303
+ description += ' (in test file)'
304
+ }
305
+
306
+ vulnerabilities.push({
307
+ id: `ai-prompt-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
308
+ filePath,
309
+ lineNumber,
310
+ lineContent,
311
+ severity,
312
+ category: 'ai_prompt_injection',
313
+ title: pattern.name,
314
+ description,
315
+ suggestedFix: pattern.suggestedFix,
316
+ confidence: severity === 'info' ? 'low' : 'medium',
317
+ layer: 2,
318
+ requiresAIValidation: severity !== 'info',
319
+ })
320
+ }
321
+ }
322
+
323
+ // Scan for secrets in prompts (B3)
324
+ for (const pattern of SECRETS_IN_PROMPTS_PATTERNS) {
325
+ const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
326
+ let match
327
+
328
+ while ((match = regex.exec(content)) !== null) {
329
+ const lineNumber = content.substring(0, match.index).split('\n').length
330
+ const lineContent = lines[lineNumber - 1]?.trim() || ''
331
+
332
+ // Skip comments
333
+ if (isComment(lineContent)) continue
334
+
335
+ // Check if it's an env var reference (safe pattern)
336
+ const isEnvRef = /process\.env|import\.meta\.env|os\.environ|getenv/i.test(lineContent)
337
+ if (isEnvRef) continue
338
+
339
+ let severity = pattern.severity
340
+ let description = pattern.description
341
+
342
+ // Downgrade test files but still flag
343
+ if (isTestFile) {
344
+ severity = severity === 'critical' ? 'medium' : 'low'
345
+ description += ' (in test file - still review for accidental commits)'
346
+ }
347
+
348
+ vulnerabilities.push({
349
+ id: `ai-secret-prompt-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
350
+ filePath,
351
+ lineNumber,
352
+ lineContent,
353
+ severity,
354
+ category: 'hardcoded_secret', // Use existing category for consistency
355
+ title: pattern.name + ' (in LLM context)',
356
+ description: description + ' Secrets in prompts are especially risky as they may be logged, shared, or sent to external AI providers.',
357
+ suggestedFix: pattern.suggestedFix,
358
+ confidence: 'high',
359
+ layer: 2,
360
+ requiresAIValidation: false, // Secrets don't need AI validation - they're definitive
361
+ })
362
+ }
363
+ }
364
+
365
+ // Scan for missing boundary patterns (B1 continued)
366
+ for (const pattern of MISSING_BOUNDARY_PATTERNS) {
367
+ const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
368
+ let match
369
+
370
+ while ((match = regex.exec(content)) !== null) {
371
+ const lineNumber = content.substring(0, match.index).split('\n').length
372
+ const lineContent = lines[lineNumber - 1]?.trim() || ''
373
+
374
+ // Skip comments
375
+ if (isComment(lineContent)) continue
376
+
377
+ const contextLines = getSurroundingContext(content, lineNumber - 1, 10)
378
+
379
+ // Skip if delimiters are present
380
+ if (hasPromptDelimiters(lineContent, contextLines)) continue
381
+
382
+ let severity = pattern.severity
383
+ let description = pattern.description
384
+
385
+ if (isTestFile) {
386
+ severity = 'info'
387
+ description += ' (in test file)'
388
+ }
389
+
390
+ vulnerabilities.push({
391
+ id: `ai-boundary-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
392
+ filePath,
393
+ lineNumber,
394
+ lineContent,
395
+ severity,
396
+ category: 'ai_prompt_injection',
397
+ title: pattern.name,
398
+ description,
399
+ suggestedFix: pattern.suggestedFix,
400
+ confidence: 'medium',
401
+ layer: 2,
402
+ requiresAIValidation: true,
403
+ })
404
+ }
405
+ }
406
+
407
+ return vulnerabilities
408
+ }
409
+
410
+ // Export helper for use in other modules
411
+ export { isLLMContextFile }