@oculum/scanner 1.0.11 → 1.0.12

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 (309) hide show
  1. package/dist/ai-context/index.d.ts +6 -0
  2. package/dist/ai-context/index.d.ts.map +1 -0
  3. package/dist/ai-context/index.js +13 -0
  4. package/dist/ai-context/index.js.map +1 -0
  5. package/dist/ai-context/manager.d.ts +67 -0
  6. package/dist/ai-context/manager.d.ts.map +1 -0
  7. package/dist/ai-context/manager.js +104 -0
  8. package/dist/ai-context/manager.js.map +1 -0
  9. package/dist/category-filter.d.ts +125 -0
  10. package/dist/category-filter.d.ts.map +1 -0
  11. package/dist/category-filter.js +360 -0
  12. package/dist/category-filter.js.map +1 -0
  13. package/dist/filtering/context-adjustments.d.ts +23 -0
  14. package/dist/filtering/context-adjustments.d.ts.map +1 -0
  15. package/dist/filtering/context-adjustments.js +100 -0
  16. package/dist/filtering/context-adjustments.js.map +1 -0
  17. package/dist/filtering/index.d.ts +3 -0
  18. package/dist/filtering/index.d.ts.map +1 -0
  19. package/dist/filtering/index.js +8 -0
  20. package/dist/filtering/index.js.map +1 -0
  21. package/dist/filtering/pipeline.d.ts +48 -0
  22. package/dist/filtering/pipeline.d.ts.map +1 -0
  23. package/dist/filtering/pipeline.js +76 -0
  24. package/dist/filtering/pipeline.js.map +1 -0
  25. package/dist/formatters/ai-context.d.ts +23 -0
  26. package/dist/formatters/ai-context.d.ts.map +1 -0
  27. package/dist/formatters/ai-context.js +238 -0
  28. package/dist/formatters/ai-context.js.map +1 -0
  29. package/dist/formatters/github-comment.d.ts +1 -1
  30. package/dist/formatters/github-comment.d.ts.map +1 -1
  31. package/dist/formatters/github-comment.js +2 -2
  32. package/dist/formatters/github-comment.js.map +1 -1
  33. package/dist/formatters/ide/claude-code.d.ts +17 -0
  34. package/dist/formatters/ide/claude-code.d.ts.map +1 -0
  35. package/dist/formatters/ide/claude-code.js +94 -0
  36. package/dist/formatters/ide/claude-code.js.map +1 -0
  37. package/dist/formatters/ide/cursor.d.ts +13 -0
  38. package/dist/formatters/ide/cursor.d.ts.map +1 -0
  39. package/dist/formatters/ide/cursor.js +125 -0
  40. package/dist/formatters/ide/cursor.js.map +1 -0
  41. package/dist/formatters/ide/index.d.ts +62 -0
  42. package/dist/formatters/ide/index.d.ts.map +1 -0
  43. package/dist/formatters/ide/index.js +184 -0
  44. package/dist/formatters/ide/index.js.map +1 -0
  45. package/dist/formatters/ide/windsurf.d.ts +13 -0
  46. package/dist/formatters/ide/windsurf.d.ts.map +1 -0
  47. package/dist/formatters/ide/windsurf.js +117 -0
  48. package/dist/formatters/ide/windsurf.js.map +1 -0
  49. package/dist/formatters/index.d.ts +2 -0
  50. package/dist/formatters/index.d.ts.map +1 -1
  51. package/dist/formatters/index.js +17 -1
  52. package/dist/formatters/index.js.map +1 -1
  53. package/dist/index.d.ts +4 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +272 -44
  56. package/dist/index.js.map +1 -1
  57. package/dist/layer1/comments.d.ts +4 -1
  58. package/dist/layer1/comments.d.ts.map +1 -1
  59. package/dist/layer1/comments.js +1 -1
  60. package/dist/layer1/comments.js.map +1 -1
  61. package/dist/layer1/config-audit.d.ts +4 -1
  62. package/dist/layer1/config-audit.d.ts.map +1 -1
  63. package/dist/layer1/config-audit.js +45 -11
  64. package/dist/layer1/config-audit.js.map +1 -1
  65. package/dist/layer1/config-mcp-audit.d.ts +4 -1
  66. package/dist/layer1/config-mcp-audit.d.ts.map +1 -1
  67. package/dist/layer1/config-mcp-audit.js +2 -2
  68. package/dist/layer1/config-mcp-audit.js.map +1 -1
  69. package/dist/layer1/entropy.d.ts +4 -1
  70. package/dist/layer1/entropy.d.ts.map +1 -1
  71. package/dist/layer1/entropy.js +212 -1
  72. package/dist/layer1/entropy.js.map +1 -1
  73. package/dist/layer1/file-flags.d.ts +4 -1
  74. package/dist/layer1/file-flags.d.ts.map +1 -1
  75. package/dist/layer1/file-flags.js +12 -5
  76. package/dist/layer1/file-flags.js.map +1 -1
  77. package/dist/layer1/index.d.ts.map +1 -1
  78. package/dist/layer1/index.js +14 -19
  79. package/dist/layer1/index.js.map +1 -1
  80. package/dist/layer1/patterns.d.ts +4 -1
  81. package/dist/layer1/patterns.d.ts.map +1 -1
  82. package/dist/layer1/patterns.js +34 -4
  83. package/dist/layer1/patterns.js.map +1 -1
  84. package/dist/layer1/urls.d.ts +4 -1
  85. package/dist/layer1/urls.d.ts.map +1 -1
  86. package/dist/layer1/urls.js +162 -14
  87. package/dist/layer1/urls.js.map +1 -1
  88. package/dist/layer1/weak-crypto.d.ts +4 -1
  89. package/dist/layer1/weak-crypto.d.ts.map +1 -1
  90. package/dist/layer1/weak-crypto.js +144 -7
  91. package/dist/layer1/weak-crypto.js.map +1 -1
  92. package/dist/layer2/ai-agent-tools.d.ts +4 -1
  93. package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
  94. package/dist/layer2/ai-agent-tools.js +661 -2
  95. package/dist/layer2/ai-agent-tools.js.map +1 -1
  96. package/dist/layer2/ai-endpoint-protection.d.ts +2 -0
  97. package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
  98. package/dist/layer2/ai-endpoint-protection.js +1 -1
  99. package/dist/layer2/ai-endpoint-protection.js.map +1 -1
  100. package/dist/layer2/ai-execution-sinks.d.ts +4 -1
  101. package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
  102. package/dist/layer2/ai-execution-sinks.js +252 -43
  103. package/dist/layer2/ai-execution-sinks.js.map +1 -1
  104. package/dist/layer2/ai-fingerprinting.d.ts +4 -1
  105. package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
  106. package/dist/layer2/ai-fingerprinting.js +25 -32
  107. package/dist/layer2/ai-fingerprinting.js.map +1 -1
  108. package/dist/layer2/ai-mcp-security.d.ts +4 -1
  109. package/dist/layer2/ai-mcp-security.d.ts.map +1 -1
  110. package/dist/layer2/ai-mcp-security.js +200 -2
  111. package/dist/layer2/ai-mcp-security.js.map +1 -1
  112. package/dist/layer2/ai-package-hallucination.d.ts +4 -1
  113. package/dist/layer2/ai-package-hallucination.d.ts.map +1 -1
  114. package/dist/layer2/ai-package-hallucination.js +136 -4
  115. package/dist/layer2/ai-package-hallucination.js.map +1 -1
  116. package/dist/layer2/ai-prompt-hygiene.d.ts +4 -1
  117. package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
  118. package/dist/layer2/ai-prompt-hygiene.js +342 -28
  119. package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
  120. package/dist/layer2/ai-rag-safety.d.ts +4 -1
  121. package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
  122. package/dist/layer2/ai-rag-safety.js +82 -2
  123. package/dist/layer2/ai-rag-safety.js.map +1 -1
  124. package/dist/layer2/ai-schema-validation.d.ts +4 -1
  125. package/dist/layer2/ai-schema-validation.d.ts.map +1 -1
  126. package/dist/layer2/ai-schema-validation.js +2 -2
  127. package/dist/layer2/ai-schema-validation.js.map +1 -1
  128. package/dist/layer2/auth-antipatterns.d.ts +2 -0
  129. package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
  130. package/dist/layer2/auth-antipatterns.js +205 -20
  131. package/dist/layer2/auth-antipatterns.js.map +1 -1
  132. package/dist/layer2/byok-patterns.d.ts +4 -1
  133. package/dist/layer2/byok-patterns.d.ts.map +1 -1
  134. package/dist/layer2/byok-patterns.js +2 -2
  135. package/dist/layer2/byok-patterns.js.map +1 -1
  136. package/dist/layer2/dangerous-functions/dom-xss.d.ts +9 -4
  137. package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -1
  138. package/dist/layer2/dangerous-functions/dom-xss.js +73 -22
  139. package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -1
  140. package/dist/layer2/dangerous-functions/index.d.ts +4 -1
  141. package/dist/layer2/dangerous-functions/index.d.ts.map +1 -1
  142. package/dist/layer2/dangerous-functions/index.js +551 -20
  143. package/dist/layer2/dangerous-functions/index.js.map +1 -1
  144. package/dist/layer2/dangerous-functions/math-random.d.ts +54 -4
  145. package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -1
  146. package/dist/layer2/dangerous-functions/math-random.js +241 -16
  147. package/dist/layer2/dangerous-functions/math-random.js.map +1 -1
  148. package/dist/layer2/dangerous-functions/patterns.d.ts.map +1 -1
  149. package/dist/layer2/dangerous-functions/patterns.js +3 -1
  150. package/dist/layer2/dangerous-functions/patterns.js.map +1 -1
  151. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +3 -2
  152. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -1
  153. package/dist/layer2/dangerous-functions/utils/control-flow.js +41 -120
  154. package/dist/layer2/dangerous-functions/utils/control-flow.js.map +1 -1
  155. package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +1 -1
  156. package/dist/layer2/dangerous-functions/utils/helpers.js +26 -3
  157. package/dist/layer2/dangerous-functions/utils/helpers.js.map +1 -1
  158. package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +1 -1
  159. package/dist/layer2/dangerous-functions/utils/schema-validation.js +14 -1
  160. package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -1
  161. package/dist/layer2/data-exposure.d.ts +4 -1
  162. package/dist/layer2/data-exposure.d.ts.map +1 -1
  163. package/dist/layer2/data-exposure.js +11 -38
  164. package/dist/layer2/data-exposure.js.map +1 -1
  165. package/dist/layer2/framework-checks.d.ts +4 -1
  166. package/dist/layer2/framework-checks.d.ts.map +1 -1
  167. package/dist/layer2/framework-checks.js +2 -2
  168. package/dist/layer2/framework-checks.js.map +1 -1
  169. package/dist/layer2/index.d.ts +9 -1
  170. package/dist/layer2/index.d.ts.map +1 -1
  171. package/dist/layer2/index.js +57 -51
  172. package/dist/layer2/index.js.map +1 -1
  173. package/dist/layer2/logic-gates.d.ts +4 -1
  174. package/dist/layer2/logic-gates.d.ts.map +1 -1
  175. package/dist/layer2/logic-gates.js +54 -20
  176. package/dist/layer2/logic-gates.js.map +1 -1
  177. package/dist/layer2/model-supply-chain.d.ts +4 -1
  178. package/dist/layer2/model-supply-chain.d.ts.map +1 -1
  179. package/dist/layer2/model-supply-chain.js +72 -4
  180. package/dist/layer2/model-supply-chain.js.map +1 -1
  181. package/dist/layer2/risky-imports.d.ts +4 -1
  182. package/dist/layer2/risky-imports.d.ts.map +1 -1
  183. package/dist/layer2/risky-imports.js +2 -2
  184. package/dist/layer2/risky-imports.js.map +1 -1
  185. package/dist/layer2/variables.d.ts +4 -1
  186. package/dist/layer2/variables.d.ts.map +1 -1
  187. package/dist/layer2/variables.js +2 -2
  188. package/dist/layer2/variables.js.map +1 -1
  189. package/dist/layer3/anthropic/auto-dismiss.d.ts.map +1 -1
  190. package/dist/layer3/anthropic/auto-dismiss.js +11 -0
  191. package/dist/layer3/anthropic/auto-dismiss.js.map +1 -1
  192. package/dist/modes/incremental.js +1 -1
  193. package/dist/tiers.d.ts +2 -2
  194. package/dist/tiers.d.ts.map +1 -1
  195. package/dist/tiers.js +7 -7
  196. package/dist/tiers.js.map +1 -1
  197. package/dist/types.d.ts +78 -8
  198. package/dist/types.d.ts.map +1 -1
  199. package/dist/types.js +34 -0
  200. package/dist/types.js.map +1 -1
  201. package/dist/utils/code-analysis.d.ts +39 -0
  202. package/dist/utils/code-analysis.d.ts.map +1 -0
  203. package/dist/utils/code-analysis.js +159 -0
  204. package/dist/utils/code-analysis.js.map +1 -0
  205. package/dist/utils/comment-analyzer.d.ts +38 -0
  206. package/dist/utils/comment-analyzer.d.ts.map +1 -0
  207. package/dist/utils/comment-analyzer.js +218 -0
  208. package/dist/utils/comment-analyzer.js.map +1 -0
  209. package/dist/utils/context-helpers.d.ts +108 -1
  210. package/dist/utils/context-helpers.d.ts.map +1 -1
  211. package/dist/utils/context-helpers.js +351 -2
  212. package/dist/utils/context-helpers.js.map +1 -1
  213. package/dist/utils/environment-context.d.ts +76 -0
  214. package/dist/utils/environment-context.d.ts.map +1 -0
  215. package/dist/utils/environment-context.js +271 -0
  216. package/dist/utils/environment-context.js.map +1 -0
  217. package/dist/utils/intent-detector.d.ts +66 -0
  218. package/dist/utils/intent-detector.d.ts.map +1 -0
  219. package/dist/utils/intent-detector.js +282 -0
  220. package/dist/utils/intent-detector.js.map +1 -0
  221. package/dist/utils/parsed-file.d.ts +51 -0
  222. package/dist/utils/parsed-file.d.ts.map +1 -0
  223. package/dist/utils/parsed-file.js +95 -0
  224. package/dist/utils/parsed-file.js.map +1 -0
  225. package/dist/utils/route-hierarchy.d.ts +50 -0
  226. package/dist/utils/route-hierarchy.d.ts.map +1 -0
  227. package/dist/utils/route-hierarchy.js +226 -0
  228. package/dist/utils/route-hierarchy.js.map +1 -0
  229. package/dist/utils/schema-semantics.d.ts +45 -0
  230. package/dist/utils/schema-semantics.d.ts.map +1 -0
  231. package/dist/utils/schema-semantics.js +193 -0
  232. package/dist/utils/schema-semantics.js.map +1 -0
  233. package/package.json +1 -1
  234. package/src/__tests__/benchmark/fixtures/layer2/index.ts +12 -0
  235. package/src/__tests__/benchmark/fixtures/layer2/phase5-excessive-agency.ts +580 -0
  236. package/src/__tests__/benchmark/fixtures/layer2/sprint6-ai-enhancements.ts +515 -0
  237. package/src/__tests__/benchmark/run-depth-validation.ts +9 -9
  238. package/src/__tests__/category-filter.test.ts +478 -0
  239. package/src/__tests__/regression/known-false-positives.test.ts +490 -0
  240. package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +18 -14
  241. package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +0 -9
  242. package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +1 -1
  243. package/src/__tests__/validation/run-validation.ts +7 -7
  244. package/src/ai-context/__tests__/manager.test.ts +193 -0
  245. package/src/ai-context/index.ts +15 -0
  246. package/src/ai-context/manager.ts +145 -0
  247. package/src/baseline/__tests__/manager.test.ts +2 -2
  248. package/src/category-filter.ts +400 -0
  249. package/src/filtering/__tests__/pipeline.test.ts +134 -0
  250. package/src/filtering/context-adjustments.ts +111 -0
  251. package/src/filtering/index.ts +10 -0
  252. package/src/filtering/pipeline.ts +130 -0
  253. package/src/formatters/__tests__/ai-context.test.ts +254 -0
  254. package/src/formatters/ai-context.ts +302 -0
  255. package/src/formatters/github-comment.ts +3 -3
  256. package/src/formatters/ide/__tests__/ide.test.ts +319 -0
  257. package/src/formatters/ide/claude-code.ts +110 -0
  258. package/src/formatters/ide/cursor.ts +147 -0
  259. package/src/formatters/ide/index.ts +216 -0
  260. package/src/formatters/ide/windsurf.ts +135 -0
  261. package/src/formatters/index.ts +24 -0
  262. package/src/index.ts +312 -34
  263. package/src/layer1/comments.ts +3 -1
  264. package/src/layer1/config-audit.ts +50 -11
  265. package/src/layer1/config-mcp-audit.ts +4 -2
  266. package/src/layer1/entropy.ts +234 -1
  267. package/src/layer1/file-flags.ts +17 -6
  268. package/src/layer1/index.ts +14 -18
  269. package/src/layer1/patterns.ts +42 -4
  270. package/src/layer1/urls.ts +188 -14
  271. package/src/layer1/weak-crypto.ts +168 -16
  272. package/src/layer2/ai-agent-tools.ts +707 -2
  273. package/src/layer2/ai-endpoint-protection.ts +3 -1
  274. package/src/layer2/ai-execution-sinks.ts +265 -43
  275. package/src/layer2/ai-fingerprinting.ts +28 -32
  276. package/src/layer2/ai-mcp-security.ts +206 -3
  277. package/src/layer2/ai-package-hallucination.ts +153 -4
  278. package/src/layer2/ai-prompt-hygiene.ts +369 -26
  279. package/src/layer2/ai-rag-safety.ts +85 -2
  280. package/src/layer2/ai-schema-validation.ts +4 -2
  281. package/src/layer2/auth-antipatterns.ts +230 -20
  282. package/src/layer2/byok-patterns.ts +4 -2
  283. package/src/layer2/dangerous-functions/dom-xss.ts +94 -22
  284. package/src/layer2/dangerous-functions/index.ts +635 -51
  285. package/src/layer2/dangerous-functions/math-random.ts +268 -16
  286. package/src/layer2/dangerous-functions/patterns.ts +3 -1
  287. package/src/layer2/dangerous-functions/utils/control-flow.ts +8 -135
  288. package/src/layer2/dangerous-functions/utils/schema-validation.ts +16 -1
  289. package/src/layer2/data-exposure.ts +13 -38
  290. package/src/layer2/framework-checks.ts +4 -2
  291. package/src/layer2/index.ts +69 -50
  292. package/src/layer2/logic-gates.ts +59 -22
  293. package/src/layer2/model-supply-chain.ts +79 -4
  294. package/src/layer2/risky-imports.ts +4 -2
  295. package/src/layer2/variables.ts +4 -2
  296. package/src/layer3/anthropic/auto-dismiss.ts +11 -0
  297. package/src/modes/incremental.ts +1 -1
  298. package/src/tiers.ts +9 -9
  299. package/src/types.ts +122 -8
  300. package/src/utils/__tests__/code-analysis.test.ts +165 -0
  301. package/src/utils/__tests__/parsed-file.test.ts +124 -0
  302. package/src/utils/code-analysis.ts +179 -0
  303. package/src/utils/comment-analyzer.ts +249 -0
  304. package/src/utils/context-helpers.ts +408 -2
  305. package/src/utils/environment-context.ts +304 -0
  306. package/src/utils/intent-detector.ts +318 -0
  307. package/src/utils/parsed-file.ts +103 -0
  308. package/src/utils/route-hierarchy.ts +250 -0
  309. package/src/utils/schema-semantics.ts +233 -0
