javi-forge 1.5.0 → 1.6.1

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 (217) hide show
  1. package/README.md +191 -3
  2. package/ci-local/hooks/pre-push +17 -13
  3. package/dist/commands/analyze.d.ts +1 -1
  4. package/dist/commands/analyze.js +15 -15
  5. package/dist/commands/atlassian-mcp.d.ts +42 -0
  6. package/dist/commands/atlassian-mcp.js +98 -0
  7. package/dist/commands/ci.d.ts +3 -3
  8. package/dist/commands/ci.js +185 -147
  9. package/dist/commands/crash-recovery.d.ts +34 -0
  10. package/dist/commands/crash-recovery.js +123 -0
  11. package/dist/commands/doctor.d.ts +2 -2
  12. package/dist/commands/doctor.js +113 -61
  13. package/dist/commands/harness-audit.d.ts +35 -0
  14. package/dist/commands/harness-audit.js +277 -0
  15. package/dist/commands/init.d.ts +1 -1
  16. package/dist/commands/init.js +415 -118
  17. package/dist/commands/llmstxt.d.ts +1 -1
  18. package/dist/commands/llmstxt.js +36 -34
  19. package/dist/commands/parallel-batch.d.ts +42 -0
  20. package/dist/commands/parallel-batch.js +90 -0
  21. package/dist/commands/plugin.d.ts +26 -1
  22. package/dist/commands/plugin.js +138 -24
  23. package/dist/commands/secret-scanner.d.ts +30 -0
  24. package/dist/commands/secret-scanner.js +272 -0
  25. package/dist/commands/security-analysis.d.ts +74 -0
  26. package/dist/commands/security-analysis.js +487 -0
  27. package/dist/commands/security.d.ts +31 -0
  28. package/dist/commands/security.js +445 -0
  29. package/dist/commands/skill-scanner.d.ts +63 -0
  30. package/dist/commands/skill-scanner.js +383 -0
  31. package/dist/commands/skills.d.ts +139 -0
  32. package/dist/commands/skills.js +895 -0
  33. package/dist/commands/supply-chain.d.ts +23 -0
  34. package/dist/commands/supply-chain.js +126 -0
  35. package/dist/commands/tdd-pipeline.d.ts +17 -0
  36. package/dist/commands/tdd-pipeline.js +144 -0
  37. package/dist/commands/tdd.d.ts +21 -0
  38. package/dist/commands/tdd.js +120 -0
  39. package/dist/commands/team-presets.d.ts +53 -0
  40. package/dist/commands/team-presets.js +201 -0
  41. package/dist/commands/workflow.d.ts +23 -0
  42. package/dist/commands/workflow.js +114 -0
  43. package/dist/constants.d.ts +21 -0
  44. package/dist/constants.js +208 -37
  45. package/dist/index.js +400 -54
  46. package/dist/lib/agent-skills.d.ts +73 -0
  47. package/dist/lib/agent-skills.js +260 -0
  48. package/dist/lib/auto-skill-install.d.ts +37 -0
  49. package/dist/lib/auto-skill-install.js +92 -0
  50. package/dist/lib/auto-wire.d.ts +20 -0
  51. package/dist/lib/auto-wire.js +240 -0
  52. package/dist/lib/claudemd.d.ts +20 -0
  53. package/dist/lib/claudemd.js +222 -0
  54. package/dist/lib/codex-export.d.ts +16 -0
  55. package/dist/lib/codex-export.js +109 -0
  56. package/dist/lib/common.d.ts +1 -1
  57. package/dist/lib/common.js +52 -44
  58. package/dist/lib/context.d.ts +27 -0
  59. package/dist/lib/context.js +204 -0
  60. package/dist/lib/docker.d.ts +1 -1
  61. package/dist/lib/docker.js +141 -112
  62. package/dist/lib/frontmatter.d.ts +1 -1
  63. package/dist/lib/frontmatter.js +29 -15
  64. package/dist/lib/plugin.d.ts +19 -1
  65. package/dist/lib/plugin.js +174 -47
  66. package/dist/lib/skill-publish.d.ts +40 -0
  67. package/dist/lib/skill-publish.js +146 -0
  68. package/dist/lib/stack-detector.d.ts +38 -0
  69. package/dist/lib/stack-detector.js +207 -0
  70. package/dist/lib/template.d.ts +16 -1
  71. package/dist/lib/template.js +46 -17
  72. package/dist/lib/workflow/discovery.d.ts +19 -0
  73. package/dist/lib/workflow/discovery.js +68 -0
  74. package/dist/lib/workflow/index.d.ts +5 -0
  75. package/dist/lib/workflow/index.js +5 -0
  76. package/dist/lib/workflow/parser.d.ts +16 -0
  77. package/dist/lib/workflow/parser.js +198 -0
  78. package/dist/lib/workflow/renderer.d.ts +9 -0
  79. package/dist/lib/workflow/renderer.js +152 -0
  80. package/dist/lib/workflow/validator.d.ts +10 -0
  81. package/dist/lib/workflow/validator.js +189 -0
  82. package/dist/tasks/index.d.ts +4 -0
  83. package/dist/tasks/index.js +4 -0
  84. package/dist/tasks/scaffold-tasks.d.ts +3 -0
  85. package/dist/tasks/scaffold-tasks.js +14 -0
  86. package/dist/tasks/task-id.d.ts +30 -0
  87. package/dist/tasks/task-id.js +55 -0
  88. package/dist/tasks/task-tracker.d.ts +15 -0
  89. package/dist/tasks/task-tracker.js +81 -0
  90. package/dist/types/index.d.ts +252 -5
  91. package/dist/types/index.js +11 -1
  92. package/dist/ui/AnalyzeUI.d.ts +1 -1
  93. package/dist/ui/AnalyzeUI.js +38 -39
  94. package/dist/ui/App.d.ts +5 -3
  95. package/dist/ui/App.js +92 -46
  96. package/dist/ui/AutoSkills.d.ts +9 -0
  97. package/dist/ui/AutoSkills.js +124 -0
  98. package/dist/ui/CI.d.ts +2 -2
  99. package/dist/ui/CI.js +24 -26
  100. package/dist/ui/CIContext.d.ts +1 -1
  101. package/dist/ui/CIContext.js +3 -2
  102. package/dist/ui/CISelector.d.ts +2 -2
  103. package/dist/ui/CISelector.js +23 -15
  104. package/dist/ui/Doctor.d.ts +1 -1
  105. package/dist/ui/Doctor.js +35 -29
  106. package/dist/ui/Header.d.ts +1 -1
  107. package/dist/ui/Header.js +14 -14
  108. package/dist/ui/HookProfileSelector.d.ts +9 -0
  109. package/dist/ui/HookProfileSelector.js +54 -0
  110. package/dist/ui/LlmsTxt.d.ts +1 -1
  111. package/dist/ui/LlmsTxt.js +31 -22
  112. package/dist/ui/MemorySelector.d.ts +2 -2
  113. package/dist/ui/MemorySelector.js +28 -16
  114. package/dist/ui/NameInput.d.ts +1 -1
  115. package/dist/ui/NameInput.js +21 -21
  116. package/dist/ui/OptionSelector.d.ts +8 -2
  117. package/dist/ui/OptionSelector.js +83 -26
  118. package/dist/ui/Plugin.d.ts +4 -3
  119. package/dist/ui/Plugin.js +89 -29
  120. package/dist/ui/Progress.d.ts +3 -3
  121. package/dist/ui/Progress.js +23 -22
  122. package/dist/ui/Skills.d.ts +11 -0
  123. package/dist/ui/Skills.js +148 -0
  124. package/dist/ui/StackSelector.d.ts +2 -2
  125. package/dist/ui/StackSelector.js +26 -16
  126. package/dist/ui/Summary.d.ts +3 -3
  127. package/dist/ui/Summary.js +60 -50
  128. package/dist/ui/Welcome.d.ts +1 -1
  129. package/dist/ui/Welcome.js +15 -16
  130. package/dist/ui/theme.d.ts +1 -1
  131. package/dist/ui/theme.js +6 -6
  132. package/package.json +9 -6
  133. package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
  134. package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
  135. package/templates/common/repoforge/repoforge.yaml +34 -0
  136. package/templates/github/deploy-docker-zero-downtime.yml +140 -0
  137. package/templates/github/repoforge-graph.yml +45 -0
  138. package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
  139. package/templates/local-ai/.env.example +17 -0
  140. package/templates/local-ai/docker-compose.yml +95 -0
  141. package/templates/security-hooks/claude-settings-security.json +30 -0
  142. package/templates/security-hooks/commit-msg-signing +29 -0
  143. package/templates/security-hooks/pre-commit-permissions +74 -0
  144. package/templates/security-hooks/pre-commit-secrets +74 -0
  145. package/templates/security-hooks/pre-push-branch-protection +62 -0
  146. package/templates/security-hooks/pre-push-deps +83 -0
  147. package/templates/security-hooks/pre-push-signing +67 -0
  148. package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
  149. package/templates/workflows/ci-pipeline.dot +15 -0
  150. package/templates/workflows/feature-flow.dot +21 -0
  151. package/templates/workflows/release.dot +16 -0
  152. package/dist/__integration__/helpers.d.ts +0 -20
  153. package/dist/__integration__/helpers.d.ts.map +0 -1
  154. package/dist/__integration__/helpers.js +0 -31
  155. package/dist/__integration__/helpers.js.map +0 -1
  156. package/dist/commands/analyze.d.ts.map +0 -1
  157. package/dist/commands/analyze.js.map +0 -1
  158. package/dist/commands/ci.d.ts.map +0 -1
  159. package/dist/commands/ci.js.map +0 -1
  160. package/dist/commands/doctor.d.ts.map +0 -1
  161. package/dist/commands/doctor.js.map +0 -1
  162. package/dist/commands/init.d.ts.map +0 -1
  163. package/dist/commands/init.js.map +0 -1
  164. package/dist/commands/llmstxt.d.ts.map +0 -1
  165. package/dist/commands/llmstxt.js.map +0 -1
  166. package/dist/commands/plugin.d.ts.map +0 -1
  167. package/dist/commands/plugin.js.map +0 -1
  168. package/dist/constants.d.ts.map +0 -1
  169. package/dist/constants.js.map +0 -1
  170. package/dist/index.d.ts.map +0 -1
  171. package/dist/index.js.map +0 -1
  172. package/dist/lib/common.d.ts.map +0 -1
  173. package/dist/lib/common.js.map +0 -1
  174. package/dist/lib/docker.d.ts.map +0 -1
  175. package/dist/lib/docker.js.map +0 -1
  176. package/dist/lib/frontmatter.d.ts.map +0 -1
  177. package/dist/lib/frontmatter.js.map +0 -1
  178. package/dist/lib/plugin.d.ts.map +0 -1
  179. package/dist/lib/plugin.js.map +0 -1
  180. package/dist/lib/template.d.ts.map +0 -1
  181. package/dist/lib/template.js.map +0 -1
  182. package/dist/types/index.d.ts.map +0 -1
  183. package/dist/types/index.js.map +0 -1
  184. package/dist/ui/AnalyzeUI.d.ts.map +0 -1
  185. package/dist/ui/AnalyzeUI.js.map +0 -1
  186. package/dist/ui/App.d.ts.map +0 -1
  187. package/dist/ui/App.js.map +0 -1
  188. package/dist/ui/CI.d.ts.map +0 -1
  189. package/dist/ui/CI.js.map +0 -1
  190. package/dist/ui/CIContext.d.ts.map +0 -1
  191. package/dist/ui/CIContext.js.map +0 -1
  192. package/dist/ui/CISelector.d.ts.map +0 -1
  193. package/dist/ui/CISelector.js.map +0 -1
  194. package/dist/ui/Doctor.d.ts.map +0 -1
  195. package/dist/ui/Doctor.js.map +0 -1
  196. package/dist/ui/Header.d.ts.map +0 -1
  197. package/dist/ui/Header.js.map +0 -1
  198. package/dist/ui/LlmsTxt.d.ts.map +0 -1
  199. package/dist/ui/LlmsTxt.js.map +0 -1
  200. package/dist/ui/MemorySelector.d.ts.map +0 -1
  201. package/dist/ui/MemorySelector.js.map +0 -1
  202. package/dist/ui/NameInput.d.ts.map +0 -1
  203. package/dist/ui/NameInput.js.map +0 -1
  204. package/dist/ui/OptionSelector.d.ts.map +0 -1
  205. package/dist/ui/OptionSelector.js.map +0 -1
  206. package/dist/ui/Plugin.d.ts.map +0 -1
  207. package/dist/ui/Plugin.js.map +0 -1
  208. package/dist/ui/Progress.d.ts.map +0 -1
  209. package/dist/ui/Progress.js.map +0 -1
  210. package/dist/ui/StackSelector.d.ts.map +0 -1
  211. package/dist/ui/StackSelector.js.map +0 -1
  212. package/dist/ui/Summary.d.ts.map +0 -1
  213. package/dist/ui/Summary.js.map +0 -1
  214. package/dist/ui/Welcome.d.ts.map +0 -1
  215. package/dist/ui/Welcome.js.map +0 -1
  216. package/dist/ui/theme.d.ts.map +0 -1
  217. package/dist/ui/theme.js.map +0 -1
