ghostpatch 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +213 -0
- package/__tests__/detectors.test.ts +224 -0
- package/__tests__/rules.test.ts +117 -0
- package/__tests__/scanner.test.ts +222 -0
- package/dist/ai/anthropic.d.ts +11 -0
- package/dist/ai/anthropic.d.ts.map +1 -0
- package/dist/ai/anthropic.js +76 -0
- package/dist/ai/anthropic.js.map +1 -0
- package/dist/ai/huggingface.d.ts +12 -0
- package/dist/ai/huggingface.d.ts.map +1 -0
- package/dist/ai/huggingface.js +95 -0
- package/dist/ai/huggingface.js.map +1 -0
- package/dist/ai/openai.d.ts +11 -0
- package/dist/ai/openai.d.ts.map +1 -0
- package/dist/ai/openai.js +71 -0
- package/dist/ai/openai.js.map +1 -0
- package/dist/ai/prompts.d.ts +5 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +101 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/ai/provider.d.ts +9 -0
- package/dist/ai/provider.d.ts.map +1 -0
- package/dist/ai/provider.js +66 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +318 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/reporter.d.ts +7 -0
- package/dist/core/reporter.d.ts.map +1 -0
- package/dist/core/reporter.js +366 -0
- package/dist/core/reporter.js.map +1 -0
- package/dist/core/rules.d.ts +8 -0
- package/dist/core/rules.d.ts.map +1 -0
- package/dist/core/rules.js +1077 -0
- package/dist/core/rules.js.map +1 -0
- package/dist/core/scanner.d.ts +6 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +217 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/core/severity.d.ts +100 -0
- package/dist/core/severity.d.ts.map +1 -0
- package/dist/core/severity.js +52 -0
- package/dist/core/severity.js.map +1 -0
- package/dist/detectors/auth.d.ts +3 -0
- package/dist/detectors/auth.d.ts.map +1 -0
- package/dist/detectors/auth.js +138 -0
- package/dist/detectors/auth.js.map +1 -0
- package/dist/detectors/crypto.d.ts +3 -0
- package/dist/detectors/crypto.d.ts.map +1 -0
- package/dist/detectors/crypto.js +128 -0
- package/dist/detectors/crypto.js.map +1 -0
- package/dist/detectors/dependency.d.ts +4 -0
- package/dist/detectors/dependency.d.ts.map +1 -0
- package/dist/detectors/dependency.js +267 -0
- package/dist/detectors/dependency.js.map +1 -0
- package/dist/detectors/deserialize.d.ts +3 -0
- package/dist/detectors/deserialize.d.ts.map +1 -0
- package/dist/detectors/deserialize.js +107 -0
- package/dist/detectors/deserialize.js.map +1 -0
- package/dist/detectors/injection.d.ts +3 -0
- package/dist/detectors/injection.d.ts.map +1 -0
- package/dist/detectors/injection.js +158 -0
- package/dist/detectors/injection.js.map +1 -0
- package/dist/detectors/misconfig.d.ts +3 -0
- package/dist/detectors/misconfig.d.ts.map +1 -0
- package/dist/detectors/misconfig.js +153 -0
- package/dist/detectors/misconfig.js.map +1 -0
- package/dist/detectors/pathtraversal.d.ts +3 -0
- package/dist/detectors/pathtraversal.d.ts.map +1 -0
- package/dist/detectors/pathtraversal.js +90 -0
- package/dist/detectors/pathtraversal.js.map +1 -0
- package/dist/detectors/prototype.d.ts +3 -0
- package/dist/detectors/prototype.d.ts.map +1 -0
- package/dist/detectors/prototype.js +79 -0
- package/dist/detectors/prototype.js.map +1 -0
- package/dist/detectors/secrets.d.ts +4 -0
- package/dist/detectors/secrets.d.ts.map +1 -0
- package/dist/detectors/secrets.js +137 -0
- package/dist/detectors/secrets.js.map +1 -0
- package/dist/detectors/ssrf.d.ts +3 -0
- package/dist/detectors/ssrf.d.ts.map +1 -0
- package/dist/detectors/ssrf.js +78 -0
- package/dist/detectors/ssrf.js.map +1 -0
- package/dist/detectors/zeroday.d.ts +9 -0
- package/dist/detectors/zeroday.d.ts.map +1 -0
- package/dist/detectors/zeroday.js +77 -0
- package/dist/detectors/zeroday.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +358 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +97 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/fingerprint.d.ts +5 -0
- package/dist/utils/fingerprint.d.ts.map +1 -0
- package/dist/utils/fingerprint.js +55 -0
- package/dist/utils/fingerprint.js.map +1 -0
- package/dist/utils/languages.d.ts +8 -0
- package/dist/utils/languages.d.ts.map +1 -0
- package/dist/utils/languages.js +128 -0
- package/dist/utils/languages.js.map +1 -0
- package/package.json +53 -0
- package/src/ai/anthropic.ts +82 -0
- package/src/ai/huggingface.ts +111 -0
- package/src/ai/openai.ts +75 -0
- package/src/ai/prompts.ts +100 -0
- package/src/ai/provider.ts +68 -0
- package/src/cli/index.ts +314 -0
- package/src/core/reporter.ts +356 -0
- package/src/core/rules.ts +1089 -0
- package/src/core/scanner.ts +201 -0
- package/src/core/severity.ts +140 -0
- package/src/detectors/auth.ts +152 -0
- package/src/detectors/crypto.ts +128 -0
- package/src/detectors/dependency.ts +240 -0
- package/src/detectors/deserialize.ts +106 -0
- package/src/detectors/injection.ts +172 -0
- package/src/detectors/misconfig.ts +152 -0
- package/src/detectors/pathtraversal.ts +89 -0
- package/src/detectors/prototype.ts +77 -0
- package/src/detectors/secrets.ts +138 -0
- package/src/detectors/ssrf.ts +77 -0
- package/src/detectors/zeroday.ts +93 -0
- package/src/index.ts +24 -0
- package/src/mcp/server.ts +379 -0
- package/src/utils/config.ts +64 -0
- package/src/utils/fingerprint.ts +21 -0
- package/src/utils/languages.ts +95 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GhostPatch Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# GhostPatch
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/ghostpatch)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](https://www.typescriptlang.org)
|
|
7
|
+
[](https://github.com/NeuralRays/ghostpatch)
|
|
8
|
+
[](https://owasp.org/www-project-top-ten/)
|
|
9
|
+
[](https://github.com/NeuralRays/ghostpatch)
|
|
10
|
+
[](https://github.com/NeuralRays/ghostpatch)
|
|
11
|
+
[](https://github.com/NeuralRays/ghostpatch)
|
|
12
|
+
[](https://github.com/NeuralRays/ghostpatch)
|
|
13
|
+
|
|
14
|
+
**AI-powered security vulnerability scanner** that runs locally via npm with zero infrastructure setup.
|
|
15
|
+
|
|
16
|
+
Uses **HuggingFace free models by default** (zero cost), with optional **Anthropic Claude** and **OpenAI GPT** for deeper analysis. Includes CLI, library API, and MCP server for AI coding agent integration.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **200+ security rules** covering OWASP Top 10, CWE, and more
|
|
21
|
+
- **15 languages**: TypeScript, JavaScript, Python, Java, Go, Rust, C, C++, C#, PHP, Ruby, Swift, Kotlin, Shell, SQL
|
|
22
|
+
- **10 specialized detectors**: injection, auth, crypto, secrets, SSRF, path traversal, prototype pollution, deserialization, dependency, misconfiguration
|
|
23
|
+
- **AI-powered zero-day detection** using HuggingFace (free), Anthropic, or OpenAI
|
|
24
|
+
- **4 output formats**: Terminal (colored), JSON, SARIF (GitHub/VS Code), HTML report
|
|
25
|
+
- **MCP server** with 8 tools for AI coding agent integration
|
|
26
|
+
- **Watch mode** for continuous scanning during development
|
|
27
|
+
- **Zero config** — works out of the box, configurable via `.ghostpatch.json`
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Install globally
|
|
33
|
+
npm install -g ghostpatch
|
|
34
|
+
|
|
35
|
+
# Scan current directory
|
|
36
|
+
ghostpatch scan
|
|
37
|
+
|
|
38
|
+
# Scan specific path
|
|
39
|
+
ghostpatch scan ./src
|
|
40
|
+
|
|
41
|
+
# Short alias
|
|
42
|
+
gp scan
|
|
43
|
+
|
|
44
|
+
# Scan for secrets only
|
|
45
|
+
ghostpatch secrets
|
|
46
|
+
|
|
47
|
+
# Check dependencies
|
|
48
|
+
ghostpatch deps
|
|
49
|
+
|
|
50
|
+
# Generate HTML report
|
|
51
|
+
ghostpatch report
|
|
52
|
+
|
|
53
|
+
# Enable AI analysis
|
|
54
|
+
ghostpatch scan --ai
|
|
55
|
+
ghostpatch scan --ai --provider anthropic
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## CLI Commands
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
ghostpatch scan [path] # Full security scan
|
|
62
|
+
-o, --output <format> # json | sarif | html | terminal (default: terminal)
|
|
63
|
+
-s, --severity <level> # critical | high | medium | low | info
|
|
64
|
+
--ai # Enable AI-enhanced analysis
|
|
65
|
+
--provider <name> # huggingface | anthropic | openai
|
|
66
|
+
--fix # Show fix suggestions
|
|
67
|
+
-q, --quiet # Minimal output
|
|
68
|
+
|
|
69
|
+
ghostpatch secrets [path] # Scan for hardcoded secrets only
|
|
70
|
+
ghostpatch deps [path] # Dependency vulnerability check
|
|
71
|
+
ghostpatch watch [path] # Watch mode — scan on file changes
|
|
72
|
+
ghostpatch report [path] # Generate HTML report
|
|
73
|
+
ghostpatch serve # Start MCP server (stdio)
|
|
74
|
+
ghostpatch install # Configure MCP for Claude Code
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## AI Providers
|
|
78
|
+
|
|
79
|
+
| Provider | Cost | Setup | Model |
|
|
80
|
+
|----------|------|-------|-------|
|
|
81
|
+
| **HuggingFace** (default) | Free | Optional `HF_TOKEN` env var | Qwen2.5-Coder-32B |
|
|
82
|
+
| **Anthropic** | Paid | `ANTHROPIC_API_KEY` env var | Claude Sonnet 4.5 |
|
|
83
|
+
| **OpenAI** | Paid | `OPENAI_API_KEY` env var | GPT-4o |
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Use free HuggingFace (default)
|
|
87
|
+
ghostpatch scan --ai
|
|
88
|
+
|
|
89
|
+
# Use Anthropic Claude
|
|
90
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
91
|
+
ghostpatch scan --ai --provider anthropic
|
|
92
|
+
|
|
93
|
+
# Use OpenAI
|
|
94
|
+
export OPENAI_API_KEY=sk-...
|
|
95
|
+
ghostpatch scan --ai --provider openai
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Library API
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { scan, generateReport, Severity } from 'ghostpatch';
|
|
102
|
+
|
|
103
|
+
// Full scan
|
|
104
|
+
const result = await scan('./my-project', {
|
|
105
|
+
severity: Severity.MEDIUM,
|
|
106
|
+
ai: true,
|
|
107
|
+
provider: 'huggingface',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Generate report
|
|
111
|
+
const html = generateReport(result, 'html');
|
|
112
|
+
const json = generateReport(result, 'json');
|
|
113
|
+
const sarif = generateReport(result, 'sarif');
|
|
114
|
+
|
|
115
|
+
// Access findings
|
|
116
|
+
console.log(`Found ${result.summary.total} issues`);
|
|
117
|
+
for (const finding of result.findings) {
|
|
118
|
+
console.log(`${finding.severity}: ${finding.title} at ${finding.filePath}:${finding.line}`);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## MCP Server (AI Coding Agent Integration)
|
|
123
|
+
|
|
124
|
+
GhostPatch includes an MCP server with 8 tools for seamless integration with AI coding agents like Claude Code.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Auto-configure for Claude Code
|
|
128
|
+
ghostpatch install
|
|
129
|
+
|
|
130
|
+
# Or manually start
|
|
131
|
+
ghostpatch serve
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### MCP Tools
|
|
135
|
+
|
|
136
|
+
| Tool | Description |
|
|
137
|
+
|------|-------------|
|
|
138
|
+
| `ghostpatch_scan` | Full security scan of project |
|
|
139
|
+
| `ghostpatch_scan_file` | Scan a single file |
|
|
140
|
+
| `ghostpatch_findings` | Get findings with filters |
|
|
141
|
+
| `ghostpatch_finding` | Detailed info on specific finding |
|
|
142
|
+
| `ghostpatch_secrets` | Scan for hardcoded secrets |
|
|
143
|
+
| `ghostpatch_dependencies` | Check dependencies for CVEs |
|
|
144
|
+
| `ghostpatch_ai_analyze` | AI-powered deep analysis |
|
|
145
|
+
| `ghostpatch_status` | Scanner status and stats |
|
|
146
|
+
|
|
147
|
+
## Configuration
|
|
148
|
+
|
|
149
|
+
Create `.ghostpatch.json` in your project root:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"exclude": ["node_modules/**", "dist/**", "*.min.js"],
|
|
154
|
+
"severity": "medium",
|
|
155
|
+
"ai": {
|
|
156
|
+
"provider": "huggingface",
|
|
157
|
+
"model": "auto"
|
|
158
|
+
},
|
|
159
|
+
"rules": {
|
|
160
|
+
"disabled": ["LOG003"],
|
|
161
|
+
"custom": []
|
|
162
|
+
},
|
|
163
|
+
"maxFileSize": 1048576,
|
|
164
|
+
"languages": "auto"
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Security Categories
|
|
169
|
+
|
|
170
|
+
| OWASP | Category | Rules |
|
|
171
|
+
|-------|----------|-------|
|
|
172
|
+
| A01 | Broken Access Control | BAC001–BAC010 |
|
|
173
|
+
| A02 | Cryptographic Failures | CRYPTO001–CRYPTO012, SEC001–SEC014 |
|
|
174
|
+
| A03 | Injection | INJ001–INJ018, PROTO001–PROTO002 |
|
|
175
|
+
| A04 | Insecure Design | DES001–DES007 |
|
|
176
|
+
| A05 | Security Misconfiguration | CFG001–CFG010 |
|
|
177
|
+
| A06 | Vulnerable Components | DEP001–DEP003 |
|
|
178
|
+
| A07 | Authentication Failures | AUTH001–AUTH008 |
|
|
179
|
+
| A08 | Data Integrity Failures | SER001–SER004 |
|
|
180
|
+
| A09 | Logging Failures | LOG001–LOG003 |
|
|
181
|
+
| A10 | SSRF | SSRF001–SSRF002 |
|
|
182
|
+
|
|
183
|
+
## Output Formats
|
|
184
|
+
|
|
185
|
+
### Terminal
|
|
186
|
+
Colored output with severity icons, code snippets, and fix suggestions.
|
|
187
|
+
|
|
188
|
+
### JSON
|
|
189
|
+
Machine-readable structured output for CI/CD integration.
|
|
190
|
+
|
|
191
|
+
### SARIF
|
|
192
|
+
Static Analysis Results Interchange Format — compatible with GitHub Code Scanning and VS Code.
|
|
193
|
+
|
|
194
|
+
### HTML
|
|
195
|
+
Professional standalone report with severity charts, finding details, and remediation advice.
|
|
196
|
+
|
|
197
|
+
## CI/CD Integration
|
|
198
|
+
|
|
199
|
+
```yaml
|
|
200
|
+
# GitHub Actions
|
|
201
|
+
- name: Security Scan
|
|
202
|
+
run: |
|
|
203
|
+
npx ghostpatch scan --output sarif -s medium > results.sarif
|
|
204
|
+
|
|
205
|
+
- name: Upload SARIF
|
|
206
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
207
|
+
with:
|
|
208
|
+
sarif_file: results.sarif
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import * as injectionDetector from '../src/detectors/injection';
|
|
3
|
+
import * as authDetector from '../src/detectors/auth';
|
|
4
|
+
import * as cryptoDetector from '../src/detectors/crypto';
|
|
5
|
+
import * as secretsDetector from '../src/detectors/secrets';
|
|
6
|
+
import * as ssrfDetector from '../src/detectors/ssrf';
|
|
7
|
+
import * as pathTraversalDetector from '../src/detectors/pathtraversal';
|
|
8
|
+
import * as prototypeDetector from '../src/detectors/prototype';
|
|
9
|
+
import * as deserializeDetector from '../src/detectors/deserialize';
|
|
10
|
+
import * as misconfigDetector from '../src/detectors/misconfig';
|
|
11
|
+
import { Severity } from '../src/core/severity';
|
|
12
|
+
|
|
13
|
+
describe('Injection Detector', () => {
|
|
14
|
+
it('should detect SQL injection via concatenation', () => {
|
|
15
|
+
const code = `
|
|
16
|
+
const userId = req.params.id;
|
|
17
|
+
db.query("SELECT * FROM users WHERE id=" + userId);
|
|
18
|
+
`;
|
|
19
|
+
const findings = injectionDetector.detect(code, 'test.js', 'javascript');
|
|
20
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
21
|
+
expect(findings[0].severity).toBe(Severity.CRITICAL);
|
|
22
|
+
expect(findings[0].ruleId).toContain('SQL');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should detect SQL injection via template literal', () => {
|
|
26
|
+
const code = 'db.query(`SELECT * FROM users WHERE id=${userId}`);';
|
|
27
|
+
const findings = injectionDetector.detect(code, 'test.ts', 'typescript');
|
|
28
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should detect eval usage', () => {
|
|
32
|
+
const code = 'eval(userInput);';
|
|
33
|
+
const findings = injectionDetector.detect(code, 'test.js', 'javascript');
|
|
34
|
+
const evalFindings = findings.filter(f => f.ruleId.includes('EVAL'));
|
|
35
|
+
expect(evalFindings.length).toBeGreaterThan(0);
|
|
36
|
+
expect(evalFindings[0].severity).toBe(Severity.CRITICAL);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should detect XSS via innerHTML', () => {
|
|
40
|
+
const code = 'element.innerHTML = userData;';
|
|
41
|
+
const findings = injectionDetector.detect(code, 'test.js', 'javascript');
|
|
42
|
+
const xssFindings = findings.filter(f => f.ruleId.includes('XSS'));
|
|
43
|
+
expect(xssFindings.length).toBeGreaterThan(0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should detect command injection', () => {
|
|
47
|
+
const code = 'exec(`rm -rf ${req.body.path}`);';
|
|
48
|
+
const findings = injectionDetector.detect(code, 'test.js', 'javascript');
|
|
49
|
+
const cmdFindings = findings.filter(f => f.ruleId.includes('CMD'));
|
|
50
|
+
expect(cmdFindings.length).toBeGreaterThan(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should detect NoSQL injection', () => {
|
|
54
|
+
const code = 'User.findOne(req.body);';
|
|
55
|
+
const findings = injectionDetector.detect(code, 'test.js', 'javascript');
|
|
56
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should not flag safe code', () => {
|
|
60
|
+
const code = `
|
|
61
|
+
const name = "hello";
|
|
62
|
+
console.log(name);
|
|
63
|
+
const x = 1 + 2;
|
|
64
|
+
`;
|
|
65
|
+
const findings = injectionDetector.detect(code, 'test.js', 'javascript');
|
|
66
|
+
expect(findings.length).toBe(0);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('Auth Detector', () => {
|
|
71
|
+
it('should detect hardcoded JWT secret', () => {
|
|
72
|
+
const code = 'jwt.sign(payload, "mySecretKey123456");';
|
|
73
|
+
const findings = authDetector.detect(code, 'test.js', 'javascript');
|
|
74
|
+
const jwtFindings = findings.filter(f => f.ruleId.includes('JWT'));
|
|
75
|
+
expect(jwtFindings.length).toBeGreaterThan(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should detect default credentials', () => {
|
|
79
|
+
const code = 'const password = "admin";';
|
|
80
|
+
const findings = authDetector.detect(code, 'test.js', 'javascript');
|
|
81
|
+
const credFindings = findings.filter(f => f.ruleId.includes('DEFAULT'));
|
|
82
|
+
expect(credFindings.length).toBeGreaterThan(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should detect privilege escalation', () => {
|
|
86
|
+
const code = 'user.role = req.body.role;';
|
|
87
|
+
const findings = authDetector.detect(code, 'test.js', 'javascript');
|
|
88
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Crypto Detector', () => {
|
|
93
|
+
it('should detect MD5 usage', () => {
|
|
94
|
+
const code = 'const hash = md5(data);';
|
|
95
|
+
const findings = cryptoDetector.detect(code, 'test.js', 'javascript');
|
|
96
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should detect Math.random for security', () => {
|
|
100
|
+
const code = 'const token = Math.random().toString(36);';
|
|
101
|
+
const findings = cryptoDetector.detect(code, 'test.js', 'javascript');
|
|
102
|
+
const randomFindings = findings.filter(f => f.ruleId.includes('RANDOM'));
|
|
103
|
+
expect(randomFindings.length).toBeGreaterThan(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should detect TLS verification disabled', () => {
|
|
107
|
+
const code = '{ rejectUnauthorized: false }';
|
|
108
|
+
const findings = cryptoDetector.detect(code, 'test.js', 'javascript');
|
|
109
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('Secrets Detector', () => {
|
|
114
|
+
it('should detect AWS access key', () => {
|
|
115
|
+
const code = 'const key = "AKIAIOSFODNN7REALKEY1";';
|
|
116
|
+
const findings = secretsDetector.detect(code, 'test.js', 'javascript');
|
|
117
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
118
|
+
expect(findings[0].severity).toBe(Severity.CRITICAL);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should detect GitHub token', () => {
|
|
122
|
+
const code = 'const token = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij";';
|
|
123
|
+
const findings = secretsDetector.detect(code, 'test.js', 'javascript');
|
|
124
|
+
const ghFindings = findings.filter(f => f.ruleId.includes('GITHUB'));
|
|
125
|
+
expect(ghFindings.length).toBeGreaterThan(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should detect private key', () => {
|
|
129
|
+
const code = '-----BEGIN RSA PRIVATE KEY-----';
|
|
130
|
+
const findings = secretsDetector.detect(code, 'test.pem', 'generic');
|
|
131
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should detect database connection string', () => {
|
|
135
|
+
const code = 'const uri = "mongodb://admin:password123@prod-server:27017/mydb";';
|
|
136
|
+
const findings = secretsDetector.detect(code, 'test.js', 'javascript');
|
|
137
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should not flag example credentials', () => {
|
|
141
|
+
const code = 'const key = "your_api_key_here";';
|
|
142
|
+
const findings = secretsDetector.detect(code, 'test.js', 'javascript');
|
|
143
|
+
const apiKeyFindings = findings.filter(f => f.ruleId === 'SEC-GENERIC-API-KEY');
|
|
144
|
+
expect(apiKeyFindings.length).toBe(0);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('SSRF Detector', () => {
|
|
149
|
+
it('should detect SSRF via user URL', () => {
|
|
150
|
+
const code = 'fetch(req.query.url);';
|
|
151
|
+
const findings = ssrfDetector.detect(code, 'test.js', 'javascript');
|
|
152
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should not flag non-backend languages', () => {
|
|
156
|
+
const code = 'fetch(req.query.url);';
|
|
157
|
+
const findings = ssrfDetector.detect(code, 'test.html', 'html');
|
|
158
|
+
expect(findings.length).toBe(0);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('Path Traversal Detector', () => {
|
|
163
|
+
it('should detect path traversal', () => {
|
|
164
|
+
const code = 'fs.readFile(req.params.filename);';
|
|
165
|
+
const findings = pathTraversalDetector.detect(code, 'test.js', 'javascript');
|
|
166
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should detect directory traversal sequences', () => {
|
|
170
|
+
const code = 'const path = "../../../etc/passwd";';
|
|
171
|
+
const findings = pathTraversalDetector.detect(code, 'test.js', 'javascript');
|
|
172
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('Prototype Pollution Detector', () => {
|
|
177
|
+
it('should detect Object.assign merge', () => {
|
|
178
|
+
const code = 'Object.assign(target, userInput);';
|
|
179
|
+
const findings = prototypeDetector.detect(code, 'test.js', 'javascript');
|
|
180
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should not flag non-JS languages', () => {
|
|
184
|
+
const code = 'Object.assign(target, userInput);';
|
|
185
|
+
const findings = prototypeDetector.detect(code, 'test.py', 'python');
|
|
186
|
+
expect(findings.length).toBe(0);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('Deserialization Detector', () => {
|
|
191
|
+
it('should detect pickle.load in Python', () => {
|
|
192
|
+
const code = 'data = pickle.load(file)';
|
|
193
|
+
const findings = deserializeDetector.detect(code, 'test.py', 'python');
|
|
194
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
195
|
+
expect(findings[0].severity).toBe(Severity.CRITICAL);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should detect Java ObjectInputStream', () => {
|
|
199
|
+
const code = 'ObjectInputStream ois = new ObjectInputStream(input);';
|
|
200
|
+
const findings = deserializeDetector.detect(code, 'Test.java', 'java');
|
|
201
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('Misconfiguration Detector', () => {
|
|
206
|
+
it('should detect insecure cookies', () => {
|
|
207
|
+
const code = '{ secure: false, httpOnly: false }';
|
|
208
|
+
const findings = misconfigDetector.detect(code, 'test.js', 'javascript');
|
|
209
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should detect CORS wildcard', () => {
|
|
213
|
+
const code = "cors({ origin: '*' })";
|
|
214
|
+
const findings = misconfigDetector.detect(code, 'test.js', 'javascript');
|
|
215
|
+
const corsFindings = findings.filter(f => f.ruleId.includes('CORS'));
|
|
216
|
+
expect(corsFindings.length).toBeGreaterThan(0);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should detect nodeIntegration enabled', () => {
|
|
220
|
+
const code = 'webPreferences: { nodeIntegration: true }';
|
|
221
|
+
const findings = misconfigDetector.detect(code, 'test.js', 'javascript');
|
|
222
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_RULES, getRulesForLanguage, getRuleById, getRulesByOwasp, getEnabledRules } from '../src/core/rules';
|
|
3
|
+
import { Severity } from '../src/core/severity';
|
|
4
|
+
|
|
5
|
+
describe('Rules Engine', () => {
|
|
6
|
+
it('should have 100+ rules', () => {
|
|
7
|
+
expect(ALL_RULES.length).toBeGreaterThanOrEqual(100);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should have rules for each OWASP category', () => {
|
|
11
|
+
const categories = ['A01', 'A02', 'A03', 'A04', 'A05', 'A06', 'A07', 'A08', 'A09', 'A10'];
|
|
12
|
+
for (const cat of categories) {
|
|
13
|
+
const rules = getRulesByOwasp(cat);
|
|
14
|
+
expect(rules.length).toBeGreaterThan(0);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return rules for JavaScript', () => {
|
|
19
|
+
const rules = getRulesForLanguage('javascript');
|
|
20
|
+
expect(rules.length).toBeGreaterThan(20);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return rules for Python', () => {
|
|
24
|
+
const rules = getRulesForLanguage('python');
|
|
25
|
+
expect(rules.length).toBeGreaterThan(10);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return rules for Java', () => {
|
|
29
|
+
const rules = getRulesForLanguage('java');
|
|
30
|
+
expect(rules.length).toBeGreaterThan(5);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should find rule by ID', () => {
|
|
34
|
+
const rule = getRuleById('INJ001');
|
|
35
|
+
expect(rule).toBeDefined();
|
|
36
|
+
expect(rule!.name).toContain('SQL Injection');
|
|
37
|
+
expect(rule!.severity).toBe(Severity.CRITICAL);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should filter disabled rules', () => {
|
|
41
|
+
const allRules = getEnabledRules();
|
|
42
|
+
const filtered = getEnabledRules(['INJ001', 'INJ002']);
|
|
43
|
+
expect(filtered.length).toBe(allRules.length - 2);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('every rule should have required fields', () => {
|
|
47
|
+
for (const rule of ALL_RULES) {
|
|
48
|
+
expect(rule.id).toBeTruthy();
|
|
49
|
+
expect(rule.name).toBeTruthy();
|
|
50
|
+
expect(rule.severity).toBeTruthy();
|
|
51
|
+
expect(rule.pattern).toBeInstanceOf(RegExp);
|
|
52
|
+
expect(rule.languages.length).toBeGreaterThan(0);
|
|
53
|
+
expect(rule.description).toBeTruthy();
|
|
54
|
+
expect(rule.remediation).toBeTruthy();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('SQL injection rule should match vulnerable code', () => {
|
|
59
|
+
const rule = getRuleById('INJ001')!;
|
|
60
|
+
expect(rule.pattern.test('db.query("SELECT * FROM users WHERE id=" + userId)')).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('SQL injection rule should match INSERT', () => {
|
|
64
|
+
const rule = getRuleById('INJ001')!;
|
|
65
|
+
expect(rule.pattern.test('db.query("INSERT INTO users VALUES(" + name + ")")')).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('eval rule should match eval with variable', () => {
|
|
69
|
+
const rule = getRuleById('INJ010')!;
|
|
70
|
+
expect(rule.pattern.test('eval(userInput)')).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('eval rule should not match eval with static string', () => {
|
|
74
|
+
const rule = getRuleById('INJ010')!;
|
|
75
|
+
expect(rule.pattern.test('eval("2+2")')).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('AWS key pattern should match real format', () => {
|
|
79
|
+
const rule = getRuleById('SEC001')!;
|
|
80
|
+
expect(rule.pattern.test('AKIAIOSFODNN7EXAMPLE')).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('GitHub token pattern should match', () => {
|
|
84
|
+
const rule = getRuleById('SEC005')!;
|
|
85
|
+
expect(rule.pattern.test('ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij')).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('private key pattern should match', () => {
|
|
89
|
+
const rule = getRuleById('SEC004')!;
|
|
90
|
+
expect(rule.pattern.test('-----BEGIN RSA PRIVATE KEY-----')).toBe(true);
|
|
91
|
+
expect(rule.pattern.test('-----BEGIN PRIVATE KEY-----')).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('TLS verification disabled pattern should match', () => {
|
|
95
|
+
const rule = getRuleById('CRYPTO009')!;
|
|
96
|
+
expect(rule.pattern.test('rejectUnauthorized: false')).toBe(true);
|
|
97
|
+
expect(rule.pattern.test('verify = False')).toBe(true);
|
|
98
|
+
expect(rule.pattern.test('InsecureSkipVerify: true')).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('default credentials pattern should match common passwords', () => {
|
|
102
|
+
const rule = getRuleById('CFG002')!;
|
|
103
|
+
expect(rule.pattern.test('password: "admin"')).toBe(true);
|
|
104
|
+
expect(rule.pattern.test("password = 'password'")).toBe(true);
|
|
105
|
+
expect(rule.pattern.test('pwd: "123456"')).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('CORS wildcard should match', () => {
|
|
109
|
+
const rule = getRuleById('BAC004')!;
|
|
110
|
+
expect(rule.pattern.test("Access-Control-Allow-Origin: '*'")).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('command injection pattern should match', () => {
|
|
114
|
+
const rule = getRuleById('INJ005')!;
|
|
115
|
+
expect(rule.pattern.test('exec(`ls ${userInput}`)')).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
});
|