@indicated/vibeguard 1.5.3 → 1.7.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/.claude/hooks/agentspace-notify.sh +103 -0
- package/.claude/settings.local.json +40 -1
- package/README.md +49 -1
- package/SECURITY_GAPS.md +160 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +238 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/scanner/parsers/python.d.ts.map +1 -1
- package/dist/scanner/parsers/python.js +110 -0
- package/dist/scanner/parsers/python.js.map +1 -1
- package/dist/scanner/rules/definitions.d.ts.map +1 -1
- package/dist/scanner/rules/definitions.js +463 -0
- package/dist/scanner/rules/definitions.js.map +1 -1
- package/package.json +1 -1
- package/src/mcp/server.ts +250 -0
- package/src/scanner/parsers/python.ts +117 -0
- package/src/scanner/rules/definitions.ts +482 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# AgentSpace hook — posts Claude Code activity
|
|
3
|
+
# Auto-generated by AgentSpace connect. Re-run the connect command to update.
|
|
4
|
+
|
|
5
|
+
API_KEY="1PrB_oCCtbm5ghzSNQ2VmL2HN0Asi1xE2m6RgjjxVl0"
|
|
6
|
+
API_URL="https://agentspace-efp3.vercel.app/api/spaces/boys/events"
|
|
7
|
+
AGENT_NAME="VibeGuard"
|
|
8
|
+
|
|
9
|
+
if [ -z "$API_KEY" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Read hook input from stdin
|
|
14
|
+
INPUT=$(cat)
|
|
15
|
+
|
|
16
|
+
EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"')
|
|
17
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
18
|
+
|
|
19
|
+
# Privacy-first project label: include only directory basename.
|
|
20
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
21
|
+
PROJECT_NAME=$(basename "$PROJECT_DIR" 2>/dev/null || echo "current")
|
|
22
|
+
if [ -z "$PROJECT_NAME" ] || [ "$PROJECT_NAME" = "/" ] || [ "$PROJECT_NAME" = "." ]; then
|
|
23
|
+
PROJECT_NAME="current"
|
|
24
|
+
fi
|
|
25
|
+
PROJECT_TASK="Working in ${PROJECT_NAME} project"
|
|
26
|
+
|
|
27
|
+
# Map hook events to coarse, non-sensitive activity categories.
|
|
28
|
+
case "$EVENT" in
|
|
29
|
+
SessionStart)
|
|
30
|
+
STATUS="working"
|
|
31
|
+
TYPE="session_start"
|
|
32
|
+
CONTENT="Session started"
|
|
33
|
+
TASK="$PROJECT_TASK"
|
|
34
|
+
;;
|
|
35
|
+
PostToolUse)
|
|
36
|
+
STATUS="working"
|
|
37
|
+
case "$TOOL" in
|
|
38
|
+
Bash)
|
|
39
|
+
TYPE="running_terminal"
|
|
40
|
+
CONTENT="Running checks"
|
|
41
|
+
TASK="$PROJECT_TASK"
|
|
42
|
+
;;
|
|
43
|
+
Read|Write|Edit|MultiEdit)
|
|
44
|
+
TYPE="coding"
|
|
45
|
+
CONTENT="Coding"
|
|
46
|
+
TASK="$PROJECT_TASK"
|
|
47
|
+
;;
|
|
48
|
+
Grep)
|
|
49
|
+
TYPE="searching_code"
|
|
50
|
+
CONTENT="Searching code"
|
|
51
|
+
TASK="$PROJECT_TASK"
|
|
52
|
+
;;
|
|
53
|
+
Glob)
|
|
54
|
+
TYPE="searching_file"
|
|
55
|
+
CONTENT="Searching for files"
|
|
56
|
+
TASK="$PROJECT_TASK"
|
|
57
|
+
;;
|
|
58
|
+
WebFetch|WebSearch)
|
|
59
|
+
TYPE="searching_web"
|
|
60
|
+
CONTENT="Researching online"
|
|
61
|
+
STATUS="thinking"
|
|
62
|
+
TASK="$PROJECT_TASK"
|
|
63
|
+
;;
|
|
64
|
+
Task)
|
|
65
|
+
TYPE="delegating"
|
|
66
|
+
CONTENT="Delegating work"
|
|
67
|
+
STATUS="thinking"
|
|
68
|
+
TASK="$PROJECT_TASK"
|
|
69
|
+
;;
|
|
70
|
+
*)
|
|
71
|
+
TYPE="activity"
|
|
72
|
+
CONTENT="Working"
|
|
73
|
+
TASK="$PROJECT_TASK"
|
|
74
|
+
;;
|
|
75
|
+
esac
|
|
76
|
+
;;
|
|
77
|
+
Stop)
|
|
78
|
+
STATUS="offline"
|
|
79
|
+
TYPE="session_end"
|
|
80
|
+
CONTENT="Session ended"
|
|
81
|
+
TASK="$PROJECT_TASK"
|
|
82
|
+
;;
|
|
83
|
+
*)
|
|
84
|
+
exit 0
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
|
|
88
|
+
PAYLOAD=$(jq -n \
|
|
89
|
+
--arg agentName "$AGENT_NAME" \
|
|
90
|
+
--arg content "$CONTENT" \
|
|
91
|
+
--arg status "$STATUS" \
|
|
92
|
+
--arg task "$TASK" \
|
|
93
|
+
--arg type "$TYPE" \
|
|
94
|
+
'{agentName: $agentName, content: $content, status: $status, task: $task, type: $type}')
|
|
95
|
+
|
|
96
|
+
# POST to AgentSpace
|
|
97
|
+
curl -s -X POST "$API_URL" \
|
|
98
|
+
-H "Content-Type: application/json" \
|
|
99
|
+
-H "Authorization: Bearer $API_KEY" \
|
|
100
|
+
-d "$PAYLOAD" \
|
|
101
|
+
-o /dev/null 2>/dev/null
|
|
102
|
+
|
|
103
|
+
exit 0
|
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"enabledMcpjsonServers": [
|
|
3
3
|
"vibeguard"
|
|
4
|
-
]
|
|
4
|
+
],
|
|
5
|
+
"hooks": {
|
|
6
|
+
"PostToolUse": [
|
|
7
|
+
{
|
|
8
|
+
"matcher": ".*",
|
|
9
|
+
"hooks": [
|
|
10
|
+
{
|
|
11
|
+
"type": "command",
|
|
12
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/agentspace-notify.sh",
|
|
13
|
+
"timeout": 10,
|
|
14
|
+
"async": true
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"Stop": [
|
|
20
|
+
{
|
|
21
|
+
"hooks": [
|
|
22
|
+
{
|
|
23
|
+
"type": "command",
|
|
24
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/agentspace-notify.sh",
|
|
25
|
+
"timeout": 10,
|
|
26
|
+
"async": true
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"SessionStart": [
|
|
32
|
+
{
|
|
33
|
+
"hooks": [
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/agentspace-notify.sh",
|
|
37
|
+
"timeout": 10,
|
|
38
|
+
"async": true
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
5
44
|
}
|
package/README.md
CHANGED
|
@@ -143,12 +143,22 @@ Create a `.vibeguardrc.json` in your project root:
|
|
|
143
143
|
- TypeScript (.ts, .tsx)
|
|
144
144
|
- Python (.py)
|
|
145
145
|
|
|
146
|
-
## Security Rules
|
|
146
|
+
## Security Rules (74 checks)
|
|
147
147
|
|
|
148
148
|
### Critical
|
|
149
149
|
- **hardcoded-secret**: Detects hardcoded API keys, tokens, and passwords
|
|
150
150
|
- **sql-injection**: Detects SQL injection vulnerabilities
|
|
151
151
|
- **eval-usage**: Detects dangerous eval() usage
|
|
152
|
+
- **command-injection**: User input passed to shell commands
|
|
153
|
+
- **insecure-deserialization**: Unsafe deserialization of untrusted data
|
|
154
|
+
- **insecure-randomness**: Math.random()/random module used for security tokens
|
|
155
|
+
- **weak-cryptography**: MD5/SHA1 used for passwords or security hashing
|
|
156
|
+
- **nosql-injection**: User input passed directly to MongoDB/NoSQL queries
|
|
157
|
+
- **password-plaintext-storage**: Passwords stored in database without hashing
|
|
158
|
+
- **django-debug-true**: Django DEBUG=True in production
|
|
159
|
+
- **django-secret-key-exposed**: Django SECRET_KEY hardcoded
|
|
160
|
+
- **django-raw-sql**: Django raw SQL with string formatting
|
|
161
|
+
- **flask-secret-key-exposed**: Flask SECRET_KEY hardcoded
|
|
152
162
|
|
|
153
163
|
### High
|
|
154
164
|
- **missing-auth-route**: API routes without authentication
|
|
@@ -157,15 +167,53 @@ Create a `.vibeguardrc.json` in your project root:
|
|
|
157
167
|
- **supabase-no-rls**: Supabase queries without RLS
|
|
158
168
|
- **firebase-no-rules**: Firebase without security rules
|
|
159
169
|
- **idor-vulnerability**: Potential IDOR vulnerabilities
|
|
170
|
+
- **path-traversal**: User input in file paths
|
|
171
|
+
- **ssrf-vulnerability**: Server-side request forgery
|
|
172
|
+
- **open-redirect**: Redirecting to user-supplied URLs
|
|
173
|
+
- **insecure-cookie**: Cookies without security flags
|
|
174
|
+
- **missing-csrf**: Forms without CSRF tokens
|
|
175
|
+
- **disabled-tls-verification**: SSL/TLS certificate verification disabled
|
|
176
|
+
- **unsafe-regex-construction**: User input in RegExp constructor (ReDoS)
|
|
177
|
+
- **postmessage-no-origin**: postMessage listener without origin check
|
|
178
|
+
- **hardcoded-db-credentials**: Database connection strings with embedded passwords
|
|
179
|
+
- **ssti-vulnerability**: Server-side template injection
|
|
180
|
+
- **unvalidated-file-upload**: File uploads without type/size validation
|
|
181
|
+
- **electron-insecure-config**: nodeIntegration/contextIsolation misconfiguration
|
|
182
|
+
- **prisma-raw-query**: Prisma raw queries with user input
|
|
183
|
+
- **nextjs-exposed-server-action**: Next.js server actions without auth
|
|
184
|
+
- **nextjs-api-route-no-auth**: Next.js API routes without auth
|
|
185
|
+
- **express-session-insecure**: Express session without secure flags
|
|
186
|
+
- **jwt-missing-exp**: JWT tokens signed without expiration
|
|
187
|
+
- **jwt-weak-secret**: JWT signed with short hardcoded secret
|
|
188
|
+
- **cors-credentials-wildcard**: CORS wildcard origin with credentials enabled
|
|
189
|
+
- **password-hash-weak**: Passwords hashed with MD5/SHA1/raw SHA256 instead of KDF
|
|
190
|
+
- **zip-slip**: Archive extraction without path validation
|
|
191
|
+
- **s3-public-read**: S3 bucket with public access policy
|
|
160
192
|
|
|
161
193
|
### Medium
|
|
162
194
|
- **permissive-cors**: Overly permissive CORS configuration
|
|
163
195
|
- **http-not-https**: HTTP instead of HTTPS
|
|
164
196
|
- **weak-password**: Weak password requirements
|
|
197
|
+
- **hardcoded-ip**: Hardcoded IP addresses
|
|
198
|
+
- **xxe-vulnerability**: XML External Entity injection
|
|
199
|
+
- **jwt-none-algorithm**: JWT accepting "none" algorithm
|
|
200
|
+
- **insecure-websocket**: ws:// instead of wss://
|
|
201
|
+
- **timing-attack**: Non-constant-time secret comparison
|
|
202
|
+
- **graphql-introspection-enabled**: GraphQL schema exposed in production
|
|
203
|
+
- **mass-assignment**: User input passed directly to ORM create/update
|
|
204
|
+
- **log-injection**: User input in log messages (CRLF injection)
|
|
205
|
+
- **python-assert-security**: Assert used for security checks (stripped with -O)
|
|
206
|
+
- **unsafe-tempfile**: tempfile.mktemp() race condition
|
|
207
|
+
- **missing-security-headers**: Express app without security headers (CSP, HSTS, X-Frame-Options)
|
|
208
|
+
- **csp-unsafe-inline**: CSP policy allows unsafe-inline or unsafe-eval
|
|
209
|
+
- **http-client-no-timeout**: Outbound HTTP requests without timeout
|
|
165
210
|
|
|
166
211
|
### Low
|
|
167
212
|
- **verbose-errors**: Detailed errors exposed to clients
|
|
168
213
|
- **missing-rate-limit**: Missing rate limiting on sensitive endpoints
|
|
214
|
+
- **console-log-sensitive**: Logging passwords/tokens/secrets
|
|
215
|
+
- **debug-mode-enabled**: Debug mode enabled in production
|
|
216
|
+
- **prototype-pollution**: Deep merging user input
|
|
169
217
|
|
|
170
218
|
## Exit Codes
|
|
171
219
|
|
package/SECURITY_GAPS.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# VibeGuard Security Gaps and Rule Backlog
|
|
2
|
+
|
|
3
|
+
This document summarizes what VibeGuard currently covers and the most important missing security areas. It also proposes a concrete backlog of new rules (with severity, tier, detection approach, and test ideas) to close the highest-impact gaps.
|
|
4
|
+
|
|
5
|
+
## Current Coverage Summary
|
|
6
|
+
|
|
7
|
+
VibeGuard currently focuses on code-level issues via regex and limited AST checks across JS/TS/Python. It includes secrets detection, injections (SQL/NoSQL/command), XSS via innerHTML, SSRF, IDOR, CSRF, insecure cookies, TLS verification disabled, weak crypto/random, path traversal, open redirects, prototype pollution, and several framework-specific rules.
|
|
8
|
+
|
|
9
|
+
## Highest-Impact Missing Areas (Prioritized)
|
|
10
|
+
|
|
11
|
+
1) Dependency and supply-chain security
|
|
12
|
+
- No dependency vulnerability scanning (SCA), lockfile parsing, or SBOM output.
|
|
13
|
+
- No license compliance checks.
|
|
14
|
+
|
|
15
|
+
2) Dataflow/taint analysis
|
|
16
|
+
- Most detections are syntactic. There is no inter-procedural or cross-file dataflow tracking.
|
|
17
|
+
- This leads to false positives and misses for real-world flows (user input -> sanitizer -> sink).
|
|
18
|
+
|
|
19
|
+
3) Infrastructure and deployment misconfiguration
|
|
20
|
+
- No IaC scanning for Terraform/CloudFormation/Kubernetes/Dockerfile.
|
|
21
|
+
- No cloud provider checks (S3 public buckets, overly permissive IAM, SGs, etc.).
|
|
22
|
+
|
|
23
|
+
4) Web security coverage gaps
|
|
24
|
+
- Missing checks for CSP/HSTS/X-Frame-Options/Referrer-Policy.
|
|
25
|
+
- Limited DOM XSS sink coverage (innerHTML only).
|
|
26
|
+
- Limited session/JWT hardening checks (missing exp, weak signing key, alg confusion beyond none, missing audience/issuer checks).
|
|
27
|
+
|
|
28
|
+
5) AuthN/AuthZ hardening
|
|
29
|
+
- Missing checks for weak password hashing (bcrypt cost, scrypt/argon2), plaintext storage.
|
|
30
|
+
- No MFA/2FA requirements detection (hard to do via static scanning, but common expectations).
|
|
31
|
+
|
|
32
|
+
6) File handling edge cases
|
|
33
|
+
- No archive extraction (zip slip) checks.
|
|
34
|
+
- No content-based validation for uploads (magic bytes).
|
|
35
|
+
|
|
36
|
+
## Proposed Rule Backlog (Concrete)
|
|
37
|
+
|
|
38
|
+
Each rule includes a pragmatic detection approach designed to fit the current regex/AST model, plus test ideas. These are scoped for low false positives.
|
|
39
|
+
|
|
40
|
+
### 1) jwt-missing-exp
|
|
41
|
+
- Severity: high
|
|
42
|
+
- Tier: free
|
|
43
|
+
- Languages: javascript, typescript, python
|
|
44
|
+
- Detect: JWT sign/encode without `exp` claim, or verification that explicitly ignores expiration.
|
|
45
|
+
- Patterns:
|
|
46
|
+
- JS: `jwt.sign(` without `expiresIn` in options.
|
|
47
|
+
- Python: `jwt.encode(` without `exp` in payload.
|
|
48
|
+
- Tests:
|
|
49
|
+
- Positive: `jwt.sign(payload, secret)`
|
|
50
|
+
- Negative: `jwt.sign(payload, secret, { expiresIn: '1h' })`
|
|
51
|
+
|
|
52
|
+
### 2) jwt-weak-secret
|
|
53
|
+
- Severity: high
|
|
54
|
+
- Tier: free
|
|
55
|
+
- Languages: javascript, typescript, python
|
|
56
|
+
- Detect: JWT signing secret hardcoded and short (e.g., < 16 chars).
|
|
57
|
+
- Patterns:
|
|
58
|
+
- JS: `jwt.sign(..., 'shortsecret'`)
|
|
59
|
+
- Python: `jwt.encode(..., 'shortsecret', algorithm='HS256')`
|
|
60
|
+
- Tests:
|
|
61
|
+
- Positive: `jwt.sign(payload, 'secret')`
|
|
62
|
+
- Negative: `jwt.sign(payload, process.env.JWT_SECRET)`
|
|
63
|
+
|
|
64
|
+
### 3) missing-security-headers
|
|
65
|
+
- Severity: medium
|
|
66
|
+
- Tier: free
|
|
67
|
+
- Languages: javascript, typescript
|
|
68
|
+
- Detect: Express apps missing a basic security headers setup when `express()` is instantiated.
|
|
69
|
+
- Approach: similar to `express-helmet-missing` but broaden to check manual header config (CSP/HSTS/X-Frame-Options) as alternatives.
|
|
70
|
+
- Tests:
|
|
71
|
+
- Positive: `const app = express();` with no helmet or header setup
|
|
72
|
+
- Negative: `app.use(helmet())` or `res.setHeader('Content-Security-Policy', ...)`
|
|
73
|
+
|
|
74
|
+
### 4) csp-unsafe-inline
|
|
75
|
+
- Severity: medium
|
|
76
|
+
- Tier: pro
|
|
77
|
+
- Languages: javascript, typescript
|
|
78
|
+
- Detect: CSP allows `unsafe-inline` or `unsafe-eval`.
|
|
79
|
+
- Patterns:
|
|
80
|
+
- `Content-Security-Policy` containing `unsafe-inline` or `unsafe-eval`.
|
|
81
|
+
- Tests:
|
|
82
|
+
- Positive: `res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'unsafe-inline'")`
|
|
83
|
+
- Negative: `script-src 'self' 'nonce-...'
|
|
84
|
+
|
|
85
|
+
### 5) cors-credentials-wildcard
|
|
86
|
+
- Severity: high
|
|
87
|
+
- Tier: free
|
|
88
|
+
- Languages: javascript, typescript, python
|
|
89
|
+
- Detect: `Access-Control-Allow-Origin: *` with `credentials: true` or `allow_credentials=True`.
|
|
90
|
+
- Patterns:
|
|
91
|
+
- Express: `cors({ origin: '*', credentials: true })`
|
|
92
|
+
- FastAPI: `allow_origins=['*'], allow_credentials=True`
|
|
93
|
+
- Tests:
|
|
94
|
+
- Positive: `cors({ origin: '*', credentials: true })`
|
|
95
|
+
- Negative: `cors({ origin: ['https://app.example.com'], credentials: true })`
|
|
96
|
+
|
|
97
|
+
### 6) password-hash-weak
|
|
98
|
+
- Severity: high
|
|
99
|
+
- Tier: free
|
|
100
|
+
- Languages: javascript, typescript, python
|
|
101
|
+
- Detect: password hashing with md5/sha1 or raw sha256 without salt or KDF for passwords.
|
|
102
|
+
- Patterns:
|
|
103
|
+
- JS: `crypto.createHash('sha256').update(password)`
|
|
104
|
+
- Python: `hashlib.sha256(password.encode())`
|
|
105
|
+
- Tests:
|
|
106
|
+
- Positive: `hashlib.sha256(password.encode()).hexdigest()`
|
|
107
|
+
- Negative: `bcrypt.hash(password, 12)`
|
|
108
|
+
|
|
109
|
+
### 7) password-plaintext-storage
|
|
110
|
+
- Severity: critical
|
|
111
|
+
- Tier: free
|
|
112
|
+
- Languages: javascript, typescript, python
|
|
113
|
+
- Detect: `password` field stored directly in DB create/update without hashing function present nearby.
|
|
114
|
+
- Approach: regex heuristic for `.create({ ... password: req.body.password ... })` without `hash`/`bcrypt` in same line.
|
|
115
|
+
- Tests:
|
|
116
|
+
- Positive: `User.create({ email, password: req.body.password })`
|
|
117
|
+
- Negative: `User.create({ email, password: await bcrypt.hash(req.body.password, 12) })`
|
|
118
|
+
|
|
119
|
+
### 8) zip-slip
|
|
120
|
+
- Severity: high
|
|
121
|
+
- Tier: pro
|
|
122
|
+
- Languages: javascript, typescript, python
|
|
123
|
+
- Detect: archive extraction without path validation.
|
|
124
|
+
- Patterns:
|
|
125
|
+
- JS: `extract` or `tar.x` with user-controlled path and no `path.resolve`/`path.normalize` checks.
|
|
126
|
+
- Python: `ZipFile.extractall(` with user path and no safe path check.
|
|
127
|
+
- Tests:
|
|
128
|
+
- Positive: `zipfile.ZipFile(file).extractall(upload_dir)`
|
|
129
|
+
- Negative: custom safe extraction that checks `os.path.abspath` and prefix
|
|
130
|
+
|
|
131
|
+
### 9) http-client-no-timeout
|
|
132
|
+
- Severity: medium
|
|
133
|
+
- Tier: free
|
|
134
|
+
- Languages: javascript, typescript, python
|
|
135
|
+
- Detect: outbound HTTP calls without timeout, which can be abused for resource exhaustion.
|
|
136
|
+
- Patterns:
|
|
137
|
+
- JS: `fetch(` without `timeout` or `AbortController` usage nearby.
|
|
138
|
+
- Python: `requests.get(` without `timeout=`
|
|
139
|
+
- Tests:
|
|
140
|
+
- Positive: `requests.get(url)`
|
|
141
|
+
- Negative: `requests.get(url, timeout=5)`
|
|
142
|
+
|
|
143
|
+
### 10) s3-public-read
|
|
144
|
+
- Severity: high
|
|
145
|
+
- Tier: pro
|
|
146
|
+
- Languages: javascript, typescript, python
|
|
147
|
+
- Detect: S3 bucket policy or ACL that grants public read/write.
|
|
148
|
+
- Patterns:
|
|
149
|
+
- JSON strings containing `"Principal": "*"` with `s3:GetObject` or `s3:*`.
|
|
150
|
+
- Tests:
|
|
151
|
+
- Positive: policy with `"Principal": "*"` and `"s3:GetObject"`
|
|
152
|
+
- Negative: policy limited to specific IAM roles
|
|
153
|
+
|
|
154
|
+
## Recommendations Beyond New Rules
|
|
155
|
+
|
|
156
|
+
- Add optional dependency scanning with `npm audit` / `pip-audit` integration (CLI opt-in).
|
|
157
|
+
- Add a simple taint engine for JS/TS to track user input through common sanitizers to sinks.
|
|
158
|
+
- Add SARIF output for GitHub/GitLab security reporting.
|
|
159
|
+
- Expand test fixtures to include full sample apps for end-to-end validation.
|
|
160
|
+
|
package/dist/mcp/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAojBA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA+OpD"}
|
package/dist/mcp/server.js
CHANGED
|
@@ -256,6 +256,244 @@ function analyzeContext(finding, cwd) {
|
|
|
256
256
|
}
|
|
257
257
|
question = 'Is this a public key (anon/publishable) or an actual secret? Supabase anon keys are safe to expose.';
|
|
258
258
|
break;
|
|
259
|
+
case 'insecure-randomness':
|
|
260
|
+
// Check if used for non-security purposes
|
|
261
|
+
if (finding.code.includes('color') || finding.code.includes('animation') ||
|
|
262
|
+
finding.code.includes('shuffle') || finding.code.includes('sample') ||
|
|
263
|
+
finding.code.includes('placeholder') || finding.code.includes('display')) {
|
|
264
|
+
signals.push({ signal: 'Appears to be used for non-security purposes (UI/display)', type: 'positive' });
|
|
265
|
+
confidence = 'low';
|
|
266
|
+
}
|
|
267
|
+
if (finding.code.includes('token') || finding.code.includes('secret') ||
|
|
268
|
+
finding.code.includes('password') || finding.code.includes('session') ||
|
|
269
|
+
finding.code.includes('nonce') || finding.code.includes('otp')) {
|
|
270
|
+
signals.push({ signal: 'Used for security-sensitive value generation', type: 'negative' });
|
|
271
|
+
confidence = 'high';
|
|
272
|
+
}
|
|
273
|
+
question = 'Is Math.random()/random used for security tokens or just UI/cosmetic purposes?';
|
|
274
|
+
break;
|
|
275
|
+
case 'weak-cryptography':
|
|
276
|
+
// Check if used for password hashing vs checksums
|
|
277
|
+
if (finding.code.includes('password') || finding.code.includes('secret') ||
|
|
278
|
+
finding.code.includes('token') || finding.code.includes('auth')) {
|
|
279
|
+
signals.push({ signal: 'Weak hash used for security-sensitive data', type: 'negative' });
|
|
280
|
+
confidence = 'high';
|
|
281
|
+
}
|
|
282
|
+
if (finding.code.includes('checksum') || finding.code.includes('etag') ||
|
|
283
|
+
finding.code.includes('cache') || finding.code.includes('fingerprint')) {
|
|
284
|
+
signals.push({ signal: 'May be used for non-security checksum/cache key', type: 'positive' });
|
|
285
|
+
confidence = 'low';
|
|
286
|
+
}
|
|
287
|
+
question = 'Is MD5/SHA1 used for security (bad) or for checksums/cache keys (acceptable)?';
|
|
288
|
+
break;
|
|
289
|
+
case 'nosql-injection':
|
|
290
|
+
// Check for input sanitization
|
|
291
|
+
if (fileContent.includes('mongo-sanitize') || fileContent.includes('express-mongo-sanitize') ||
|
|
292
|
+
fileContent.includes('sanitize') || fileContent.includes('validator')) {
|
|
293
|
+
signals.push({ signal: 'File uses input sanitization library', type: 'positive' });
|
|
294
|
+
confidence = 'low';
|
|
295
|
+
}
|
|
296
|
+
if (finding.code.includes('req.body') || finding.code.includes('req.query')) {
|
|
297
|
+
signals.push({ signal: 'User input passed directly to query', type: 'negative' });
|
|
298
|
+
confidence = 'high';
|
|
299
|
+
}
|
|
300
|
+
question = 'Is user input sanitized before being used in the NoSQL query?';
|
|
301
|
+
break;
|
|
302
|
+
case 'disabled-tls-verification':
|
|
303
|
+
if (fileContent.includes('development') || fileContent.includes('dev') ||
|
|
304
|
+
fileContent.includes('node_env') || fileContent.includes('test')) {
|
|
305
|
+
signals.push({ signal: 'May be conditionally enabled for development only', type: 'positive' });
|
|
306
|
+
confidence = 'medium';
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
signals.push({ signal: 'TLS verification unconditionally disabled', type: 'negative' });
|
|
310
|
+
confidence = 'high';
|
|
311
|
+
}
|
|
312
|
+
question = 'Is TLS verification only disabled in development, or also in production?';
|
|
313
|
+
break;
|
|
314
|
+
case 'unsafe-regex-construction':
|
|
315
|
+
if (fileContent.includes('escaperegex') || fileContent.includes('escape-string-regexp') ||
|
|
316
|
+
fileContent.includes('escaperegexp') || fileContent.includes('lodash') && fileContent.includes('escaperegexp')) {
|
|
317
|
+
signals.push({ signal: 'File imports regex escaping utility', type: 'positive' });
|
|
318
|
+
confidence = 'low';
|
|
319
|
+
}
|
|
320
|
+
if (finding.code.includes('req.') || finding.code.includes('query.') ||
|
|
321
|
+
finding.code.includes('params.') || finding.code.includes('body.')) {
|
|
322
|
+
signals.push({ signal: 'User input used directly in RegExp constructor', type: 'negative' });
|
|
323
|
+
confidence = 'high';
|
|
324
|
+
}
|
|
325
|
+
question = 'Is the user input escaped before being used in the RegExp constructor?';
|
|
326
|
+
break;
|
|
327
|
+
case 'postmessage-no-origin':
|
|
328
|
+
if (fileContent.includes('event.origin') || fileContent.includes('e.origin') ||
|
|
329
|
+
fileContent.includes('msg.origin')) {
|
|
330
|
+
signals.push({ signal: 'File checks origin elsewhere (may not be in handler)', type: 'positive' });
|
|
331
|
+
confidence = 'medium';
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
signals.push({ signal: 'No origin check found in file', type: 'negative' });
|
|
335
|
+
confidence = 'high';
|
|
336
|
+
}
|
|
337
|
+
question = 'Does the message event handler validate event.origin before processing data?';
|
|
338
|
+
break;
|
|
339
|
+
case 'hardcoded-db-credentials':
|
|
340
|
+
if (finding.code.includes('localhost') || finding.code.includes('127.0.0.1') ||
|
|
341
|
+
finding.code.includes('example.com') || finding.code.includes('test')) {
|
|
342
|
+
signals.push({ signal: 'Connection string points to localhost/test (likely development)', type: 'positive' });
|
|
343
|
+
confidence = 'low';
|
|
344
|
+
}
|
|
345
|
+
if (finding.code.includes('.env') || fileContent.includes('process.env') ||
|
|
346
|
+
fileContent.includes('os.environ')) {
|
|
347
|
+
signals.push({ signal: 'File also uses environment variables (may be fallback)', type: 'positive' });
|
|
348
|
+
confidence = 'medium';
|
|
349
|
+
}
|
|
350
|
+
question = 'Is this a development-only connection string, or does it contain production credentials?';
|
|
351
|
+
break;
|
|
352
|
+
case 'ssti-vulnerability':
|
|
353
|
+
signals.push({ signal: 'User-controlled template rendering is almost always dangerous', type: 'negative' });
|
|
354
|
+
confidence = 'high';
|
|
355
|
+
question = 'Is user input being rendered as a template? This is nearly always a critical vulnerability.';
|
|
356
|
+
break;
|
|
357
|
+
case 'timing-attack':
|
|
358
|
+
if (finding.code.includes('timingsafeequal') || finding.code.includes('compare_digest') ||
|
|
359
|
+
fileContent.includes('timingsafeequal') || fileContent.includes('compare_digest')) {
|
|
360
|
+
signals.push({ signal: 'File uses constant-time comparison elsewhere', type: 'positive' });
|
|
361
|
+
confidence = 'low';
|
|
362
|
+
}
|
|
363
|
+
if (finding.code.includes('===') && (finding.code.includes('token') || finding.code.includes('secret'))) {
|
|
364
|
+
signals.push({ signal: 'Direct === comparison of secret values', type: 'negative' });
|
|
365
|
+
confidence = 'medium';
|
|
366
|
+
}
|
|
367
|
+
question = 'Is this comparing secrets/tokens? If so, use constant-time comparison.';
|
|
368
|
+
break;
|
|
369
|
+
case 'electron-insecure-config':
|
|
370
|
+
if (finding.code.includes('nodeIntegration') && finding.code.includes('true')) {
|
|
371
|
+
signals.push({ signal: 'nodeIntegration enabled exposes Node.js APIs to web content', type: 'negative' });
|
|
372
|
+
confidence = 'high';
|
|
373
|
+
}
|
|
374
|
+
if (finding.code.includes('contextIsolation') && finding.code.includes('false')) {
|
|
375
|
+
signals.push({ signal: 'contextIsolation disabled allows prototype pollution from web content', type: 'negative' });
|
|
376
|
+
confidence = 'high';
|
|
377
|
+
}
|
|
378
|
+
question = 'Are these Electron security settings intentionally relaxed? This is dangerous for apps loading remote content.';
|
|
379
|
+
break;
|
|
380
|
+
case 'mass-assignment':
|
|
381
|
+
if (fileContent.includes('whitelist') || fileContent.includes('allowedfields') ||
|
|
382
|
+
fileContent.includes('pick') || fileContent.includes('only')) {
|
|
383
|
+
signals.push({ signal: 'File may use field whitelisting', type: 'positive' });
|
|
384
|
+
confidence = 'medium';
|
|
385
|
+
}
|
|
386
|
+
if (finding.code.includes('req.body') || finding.code.includes('request.data')) {
|
|
387
|
+
signals.push({ signal: 'Full request body passed to ORM operation', type: 'negative' });
|
|
388
|
+
confidence = 'high';
|
|
389
|
+
}
|
|
390
|
+
question = 'Is the user input filtered/whitelisted before being passed to the ORM create/update?';
|
|
391
|
+
break;
|
|
392
|
+
case 'jwt-missing-exp':
|
|
393
|
+
if (fileContent.includes('expiresIn') || fileContent.includes('exp:') || fileContent.includes("'exp'")) {
|
|
394
|
+
signals.push({ signal: 'File sets expiration elsewhere', type: 'positive' });
|
|
395
|
+
confidence = 'low';
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
signals.push({ signal: 'No expiration configuration found in file', type: 'negative' });
|
|
399
|
+
confidence = 'high';
|
|
400
|
+
}
|
|
401
|
+
question = 'Does the JWT payload include an exp claim, or is expiresIn set elsewhere?';
|
|
402
|
+
break;
|
|
403
|
+
case 'jwt-weak-secret':
|
|
404
|
+
if (finding.code.includes('process.env') || finding.code.includes('os.environ') ||
|
|
405
|
+
finding.code.includes('config.')) {
|
|
406
|
+
signals.push({ signal: 'Secret loaded from environment/config', type: 'positive' });
|
|
407
|
+
confidence = 'low';
|
|
408
|
+
}
|
|
409
|
+
if (/['"`][^'"`]{1,15}['"`]/.test(finding.code)) {
|
|
410
|
+
signals.push({ signal: 'Short hardcoded string used as signing secret', type: 'negative' });
|
|
411
|
+
confidence = 'high';
|
|
412
|
+
}
|
|
413
|
+
question = 'Is the JWT signing secret loaded from environment variables or hardcoded?';
|
|
414
|
+
break;
|
|
415
|
+
case 'csp-unsafe-inline':
|
|
416
|
+
if (fileContent.includes('nonce') || fileContent.includes('hash-')) {
|
|
417
|
+
signals.push({ signal: 'File uses nonce or hash-based CSP (may be transitioning)', type: 'positive' });
|
|
418
|
+
confidence = 'medium';
|
|
419
|
+
}
|
|
420
|
+
if (finding.code.includes('unsafe-eval')) {
|
|
421
|
+
signals.push({ signal: 'unsafe-eval allows arbitrary script execution', type: 'negative' });
|
|
422
|
+
confidence = 'high';
|
|
423
|
+
}
|
|
424
|
+
if (finding.code.includes('unsafe-inline')) {
|
|
425
|
+
signals.push({ signal: 'unsafe-inline defeats XSS protection from CSP', type: 'negative' });
|
|
426
|
+
confidence = 'high';
|
|
427
|
+
}
|
|
428
|
+
question = 'Is unsafe-inline/unsafe-eval required for third-party scripts, or can nonces/hashes be used instead?';
|
|
429
|
+
break;
|
|
430
|
+
case 'cors-credentials-wildcard':
|
|
431
|
+
signals.push({ signal: 'Wildcard origin with credentials allows any site to make authenticated requests', type: 'negative' });
|
|
432
|
+
confidence = 'high';
|
|
433
|
+
question = 'Should this API be accessible from any origin with credentials? Restrict to specific trusted origins.';
|
|
434
|
+
break;
|
|
435
|
+
case 'password-hash-weak':
|
|
436
|
+
if (fileContent.includes('bcrypt') || fileContent.includes('scrypt') || fileContent.includes('argon2')) {
|
|
437
|
+
signals.push({ signal: 'File also uses a proper KDF (may be migrating)', type: 'positive' });
|
|
438
|
+
confidence = 'medium';
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
signals.push({ signal: 'No proper password KDF found in file', type: 'negative' });
|
|
442
|
+
confidence = 'high';
|
|
443
|
+
}
|
|
444
|
+
question = 'Is this hashing passwords for storage? Use bcrypt/scrypt/argon2 instead of raw hash functions.';
|
|
445
|
+
break;
|
|
446
|
+
case 'password-plaintext-storage':
|
|
447
|
+
if (fileContent.includes('bcrypt') || fileContent.includes('hash') ||
|
|
448
|
+
fileContent.includes('scrypt') || fileContent.includes('argon2')) {
|
|
449
|
+
signals.push({ signal: 'File contains hashing logic (may be applied before this line)', type: 'positive' });
|
|
450
|
+
confidence = 'medium';
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
signals.push({ signal: 'No password hashing found in file', type: 'negative' });
|
|
454
|
+
confidence = 'high';
|
|
455
|
+
}
|
|
456
|
+
question = 'Is the password hashed before being stored? Check if bcrypt.hash() or similar is called upstream.';
|
|
457
|
+
break;
|
|
458
|
+
case 'zip-slip':
|
|
459
|
+
if (fileContent.includes('path.resolve') || fileContent.includes('path.normalize') ||
|
|
460
|
+
fileContent.includes('os.path.abspath') || fileContent.includes('startswith')) {
|
|
461
|
+
signals.push({ signal: 'File has path validation logic', type: 'positive' });
|
|
462
|
+
confidence = 'low';
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
signals.push({ signal: 'No path validation found before extraction', type: 'negative' });
|
|
466
|
+
confidence = 'high';
|
|
467
|
+
}
|
|
468
|
+
question = 'Are extracted file paths validated to prevent writing outside the target directory?';
|
|
469
|
+
break;
|
|
470
|
+
case 'http-client-no-timeout':
|
|
471
|
+
if (fileContent.includes('timeout') || fileContent.includes('AbortController') ||
|
|
472
|
+
fileContent.includes('signal')) {
|
|
473
|
+
signals.push({ signal: 'File configures timeout elsewhere (may be set globally)', type: 'positive' });
|
|
474
|
+
confidence = 'low';
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
signals.push({ signal: 'No timeout configuration found in file', type: 'negative' });
|
|
478
|
+
confidence = 'medium';
|
|
479
|
+
}
|
|
480
|
+
question = 'Is a timeout configured globally (e.g., via session defaults) or should one be added per-request?';
|
|
481
|
+
break;
|
|
482
|
+
case 's3-public-read':
|
|
483
|
+
if (relativePath.includes('test') || relativePath.includes('example') || relativePath.includes('sample')) {
|
|
484
|
+
signals.push({ signal: 'File appears to be test/example code', type: 'positive' });
|
|
485
|
+
confidence = 'low';
|
|
486
|
+
}
|
|
487
|
+
if (fileContent.includes('public') && (fileContent.includes('static') || fileContent.includes('assets') || fileContent.includes('cdn'))) {
|
|
488
|
+
signals.push({ signal: 'May be intentional public bucket for static assets/CDN', type: 'positive' });
|
|
489
|
+
confidence = 'medium';
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
signals.push({ signal: 'S3 bucket with public access policy', type: 'negative' });
|
|
493
|
+
confidence = 'high';
|
|
494
|
+
}
|
|
495
|
+
question = 'Is this S3 bucket intentionally public (static assets/CDN) or should access be restricted?';
|
|
496
|
+
break;
|
|
259
497
|
default:
|
|
260
498
|
question = `Verify if this ${finding.rule.name} finding is a real security issue in your specific context.`;
|
|
261
499
|
}
|