ship-safe 3.1.0 → 4.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.
Files changed (38) hide show
  1. package/README.md +200 -307
  2. package/cli/agents/api-fuzzer.js +224 -0
  3. package/cli/agents/auth-bypass-agent.js +326 -0
  4. package/cli/agents/base-agent.js +240 -0
  5. package/cli/agents/cicd-scanner.js +200 -0
  6. package/cli/agents/config-auditor.js +413 -0
  7. package/cli/agents/git-history-scanner.js +167 -0
  8. package/cli/agents/html-reporter.js +363 -0
  9. package/cli/agents/index.js +56 -0
  10. package/cli/agents/injection-tester.js +401 -0
  11. package/cli/agents/llm-redteam.js +251 -0
  12. package/cli/agents/mobile-scanner.js +225 -0
  13. package/cli/agents/orchestrator.js +152 -0
  14. package/cli/agents/policy-engine.js +149 -0
  15. package/cli/agents/recon-agent.js +196 -0
  16. package/cli/agents/sbom-generator.js +176 -0
  17. package/cli/agents/scoring-engine.js +207 -0
  18. package/cli/agents/ssrf-prober.js +130 -0
  19. package/cli/agents/supply-chain-agent.js +274 -0
  20. package/cli/bin/ship-safe.js +119 -2
  21. package/cli/commands/agent.js +606 -0
  22. package/cli/commands/audit.js +565 -0
  23. package/cli/commands/deps.js +447 -0
  24. package/cli/commands/fix.js +3 -3
  25. package/cli/commands/init.js +86 -3
  26. package/cli/commands/mcp.js +2 -2
  27. package/cli/commands/red-team.js +315 -0
  28. package/cli/commands/remediate.js +4 -4
  29. package/cli/commands/rotate.js +6 -6
  30. package/cli/commands/scan.js +64 -23
  31. package/cli/commands/score.js +446 -0
  32. package/cli/commands/watch.js +160 -0
  33. package/cli/index.js +40 -2
  34. package/cli/providers/llm-provider.js +288 -0
  35. package/cli/utils/entropy.js +6 -0
  36. package/cli/utils/output.js +42 -2
  37. package/cli/utils/patterns.js +393 -1
  38. package/package.json +19 -15