@@ -0,0 +1,319 @@
1
+ /**
2
+ * IDE Integration Tests
3
+ */
4
+
5
+ import { existsSync, mkdirSync, writeFileSync, rmSync } from 'fs'
6
+ import { join } from 'path'
7
+ import { tmpdir } from 'os'
8
+ import {
9
+ formatCursorRules,
10
+ formatWindsurfRules,
11
+ formatClaudeCodeSection,
12
+ detectIDEConfigs,
13
+ writeIDEFile,
14
+ updateClaudeMdSection,
15
+ clearIDEFiles,
16
+ } from '../index'
17
+ import type { ScanResult, Vulnerability } from '../../../types'
18
+
19
+ const createMockVulnerability = (overrides: Partial<Vulnerability> = {}): Vulnerability => ({
20
+ id: 'test-vuln-1',
21
+ filePath: 'src/api/users.ts',
22
+ lineNumber: 42,
23
+ lineContent: 'const query = `SELECT * FROM users WHERE id = ${userId}`',
24
+ severity: 'high',
25
+ category: 'sql_injection',
26
+ title: 'SQL Injection Vulnerability',
27
+ description: 'User input is directly concatenated into SQL query without parameterization.',
28
+ suggestedFix: 'Use parameterized queries instead of string concatenation.',
29
+ confidence: 'high',
30
+ layer: 2,
31
+ fixSteps: [
32
+ 'Replace string concatenation with parameterized query',
33
+ 'Use prepared statements or an ORM',
34
+ ],
35
+ ...overrides,
36
+ })
37
+
38
+ const createMockScanResult = (vulnerabilities: Vulnerability[] = []): ScanResult => ({
39
+ repoName: 'test-repo',
40
+ repoUrl: 'https://github.com/test/test-repo',
41
+ branch: 'main',
42
+ filesScanned: 50,
43
+ filesSkipped: 5,
44
+ vulnerabilities,
45
+ severityCounts: {
46
+ critical: vulnerabilities.filter(v => v.severity === 'critical').length,
47
+ high: vulnerabilities.filter(v => v.severity === 'high').length,
48
+ medium: vulnerabilities.filter(v => v.severity === 'medium').length,
49
+ low: vulnerabilities.filter(v => v.severity === 'low').length,
50
+ info: vulnerabilities.filter(v => v.severity === 'info').length,
51
+ },
52
+ categoryCounts: {},
53
+ hasBlockingIssues: vulnerabilities.some(v => v.severity === 'critical' || v.severity === 'high'),
54
+ scanDuration: 1500,
55
+ timestamp: '2024-01-15T10:30:00.000Z',
56
+ })
57
+
58
+ describe('formatCursorRules', () => {
59
+ it('should generate MDC format with frontmatter', () => {
60
+ const vuln = createMockVulnerability()
61
+ const result = createMockScanResult([vuln])
62
+
63
+ const output = formatCursorRules(result)
64
+
65
+ // Check frontmatter
66
+ expect(output).toContain('---')
67
+ expect(output).toContain('description: Oculum Security Findings')
68
+ expect(output).toContain('alwaysApply: false')
69
+ expect(output).toContain('---')
70
+ })
71
+
72
+ it('should include glob patterns for affected files', () => {
73
+ const vulns = [
74
+ createMockVulnerability({ filePath: 'src/api/users.ts' }),
75
+ createMockVulnerability({ id: 'v2', filePath: 'src/api/orders.ts' }),
76
+ createMockVulnerability({ id: 'v3', filePath: 'src/config/db.ts' }),
77
+ ]
78
+ const result = createMockScanResult(vulns)
79
+
80
+ const output = formatCursorRules(result)
81
+
82
+ // Should include globs for affected directories
83
+ expect(output).toContain('globs:')
84
+ expect(output).toContain('src/api/**')
85
+ expect(output).toContain('src/config/**')
86
+ })
87
+
88
+ it('should include security findings', () => {
89
+ const vuln = createMockVulnerability()
90
+ const result = createMockScanResult([vuln])
91
+
92
+ const output = formatCursorRules(result)
93
+
94
+ expect(output).toContain('SQL Injection Vulnerability')
95
+ expect(output).toContain('src/api/users.ts:42')
96
+ })
97
+
98
+ it('should include fix instructions', () => {
99
+ const vuln = createMockVulnerability()
100
+ const result = createMockScanResult([vuln])
101
+
102
+ const output = formatCursorRules(result)
103
+
104
+ expect(output).toContain('DO NOT')
105
+ // The fix shows example code with '?' instead of the word 'parameterized'
106
+ expect(output).toContain('GOOD')
107
+ expect(output).toContain("'SELECT * FROM users WHERE id = ?'")
108
+ })
109
+
110
+ it('should return minimal output for zero findings', () => {
111
+ const result = createMockScanResult([])
112
+
113
+ const output = formatCursorRules(result)
114
+
115
+ expect(output).toContain('description: Oculum Security Findings')
116
+ expect(output).toContain('No security issues found')
117
+ })
118
+ })
119
+
120
+ describe('formatWindsurfRules', () => {
121
+ it('should generate markdown security rules', () => {
122
+ const vuln = createMockVulnerability()
123
+ const result = createMockScanResult([vuln])
124
+
125
+ const output = formatWindsurfRules(result)
126
+
127
+ expect(output).toContain('# Oculum Security Rules')
128
+ expect(output).toContain('## Security Issues')
129
+ })
130
+
131
+ it('should list findings by file', () => {
132
+ const vulns = [
133
+ createMockVulnerability({ filePath: 'src/api/users.ts' }),
134
+ createMockVulnerability({ id: 'v2', filePath: 'src/api/orders.ts' }),
135
+ ]
136
+ const result = createMockScanResult(vulns)
137
+
138
+ const output = formatWindsurfRules(result)
139
+
140
+ expect(output).toContain('src/api/users.ts')
141
+ expect(output).toContain('src/api/orders.ts')
142
+ expect(output).toContain('Line 42')
143
+ })
144
+
145
+ it('should include code patterns section', () => {
146
+ const vuln = createMockVulnerability()
147
+ const result = createMockScanResult([vuln])
148
+
149
+ const output = formatWindsurfRules(result)
150
+
151
+ expect(output).toContain('## Code Patterns')
152
+ expect(output).toContain('ALWAYS')
153
+ })
154
+ })
155
+
156
+ describe('formatClaudeCodeSection', () => {
157
+ it('should return section with markers', () => {
158
+ const vuln = createMockVulnerability()
159
+ const result = createMockScanResult([vuln])
160
+
161
+ const output = formatClaudeCodeSection(result)
162
+
163
+ expect(output).toContain('<!-- OCULUM_SECURITY_START -->')
164
+ expect(output).toContain('<!-- OCULUM_SECURITY_END -->')
165
+ })
166
+
167
+ it('should include security issues', () => {
168
+ const vuln = createMockVulnerability()
169
+ const result = createMockScanResult([vuln])
170
+
171
+ const output = formatClaudeCodeSection(result)
172
+
173
+ expect(output).toContain('## Security Issues')
174
+ expect(output).toContain('SQL Injection')
175
+ expect(output).toContain('src/api/users.ts:42')
176
+ })
177
+
178
+ it('should include verification instructions', () => {
179
+ const vuln = createMockVulnerability()
180
+ const result = createMockScanResult([vuln])
181
+
182
+ const output = formatClaudeCodeSection(result)
183
+
184
+ expect(output).toContain('oculum scan')
185
+ })
186
+ })
187
+
188
+ describe('detectIDEConfigs', () => {
189
+ const testDir = join(tmpdir(), 'oculum-ide-detect-test-' + Date.now())
190
+
191
+ beforeAll(() => {
192
+ mkdirSync(testDir, { recursive: true })
193
+ })
194
+
195
+ afterAll(() => {
196
+ try {
197
+ rmSync(testDir, { recursive: true, force: true })
198
+ } catch {
199
+ // Ignore cleanup errors
200
+ }
201
+ })
202
+
203
+ it('should detect .cursor directory', () => {
204
+ // Create .cursor directory
205
+ mkdirSync(join(testDir, '.cursor'), { recursive: true })
206
+
207
+ const detected = detectIDEConfigs(testDir)
208
+
209
+ expect(detected).toContain('cursor')
210
+ })
211
+
212
+ it('should detect CLAUDE.md file', () => {
213
+ // Create CLAUDE.md file
214
+ writeFileSync(join(testDir, 'CLAUDE.md'), '# Claude Code\n')
215
+
216
+ const detected = detectIDEConfigs(testDir)
217
+
218
+ expect(detected).toContain('claude-code')
219
+ })
220
+
221
+ it('should return array of detected IDEs', () => {
222
+ const detected = detectIDEConfigs(testDir)
223
+
224
+ expect(Array.isArray(detected)).toBe(true)
225
+ })
226
+ })
227
+
228
+ describe('IDE file operations', () => {
229
+ const testDir = join(tmpdir(), 'oculum-ide-ops-test-' + Date.now())
230
+
231
+ beforeAll(() => {
232
+ mkdirSync(testDir, { recursive: true })
233
+ })
234
+
235
+ afterAll(() => {
236
+ try {
237
+ rmSync(testDir, { recursive: true, force: true })
238
+ } catch {
239
+ // Ignore cleanup errors
240
+ }
241
+ })
242
+
243
+ it('should write IDE file and create directories', () => {
244
+ const result = writeIDEFile(
245
+ testDir,
246
+ '.cursor/rules/security.mdc',
247
+ '# Security Rules'
248
+ )
249
+
250
+ expect(result.success).toBe(true)
251
+ expect(existsSync(join(testDir, '.cursor/rules/security.mdc'))).toBe(true)
252
+ })
253
+
254
+ it('should update CLAUDE.md section between markers', () => {
255
+ // Create initial CLAUDE.md
256
+ const initialContent = `# Project
257
+
258
+ Some existing content.
259
+
260
+ <!-- OCULUM_SECURITY_START -->
261
+ Old security content
262
+ <!-- OCULUM_SECURITY_END -->
263
+
264
+ More content.
265
+ `
266
+ writeFileSync(join(testDir, 'CLAUDE.md'), initialContent)
267
+
268
+ // Update section
269
+ const result = updateClaudeMdSection(
270
+ testDir,
271
+ '<!-- OCULUM_SECURITY_START -->\nNew security content\n<!-- OCULUM_SECURITY_END -->'
272
+ )
273
+
274
+ expect(result.success).toBe(true)
275
+
276
+ // Read and verify
277
+ const { readFileSync } = require('fs')
278
+ const updated = readFileSync(join(testDir, 'CLAUDE.md'), 'utf-8')
279
+
280
+ expect(updated).toContain('New security content')
281
+ expect(updated).not.toContain('Old security content')
282
+ expect(updated).toContain('Some existing content')
283
+ expect(updated).toContain('More content')
284
+ })
285
+
286
+ it('should append section if no markers exist', () => {
287
+ // Create CLAUDE.md without markers
288
+ const noMarkersDir = join(testDir, 'no-markers')
289
+ mkdirSync(noMarkersDir, { recursive: true })
290
+ writeFileSync(join(noMarkersDir, 'CLAUDE.md'), '# Project\n\nSome content.\n')
291
+
292
+ const result = updateClaudeMdSection(
293
+ noMarkersDir,
294
+ '<!-- OCULUM_SECURITY_START -->\nSecurity content\n<!-- OCULUM_SECURITY_END -->'
295
+ )
296
+
297
+ expect(result.success).toBe(true)
298
+
299
+ const { readFileSync } = require('fs')
300
+ const updated = readFileSync(join(noMarkersDir, 'CLAUDE.md'), 'utf-8')
301
+
302
+ expect(updated).toContain('Security content')
303
+ expect(updated).toContain('Some content')
304
+ })
305
+
306
+ it('should clear IDE files', () => {
307
+ // Create some IDE files
308
+ const clearDir = join(testDir, 'clear-test')
309
+ mkdirSync(join(clearDir, '.cursor/rules'), { recursive: true })
310
+ writeFileSync(join(clearDir, '.cursor/rules/security.mdc'), 'content')
311
+ writeFileSync(join(clearDir, '.windsurfrules'), 'content')
312
+
313
+ const result = clearIDEFiles(clearDir)
314
+
315
+ expect(result.success).toBe(true)
316
+ expect(existsSync(join(clearDir, '.cursor/rules/security.mdc'))).toBe(false)
317
+ expect(existsSync(join(clearDir, '.windsurfrules'))).toBe(false)
318
+ })
319
+ })
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Claude Code IDE Integration
3
+ * Generates CLAUDE.md section format
4
+ */
5
+
6
+ import type { ScanResult, Vulnerability, VulnerabilityCategory } from '../../types'
7
+ import { sortBySeverity } from '../grouping'
8
+
9
+ /** Start marker for Oculum section in CLAUDE.md */
10
+ export const OCULUM_SECTION_START = '<!-- OCULUM_SECURITY_START -->'
11
+
12
+ /** End marker for Oculum section in CLAUDE.md */
13
+ export const OCULUM_SECTION_END = '<!-- OCULUM_SECURITY_END -->'
14
+
15
+ /**
16
+ * Get "ALWAYS" rule for a category
17
+ */
18
+ function getAlwaysRule(category: VulnerabilityCategory): string | null {
19
+ const rules: Partial<Record<VulnerabilityCategory, string>> = {
20
+ sql_injection: 'Use parameterized queries for all database operations',
21
+ xss: 'Escape or sanitize user input before rendering in HTML',
22
+ hardcoded_secret: 'Use environment variables for secrets and API keys',
23
+ high_entropy_string: 'Store credentials in secure vaults, not in code',
24
+ missing_auth: 'Add authentication middleware to API endpoints',
25
+ dangerous_function: 'Avoid eval() and exec() - use safe alternatives',
26
+ command_injection: 'Sanitize all input passed to shell commands',
27
+ data_exposure: 'Never log sensitive data or expose it in responses',
28
+ weak_crypto: 'Use modern cryptographic algorithms (SHA-256+, AES-256)',
29
+ ai_prompt_injection: 'Sanitize user input before including in prompts',
30
+ ai_unsafe_execution: 'Validate AI-generated code before execution',
31
+ }
32
+
33
+ return rules[category] || null
34
+ }
35
+
36
+ /**
37
+ * Format scan result as CLAUDE.md section
38
+ *
39
+ * @param result - The scan result to format
40
+ * @returns Markdown string with markers for CLAUDE.md
41
+ */
42
+ export function formatClaudeCodeSection(result: ScanResult): string {
43
+ const { vulnerabilities, timestamp } = result
44
+
45
+ // Sort by severity
46
+ const sorted = sortBySeverity(vulnerabilities)
47
+
48
+ let md = ''
49
+
50
+ // Section markers
51
+ md += `${OCULUM_SECTION_START}\n`
52
+ md += `## Security Issues (Auto-Generated)\n\n`
53
+ md += `> Last scan: ${timestamp}\n\n`
54
+
55
+ // No findings case
56
+ if (vulnerabilities.length === 0) {
57
+ md += `No security issues detected.\n\n`
58
+ md += `Run \`oculum scan\` to scan for vulnerabilities.\n`
59
+ md += `${OCULUM_SECTION_END}\n`
60
+ return md
61
+ }
62
+
63
+ // Summary
64
+ const { severityCounts, hasBlockingIssues } = result
65
+ if (hasBlockingIssues) {
66
+ md += `**BLOCKING ISSUES:** ${severityCounts.critical + severityCounts.high} issues must be fixed.\n\n`
67
+ }
68
+
69
+ // DO NOT section - list findings
70
+ md += `**DO NOT** ignore these findings:\n\n`
71
+
72
+ let count = 0
73
+ for (const vuln of sorted.slice(0, 10)) {
74
+ count++
75
+ const severityLabel =
76
+ vuln.severity === 'critical' ? 'CRITICAL' :
77
+ vuln.severity === 'high' ? 'HIGH' :
78
+ vuln.severity.toUpperCase()
79
+
80
+ md += `${count}. **${vuln.title}** [${severityLabel}] in \`${vuln.filePath}:${vuln.lineNumber}\`\n`
81
+ }
82
+
83
+ if (sorted.length > 10) {
84
+ md += `\n... and ${sorted.length - 10} more issues.\n`
85
+ }
86
+ md += '\n'
87
+
88
+ // ALWAYS section - best practices
89
+ const alwaysRules = new Set<string>()
90
+ for (const vuln of vulnerabilities) {
91
+ const rule = getAlwaysRule(vuln.category)
92
+ if (rule) {
93
+ alwaysRules.add(rule)
94
+ }
95
+ }
96
+
97
+ if (alwaysRules.size > 0) {
98
+ md += `**ALWAYS:**\n`
99
+ for (const rule of alwaysRules) {
100
+ md += `- ${rule}\n`
101
+ }
102
+ md += '\n'
103
+ }
104
+
105
+ // Verification instructions
106
+ md += `Run \`oculum scan\` to verify fixes.\n`
107
+ md += `${OCULUM_SECTION_END}\n`
108
+
109
+ return md
110
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Cursor IDE Integration
3
+ * Generates .cursor/rules/security.mdc format
4
+ */
5
+
6
+ import type { ScanResult, Vulnerability } from '../../types'
7
+ import { sortBySeverity } from '../grouping'
8
+
9
+ /**
10
+ * Extract unique directory paths from vulnerabilities for glob patterns
11
+ */
12
+ function extractGlobPatterns(vulnerabilities: Vulnerability[]): string[] {
13
+ const directories = new Set<string>()
14
+
15
+ for (const vuln of vulnerabilities) {
16
+ // Get directory path (e.g., 'src/api' from 'src/api/users.ts')
17
+ const parts = vuln.filePath.split('/')
18
+ if (parts.length > 1) {
19
+ // Take up to 2 levels of directory
20
+ const dir = parts.slice(0, Math.min(parts.length - 1, 2)).join('/')
21
+ directories.add(`${dir}/**`)
22
+ }
23
+ }
24
+
25
+ return Array.from(directories).sort()
26
+ }
27
+
28
+ /**
29
+ * Format fix pattern (DO NOT / DO) for a vulnerability
30
+ */
31
+ function formatFixPattern(vuln: Vulnerability): string {
32
+ const category = vuln.category
33
+ let md = ''
34
+
35
+ // Common patterns by category
36
+ switch (category) {
37
+ case 'sql_injection':
38
+ md += `**DO NOT** concatenate user input into SQL queries.\n\n`
39
+ md += `\`\`\`typescript\n// BAD\nconst query = \`SELECT * FROM users WHERE id = \${userId}\`\n\n// GOOD\nconst query = 'SELECT * FROM users WHERE id = ?'\n\`\`\`\n`
40
+ break
41
+
42
+ case 'xss':
43
+ md += `**DO NOT** insert user input into HTML without escaping.\n\n`
44
+ md += `\`\`\`typescript\n// BAD\nelement.innerHTML = userInput\n\n// GOOD\nelement.textContent = userInput\n\`\`\`\n`
45
+ break
46
+
47
+ case 'hardcoded_secret':
48
+ case 'high_entropy_string':
49
+ md += `**DO NOT** hardcode secrets or API keys in source code.\n\n`
50
+ md += `\`\`\`typescript\n// BAD\nconst apiKey = 'sk-abc123...'\n\n// GOOD\nconst apiKey = process.env.API_KEY\n\`\`\`\n`
51
+ break
52
+
53
+ case 'missing_auth':
54
+ md += `**DO NOT** expose endpoints without authentication.\n\n`
55
+ md += `\`\`\`typescript\n// BAD\napp.get('/api/data', handler)\n\n// GOOD\napp.get('/api/data', authMiddleware, handler)\n\`\`\`\n`
56
+ break
57
+
58
+ case 'dangerous_function':
59
+ md += `**DO NOT** use eval() or similar dangerous functions with user input.\n\n`
60
+ md += `\`\`\`typescript\n// BAD\neval(userInput)\n\n// GOOD\nJSON.parse(userInput)\n\`\`\`\n`
61
+ break
62
+
63
+ default:
64
+ // Generic fix pattern
65
+ if (vuln.fixSteps && vuln.fixSteps.length > 0) {
66
+ md += `**FIX:**\n`
67
+ vuln.fixSteps.forEach((step, i) => {
68
+ md += `${i + 1}. ${step}\n`
69
+ })
70
+ } else if (vuln.suggestedFix) {
71
+ md += `**FIX:** ${vuln.suggestedFix}\n`
72
+ }
73
+ }
74
+
75
+ return md
76
+ }
77
+
78
+ /**
79
+ * Format scan result as Cursor MDC rules
80
+ *
81
+ * @param result - The scan result to format
82
+ * @returns MDC format string for .cursor/rules/security.mdc
83
+ */
84
+ export function formatCursorRules(result: ScanResult): string {
85
+ const { vulnerabilities, timestamp } = result
86
+
87
+ // Sort by severity
88
+ const sorted = sortBySeverity(vulnerabilities)
89
+
90
+ // Extract glob patterns
91
+ const globs = extractGlobPatterns(sorted)
92
+
93
+ let md = ''
94
+
95
+ // MDC Frontmatter
96
+ md += `---\n`
97
+ md += `description: Oculum Security Findings\n`
98
+ if (globs.length > 0) {
99
+ md += `globs: "${globs.join(',')}"\n`
100
+ }
101
+ md += `alwaysApply: false\n`
102
+ md += `---\n\n`
103
+
104
+ // No findings case
105
+ if (vulnerabilities.length === 0) {
106
+ md += `# Security Scan Results\n\n`
107
+ md += `No security issues found. Last scan: ${timestamp}\n\n`
108
+ md += `*Run \`oculum scan --cursor --clear\` to remove this file.*\n`
109
+ return md
110
+ }
111
+
112
+ // Header
113
+ md += `# Security Issues (${vulnerabilities.length})\n\n`
114
+ md += `> Last scan: ${timestamp}\n\n`
115
+
116
+ // Group by severity
117
+ const bySeverity = new Map<string, Vulnerability[]>()
118
+ for (const vuln of sorted) {
119
+ const group = bySeverity.get(vuln.severity) || []
120
+ group.push(vuln)
121
+ bySeverity.set(vuln.severity, group)
122
+ }
123
+
124
+ // Output by severity
125
+ for (const [severity, vulns] of bySeverity) {
126
+ md += `## ${severity.toUpperCase()} (${vulns.length})\n\n`
127
+
128
+ for (const vuln of vulns.slice(0, 10)) {
129
+ md += `### ${vuln.title}\n\n`
130
+ md += `**File:** \`${vuln.filePath}:${vuln.lineNumber}\`\n\n`
131
+
132
+ // Add fix pattern
133
+ md += formatFixPattern(vuln)
134
+ md += '\n'
135
+ }
136
+
137
+ if (vulns.length > 10) {
138
+ md += `> + ${vulns.length - 10} more ${severity} issues\n\n`
139
+ }
140
+ }
141
+
142
+ // Footer
143
+ md += `---\n\n`
144
+ md += `*Run \`oculum scan --cursor --clear\` after fixing to remove this file.*\n`
145
+
146
+ return md
147
+ }