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.
- package/README.md +191 -3
- package/ci-local/hooks/pre-push +17 -13
- package/dist/commands/analyze.d.ts +1 -1
- package/dist/commands/analyze.js +15 -15
- package/dist/commands/atlassian-mcp.d.ts +42 -0
- package/dist/commands/atlassian-mcp.js +98 -0
- package/dist/commands/ci.d.ts +3 -3
- package/dist/commands/ci.js +185 -147
- package/dist/commands/crash-recovery.d.ts +34 -0
- package/dist/commands/crash-recovery.js +123 -0
- package/dist/commands/doctor.d.ts +2 -2
- package/dist/commands/doctor.js +113 -61
- package/dist/commands/harness-audit.d.ts +35 -0
- package/dist/commands/harness-audit.js +277 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +415 -118
- package/dist/commands/llmstxt.d.ts +1 -1
- package/dist/commands/llmstxt.js +36 -34
- package/dist/commands/parallel-batch.d.ts +42 -0
- package/dist/commands/parallel-batch.js +90 -0
- package/dist/commands/plugin.d.ts +26 -1
- package/dist/commands/plugin.js +138 -24
- package/dist/commands/secret-scanner.d.ts +30 -0
- package/dist/commands/secret-scanner.js +272 -0
- package/dist/commands/security-analysis.d.ts +74 -0
- package/dist/commands/security-analysis.js +487 -0
- package/dist/commands/security.d.ts +31 -0
- package/dist/commands/security.js +445 -0
- package/dist/commands/skill-scanner.d.ts +63 -0
- package/dist/commands/skill-scanner.js +383 -0
- package/dist/commands/skills.d.ts +139 -0
- package/dist/commands/skills.js +895 -0
- package/dist/commands/supply-chain.d.ts +23 -0
- package/dist/commands/supply-chain.js +126 -0
- package/dist/commands/tdd-pipeline.d.ts +17 -0
- package/dist/commands/tdd-pipeline.js +144 -0
- package/dist/commands/tdd.d.ts +21 -0
- package/dist/commands/tdd.js +120 -0
- package/dist/commands/team-presets.d.ts +53 -0
- package/dist/commands/team-presets.js +201 -0
- package/dist/commands/workflow.d.ts +23 -0
- package/dist/commands/workflow.js +114 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.js +208 -37
- package/dist/index.js +400 -54
- package/dist/lib/agent-skills.d.ts +73 -0
- package/dist/lib/agent-skills.js +260 -0
- package/dist/lib/auto-skill-install.d.ts +37 -0
- package/dist/lib/auto-skill-install.js +92 -0
- package/dist/lib/auto-wire.d.ts +20 -0
- package/dist/lib/auto-wire.js +240 -0
- package/dist/lib/claudemd.d.ts +20 -0
- package/dist/lib/claudemd.js +222 -0
- package/dist/lib/codex-export.d.ts +16 -0
- package/dist/lib/codex-export.js +109 -0
- package/dist/lib/common.d.ts +1 -1
- package/dist/lib/common.js +52 -44
- package/dist/lib/context.d.ts +27 -0
- package/dist/lib/context.js +204 -0
- package/dist/lib/docker.d.ts +1 -1
- package/dist/lib/docker.js +141 -112
- package/dist/lib/frontmatter.d.ts +1 -1
- package/dist/lib/frontmatter.js +29 -15
- package/dist/lib/plugin.d.ts +19 -1
- package/dist/lib/plugin.js +174 -47
- package/dist/lib/skill-publish.d.ts +40 -0
- package/dist/lib/skill-publish.js +146 -0
- package/dist/lib/stack-detector.d.ts +38 -0
- package/dist/lib/stack-detector.js +207 -0
- package/dist/lib/template.d.ts +16 -1
- package/dist/lib/template.js +46 -17
- package/dist/lib/workflow/discovery.d.ts +19 -0
- package/dist/lib/workflow/discovery.js +68 -0
- package/dist/lib/workflow/index.d.ts +5 -0
- package/dist/lib/workflow/index.js +5 -0
- package/dist/lib/workflow/parser.d.ts +16 -0
- package/dist/lib/workflow/parser.js +198 -0
- package/dist/lib/workflow/renderer.d.ts +9 -0
- package/dist/lib/workflow/renderer.js +152 -0
- package/dist/lib/workflow/validator.d.ts +10 -0
- package/dist/lib/workflow/validator.js +189 -0
- package/dist/tasks/index.d.ts +4 -0
- package/dist/tasks/index.js +4 -0
- package/dist/tasks/scaffold-tasks.d.ts +3 -0
- package/dist/tasks/scaffold-tasks.js +14 -0
- package/dist/tasks/task-id.d.ts +30 -0
- package/dist/tasks/task-id.js +55 -0
- package/dist/tasks/task-tracker.d.ts +15 -0
- package/dist/tasks/task-tracker.js +81 -0
- package/dist/types/index.d.ts +252 -5
- package/dist/types/index.js +11 -1
- package/dist/ui/AnalyzeUI.d.ts +1 -1
- package/dist/ui/AnalyzeUI.js +38 -39
- package/dist/ui/App.d.ts +5 -3
- package/dist/ui/App.js +92 -46
- package/dist/ui/AutoSkills.d.ts +9 -0
- package/dist/ui/AutoSkills.js +124 -0
- package/dist/ui/CI.d.ts +2 -2
- package/dist/ui/CI.js +24 -26
- package/dist/ui/CIContext.d.ts +1 -1
- package/dist/ui/CIContext.js +3 -2
- package/dist/ui/CISelector.d.ts +2 -2
- package/dist/ui/CISelector.js +23 -15
- package/dist/ui/Doctor.d.ts +1 -1
- package/dist/ui/Doctor.js +35 -29
- package/dist/ui/Header.d.ts +1 -1
- package/dist/ui/Header.js +14 -14
- package/dist/ui/HookProfileSelector.d.ts +9 -0
- package/dist/ui/HookProfileSelector.js +54 -0
- package/dist/ui/LlmsTxt.d.ts +1 -1
- package/dist/ui/LlmsTxt.js +31 -22
- package/dist/ui/MemorySelector.d.ts +2 -2
- package/dist/ui/MemorySelector.js +28 -16
- package/dist/ui/NameInput.d.ts +1 -1
- package/dist/ui/NameInput.js +21 -21
- package/dist/ui/OptionSelector.d.ts +8 -2
- package/dist/ui/OptionSelector.js +83 -26
- package/dist/ui/Plugin.d.ts +4 -3
- package/dist/ui/Plugin.js +89 -29
- package/dist/ui/Progress.d.ts +3 -3
- package/dist/ui/Progress.js +23 -22
- package/dist/ui/Skills.d.ts +11 -0
- package/dist/ui/Skills.js +148 -0
- package/dist/ui/StackSelector.d.ts +2 -2
- package/dist/ui/StackSelector.js +26 -16
- package/dist/ui/Summary.d.ts +3 -3
- package/dist/ui/Summary.js +60 -50
- package/dist/ui/Welcome.d.ts +1 -1
- package/dist/ui/Welcome.js +15 -16
- package/dist/ui/theme.d.ts +1 -1
- package/dist/ui/theme.js +6 -6
- package/package.json +9 -6
- package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
- package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
- package/templates/common/repoforge/repoforge.yaml +34 -0
- package/templates/github/deploy-docker-zero-downtime.yml +140 -0
- package/templates/github/repoforge-graph.yml +45 -0
- package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
- package/templates/local-ai/.env.example +17 -0
- package/templates/local-ai/docker-compose.yml +95 -0
- package/templates/security-hooks/claude-settings-security.json +30 -0
- package/templates/security-hooks/commit-msg-signing +29 -0
- package/templates/security-hooks/pre-commit-permissions +74 -0
- package/templates/security-hooks/pre-commit-secrets +74 -0
- package/templates/security-hooks/pre-push-branch-protection +62 -0
- package/templates/security-hooks/pre-push-deps +83 -0
- package/templates/security-hooks/pre-push-signing +67 -0
- package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
- package/templates/workflows/ci-pipeline.dot +15 -0
- package/templates/workflows/feature-flow.dot +21 -0
- package/templates/workflows/release.dot +16 -0
- package/dist/__integration__/helpers.d.ts +0 -20
- package/dist/__integration__/helpers.d.ts.map +0 -1
- package/dist/__integration__/helpers.js +0 -31
- package/dist/__integration__/helpers.js.map +0 -1
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js.map +0 -1
- package/dist/commands/ci.d.ts.map +0 -1
- package/dist/commands/ci.js.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/llmstxt.d.ts.map +0 -1
- package/dist/commands/llmstxt.js.map +0 -1
- package/dist/commands/plugin.d.ts.map +0 -1
- package/dist/commands/plugin.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/common.d.ts.map +0 -1
- package/dist/lib/common.js.map +0 -1
- package/dist/lib/docker.d.ts.map +0 -1
- package/dist/lib/docker.js.map +0 -1
- package/dist/lib/frontmatter.d.ts.map +0 -1
- package/dist/lib/frontmatter.js.map +0 -1
- package/dist/lib/plugin.d.ts.map +0 -1
- package/dist/lib/plugin.js.map +0 -1
- package/dist/lib/template.d.ts.map +0 -1
- package/dist/lib/template.js.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/ui/AnalyzeUI.d.ts.map +0 -1
- package/dist/ui/AnalyzeUI.js.map +0 -1
- package/dist/ui/App.d.ts.map +0 -1
- package/dist/ui/App.js.map +0 -1
- package/dist/ui/CI.d.ts.map +0 -1
- package/dist/ui/CI.js.map +0 -1
- package/dist/ui/CIContext.d.ts.map +0 -1
- package/dist/ui/CIContext.js.map +0 -1
- package/dist/ui/CISelector.d.ts.map +0 -1
- package/dist/ui/CISelector.js.map +0 -1
- package/dist/ui/Doctor.d.ts.map +0 -1
- package/dist/ui/Doctor.js.map +0 -1
- package/dist/ui/Header.d.ts.map +0 -1
- package/dist/ui/Header.js.map +0 -1
- package/dist/ui/LlmsTxt.d.ts.map +0 -1
- package/dist/ui/LlmsTxt.js.map +0 -1
- package/dist/ui/MemorySelector.d.ts.map +0 -1
- package/dist/ui/MemorySelector.js.map +0 -1
- package/dist/ui/NameInput.d.ts.map +0 -1
- package/dist/ui/NameInput.js.map +0 -1
- package/dist/ui/OptionSelector.d.ts.map +0 -1
- package/dist/ui/OptionSelector.js.map +0 -1
- package/dist/ui/Plugin.d.ts.map +0 -1
- package/dist/ui/Plugin.js.map +0 -1
- package/dist/ui/Progress.d.ts.map +0 -1
- package/dist/ui/Progress.js.map +0 -1
- package/dist/ui/StackSelector.d.ts.map +0 -1
- package/dist/ui/StackSelector.js.map +0 -1
- package/dist/ui/Summary.d.ts.map +0 -1
- package/dist/ui/Summary.js.map +0 -1
- package/dist/ui/Welcome.d.ts.map +0 -1
- package/dist/ui/Welcome.js.map +0 -1
- package/dist/ui/theme.d.ts.map +0 -1
- 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
|