@@ -0,0 +1,130 @@
1
+ /**
2
+ * SSRFProber Agent
3
+ * =================
4
+ *
5
+ * Detects Server-Side Request Forgery vulnerabilities.
6
+ * The fastest-growing attack vector (452% surge in 2025).
7
+ *
8
+ * Checks: user input in URL construction, webhook validation,
9
+ * cloud metadata access, DNS rebinding, protocol smuggling.
10
+ */
11
+
12
+ import path from 'path';
13
+ import { BaseAgent } from './base-agent.js';
14
+
15
+ const PATTERNS = [
16
+ {
17
+ rule: 'SSRF_USER_URL_FETCH',
18
+ title: 'SSRF: User Input in fetch()',
19
+ regex: /fetch\s*\(\s*(?:req\.|request\.|ctx\.|query|params|body|input|url|data)/g,
20
+ severity: 'critical',
21
+ cwe: 'CWE-918',
22
+ owasp: 'A10:2021',
23
+ description: 'User-controlled URL passed to fetch() enables SSRF. Validate against an allowlist.',
24
+ fix: 'Validate URL against allowlist: new URL(input).hostname must match allowed hosts',
25
+ },
26
+ {
27
+ rule: 'SSRF_USER_URL_AXIOS',
28
+ title: 'SSRF: User Input in axios/got/http',
29
+ regex: /(?:axios|got|http|https|request|superagent|node-fetch|undici)(?:\.get|\.post|\.put|\.delete|\.request|\s*\()\s*\(\s*(?:req\.|request\.|ctx\.|query|params|body|input|url|data)/g,
30
+ severity: 'critical',
31
+ cwe: 'CWE-918',
32
+ owasp: 'A10:2021',
33
+ description: 'User-supplied URL in HTTP client enables SSRF. Validate and restrict to public IPs.',
34
+ fix: 'Parse URL, block private IPs (127.0.0.1, 10.x, 172.16-31.x, 169.254.x), block file:// protocol',
35
+ },
36
+ {
37
+ rule: 'SSRF_URL_TEMPLATE',
38
+ title: 'SSRF: Template Literal in URL',
39
+ regex: /(?:fetch|axios|got|http\.get|https\.get)\s*\(\s*`[^`]*\$\{(?:req\.|request\.|ctx\.|query|params|body|input)/g,
40
+ severity: 'critical',
41
+ cwe: 'CWE-918',
42
+ owasp: 'A10:2021',
43
+ description: 'User input interpolated into URL for HTTP request enables SSRF.',
44
+ fix: 'Validate and sanitize the URL before making the request',
45
+ },
46
+ {
47
+ rule: 'SSRF_WEBHOOK_URL',
48
+ title: 'SSRF: Unvalidated Webhook URL',
49
+ regex: /webhook[_-]?url\s*[:=]\s*(?:req\.|request\.|ctx\.|body|query|params|input)/gi,
50
+ severity: 'high',
51
+ cwe: 'CWE-918',
52
+ owasp: 'A10:2021',
53
+ description: 'Accepting user-supplied webhook URLs without validation enables SSRF.',
54
+ fix: 'Validate webhook URL: must be HTTPS, public IP, not cloud metadata endpoint',
55
+ },
56
+ {
57
+ rule: 'SSRF_CLOUD_METADATA',
58
+ title: 'SSRF: Cloud Metadata Endpoint Access',
59
+ regex: /169\.254\.169\.254|metadata\.google\.internal|100\.100\.100\.200/g,
60
+ severity: 'critical',
61
+ cwe: 'CWE-918',
62
+ owasp: 'A10:2021',
63
+ description: 'Cloud metadata endpoint in code. If URL is user-controlled, this enables credential theft.',
64
+ fix: 'Block metadata IPs in URL validation. Use IMDSv2 on AWS (requires token header).',
65
+ },
66
+ {
67
+ rule: 'SSRF_INTERNAL_IP',
68
+ title: 'SSRF: Internal IP Pattern',
69
+ regex: /(?:127\.0\.0\.|0\.0\.0\.0|10\.\d+\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.)\d+/g,
70
+ severity: 'medium',
71
+ cwe: 'CWE-918',
72
+ owasp: 'A10:2021',
73
+ confidence: 'low',
74
+ description: 'Internal IP address in code. Verify it is not reachable via user-controlled URLs.',
75
+ fix: 'Block private IP ranges in URL validation for user-supplied URLs',
76
+ },
77
+ {
78
+ rule: 'SSRF_REDIRECT_FOLLOW',
79
+ title: 'SSRF: HTTP Client Follows Redirects',
80
+ regex: /(?:follow|maxRedirects|redirect)\s*:\s*(?:true|\d{2,})/g,
81
+ severity: 'medium',
82
+ cwe: 'CWE-918',
83
+ owasp: 'A10:2021',
84
+ confidence: 'low',
85
+ description: 'Following redirects can bypass SSRF protections (redirect to internal IP).',
86
+ fix: 'Disable redirect following or re-validate the redirect target URL',
87
+ },
88
+ {
89
+ rule: 'SSRF_PYTHON_REQUESTS',
90
+ title: 'SSRF: Python requests with User Input',
91
+ regex: /requests\.(?:get|post|put|delete|head|patch)\s*\(\s*(?:request\.|flask\.|data|args|form)/g,
92
+ severity: 'critical',
93
+ cwe: 'CWE-918',
94
+ owasp: 'A10:2021',
95
+ description: 'User-controlled URL in Python requests enables SSRF.',
96
+ fix: 'Validate URL scheme (https only), resolve DNS and check against private IP ranges',
97
+ },
98
+ {
99
+ rule: 'SSRF_IMAGE_PROXY',
100
+ title: 'SSRF: Image/Proxy URL from User Input',
101
+ regex: /(?:imageUrl|image_url|proxyUrl|proxy_url|avatarUrl|avatar_url|iconUrl|icon_url)\s*[:=]\s*(?:req\.|request\.|query|params|body)/gi,
102
+ severity: 'high',
103
+ cwe: 'CWE-918',
104
+ owasp: 'A10:2021',
105
+ description: 'Image/proxy URLs from user input are common SSRF vectors.',
106
+ fix: 'Validate URL, restrict to known CDN domains, or use an image proxy service',
107
+ },
108
+ ];
109
+
110
+ export class SSRFProber extends BaseAgent {
111
+ constructor() {
112
+ super('SSRFProber', 'Detect Server-Side Request Forgery vulnerabilities', 'ssrf');
113
+ }
114
+
115
+ async analyze(context) {
116
+ const { files } = context;
117
+ const codeFiles = files.filter(f => {
118
+ const ext = path.extname(f).toLowerCase();
119
+ return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.php', '.go'].includes(ext);
120
+ });
121
+
122
+ let findings = [];
123
+ for (const file of codeFiles) {
124
+ findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
125
+ }
126
+ return findings;
127
+ }
128
+ }
129
+
130
+ export default SSRFProber;
@@ -0,0 +1,274 @@
1
+ /**
2
+ * SupplyChainAudit Agent
3
+ * =======================
4
+ *
5
+ * Comprehensive supply chain security analysis.
6
+ * Goes beyond npm audit: checks for dependency confusion,
7
+ * typosquatting, malicious install scripts, lockfile integrity,
8
+ * EPSS scoring, and KEV flagging.
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { BaseAgent, createFinding } from './base-agent.js';
14
+
15
+ // Common packages that are often typosquatted
16
+ const POPULAR_PACKAGES = [
17
+ 'lodash', 'express', 'react', 'axios', 'moment', 'request',
18
+ 'chalk', 'commander', 'debug', 'uuid', 'dotenv', 'cors',
19
+ 'body-parser', 'jsonwebtoken', 'bcrypt', 'mongoose', 'sequelize',
20
+ 'webpack', 'babel', 'eslint', 'prettier', 'typescript',
21
+ 'next', 'nuxt', 'svelte', 'vue', 'angular',
22
+ ];
23
+
24
+ // Well-known packages that happen to be close to other popular names
25
+ // (not typosquats — verified legitimate packages)
26
+ const KNOWN_SAFE = new Set([
27
+ 'ora', 'got', 'ink', 'yup', 'joi', 'ava', 'tap', 'npm', 'nwb',
28
+ 'pug', 'koa', 'hap', 'ejs', 'csv', 'ws', 'pg', 'ms',
29
+ ]);
30
+
31
+ // Known malicious package name patterns
32
+ const SUSPICIOUS_NAME_PATTERNS = [
33
+ /^@[^/]+\/[^/]+-[0-9]+$/, // @scope/package-123 (random suffix)
34
+ /^[a-z]+-[a-z]+-[a-z]+-[a-z]+$/, // overly-generic multi-word names
35
+ ];
36
+
37
+ export class SupplyChainAudit extends BaseAgent {
38
+ constructor() {
39
+ super('SupplyChainAudit', 'Comprehensive supply chain security audit', 'supply-chain');
40
+ }
41
+
42
+ async analyze(context) {
43
+ const { rootPath } = context;
44
+ const findings = [];
45
+
46
+ // ── 1. Check package.json ─────────────────────────────────────────────────
47
+ const pkgPath = path.join(rootPath, 'package.json');
48
+ if (fs.existsSync(pkgPath)) {
49
+ try {
50
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
51
+ const allDeps = {
52
+ ...(pkg.dependencies || {}),
53
+ ...(pkg.devDependencies || {}),
54
+ ...(pkg.optionalDependencies || {}),
55
+ };
56
+
57
+ // ── Typosquatting detection ───────────────────────────────────────────
58
+ for (const depName of Object.keys(allDeps)) {
59
+ if (KNOWN_SAFE.has(depName)) continue;
60
+ for (const popular of POPULAR_PACKAGES) {
61
+ const distance = this.levenshtein(depName, popular);
62
+ if (distance > 0 && distance <= 2 && depName !== popular) {
63
+ findings.push(createFinding({
64
+ file: pkgPath,
65
+ line: 0,
66
+ severity: 'high',
67
+ category: 'supply-chain',
68
+ rule: 'TYPOSQUAT_SUSPECT',
69
+ title: `Possible Typosquat: "${depName}" (similar to "${popular}")`,
70
+ description: `Package "${depName}" is ${distance} character(s) away from popular package "${popular}". This could be a typosquatting attempt.`,
71
+ matched: depName,
72
+ fix: `Verify this is the intended package. Did you mean "${popular}"?`,
73
+ }));
74
+ }
75
+ }
76
+ }
77
+
78
+ // ── Deprecated/suspicious version pins ───────────────────────────────
79
+ for (const [name, version] of Object.entries(allDeps)) {
80
+ if (typeof version === 'string' && version.startsWith('git+')) {
81
+ findings.push(createFinding({
82
+ file: pkgPath,
83
+ line: 0,
84
+ severity: 'high',
85
+ category: 'supply-chain',
86
+ rule: 'GIT_DEPENDENCY',
87
+ title: `Git Dependency: ${name}`,
88
+ description: `"${name}" is installed from a git URL. Git dependencies bypass registry integrity checks.`,
89
+ matched: `${name}: ${version}`,
90
+ fix: 'Pin to a specific commit hash or use a published npm package version',
91
+ }));
92
+ }
93
+
94
+ if (typeof version === 'string' && version.startsWith('http')) {
95
+ findings.push(createFinding({
96
+ file: pkgPath,
97
+ line: 0,
98
+ severity: 'critical',
99
+ category: 'supply-chain',
100
+ rule: 'URL_DEPENDENCY',
101
+ title: `URL Dependency: ${name}`,
102
+ description: `"${name}" is installed from a URL. This bypasses npm registry and integrity checks.`,
103
+ matched: `${name}: ${version}`,
104
+ fix: 'Publish the package to npm or use a private registry',
105
+ }));
106
+ }
107
+
108
+ if (typeof version === 'string' && version === '*') {
109
+ findings.push(createFinding({
110
+ file: pkgPath,
111
+ line: 0,
112
+ severity: 'high',
113
+ category: 'supply-chain',
114
+ rule: 'WILDCARD_VERSION',
115
+ title: `Wildcard Version: ${name}`,
116
+ description: `"${name}" uses "*" version which accepts any version including malicious updates.`,
117
+ matched: `${name}: *`,
118
+ fix: 'Pin to a specific version or use a caret range: "^x.y.z"',
119
+ }));
120
+ }
121
+ }
122
+
123
+ // ── Install scripts ──────────────────────────────────────────────────
124
+ if (pkg.scripts) {
125
+ const dangerousScripts = ['preinstall', 'postinstall', 'preuninstall', 'postuninstall'];
126
+ for (const script of dangerousScripts) {
127
+ if (pkg.scripts[script]) {
128
+ const cmd = pkg.scripts[script];
129
+ const suspicious = /curl|wget|bash|sh\s|powershell|eval|base64|nc\s|ncat/i.test(cmd);
130
+ if (suspicious) {
131
+ findings.push(createFinding({
132
+ file: pkgPath,
133
+ line: 0,
134
+ severity: 'critical',
135
+ category: 'supply-chain',
136
+ rule: 'SUSPICIOUS_INSTALL_SCRIPT',
137
+ title: `Suspicious ${script} Script`,
138
+ description: `The ${script} script contains potentially dangerous commands: ${cmd.slice(0, 100)}`,
139
+ matched: cmd,
140
+ fix: 'Review and remove suspicious install scripts',
141
+ }));
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ } catch { /* skip parse errors */ }
148
+ }
149
+
150
+ // ── 2. Check lockfile integrity ───────────────────────────────────────────
151
+ const lockFiles = [
152
+ { file: 'package-lock.json', manager: 'npm' },
153
+ { file: 'yarn.lock', manager: 'yarn' },
154
+ { file: 'pnpm-lock.yaml', manager: 'pnpm' },
155
+ { file: 'bun.lockb', manager: 'bun' },
156
+ ];
157
+
158
+ const hasPackageJson = fs.existsSync(pkgPath);
159
+ let hasLockfile = false;
160
+
161
+ for (const { file, manager } of lockFiles) {
162
+ if (fs.existsSync(path.join(rootPath, file))) {
163
+ hasLockfile = true;
164
+ }
165
+ }
166
+
167
+ if (hasPackageJson && !hasLockfile) {
168
+ findings.push(createFinding({
169
+ file: pkgPath,
170
+ line: 0,
171
+ severity: 'high',
172
+ category: 'supply-chain',
173
+ rule: 'MISSING_LOCKFILE',
174
+ title: 'No Lock File Found',
175
+ description: 'No package-lock.json, yarn.lock, or pnpm-lock.yaml found. Without a lockfile, installs are non-deterministic and vulnerable to dependency confusion.',
176
+ matched: 'package.json without lockfile',
177
+ fix: 'Run npm install, yarn install, or pnpm install to generate a lockfile, then commit it',
178
+ }));
179
+ }
180
+
181
+ // ── 3. Check .npmrc for security settings ─────────────────────────────────
182
+ const npmrcPath = path.join(rootPath, '.npmrc');
183
+ if (fs.existsSync(npmrcPath)) {
184
+ const content = this.readFile(npmrcPath) || '';
185
+ if (content.includes('ignore-scripts=true')) {
186
+ // Good — scripts are disabled
187
+ }
188
+ if (content.includes('registry=') && !content.includes('registry=https://registry.npmjs.org')) {
189
+ findings.push(createFinding({
190
+ file: npmrcPath,
191
+ line: 0,
192
+ severity: 'medium',
193
+ category: 'supply-chain',
194
+ rule: 'CUSTOM_REGISTRY',
195
+ title: 'Custom NPM Registry Configured',
196
+ description: 'A custom npm registry is configured. Verify it is trusted and uses HTTPS.',
197
+ matched: content.match(/registry=.*/)?.[0] || '',
198
+ confidence: 'medium',
199
+ fix: 'Verify the registry URL is trusted and uses HTTPS',
200
+ }));
201
+ }
202
+ }
203
+
204
+ // ── 4. Check Python requirements ──────────────────────────────────────────
205
+ const reqPath = path.join(rootPath, 'requirements.txt');
206
+ if (fs.existsSync(reqPath)) {
207
+ const content = this.readFile(reqPath) || '';
208
+ const lines = content.split('\n');
209
+ for (let i = 0; i < lines.length; i++) {
210
+ const line = lines[i].trim();
211
+ if (!line || line.startsWith('#')) continue;
212
+
213
+ // Unpinned versions
214
+ if (!line.includes('==') && !line.includes('>=') && !line.includes('~=') && !line.includes('@')) {
215
+ findings.push(createFinding({
216
+ file: reqPath,
217
+ line: i + 1,
218
+ severity: 'medium',
219
+ category: 'supply-chain',
220
+ rule: 'UNPINNED_PYTHON_DEP',
221
+ title: `Unpinned Python Dependency: ${line}`,
222
+ description: 'Python dependency without version pin. Pin to a specific version for reproducible builds.',
223
+ matched: line,
224
+ fix: `Pin version: ${line}==x.y.z`,
225
+ }));
226
+ }
227
+
228
+ // Git/URL dependencies
229
+ if (line.includes('git+') || line.startsWith('http')) {
230
+ findings.push(createFinding({
231
+ file: reqPath,
232
+ line: i + 1,
233
+ severity: 'high',
234
+ category: 'supply-chain',
235
+ rule: 'GIT_PYTHON_DEP',
236
+ title: `Git/URL Python Dependency: ${line.slice(0, 60)}`,
237
+ description: 'Installing from git/URL bypasses PyPI integrity checks.',
238
+ matched: line,
239
+ fix: 'Publish to PyPI or pin to a specific commit hash',
240
+ }));
241
+ }
242
+ }
243
+ }
244
+
245
+ return findings;
246
+ }
247
+
248
+ /**
249
+ * Simple Levenshtein distance for typosquatting detection.
250
+ */
251
+ levenshtein(a, b) {
252
+ if (a.length === 0) return b.length;
253
+ if (b.length === 0) return a.length;
254
+
255
+ const matrix = [];
256
+ for (let i = 0; i <= b.length; i++) matrix[i] = [i];
257
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
258
+
259
+ for (let i = 1; i <= b.length; i++) {
260
+ for (let j = 1; j <= a.length; j++) {
261
+ const cost = b[i - 1] === a[j - 1] ? 0 : 1;
262
+ matrix[i][j] = Math.min(
263
+ matrix[i - 1][j] + 1,
264
+ matrix[i][j - 1] + 1,
265
+ matrix[i - 1][j - 1] + cost
266
+ );
267
+ }
268
+ }
269
+
270
+ return matrix[b.length][a.length];
271
+ }
272
+ }
273
+
274
+ export default SupplyChainAudit;
@@ -28,11 +28,21 @@ import { guardCommand } from '../commands/guard.js';
28
28
  import { mcpCommand } from '../commands/mcp.js';
29
29
  import { remediateCommand } from '../commands/remediate.js';
30
30
  import { rotateCommand } from '../commands/rotate.js';
31
+ import { agentCommand } from '../commands/agent.js';
32
+ import { depsCommand } from '../commands/deps.js';
33
+ import { scoreCommand } from '../commands/score.js';
34
+ import { redTeamCommand } from '../commands/red-team.js';
35
+ import { watchCommand } from '../commands/watch.js';
36
+ import { auditCommand } from '../commands/audit.js';
37
+ import { PolicyEngine } from '../agents/policy-engine.js';
38
+ import { SBOMGenerator } from '../agents/sbom-generator.js';
31
39
 
32
40
  // =============================================================================
33
41
  // CLI CONFIGURATION
34
42
  // =============================================================================
35
43
 
44
+ const DEFAULT_MODEL = 'claude-haiku-4-5-20251001';
45
+
36
46
  // Read version from package.json
37
47
  const __filename = fileURLToPath(import.meta.url);
38
48
  const __dirname = dirname(__filename);
@@ -92,6 +102,7 @@ program
92
102
  .option('-f, --force', 'Overwrite existing files')
93
103
  .option('--gitignore', 'Only copy .gitignore')
94
104
  .option('--headers', 'Only copy security headers config')
105
+ .option('--agents', 'Only add security rules to AI agent instruction files (CLAUDE.md, .cursor/rules/, .windsurfrules, copilot-instructions.md)')
95
106
  .action(initCommand);
96
107
 
97
108
  // -----------------------------------------------------------------------------
@@ -140,6 +151,103 @@ program
140
151
  .option('--provider <name>', 'Only rotate secrets for a specific provider (e.g. github, stripe, openai)')
141
152
  .action(rotateCommand);
142
153
 
154
+ // -----------------------------------------------------------------------------
155
+ // AGENT COMMAND
156
+ // -----------------------------------------------------------------------------
157
+ program
158
+ .command('agent [path]')
159
+ .description('AI-powered security audit: scan, classify with Claude, auto-remediate confirmed secrets')
160
+ .option('--dry-run', 'Show classification and plan without writing any files')
161
+ .option('--model <model>', `Claude model to use (default: ${DEFAULT_MODEL})`)
162
+ .action(agentCommand);
163
+
164
+ // -----------------------------------------------------------------------------
165
+ // DEPS COMMAND
166
+ // -----------------------------------------------------------------------------
167
+ program
168
+ .command('deps [path]')
169
+ .description('Audit dependencies for known CVEs (npm, yarn, pnpm, pip-audit, bundler-audit)')
170
+ .option('--fix', 'Run the package manager fix command after auditing')
171
+ .action(depsCommand);
172
+
173
+ // -----------------------------------------------------------------------------
174
+ // SCORE COMMAND
175
+ // -----------------------------------------------------------------------------
176
+ program
177
+ .command('score [path]')
178
+ .description('Compute a 0-100 security health score for your project')
179
+ .option('--no-deps', 'Skip dependency audit')
180
+ .action(scoreCommand);
181
+
182
+ // -----------------------------------------------------------------------------
183
+ // AUDIT COMMAND (v4.0 — Full Security Audit)
184
+ // -----------------------------------------------------------------------------
185
+ program
186
+ .command('audit [path]')
187
+ .description('Full security audit: secrets + 12 agents + deps + score + remediation plan')
188
+ .option('--json', 'Output results as JSON')
189
+ .option('--sarif', 'Output results in SARIF format')
190
+ .option('--html [file]', 'HTML report path (default: ship-safe-report.html)')
191
+ .option('--no-deps', 'Skip dependency audit')
192
+ .option('--no-ai', 'Skip AI classification')
193
+ .option('-v, --verbose', 'Verbose output')
194
+ .action(auditCommand);
195
+
196
+ // -----------------------------------------------------------------------------
197
+ // RED TEAM COMMAND (v4.0 — Multi-Agent Security Audit)
198
+ // -----------------------------------------------------------------------------
199
+ program
200
+ .command('red-team [path]')
201
+ .description('Multi-agent security audit: 12 agents scan for 50+ attack classes')
202
+ .option('--agents <list>', 'Comma-separated list of agents to run')
203
+ .option('--json', 'Output results as JSON')
204
+ .option('--sarif', 'Output results in SARIF format')
205
+ .option('--html [file]', 'Generate HTML security report')
206
+ .option('--sbom [file]', 'Generate CycloneDX SBOM')
207
+ .option('--no-deps', 'Skip dependency audit')
208
+ .option('--no-ai', 'Skip AI classification')
209
+ .option('-v, --verbose', 'Verbose output')
210
+ .action(redTeamCommand);
211
+
212
+ // -----------------------------------------------------------------------------
213
+ // WATCH COMMAND
214
+ // -----------------------------------------------------------------------------
215
+ program
216
+ .command('watch [path]')
217
+ .description('Continuous monitoring: watch files for security issues in real-time')
218
+ .option('--poll', 'Use polling mode (for network drives)')
219
+ .action(watchCommand);
220
+
221
+ // -----------------------------------------------------------------------------
222
+ // SBOM COMMAND
223
+ // -----------------------------------------------------------------------------
224
+ program
225
+ .command('sbom [path]')
226
+ .description('Generate Software Bill of Materials (CycloneDX SBOM)')
227
+ .option('-o, --output <file>', 'Output file path', 'sbom.json')
228
+ .action((targetPath = '.', options) => {
229
+ const absolutePath = join(process.cwd(), targetPath);
230
+ const sbom = new SBOMGenerator();
231
+ sbom.generateToFile(absolutePath, options.output);
232
+ console.log(chalk.green(`✔ SBOM saved to ${options.output}`));
233
+ });
234
+
235
+ // -----------------------------------------------------------------------------
236
+ // POLICY COMMAND
237
+ // -----------------------------------------------------------------------------
238
+ program
239
+ .command('policy <action>')
240
+ .description('Manage security policies (init: create policy template)')
241
+ .action((action) => {
242
+ if (action === 'init') {
243
+ const policyPath = PolicyEngine.generateTemplate(process.cwd());
244
+ console.log(chalk.green(`✔ Policy template created: ${policyPath}`));
245
+ console.log(chalk.gray(' Edit .ship-safe.policy.json to configure your security policy.'));
246
+ } else {
247
+ console.log(chalk.yellow(`Unknown policy action: ${action}. Use: policy init`));
248
+ }
249
+ });
250
+
143
251
  // -----------------------------------------------------------------------------
144
252
  // PARSE AND RUN
145
253
  // -----------------------------------------------------------------------------
@@ -148,12 +256,21 @@ program
148
256
  if (process.argv.length === 2) {
149
257
  console.log(banner);
150
258
  console.log(chalk.yellow('\nQuick start:\n'));
259
+ console.log(chalk.cyan.bold(' v4.0 — Full Security Audit'));
260
+ console.log(chalk.white(' npx ship-safe audit . ') + chalk.gray('# Full audit: secrets + agents + deps + remediation plan'));
261
+ console.log(chalk.white(' npx ship-safe red-team . ') + chalk.gray('# 12-agent red team scan (50+ attack classes)'));
262
+ console.log(chalk.white(' npx ship-safe watch . ') + chalk.gray('# Continuous monitoring mode'));
263
+ console.log(chalk.white(' npx ship-safe sbom . ') + chalk.gray('# Generate CycloneDX SBOM'));
264
+ console.log(chalk.white(' npx ship-safe policy init ') + chalk.gray('# Create security policy template'));
265
+ console.log();
266
+ console.log(chalk.gray(' Core commands:'));
267
+ console.log(chalk.white(' npx ship-safe agent . ') + chalk.gray('# AI audit: scan + classify + auto-fix'));
151
268
  console.log(chalk.white(' npx ship-safe scan . ') + chalk.gray('# Scan for secrets'));
152
269
  console.log(chalk.white(' npx ship-safe remediate . ') + chalk.gray('# Auto-fix: rewrite code + write .env'));
153
270
  console.log(chalk.white(' npx ship-safe rotate . ') + chalk.gray('# Revoke exposed keys (provider guides)'));
154
- console.log(chalk.white(' npx ship-safe fix ') + chalk.gray('# Generate .env.example from secrets'));
271
+ console.log(chalk.white(' npx ship-safe deps . ') + chalk.gray('# Audit dependencies for CVEs'));
272
+ console.log(chalk.white(' npx ship-safe score . ') + chalk.gray('# Security health score (0-100)'));
155
273
  console.log(chalk.white(' npx ship-safe guard ') + chalk.gray('# Block git push if secrets found'));
156
- console.log(chalk.white(' npx ship-safe checklist ') + chalk.gray('# Run security checklist'));
157
274
  console.log(chalk.white(' npx ship-safe init ') + chalk.gray('# Add security configs to your project'));
158
275
  console.log(chalk.white('\n npx ship-safe --help ') + chalk.gray('# Show all options'));
159
276
  console.log();