@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.
@@ -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
 
@@ -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
+
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AA0TA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA+OpD"}
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"}
@@ -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
  }