ship-safe 5.0.1 → 6.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/cli/agents/agentic-security-agent.js +1 -1
- package/cli/agents/api-fuzzer.js +1 -1
- package/cli/agents/auth-bypass-agent.js +2 -2
- package/cli/agents/config-auditor.js +3 -11
- package/cli/agents/exception-handler-agent.js +187 -0
- package/cli/agents/html-reporter.js +511 -370
- package/cli/agents/index.js +6 -0
- package/cli/agents/mcp-security-agent.js +182 -0
- package/cli/agents/pii-compliance-agent.js +4 -4
- package/cli/agents/scoring-engine.js +14 -6
- package/cli/agents/vibe-coding-agent.js +250 -0
- package/cli/bin/ship-safe.js +43 -6
- package/cli/commands/agent.js +4 -4
- package/cli/commands/audit.js +14 -7
- package/cli/commands/baseline.js +1 -1
- package/cli/commands/benchmark.js +327 -0
- package/cli/commands/ci.js +81 -1
- package/cli/commands/deps.js +73 -4
- package/cli/commands/diff.js +200 -0
- package/cli/commands/doctor.js +14 -4
- package/cli/commands/fix.js +1 -1
- package/cli/commands/init.js +349 -349
- package/cli/commands/red-team.js +2 -2
- package/cli/commands/remediate.js +153 -7
- package/cli/commands/vibe-check.js +276 -0
- package/cli/commands/watch.js +4 -4
- package/cli/index.js +7 -0
- package/cli/utils/cache-manager.js +1 -1
- package/cli/utils/output.js +5 -2
- package/cli/utils/patterns.js +3 -0
- package/cli/utils/pdf-generator.js +1 -1
- package/package.json +2 -2
|
@@ -196,7 +196,7 @@ const PATTERNS = [
|
|
|
196
196
|
{
|
|
197
197
|
rule: 'AGENT_OUTPUT_TO_ACTION',
|
|
198
198
|
title: 'Agent: LLM Output Directly Triggers Actions',
|
|
199
|
-
regex: /(?:completion|response|output|result|generated)[\s\S]{0,100}(?:\.execute|\.run|\.send|\.post|\.delete|\.pay|\.transfer|\.deploy)/g,
|
|
199
|
+
regex: /(?:completion|response|output|result|generated)[\s\S]{0,100}(?:\.execute\b|\.run\b|\.send\b|\.post\b|\.delete\b|\.pay\b|\.transfer\b|\.deploy\b)/g,
|
|
200
200
|
severity: 'high',
|
|
201
201
|
cwe: 'CWE-862',
|
|
202
202
|
owasp: 'A01:2021',
|
package/cli/agents/api-fuzzer.js
CHANGED
|
@@ -88,7 +88,7 @@ const PATTERNS = [
|
|
|
88
88
|
{
|
|
89
89
|
rule: 'API_UPLOAD_NO_TYPE_CHECK',
|
|
90
90
|
title: 'API: File Upload Without Type Validation',
|
|
91
|
-
regex: /(?:originalname|filename)\s*(?:\)|;)/g,
|
|
91
|
+
regex: /(?<!_)(?:originalname|filename)\s*(?:\)|;)/g,
|
|
92
92
|
severity: 'high',
|
|
93
93
|
cwe: 'CWE-434',
|
|
94
94
|
owasp: 'A04:2021',
|
|
@@ -25,7 +25,7 @@ const PATTERNS = [
|
|
|
25
25
|
{
|
|
26
26
|
rule: 'JWT_WEAK_SECRET',
|
|
27
27
|
title: 'JWT Weak HMAC Secret',
|
|
28
|
-
regex: /jwt\.sign\s*\([^)]
|
|
28
|
+
regex: /jwt\.sign\s*\([^)]{0,200}?,\s*['"][^'"]{1,15}['"]/g,
|
|
29
29
|
severity: 'high',
|
|
30
30
|
cwe: 'CWE-326',
|
|
31
31
|
owasp: 'A02:2021',
|
|
@@ -208,7 +208,7 @@ const PATTERNS = [
|
|
|
208
208
|
{
|
|
209
209
|
rule: 'NO_RATE_LIMIT_LOGIN',
|
|
210
210
|
title: 'No Rate Limiting on Authentication',
|
|
211
|
-
regex: /(?:\/login|\/signin|\/auth|\/register|\/signup|\/reset-password)\s*['"]/g,
|
|
211
|
+
regex: /(?<!\w)(?:\/login|\/signin|\/auth|\/register|\/signup|\/reset-password)\s*['"]/g,
|
|
212
212
|
severity: 'medium',
|
|
213
213
|
cwe: 'CWE-307',
|
|
214
214
|
owasp: 'A07:2021',
|
|
@@ -16,17 +16,9 @@ import { BaseAgent, createFinding } from './base-agent.js';
|
|
|
16
16
|
// =============================================================================
|
|
17
17
|
|
|
18
18
|
const DOCKERFILE_PATTERNS = [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
regex: /^(?!.*USER\s+\w).*CMD|ENTRYPOINT/gm,
|
|
23
|
-
severity: 'high',
|
|
24
|
-
cwe: 'CWE-250',
|
|
25
|
-
owasp: 'A05:2021',
|
|
26
|
-
confidence: 'medium',
|
|
27
|
-
description: 'No USER instruction found. Container runs as root by default.',
|
|
28
|
-
fix: 'Add USER nonroot before CMD/ENTRYPOINT',
|
|
29
|
-
},
|
|
19
|
+
// DOCKER_RUN_AS_ROOT removed — the per-line negative lookahead was broken
|
|
20
|
+
// (USER is on a separate line from CMD). The whole-file DOCKER_NO_USER check
|
|
21
|
+
// in scanDockerfile() handles this correctly.
|
|
30
22
|
{
|
|
31
23
|
rule: 'DOCKER_LATEST_TAG',
|
|
32
24
|
title: 'Docker: Using :latest Tag',
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exception Handling Agent
|
|
3
|
+
* =========================
|
|
4
|
+
*
|
|
5
|
+
* Detects mishandling of exceptional conditions — OWASP A10:2025.
|
|
6
|
+
*
|
|
7
|
+
* This is a NEW OWASP Top 10 2025 category that addresses:
|
|
8
|
+
* - Empty catch blocks that swallow errors silently
|
|
9
|
+
* - Generic catch-all without proper handling
|
|
10
|
+
* - Unhandled promise rejections
|
|
11
|
+
* - Missing React/Vue error boundaries
|
|
12
|
+
* - Stack traces leaked to production responses
|
|
13
|
+
* - Error responses that expose internal details
|
|
14
|
+
* - Missing finally/cleanup in resource handling
|
|
15
|
+
*
|
|
16
|
+
* Maps to: OWASP A10:2025, CWE-390, CWE-754, CWE-755
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import path from 'path';
|
|
20
|
+
import { BaseAgent, createFinding } from './base-agent.js';
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// EXCEPTION HANDLING PATTERNS
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
const PATTERNS = [
|
|
27
|
+
// ── Empty/Silent Error Handling ────────────────────────────────────────────
|
|
28
|
+
{
|
|
29
|
+
rule: 'EXCEPTION_EMPTY_CATCH',
|
|
30
|
+
title: 'Exception: Empty catch Block',
|
|
31
|
+
regex: /catch\s*\(\s*(?:e|err|error|ex|exception|_)?\s*\)\s*\{\s*\}/g,
|
|
32
|
+
severity: 'medium',
|
|
33
|
+
cwe: 'CWE-390',
|
|
34
|
+
owasp: 'A10:2025',
|
|
35
|
+
description: 'Empty catch block silently swallows errors. Failures go undetected, masking bugs and security issues.',
|
|
36
|
+
fix: 'Handle the error: catch (err) { logger.error("Context:", err); throw err; }',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
rule: 'EXCEPTION_CATCH_COMMENT_ONLY',
|
|
40
|
+
title: 'Exception: catch Block With Only a Comment',
|
|
41
|
+
regex: /catch\s*\(\s*\w*\s*\)\s*\{\s*\/\/[^\n]*\s*\}/g,
|
|
42
|
+
severity: 'low',
|
|
43
|
+
cwe: 'CWE-390',
|
|
44
|
+
owasp: 'A10:2025',
|
|
45
|
+
confidence: 'medium',
|
|
46
|
+
description: 'Catch block contains only a comment — error is still silently swallowed.',
|
|
47
|
+
fix: 'At minimum, log the error for debugging: catch (err) { /* expected for X */ logger.debug(err); }',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
rule: 'EXCEPTION_PYTHON_BARE_EXCEPT',
|
|
51
|
+
title: 'Exception: Python Bare except: (catches everything)',
|
|
52
|
+
regex: /^(\s*)except\s*:\s*$/gm,
|
|
53
|
+
severity: 'high',
|
|
54
|
+
cwe: 'CWE-396',
|
|
55
|
+
owasp: 'A10:2025',
|
|
56
|
+
description: 'Bare except: catches all exceptions including SystemExit and KeyboardInterrupt. Use except Exception instead.',
|
|
57
|
+
fix: 'Use specific exception types: except ValueError as e: or at least except Exception as e:',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
rule: 'EXCEPTION_PYTHON_PASS',
|
|
61
|
+
title: 'Exception: Python except with pass',
|
|
62
|
+
regex: /except\s+\w+(?:\s+as\s+\w+)?:\s*\n\s+pass\s*$/gm,
|
|
63
|
+
severity: 'medium',
|
|
64
|
+
cwe: 'CWE-390',
|
|
65
|
+
owasp: 'A10:2025',
|
|
66
|
+
description: 'Python except block with only pass — error is silently ignored.',
|
|
67
|
+
fix: 'Log the error: except Exception as e: logger.warning(f"Handled: {e}")',
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// ── Unhandled Async Errors ────────────────────────────────────────────────
|
|
71
|
+
{
|
|
72
|
+
rule: 'EXCEPTION_UNHANDLED_PROMISE',
|
|
73
|
+
title: 'Exception: Async Function Without Error Handling',
|
|
74
|
+
regex: /(?:app|router)\.(?:get|post|put|patch|delete)\s*\(\s*['"][^'"]+['"]\s*,\s*async\s+(?:\(\s*(?:req|request|ctx)\s*(?:,\s*(?:res|response|next))?\s*\)|(?:req|request|ctx))\s*=>\s*\{(?:(?!try\s*\{|\.catch\b).){0,500}\}/gs,
|
|
75
|
+
severity: 'medium',
|
|
76
|
+
cwe: 'CWE-755',
|
|
77
|
+
owasp: 'A10:2025',
|
|
78
|
+
confidence: 'low',
|
|
79
|
+
description: 'Async route handler without try/catch or .catch(). Unhandled rejection will crash the process or return 500.',
|
|
80
|
+
fix: 'Wrap in try/catch or use an async error wrapper: app.get("/path", asyncHandler(async (req, res) => { ... }))',
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// ── Error Information Leakage ─────────────────────────────────────────────
|
|
84
|
+
{
|
|
85
|
+
rule: 'EXCEPTION_STACK_IN_RESPONSE',
|
|
86
|
+
title: 'Exception: Stack Trace in Response',
|
|
87
|
+
regex: /(?:res\.(?:json|send|status)|response\.(?:json|send))\s*\([^)]*(?:\.stack|stackTrace|stack_trace|err\.message)/g,
|
|
88
|
+
severity: 'high',
|
|
89
|
+
cwe: 'CWE-209',
|
|
90
|
+
owasp: 'A10:2025',
|
|
91
|
+
description: 'Error stack trace or raw error message sent in API response. Reveals internal paths, versions, and logic to attackers.',
|
|
92
|
+
fix: 'Return generic error to client, log details server-side: res.status(500).json({ error: "Internal error", id: errorId })',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
rule: 'EXCEPTION_FULL_ERROR_RESPONSE',
|
|
96
|
+
title: 'Exception: Full Error Object in Response',
|
|
97
|
+
regex: /(?:res\.(?:json|send)|response\.(?:json|send))\s*\(\s*(?:err|error|e|ex)\s*\)/g,
|
|
98
|
+
severity: 'high',
|
|
99
|
+
cwe: 'CWE-209',
|
|
100
|
+
owasp: 'A10:2025',
|
|
101
|
+
description: 'Full error object sent directly in response. May contain stack traces, file paths, and sensitive context.',
|
|
102
|
+
fix: 'Send only safe fields: res.json({ error: err.message, code: err.code })',
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// ── Missing Error Boundaries ──────────────────────────────────────────────
|
|
106
|
+
{
|
|
107
|
+
rule: 'EXCEPTION_NO_ERROR_BOUNDARY',
|
|
108
|
+
title: 'Exception: React App Without Error Boundary',
|
|
109
|
+
regex: /(?:createRoot|ReactDOM\.render)\s*\(\s*(?:(?!ErrorBoundary|error-boundary|Sentry\.ErrorBoundary)[\s\S]){0,200}\)/g,
|
|
110
|
+
severity: 'medium',
|
|
111
|
+
cwe: 'CWE-755',
|
|
112
|
+
owasp: 'A10:2025',
|
|
113
|
+
confidence: 'low',
|
|
114
|
+
description: 'React app rendered without an Error Boundary. Unhandled component errors will crash the entire UI.',
|
|
115
|
+
fix: 'Wrap your app: <ErrorBoundary fallback={<ErrorPage />}><App /></ErrorBoundary>',
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// ── Generic Catch-All Without Rethrow ─────────────────────────────────────
|
|
119
|
+
{
|
|
120
|
+
rule: 'EXCEPTION_CATCH_ALL_NO_RETHROW',
|
|
121
|
+
title: 'Exception: catch(Exception) Without Rethrow',
|
|
122
|
+
regex: /catch\s*\(\s*(?:Exception|Error|Throwable|BaseException)\s+\w+\s*\)\s*\{(?:(?!throw\b|rethrow\b).){0,200}\}/gs,
|
|
123
|
+
severity: 'medium',
|
|
124
|
+
cwe: 'CWE-396',
|
|
125
|
+
owasp: 'A10:2025',
|
|
126
|
+
confidence: 'low',
|
|
127
|
+
description: 'Catching broad Exception/Error without rethrowing. Unexpected errors are silently absorbed.',
|
|
128
|
+
fix: 'Either rethrow unexpected exceptions or catch only specific exception types you can handle.',
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// ── Process-Level Missing Handlers ────────────────────────────────────────
|
|
132
|
+
{
|
|
133
|
+
rule: 'EXCEPTION_NO_UNCAUGHT_HANDLER',
|
|
134
|
+
title: 'Exception: Missing uncaughtException Handler',
|
|
135
|
+
regex: /(?:http\.createServer|express\(\)|new\s+Koa|fastify\(\)|new\s+Hono)(?:(?!uncaughtException|unhandledRejection).){0,2000}(?:\.listen|module\.exports)/gs,
|
|
136
|
+
severity: 'medium',
|
|
137
|
+
cwe: 'CWE-755',
|
|
138
|
+
owasp: 'A10:2025',
|
|
139
|
+
confidence: 'low',
|
|
140
|
+
description: 'Server application without process.on("uncaughtException") handler. Unhandled errors will crash the process.',
|
|
141
|
+
fix: 'Add: process.on("uncaughtException", (err) => { logger.error("Uncaught:", err); process.exit(1); })',
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// ── Resource Cleanup ──────────────────────────────────────────────────────
|
|
145
|
+
{
|
|
146
|
+
rule: 'EXCEPTION_OPEN_WITHOUT_CLOSE',
|
|
147
|
+
title: 'Exception: Resource Opened Without finally/close',
|
|
148
|
+
regex: /(?:createConnection|createPool|open\(|connect\()[\s\S]{0,500}(?:(?!\.finally|\.close\(|\.end\(|\.release\(|\.destroy\(|finally\s*\{|with\s).){200,}$/gm,
|
|
149
|
+
severity: 'low',
|
|
150
|
+
cwe: 'CWE-404',
|
|
151
|
+
owasp: 'A10:2025',
|
|
152
|
+
confidence: 'low',
|
|
153
|
+
description: 'Database connection or resource opened without visible close/finally block. Resource leaks under error conditions.',
|
|
154
|
+
fix: 'Use try/finally or a connection pool: try { conn = await pool.connect(); ... } finally { conn.release(); }',
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
// =============================================================================
|
|
159
|
+
// EXCEPTION HANDLER AGENT CLASS
|
|
160
|
+
// =============================================================================
|
|
161
|
+
|
|
162
|
+
export class ExceptionHandlerAgent extends BaseAgent {
|
|
163
|
+
constructor() {
|
|
164
|
+
super(
|
|
165
|
+
'ExceptionHandlerAgent',
|
|
166
|
+
'Detects mishandling of exceptional conditions (OWASP A10:2025)',
|
|
167
|
+
'injection', // maps to Code Vulnerabilities
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async analyze(context) {
|
|
172
|
+
const files = this.getFilesToScan(context);
|
|
173
|
+
const findings = [];
|
|
174
|
+
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
const ext = path.extname(file).toLowerCase();
|
|
177
|
+
if (!['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
|
|
178
|
+
'.py', '.rb', '.go', '.java', '.rs', '.php',
|
|
179
|
+
'.vue', '.svelte'].includes(ext)) continue;
|
|
180
|
+
|
|
181
|
+
const results = this.scanFileWithPatterns(file, PATTERNS);
|
|
182
|
+
findings.push(...results);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return findings;
|
|
186
|
+
}
|
|
187
|
+
}
|