@@ -0,0 +1,487 @@
1
+ /**
2
+ * Trail of Bits security analysis patterns for CI pipeline.
3
+ *
4
+ * Integrates static analysis via CodeQL/Semgrep rule patterns for
5
+ * vulnerability detection, variant analysis, and structured reporting
6
+ * with severity levels (critical, high, medium, low).
7
+ */
8
+ import fs from "fs-extra";
9
+ import path from "path";
10
+ // =============================================================================
11
+ // Constants
12
+ // =============================================================================
13
+ const SEVERITY_ORDER = {
14
+ critical: 5,
15
+ high: 4,
16
+ moderate: 3,
17
+ low: 2,
18
+ info: 1,
19
+ };
20
+ /**
21
+ * Trail of Bits-inspired static analysis rules ported to pattern matching.
22
+ * These cover the most common vulnerability classes found in security audits.
23
+ */
24
+ export const BUILTIN_RULES = [
25
+ // -- Injection --
26
+ {
27
+ id: "tob-js-eval-injection",
28
+ severity: "critical",
29
+ message: "Use of eval() with dynamic input enables code injection",
30
+ category: "injection",
31
+ pattern: "\\beval\\s*\\(",
32
+ languages: ["javascript", "typescript"],
33
+ cwe: "CWE-94",
34
+ owasp: "A03:2021",
35
+ },
36
+ {
37
+ id: "tob-js-function-constructor",
38
+ severity: "critical",
39
+ message: "Function constructor with dynamic input enables code injection",
40
+ category: "injection",
41
+ pattern: "\\bnew\\s+Function\\s*\\(",
42
+ languages: ["javascript", "typescript"],
43
+ cwe: "CWE-94",
44
+ owasp: "A03:2021",
45
+ },
46
+ {
47
+ id: "tob-py-exec-injection",
48
+ severity: "critical",
49
+ message: "Use of exec() with dynamic input enables code injection",
50
+ category: "injection",
51
+ pattern: "\\bexec\\s*\\(",
52
+ languages: ["python"],
53
+ cwe: "CWE-94",
54
+ owasp: "A03:2021",
55
+ },
56
+ {
57
+ id: "tob-py-eval-injection",
58
+ severity: "critical",
59
+ message: "Use of eval() with dynamic input enables code injection",
60
+ category: "injection",
61
+ pattern: "\\beval\\s*\\(",
62
+ languages: ["python"],
63
+ cwe: "CWE-94",
64
+ owasp: "A03:2021",
65
+ },
66
+ {
67
+ id: "tob-sql-injection",
68
+ severity: "critical",
69
+ message: "String concatenation in SQL query — use parameterized queries instead",
70
+ category: "injection",
71
+ pattern: "(?:SELECT|INSERT|UPDATE|DELETE|DROP)\\s+.*\\+\\s*(?:req\\.|params\\.|query\\.|body\\.)",
72
+ languages: ["javascript", "typescript", "python"],
73
+ cwe: "CWE-89",
74
+ owasp: "A03:2021",
75
+ },
76
+ {
77
+ id: "tob-cmd-injection",
78
+ severity: "critical",
79
+ message: "Shell command with string interpolation — use execFile instead",
80
+ category: "injection",
81
+ pattern: "\\bexec(?:Sync)?\\s*\\(\\s*`",
82
+ languages: ["javascript", "typescript"],
83
+ cwe: "CWE-78",
84
+ owasp: "A03:2021",
85
+ },
86
+ // -- Cryptography --
87
+ {
88
+ id: "tob-weak-hash-md5",
89
+ severity: "high",
90
+ message: "MD5 is cryptographically broken — use SHA-256 or better",
91
+ category: "cryptography",
92
+ pattern: "(?:createHash\\s*\\(\\s*['\"]md5['\"]|hashlib\\.md5|MD5\\.Create|Digest::MD5)",
93
+ languages: ["javascript", "typescript", "python", "ruby", "go"],
94
+ cwe: "CWE-328",
95
+ },
96
+ {
97
+ id: "tob-weak-hash-sha1",
98
+ severity: "high",
99
+ message: "SHA-1 is deprecated — use SHA-256 or better",
100
+ category: "cryptography",
101
+ pattern: "(?:createHash\\s*\\(\\s*['\"]sha1['\"]|hashlib\\.sha1|SHA1\\.Create)",
102
+ languages: ["javascript", "typescript", "python"],
103
+ cwe: "CWE-328",
104
+ },
105
+ {
106
+ id: "tob-hardcoded-secret",
107
+ severity: "high",
108
+ message: "Hardcoded secret or API key detected — use environment variables",
109
+ category: "cryptography",
110
+ pattern: "(?:password|secret|api_key|apikey|auth_token|private_key)\\s*=\\s*['\"][^'\"]{8,}['\"]",
111
+ languages: ["javascript", "typescript", "python", "go", "ruby"],
112
+ cwe: "CWE-798",
113
+ owasp: "A07:2021",
114
+ },
115
+ // -- Deserialization --
116
+ {
117
+ id: "tob-unsafe-deserialize",
118
+ severity: "critical",
119
+ message: "Unsafe deserialization can lead to remote code execution — use safe alternatives",
120
+ category: "deserialization",
121
+ pattern: "(?:pickle\\.loads?|yaml\\.load\\s*\\((?!.*Loader=SafeLoader))",
122
+ languages: ["python"],
123
+ cwe: "CWE-502",
124
+ owasp: "A08:2021",
125
+ },
126
+ {
127
+ id: "tob-unsafe-json-parse",
128
+ severity: "moderate",
129
+ message: "JSON.parse without try/catch can crash on malformed input",
130
+ category: "deserialization",
131
+ pattern: "(?<!try\\s*\\{[^}]*)JSON\\.parse\\s*\\(\\s*(?:req\\.|body\\.|input)",
132
+ languages: ["javascript", "typescript"],
133
+ cwe: "CWE-502",
134
+ },
135
+ // -- Path Traversal --
136
+ {
137
+ id: "tob-path-traversal",
138
+ severity: "high",
139
+ message: "User input in file path without sanitization — validate path components",
140
+ category: "path-traversal",
141
+ pattern: "(?:readFile|writeFile|createReadStream|open)\\s*\\(.*(?:req\\.|params\\.|query\\.)",
142
+ languages: ["javascript", "typescript"],
143
+ cwe: "CWE-22",
144
+ owasp: "A01:2021",
145
+ },
146
+ {
147
+ id: "tob-py-path-traversal",
148
+ severity: "high",
149
+ message: "User input in file path without sanitization — validate path components",
150
+ category: "path-traversal",
151
+ pattern: "open\\s*\\(.*(?:request\\.|args\\.|kwargs\\.)",
152
+ languages: ["python"],
153
+ cwe: "CWE-22",
154
+ owasp: "A01:2021",
155
+ },
156
+ // -- Information Disclosure --
157
+ {
158
+ id: "tob-stack-trace-leak",
159
+ severity: "moderate",
160
+ message: "Stack trace sent to client — hide error details in production",
161
+ category: "information-disclosure",
162
+ pattern: "(?:res\\.(?:send|json)\\s*\\(.*(?:err|error)\\.(?:stack|message))",
163
+ languages: ["javascript", "typescript"],
164
+ cwe: "CWE-209",
165
+ owasp: "A04:2021",
166
+ },
167
+ {
168
+ id: "tob-debug-enabled",
169
+ severity: "moderate",
170
+ message: "Debug mode should not be enabled in production",
171
+ category: "information-disclosure",
172
+ pattern: "(?:DEBUG\\s*=\\s*True|app\\.debug\\s*=\\s*True)",
173
+ languages: ["python"],
174
+ cwe: "CWE-489",
175
+ },
176
+ // -- Authentication --
177
+ {
178
+ id: "tob-jwt-none-algorithm",
179
+ severity: "critical",
180
+ message: "JWT with 'none' algorithm allows token forgery — always specify algorithm",
181
+ category: "authentication",
182
+ pattern: "(?:algorithm\\s*[=:]\\s*['\"]none['\"]|algorithms\\s*[=:]\\s*\\[\\s*['\"]none['\"])",
183
+ languages: ["javascript", "typescript", "python"],
184
+ cwe: "CWE-345",
185
+ owasp: "A07:2021",
186
+ },
187
+ ];
188
+ // =============================================================================
189
+ // Language detection
190
+ // =============================================================================
191
+ const LANG_EXTENSIONS = {
192
+ javascript: [".js", ".mjs", ".cjs"],
193
+ typescript: [".ts", ".mts", ".cts", ".tsx"],
194
+ python: [".py"],
195
+ go: [".go"],
196
+ ruby: [".rb"],
197
+ rust: [".rs"],
198
+ };
199
+ export function detectFileLanguage(filePath) {
200
+ const ext = path.extname(filePath).toLowerCase();
201
+ for (const [lang, exts] of Object.entries(LANG_EXTENSIONS)) {
202
+ if (exts.includes(ext))
203
+ return lang;
204
+ }
205
+ return null;
206
+ }
207
+ // =============================================================================
208
+ // Pattern matching engine
209
+ // =============================================================================
210
+ export function matchRule(rule, content, filePath) {
211
+ const lang = detectFileLanguage(filePath);
212
+ if (!lang || !rule.languages.includes(lang))
213
+ return [];
214
+ const findings = [];
215
+ const lines = content.split("\n");
216
+ const regex = new RegExp(rule.pattern, "gi");
217
+ for (let i = 0; i < lines.length; i++) {
218
+ const line = lines[i];
219
+ // Skip comments
220
+ const trimmed = line.trim();
221
+ if (trimmed.startsWith("//") ||
222
+ trimmed.startsWith("#") ||
223
+ trimmed.startsWith("*") ||
224
+ trimmed.startsWith("/*")) {
225
+ continue;
226
+ }
227
+ let match;
228
+ // Reset lastIndex for global regex
229
+ regex.lastIndex = 0;
230
+ match = regex.exec(line);
231
+ while (match) {
232
+ findings.push({
233
+ ruleId: rule.id,
234
+ engine: "semgrep",
235
+ severity: rule.severity,
236
+ message: rule.message,
237
+ file: filePath,
238
+ line: i + 1,
239
+ column: match.index + 1,
240
+ category: rule.category,
241
+ cwe: rule.cwe,
242
+ owasp: rule.owasp,
243
+ });
244
+ match = regex.exec(line);
245
+ }
246
+ }
247
+ return findings;
248
+ }
249
+ // =============================================================================
250
+ // File scanning
251
+ // =============================================================================
252
+ const IGNORED_DIRS = new Set([
253
+ "node_modules",
254
+ ".git",
255
+ "dist",
256
+ "build",
257
+ "coverage",
258
+ ".next",
259
+ "__pycache__",
260
+ ".venv",
261
+ "venv",
262
+ "vendor",
263
+ "target",
264
+ ]);
265
+ const SCANNABLE_EXTENSIONS = new Set([
266
+ ".js",
267
+ ".mjs",
268
+ ".cjs",
269
+ ".ts",
270
+ ".mts",
271
+ ".cts",
272
+ ".tsx",
273
+ ".py",
274
+ ".go",
275
+ ".rb",
276
+ ".rs",
277
+ ]);
278
+ export async function collectFiles(dir) {
279
+ const results = [];
280
+ async function walk(currentDir) {
281
+ let entries;
282
+ try {
283
+ entries = await fs.readdir(currentDir);
284
+ }
285
+ catch {
286
+ return;
287
+ }
288
+ for (const entry of entries) {
289
+ if (IGNORED_DIRS.has(entry))
290
+ continue;
291
+ const fullPath = path.join(currentDir, entry);
292
+ let stat;
293
+ try {
294
+ stat = await fs.stat(fullPath);
295
+ }
296
+ catch {
297
+ continue;
298
+ }
299
+ if (stat.isDirectory()) {
300
+ await walk(fullPath);
301
+ }
302
+ else if (SCANNABLE_EXTENSIONS.has(path.extname(entry).toLowerCase())) {
303
+ results.push(fullPath);
304
+ }
305
+ }
306
+ }
307
+ await walk(dir);
308
+ return results;
309
+ }
310
+ // =============================================================================
311
+ // Severity helpers
312
+ // =============================================================================
313
+ export function severityAtOrAbove(severity, threshold) {
314
+ return SEVERITY_ORDER[severity] >= SEVERITY_ORDER[threshold];
315
+ }
316
+ // =============================================================================
317
+ // Report generation
318
+ // =============================================================================
319
+ export function buildSummary(findings, failThreshold) {
320
+ const bySeverity = {
321
+ critical: 0,
322
+ high: 0,
323
+ moderate: 0,
324
+ low: 0,
325
+ info: 0,
326
+ };
327
+ const byCategory = {};
328
+ for (const f of findings) {
329
+ bySeverity[f.severity]++;
330
+ byCategory[f.category] = (byCategory[f.category] ?? 0) + 1;
331
+ }
332
+ const passed = !findings.some((f) => severityAtOrAbove(f.severity, failThreshold));
333
+ return {
334
+ total: findings.length,
335
+ bySeverity,
336
+ byCategory,
337
+ passed,
338
+ failThreshold,
339
+ };
340
+ }
341
+ export function buildReport(findings, projectDir, options = {}) {
342
+ const failThreshold = options.failThreshold ?? "high";
343
+ return {
344
+ engine: "semgrep",
345
+ timestamp: new Date().toISOString(),
346
+ projectDir,
347
+ findings,
348
+ summary: buildSummary(findings, failThreshold),
349
+ };
350
+ }
351
+ // =============================================================================
352
+ // Filtering
353
+ // =============================================================================
354
+ export function filterRules(rules, options = {}) {
355
+ let filtered = [...rules];
356
+ if (options.includeRules && options.includeRules.length > 0) {
357
+ const includeSet = new Set(options.includeRules);
358
+ filtered = filtered.filter((r) => includeSet.has(r.id));
359
+ }
360
+ if (options.excludeRules && options.excludeRules.length > 0) {
361
+ const excludeSet = new Set(options.excludeRules);
362
+ filtered = filtered.filter((r) => !excludeSet.has(r.id));
363
+ }
364
+ return filtered;
365
+ }
366
+ // =============================================================================
367
+ // Custom rules loading
368
+ // =============================================================================
369
+ export async function loadCustomRules(rulesDir) {
370
+ if (!(await fs.pathExists(rulesDir)))
371
+ return [];
372
+ const customRules = [];
373
+ const files = await fs.readdir(rulesDir);
374
+ for (const file of files) {
375
+ if (!file.endsWith(".json"))
376
+ continue;
377
+ try {
378
+ const content = await fs.readJson(path.join(rulesDir, file));
379
+ if (Array.isArray(content)) {
380
+ for (const rule of content) {
381
+ if (isValidRule(rule)) {
382
+ customRules.push(rule);
383
+ }
384
+ }
385
+ }
386
+ else if (isValidRule(content)) {
387
+ customRules.push(content);
388
+ }
389
+ }
390
+ catch {
391
+ // Skip invalid rule files
392
+ }
393
+ }
394
+ return customRules;
395
+ }
396
+ function isValidRule(rule) {
397
+ if (!rule || typeof rule !== "object")
398
+ return false;
399
+ const r = rule;
400
+ return (typeof r.id === "string" &&
401
+ typeof r.severity === "string" &&
402
+ typeof r.message === "string" &&
403
+ typeof r.category === "string" &&
404
+ typeof r.pattern === "string" &&
405
+ Array.isArray(r.languages));
406
+ }
407
+ // =============================================================================
408
+ // Main scan function
409
+ // =============================================================================
410
+ export async function runSecurityAnalysis(projectDir, options = {}) {
411
+ // Collect rules
412
+ let rules = filterRules(BUILTIN_RULES, options);
413
+ // Load custom rules if provided
414
+ if (options.rulesDir) {
415
+ const custom = await loadCustomRules(options.rulesDir);
416
+ const customFiltered = filterRules(custom, options);
417
+ rules = [...rules, ...customFiltered];
418
+ }
419
+ // Collect files
420
+ const targetDirs = options.targetDirs ?? [projectDir];
421
+ const allFiles = [];
422
+ for (const dir of targetDirs) {
423
+ const absDir = path.isAbsolute(dir) ? dir : path.join(projectDir, dir);
424
+ const files = await collectFiles(absDir);
425
+ allFiles.push(...files);
426
+ }
427
+ // Run pattern matching
428
+ const findings = [];
429
+ for (const filePath of allFiles) {
430
+ let content;
431
+ try {
432
+ content = await fs.readFile(filePath, "utf-8");
433
+ }
434
+ catch {
435
+ continue;
436
+ }
437
+ // Use relative paths in findings for readability
438
+ const relativePath = path.relative(projectDir, filePath);
439
+ for (const rule of rules) {
440
+ const matches = matchRule(rule, content, filePath);
441
+ // Rewrite file paths to relative
442
+ for (const match of matches) {
443
+ match.file = relativePath;
444
+ }
445
+ findings.push(...matches);
446
+ }
447
+ }
448
+ // Sort by severity (critical first), then by file
449
+ findings.sort((a, b) => {
450
+ const sevDiff = SEVERITY_ORDER[b.severity] - SEVERITY_ORDER[a.severity];
451
+ if (sevDiff !== 0)
452
+ return sevDiff;
453
+ return a.file.localeCompare(b.file);
454
+ });
455
+ return buildReport(findings, projectDir, options);
456
+ }
457
+ // =============================================================================
458
+ // Report formatting (for CI output)
459
+ // =============================================================================
460
+ export function formatReportText(report) {
461
+ const lines = [];
462
+ const { summary, findings } = report;
463
+ lines.push("=== Security Analysis Report ===");
464
+ lines.push(`Engine: ${report.engine}`);
465
+ lines.push(`Findings: ${summary.total}`);
466
+ lines.push(`Severity breakdown: ${Object.entries(summary.bySeverity)
467
+ .filter(([, count]) => count > 0)
468
+ .map(([sev, count]) => `${count} ${sev}`)
469
+ .join(", ") || "none"}`);
470
+ lines.push(`Pass threshold: ${summary.failThreshold}`);
471
+ lines.push(`Result: ${summary.passed ? "PASS" : "FAIL"}`);
472
+ lines.push("");
473
+ if (findings.length > 0) {
474
+ lines.push("--- Findings ---");
475
+ for (const f of findings) {
476
+ const loc = f.column ? `${f.file}:${f.line}:${f.column}` : `${f.file}:${f.line}`;
477
+ const cwe = f.cwe ? ` [${f.cwe}]` : "";
478
+ lines.push(`[${f.severity.toUpperCase()}] ${f.ruleId}${cwe} at ${loc}`);
479
+ lines.push(` ${f.message}`);
480
+ }
481
+ }
482
+ return lines.join("\n");
483
+ }
484
+ export function formatReportJson(report) {
485
+ return JSON.stringify(report, null, 2);
486
+ }
487
+ //# sourceMappingURL=security-analysis.js.map
@@ -0,0 +1,31 @@
1
+ import type { SecurityBaseline, SecurityCheckOptions, SecurityCheckResult, SecurityFinding, SecuritySeverity, SecuritySummary, Stack } from "../types/index.js";
2
+ export type SecurityMode = "baseline" | "check" | "update" | "allowlist";
3
+ export type SecurityStepStatus = "pending" | "running" | "done" | "error" | "skipped";
4
+ export interface SecurityStep {
5
+ id: string;
6
+ label: string;
7
+ status: SecurityStepStatus;
8
+ detail?: string;
9
+ }
10
+ export type SecurityStepCallback = (step: SecurityStep) => void;
11
+ export declare function getAuditCommand(stack: Stack, buildTool: string): {
12
+ cmd: string;
13
+ args: string[];
14
+ } | null;
15
+ export declare function makeFindingKey(finding: SecurityFinding): string;
16
+ export declare function parseNpmAudit(raw: string): SecurityFinding[];
17
+ export declare function parsePipAudit(raw: string): SecurityFinding[];
18
+ export declare function parseCargoAudit(raw: string): SecurityFinding[];
19
+ export declare function parseGovulncheck(raw: string): SecurityFinding[];
20
+ export declare function parseAuditOutput(stack: Stack, raw: string): SecurityFinding[];
21
+ export declare function severityAtOrAbove(severity: SecuritySeverity, threshold: SecuritySeverity): boolean;
22
+ export declare function filterBySeverity(findings: SecurityFinding[], minSeverity: SecuritySeverity): SecurityFinding[];
23
+ export declare function filterAllowlisted(findings: SecurityFinding[], allowlist: string[]): SecurityFinding[];
24
+ export declare function checkStaleness(baseline: SecurityBaseline, staleDays: number): string | undefined;
25
+ export declare function baselineAgeDays(baseline: SecurityBaseline): number;
26
+ export declare function computeSummary(current: SecurityFinding[], regressions: SecurityFinding[], resolved: SecurityFinding[], filteredRegressions: SecurityFinding[], baseline: SecurityBaseline): SecuritySummary;
27
+ export declare function detectRegressions(baseline: SecurityBaseline, current: SecurityFinding[], options?: SecurityCheckOptions): SecurityCheckResult;
28
+ export declare function readBaseline(projectDir: string): Promise<SecurityBaseline | null>;
29
+ export declare function writeBaseline(projectDir: string, baseline: SecurityBaseline): Promise<void>;
30
+ export declare function runSecurity(mode: SecurityMode, projectDir: string, onStep: SecurityStepCallback, options?: SecurityCheckOptions): Promise<SecurityCheckResult | null>;
31
+ //# sourceMappingURL=security.d.ts.map