ship-safe 5.0.0 → 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/base-agent.js +2 -1
- 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 +301 -301
- 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 +6 -4
- package/cli/commands/audit.js +85 -14
- package/cli/commands/baseline.js +3 -2
- package/cli/commands/benchmark.js +327 -0
- package/cli/commands/ci.js +342 -260
- 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 +218 -216
- package/cli/commands/init.js +349 -349
- package/cli/commands/mcp.js +304 -303
- package/cli/commands/red-team.js +2 -2
- package/cli/commands/remediate.js +155 -7
- package/cli/commands/scan.js +567 -565
- package/cli/commands/score.js +2 -0
- package/cli/commands/vibe-check.js +276 -0
- package/cli/commands/watch.js +161 -160
- package/cli/index.js +8 -1
- package/cli/utils/cache-manager.js +1 -1
- package/cli/utils/output.js +5 -2
- package/cli/utils/patterns.js +1121 -1104
- package/cli/utils/pdf-generator.js +1 -1
- package/package.json +2 -2
package/cli/agents/index.js
CHANGED
|
@@ -23,6 +23,8 @@ export { MCPSecurityAgent } from './mcp-security-agent.js';
|
|
|
23
23
|
export { AgenticSecurityAgent } from './agentic-security-agent.js';
|
|
24
24
|
export { RAGSecurityAgent } from './rag-security-agent.js';
|
|
25
25
|
export { PIIComplianceAgent } from './pii-compliance-agent.js';
|
|
26
|
+
export { VibeCodingAgent } from './vibe-coding-agent.js';
|
|
27
|
+
export { ExceptionHandlerAgent } from './exception-handler-agent.js';
|
|
26
28
|
export { VerifierAgent } from './verifier-agent.js';
|
|
27
29
|
export { DeepAnalyzer } from './deep-analyzer.js';
|
|
28
30
|
export { ScoringEngine, GRADES, CATEGORIES } from './scoring-engine.js';
|
|
@@ -50,6 +52,8 @@ import { MCPSecurityAgent as MCPSecurityAgentClass } from './mcp-security-agent.
|
|
|
50
52
|
import { AgenticSecurityAgent as AgenticSecurityAgentClass } from './agentic-security-agent.js';
|
|
51
53
|
import { RAGSecurityAgent as RAGSecurityAgentClass } from './rag-security-agent.js';
|
|
52
54
|
import { PIIComplianceAgent as PIIComplianceAgentClass } from './pii-compliance-agent.js';
|
|
55
|
+
import { VibeCodingAgent as VibeCodingAgentClass } from './vibe-coding-agent.js';
|
|
56
|
+
import { ExceptionHandlerAgent as ExceptionHandlerAgentClass } from './exception-handler-agent.js';
|
|
53
57
|
|
|
54
58
|
export function buildOrchestrator() {
|
|
55
59
|
const orchestrator = new OrchestratorClass();
|
|
@@ -69,6 +73,8 @@ export function buildOrchestrator() {
|
|
|
69
73
|
new AgenticSecurityAgentClass(),
|
|
70
74
|
new RAGSecurityAgentClass(),
|
|
71
75
|
new PIIComplianceAgentClass(),
|
|
76
|
+
new VibeCodingAgentClass(),
|
|
77
|
+
new ExceptionHandlerAgentClass(),
|
|
72
78
|
]);
|
|
73
79
|
return orchestrator;
|
|
74
80
|
}
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
* Maps to: OWASP Agentic AI ASI02 (Tool Misuse), ASI03 (Privilege Abuse)
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import fs from 'fs';
|
|
19
20
|
import path from 'path';
|
|
21
|
+
import os from 'os';
|
|
20
22
|
import { BaseAgent } from './base-agent.js';
|
|
21
23
|
|
|
22
24
|
// =============================================================================
|
|
@@ -230,8 +232,26 @@ const MCP_CONFIG_FILES = [
|
|
|
230
232
|
'mcp-config.json',
|
|
231
233
|
'claude_desktop_config.json',
|
|
232
234
|
'.cursor/mcp.json',
|
|
235
|
+
'.vscode/mcp.json',
|
|
233
236
|
];
|
|
234
237
|
|
|
238
|
+
// Well-known official MCP server packages
|
|
239
|
+
const OFFICIAL_MCP_SERVERS = new Set([
|
|
240
|
+
'@modelcontextprotocol/server-filesystem',
|
|
241
|
+
'@modelcontextprotocol/server-github',
|
|
242
|
+
'@modelcontextprotocol/server-gitlab',
|
|
243
|
+
'@modelcontextprotocol/server-google-maps',
|
|
244
|
+
'@modelcontextprotocol/server-memory',
|
|
245
|
+
'@modelcontextprotocol/server-postgres',
|
|
246
|
+
'@modelcontextprotocol/server-puppeteer',
|
|
247
|
+
'@modelcontextprotocol/server-slack',
|
|
248
|
+
'@modelcontextprotocol/server-sqlite',
|
|
249
|
+
'@modelcontextprotocol/server-brave-search',
|
|
250
|
+
'@modelcontextprotocol/server-fetch',
|
|
251
|
+
'@modelcontextprotocol/server-everything',
|
|
252
|
+
'@modelcontextprotocol/server-sequential-thinking',
|
|
253
|
+
]);
|
|
254
|
+
|
|
235
255
|
// =============================================================================
|
|
236
256
|
// MCP SECURITY AGENT
|
|
237
257
|
// =============================================================================
|
|
@@ -281,6 +301,15 @@ export class MCPSecurityAgent extends BaseAgent {
|
|
|
281
301
|
findings = findings.concat(this._checkServerAuth(file));
|
|
282
302
|
}
|
|
283
303
|
|
|
304
|
+
// ── 4. Check MCP configs for typosquatting & over-permissioned servers ─
|
|
305
|
+
for (const file of configFiles) {
|
|
306
|
+
findings = findings.concat(this._checkMcpTyposquatting(file));
|
|
307
|
+
findings = findings.concat(this._checkOverPermissioned(file));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── 5. Detect shadow MCP configs (not in version control) ───────────
|
|
311
|
+
findings = findings.concat(this._detectShadowMcpConfigs(rootPath));
|
|
312
|
+
|
|
284
313
|
return findings;
|
|
285
314
|
}
|
|
286
315
|
|
|
@@ -353,6 +382,159 @@ export class MCPSecurityAgent extends BaseAgent {
|
|
|
353
382
|
|
|
354
383
|
return findings;
|
|
355
384
|
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Detect possible typosquatted MCP server names in config.
|
|
388
|
+
*/
|
|
389
|
+
_checkMcpTyposquatting(filePath) {
|
|
390
|
+
const content = this.readFile(filePath);
|
|
391
|
+
if (!content) return [];
|
|
392
|
+
const findings = [];
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
const config = JSON.parse(content);
|
|
396
|
+
const servers = config.mcpServers || config.servers || {};
|
|
397
|
+
|
|
398
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
399
|
+
const cmd = server.command || '';
|
|
400
|
+
const args = (server.args || []).join(' ');
|
|
401
|
+
const fullCmd = `${cmd} ${args}`;
|
|
402
|
+
|
|
403
|
+
// Check if server uses an npx package that looks like a typosquat of official ones
|
|
404
|
+
const npxMatch = fullCmd.match(/npx\s+(?:-[^\s]+\s+)*([^\s]+)/);
|
|
405
|
+
if (npxMatch) {
|
|
406
|
+
const pkg = npxMatch[1];
|
|
407
|
+
for (const official of OFFICIAL_MCP_SERVERS) {
|
|
408
|
+
const distance = this._levenshtein(pkg, official);
|
|
409
|
+
if (distance > 0 && distance <= 3 && pkg !== official) {
|
|
410
|
+
findings.push({
|
|
411
|
+
file: filePath, line: 1, column: 0,
|
|
412
|
+
severity: 'critical',
|
|
413
|
+
category: this.category,
|
|
414
|
+
rule: 'MCP_TYPOSQUAT_SERVER',
|
|
415
|
+
title: `MCP: Possible Typosquatted Server "${pkg}"`,
|
|
416
|
+
description: `MCP server package "${pkg}" is ${distance} char(s) from official "${official}". Could be a supply chain attack.`,
|
|
417
|
+
matched: pkg,
|
|
418
|
+
confidence: 'medium',
|
|
419
|
+
cwe: 'CWE-494',
|
|
420
|
+
owasp: 'A08:2021',
|
|
421
|
+
fix: `Verify this is the correct package. Did you mean "${official}"?`,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} catch { /* not valid JSON */ }
|
|
428
|
+
|
|
429
|
+
return findings;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Check for over-permissioned MCP servers (filesystem access to / or ~).
|
|
434
|
+
*/
|
|
435
|
+
_checkOverPermissioned(filePath) {
|
|
436
|
+
const content = this.readFile(filePath);
|
|
437
|
+
if (!content) return [];
|
|
438
|
+
const findings = [];
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
const config = JSON.parse(content);
|
|
442
|
+
const servers = config.mcpServers || config.servers || {};
|
|
443
|
+
|
|
444
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
445
|
+
const args = server.args || [];
|
|
446
|
+
const argsStr = args.join(' ');
|
|
447
|
+
|
|
448
|
+
// Check for root/home filesystem access
|
|
449
|
+
if (/(?:^\/\s|['" ]\/['"]?\s|\/Users\/|\/home\/|\\Users\\|C:\\)/.test(argsStr)) {
|
|
450
|
+
const hasWideAccess = args.some(a =>
|
|
451
|
+
a === '/' || a === '~' || a === '%USERPROFILE%' ||
|
|
452
|
+
/^\/(?:Users|home)\/[^/]+$/.test(a) ||
|
|
453
|
+
/^[A-Z]:\\(?:Users\\)?[^\\]*$/.test(a)
|
|
454
|
+
);
|
|
455
|
+
if (hasWideAccess) {
|
|
456
|
+
findings.push({
|
|
457
|
+
file: filePath, line: 1, column: 0,
|
|
458
|
+
severity: 'high',
|
|
459
|
+
category: this.category,
|
|
460
|
+
rule: 'MCP_OVER_PERMISSIONED',
|
|
461
|
+
title: `MCP: Server "${name}" Has Broad Filesystem Access`,
|
|
462
|
+
description: `MCP server "${name}" has access to a wide directory scope. A prompt injection attack could read or modify sensitive files.`,
|
|
463
|
+
matched: argsStr.slice(0, 200),
|
|
464
|
+
confidence: 'high',
|
|
465
|
+
cwe: 'CWE-269',
|
|
466
|
+
owasp: 'A01:2021',
|
|
467
|
+
fix: 'Restrict filesystem access to the minimum required directory: e.g., the project folder only.',
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
} catch { /* not valid JSON */ }
|
|
473
|
+
|
|
474
|
+
return findings;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Detect shadow MCP configs that exist but aren't in .gitignore or git.
|
|
479
|
+
*/
|
|
480
|
+
_detectShadowMcpConfigs(rootPath) {
|
|
481
|
+
const findings = [];
|
|
482
|
+
const home = os.homedir();
|
|
483
|
+
|
|
484
|
+
// Check common locations for MCP configs outside version control
|
|
485
|
+
const homeConfigs = [
|
|
486
|
+
path.join(home, '.cursor', 'mcp.json'),
|
|
487
|
+
path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
|
|
488
|
+
path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'),
|
|
489
|
+
];
|
|
490
|
+
|
|
491
|
+
for (const configPath of homeConfigs) {
|
|
492
|
+
try {
|
|
493
|
+
if (fs.existsSync(configPath)) {
|
|
494
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
495
|
+
const config = JSON.parse(content);
|
|
496
|
+
const servers = config.mcpServers || config.servers || {};
|
|
497
|
+
const serverCount = Object.keys(servers).length;
|
|
498
|
+
|
|
499
|
+
if (serverCount > 0) {
|
|
500
|
+
findings.push({
|
|
501
|
+
file: configPath, line: 1, column: 0,
|
|
502
|
+
severity: 'medium',
|
|
503
|
+
category: this.category,
|
|
504
|
+
rule: 'MCP_SHADOW_CONFIG',
|
|
505
|
+
title: `MCP: ${serverCount} Shadow Server(s) in User Config`,
|
|
506
|
+
description: `Found ${serverCount} MCP server(s) configured outside the project in ${configPath}. These operate outside your project's security controls.`,
|
|
507
|
+
matched: Object.keys(servers).join(', '),
|
|
508
|
+
confidence: 'medium',
|
|
509
|
+
cwe: 'CWE-269',
|
|
510
|
+
owasp: 'A05:2021',
|
|
511
|
+
fix: 'Review shadow MCP servers. Move project-specific servers to the project mcp.json and track in version control.',
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} catch { /* skip */ }
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return findings;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Simple Levenshtein distance for typosquatting detection.
|
|
523
|
+
*/
|
|
524
|
+
_levenshtein(a, b) {
|
|
525
|
+
const m = a.length, n = b.length;
|
|
526
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
527
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
528
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
529
|
+
for (let i = 1; i <= m; i++) {
|
|
530
|
+
for (let j = 1; j <= n; j++) {
|
|
531
|
+
dp[i][j] = a[i-1] === b[j-1]
|
|
532
|
+
? dp[i-1][j-1]
|
|
533
|
+
: 1 + Math.min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return dp[m][n];
|
|
537
|
+
}
|
|
356
538
|
}
|
|
357
539
|
|
|
358
540
|
export default MCPSecurityAgent;
|