ship-safe 4.1.0 → 4.3.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/README.md +65 -16
- package/cli/__tests__/agents.test.js +722 -0
- package/cli/agents/api-fuzzer.js +345 -224
- package/cli/agents/auth-bypass-agent.js +348 -326
- package/cli/agents/base-agent.js +262 -253
- package/cli/agents/cicd-scanner.js +201 -200
- package/cli/agents/config-auditor.js +529 -413
- package/cli/agents/git-history-scanner.js +170 -167
- package/cli/agents/html-reporter.js +370 -363
- package/cli/agents/index.js +59 -56
- package/cli/agents/injection-tester.js +455 -401
- package/cli/agents/llm-redteam.js +251 -251
- package/cli/agents/mobile-scanner.js +225 -225
- package/cli/agents/orchestrator.js +263 -157
- package/cli/agents/scoring-engine.js +225 -207
- package/cli/agents/supabase-rls-agent.js +148 -0
- package/cli/agents/supply-chain-agent.js +356 -274
- package/cli/bin/ship-safe.js +29 -1
- package/cli/commands/audit.js +875 -620
- package/cli/commands/baseline.js +192 -0
- package/cli/commands/doctor.js +149 -0
- package/cli/commands/remediate.js +7 -3
- package/cli/index.js +60 -53
- package/cli/providers/llm-provider.js +287 -288
- package/cli/utils/autofix-rules.js +74 -0
- package/cli/utils/cache-manager.js +311 -258
- package/cli/utils/pdf-generator.js +94 -0
- package/package.json +2 -2
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline Command
|
|
3
|
+
* =================
|
|
4
|
+
*
|
|
5
|
+
* Accept current findings as a baseline and only report new findings
|
|
6
|
+
* on subsequent scans. This is how teams adopt security scanners:
|
|
7
|
+
* baseline existing debt, focus on not making it worse.
|
|
8
|
+
*
|
|
9
|
+
* USAGE:
|
|
10
|
+
* ship-safe baseline . Create a new baseline from current scan
|
|
11
|
+
* ship-safe baseline . --diff Show what changed since baseline
|
|
12
|
+
* ship-safe baseline . --clear Remove the baseline
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import chalk from 'chalk';
|
|
18
|
+
import ora from 'ora';
|
|
19
|
+
import { buildOrchestrator } from '../agents/index.js';
|
|
20
|
+
import { SECRET_PATTERNS, SKIP_DIRS, SKIP_EXTENSIONS, MAX_FILE_SIZE } from '../utils/patterns.js';
|
|
21
|
+
import { isHighEntropyMatch } from '../utils/entropy.js';
|
|
22
|
+
import fg from 'fast-glob';
|
|
23
|
+
|
|
24
|
+
const BASELINE_FILE = '.ship-safe/baseline.json';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a fingerprint for a finding that survives line-number shifts.
|
|
28
|
+
* Uses rule + relative file path + first 40 chars of matched text.
|
|
29
|
+
*/
|
|
30
|
+
function fingerprint(finding, rootPath) {
|
|
31
|
+
const relFile = path.relative(rootPath, finding.file || '').replace(/\\/g, '/');
|
|
32
|
+
const matched = (finding.matched || '').slice(0, 40);
|
|
33
|
+
return `${finding.rule}:${relFile}:${matched}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Quick secret scan (same as audit Phase 1) to get current findings.
|
|
38
|
+
*/
|
|
39
|
+
async function quickScan(rootPath) {
|
|
40
|
+
const globIgnore = Array.from(SKIP_DIRS).map(dir => `**/${dir}/**`);
|
|
41
|
+
const files = await fg('**/*', {
|
|
42
|
+
cwd: rootPath, absolute: true, onlyFiles: true, ignore: globIgnore, dot: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const filtered = files.filter(f => {
|
|
46
|
+
const ext = path.extname(f).toLowerCase();
|
|
47
|
+
if (SKIP_EXTENSIONS.has(ext)) return false;
|
|
48
|
+
try { return fs.statSync(f).size <= MAX_FILE_SIZE; } catch { return false; }
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const findings = [];
|
|
52
|
+
for (const file of filtered) {
|
|
53
|
+
try {
|
|
54
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
for (let i = 0; i < lines.length; i++) {
|
|
57
|
+
if (/ship-safe-ignore/i.test(lines[i])) continue;
|
|
58
|
+
for (const p of SECRET_PATTERNS) {
|
|
59
|
+
p.pattern.lastIndex = 0;
|
|
60
|
+
let m;
|
|
61
|
+
while ((m = p.pattern.exec(lines[i])) !== null) {
|
|
62
|
+
if (p.requiresEntropyCheck && !isHighEntropyMatch(m[0])) continue;
|
|
63
|
+
findings.push({ file, line: i + 1, rule: p.name, matched: m[0], severity: p.severity });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch { /* skip */ }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { findings, files: filtered };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Run agents and combine with secret scan findings.
|
|
75
|
+
*/
|
|
76
|
+
async function fullScan(rootPath) {
|
|
77
|
+
const { findings: secretFindings, files } = await quickScan(rootPath);
|
|
78
|
+
|
|
79
|
+
const orchestrator = buildOrchestrator();
|
|
80
|
+
const { findings: agentFindings } = await orchestrator.runAll(rootPath, { quiet: true });
|
|
81
|
+
|
|
82
|
+
return [...secretFindings, ...agentFindings];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Load existing baseline from disk.
|
|
87
|
+
*/
|
|
88
|
+
function loadBaseline(rootPath) {
|
|
89
|
+
const baselinePath = path.join(rootPath, BASELINE_FILE);
|
|
90
|
+
if (!fs.existsSync(baselinePath)) return null;
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Save baseline to disk.
|
|
100
|
+
*/
|
|
101
|
+
function saveBaseline(rootPath, fingerprints, findingCount) {
|
|
102
|
+
const baselinePath = path.join(rootPath, BASELINE_FILE);
|
|
103
|
+
const dir = path.dirname(baselinePath);
|
|
104
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
105
|
+
|
|
106
|
+
const baseline = {
|
|
107
|
+
version: '4.3.0',
|
|
108
|
+
createdAt: new Date().toISOString(),
|
|
109
|
+
fingerprints,
|
|
110
|
+
findingCount,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
fs.writeFileSync(baselinePath, JSON.stringify(baseline, null, 2));
|
|
114
|
+
return baseline;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Filter out baselined findings. Returns only new findings.
|
|
119
|
+
*/
|
|
120
|
+
export function filterBaseline(findings, rootPath) {
|
|
121
|
+
const baseline = loadBaseline(rootPath);
|
|
122
|
+
if (!baseline) return findings;
|
|
123
|
+
|
|
124
|
+
const baseSet = new Set(baseline.fingerprints);
|
|
125
|
+
return findings.filter(f => !baseSet.has(fingerprint(f, rootPath)));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Main baseline command.
|
|
130
|
+
*/
|
|
131
|
+
export async function baselineCommand(targetPath = '.', options = {}) {
|
|
132
|
+
const rootPath = path.resolve(targetPath);
|
|
133
|
+
|
|
134
|
+
if (options.clear) {
|
|
135
|
+
const baselinePath = path.join(rootPath, BASELINE_FILE);
|
|
136
|
+
if (fs.existsSync(baselinePath)) {
|
|
137
|
+
fs.unlinkSync(baselinePath);
|
|
138
|
+
console.log(chalk.green(' Baseline removed.'));
|
|
139
|
+
} else {
|
|
140
|
+
console.log(chalk.gray(' No baseline found.'));
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (options.diff) {
|
|
146
|
+
const baseline = loadBaseline(rootPath);
|
|
147
|
+
if (!baseline) {
|
|
148
|
+
console.log(chalk.yellow(' No baseline found. Run `ship-safe baseline .` first.'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const spinner = ora({ text: 'Scanning for comparison...', color: 'cyan' }).start();
|
|
153
|
+
const findings = await fullScan(rootPath);
|
|
154
|
+
spinner.stop();
|
|
155
|
+
|
|
156
|
+
const currentFingerprints = new Set(findings.map(f => fingerprint(f, rootPath)));
|
|
157
|
+
const baseSet = new Set(baseline.fingerprints);
|
|
158
|
+
|
|
159
|
+
const newFindings = findings.filter(f => !baseSet.has(fingerprint(f, rootPath)));
|
|
160
|
+
const resolvedCount = baseline.fingerprints.filter(fp => !currentFingerprints.has(fp)).length;
|
|
161
|
+
|
|
162
|
+
console.log(chalk.cyan.bold('\n Baseline Comparison'));
|
|
163
|
+
console.log(chalk.gray(` Baseline: ${baseline.findingCount} findings (${baseline.createdAt.slice(0, 10)})`));
|
|
164
|
+
console.log(chalk.gray(` Current: ${findings.length} findings`));
|
|
165
|
+
console.log();
|
|
166
|
+
if (newFindings.length > 0) {
|
|
167
|
+
console.log(chalk.red(` + ${newFindings.length} new finding(s)`));
|
|
168
|
+
}
|
|
169
|
+
if (resolvedCount > 0) {
|
|
170
|
+
console.log(chalk.green(` - ${resolvedCount} resolved finding(s)`));
|
|
171
|
+
}
|
|
172
|
+
if (newFindings.length === 0 && resolvedCount === 0) {
|
|
173
|
+
console.log(chalk.green(' No changes since baseline.'));
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Default: create/update baseline
|
|
179
|
+
const spinner = ora({ text: 'Running full scan for baseline...', color: 'cyan' }).start();
|
|
180
|
+
const findings = await fullScan(rootPath);
|
|
181
|
+
spinner.stop();
|
|
182
|
+
|
|
183
|
+
const fingerprints = [...new Set(findings.map(f => fingerprint(f, rootPath)))];
|
|
184
|
+
const baseline = saveBaseline(rootPath, fingerprints, findings.length);
|
|
185
|
+
|
|
186
|
+
console.log(chalk.green.bold('\n Baseline created'));
|
|
187
|
+
console.log(chalk.gray(` Findings baselined: ${findings.length}`));
|
|
188
|
+
console.log(chalk.gray(` Unique fingerprints: ${fingerprints.length}`));
|
|
189
|
+
console.log(chalk.gray(` Saved to: ${BASELINE_FILE}`));
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(chalk.gray(' Run `ship-safe audit . --baseline` to only see new findings.'));
|
|
192
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor Command — Environment Diagnostics
|
|
3
|
+
* ==========================================
|
|
4
|
+
*
|
|
5
|
+
* Checks Node.js version, git, npm, API keys, ignore files,
|
|
6
|
+
* cache directory, and package version.
|
|
7
|
+
*
|
|
8
|
+
* USAGE:
|
|
9
|
+
* npx ship-safe doctor
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execFileSync } from 'child_process';
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import { readFileSync } from 'fs';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import { dirname, join } from 'path';
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
const PACKAGE_VERSION = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8')).version;
|
|
23
|
+
|
|
24
|
+
export async function doctorCommand() {
|
|
25
|
+
console.log();
|
|
26
|
+
console.log(chalk.cyan.bold(' Ship Safe Doctor'));
|
|
27
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)));
|
|
28
|
+
console.log();
|
|
29
|
+
|
|
30
|
+
let allGood = true;
|
|
31
|
+
|
|
32
|
+
// 1. Node.js version
|
|
33
|
+
const nodeVersion = process.version;
|
|
34
|
+
const nodeMajor = parseInt(nodeVersion.slice(1), 10);
|
|
35
|
+
if (nodeMajor >= 18) {
|
|
36
|
+
pass(`Node.js ${nodeVersion} (requires ≥18)`);
|
|
37
|
+
} else {
|
|
38
|
+
fail(`Node.js ${nodeVersion} — requires ≥18`);
|
|
39
|
+
allGood = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. Git
|
|
43
|
+
try {
|
|
44
|
+
const gitVersion = execFileSync('git', ['--version'], { encoding: 'utf-8' }).trim();
|
|
45
|
+
pass(gitVersion.replace('git version ', 'git v'));
|
|
46
|
+
} catch {
|
|
47
|
+
fail('git not found (needed for guard, git-history-scanner)');
|
|
48
|
+
allGood = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Package manager
|
|
52
|
+
const managers = ['npm', 'yarn', 'pnpm'];
|
|
53
|
+
let foundPm = false;
|
|
54
|
+
for (const pm of managers) {
|
|
55
|
+
try {
|
|
56
|
+
const ver = execFileSync(pm, ['--version'], { encoding: 'utf-8', shell: true }).trim();
|
|
57
|
+
pass(`${pm} v${ver}`);
|
|
58
|
+
foundPm = true;
|
|
59
|
+
break;
|
|
60
|
+
} catch { /* try next */ }
|
|
61
|
+
}
|
|
62
|
+
if (!foundPm) {
|
|
63
|
+
fail('No package manager found (npm/yarn/pnpm)');
|
|
64
|
+
allGood = false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 4. API keys
|
|
68
|
+
const apiKeys = [
|
|
69
|
+
{ name: 'Anthropic API key', env: 'ANTHROPIC_API_KEY', required: false },
|
|
70
|
+
{ name: 'OpenAI API key', env: 'OPENAI_API_KEY', required: false },
|
|
71
|
+
{ name: 'Google AI API key', env: 'GOOGLE_API_KEY', required: false },
|
|
72
|
+
];
|
|
73
|
+
for (const key of apiKeys) {
|
|
74
|
+
if (process.env[key.env]) {
|
|
75
|
+
pass(`${key.name} configured`);
|
|
76
|
+
} else {
|
|
77
|
+
info(`${key.name} not set (optional — for AI classification)`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 5. .ship-safeignore
|
|
82
|
+
const cwd = process.cwd();
|
|
83
|
+
const ignorePath = path.join(cwd, '.ship-safeignore');
|
|
84
|
+
if (fs.existsSync(ignorePath)) {
|
|
85
|
+
try {
|
|
86
|
+
const patterns = fs.readFileSync(ignorePath, 'utf-8')
|
|
87
|
+
.split('\n').filter(l => l.trim() && !l.startsWith('#')).length;
|
|
88
|
+
pass(`.ship-safeignore found (${patterns} patterns)`);
|
|
89
|
+
} catch {
|
|
90
|
+
pass('.ship-safeignore found');
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
info('.ship-safeignore not found (run: ship-safe init)');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 6. Cache directory
|
|
97
|
+
const cacheDir = path.join(cwd, '.ship-safe');
|
|
98
|
+
if (fs.existsSync(cacheDir)) {
|
|
99
|
+
try {
|
|
100
|
+
const testFile = path.join(cacheDir, '.doctor-test');
|
|
101
|
+
fs.writeFileSync(testFile, 'test');
|
|
102
|
+
fs.unlinkSync(testFile);
|
|
103
|
+
pass('Cache directory writable');
|
|
104
|
+
} catch {
|
|
105
|
+
fail('Cache directory not writable');
|
|
106
|
+
allGood = false;
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
info('Cache directory does not exist yet (created on first scan)');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 7. Version check
|
|
113
|
+
pass(`ship-safe v${PACKAGE_VERSION}`);
|
|
114
|
+
try {
|
|
115
|
+
const latest = execFileSync('npm', ['view', 'ship-safe', 'version'], {
|
|
116
|
+
encoding: 'utf-8', timeout: 5000, shell: true,
|
|
117
|
+
}).trim();
|
|
118
|
+
if (latest && latest !== PACKAGE_VERSION) {
|
|
119
|
+
const msg = ['v', latest, ' available (current: v', PACKAGE_VERSION, ')'].join('');
|
|
120
|
+
info(msg);
|
|
121
|
+
} else if (latest) {
|
|
122
|
+
pass('Up to date');
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
// Skip version check if npm view fails
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log();
|
|
129
|
+
if (allGood) {
|
|
130
|
+
console.log(chalk.green.bold(' All checks passed!'));
|
|
131
|
+
} else {
|
|
132
|
+
console.log(chalk.yellow.bold(' Some checks failed. See above for details.'));
|
|
133
|
+
}
|
|
134
|
+
console.log();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function pass(msg) {
|
|
138
|
+
console.log(chalk.green(' ✔ ') + chalk.white(msg));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function fail(msg) {
|
|
142
|
+
console.log(chalk.red(' ✗ ') + chalk.red(msg));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function info(msg) {
|
|
146
|
+
console.log(chalk.gray(' ○ ') + chalk.gray(msg));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default doctorCommand;
|
|
@@ -34,7 +34,7 @@ import fs from 'fs';
|
|
|
34
34
|
import path from 'path';
|
|
35
35
|
import os from 'os';
|
|
36
36
|
import { createInterface } from 'readline';
|
|
37
|
-
import { execSync } from 'child_process';
|
|
37
|
+
import { execSync, execFileSync } from 'child_process';
|
|
38
38
|
import chalk from 'chalk';
|
|
39
39
|
import ora from 'ora';
|
|
40
40
|
import pkg from 'write-file-atomic';
|
|
@@ -270,6 +270,11 @@ function createBackupDir(rootPath) {
|
|
|
270
270
|
function backupFile(filePath, backupDir, rootPath) {
|
|
271
271
|
const rel = path.relative(rootPath, filePath);
|
|
272
272
|
const dest = path.join(backupDir, rel);
|
|
273
|
+
const resolvedDest = path.resolve(dest);
|
|
274
|
+
const resolvedBackupDir = path.resolve(backupDir);
|
|
275
|
+
if (!resolvedDest.startsWith(resolvedBackupDir + path.sep) && resolvedDest !== resolvedBackupDir) {
|
|
276
|
+
throw new Error(`Path traversal detected: ${rel} escapes backup directory`);
|
|
277
|
+
}
|
|
273
278
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
274
279
|
fs.copyFileSync(filePath, dest);
|
|
275
280
|
}
|
|
@@ -414,8 +419,7 @@ function checkPublicRepo(rootPath) {
|
|
|
414
419
|
function stageFiles(files, rootPath) {
|
|
415
420
|
if (files.length === 0) return;
|
|
416
421
|
try {
|
|
417
|
-
|
|
418
|
-
execSync(`git add ${quoted}`, { cwd: rootPath, stdio: 'inherit' }); // ship-safe-ignore — paths come from our own file scan
|
|
422
|
+
execFileSync('git', ['add', ...files], { cwd: rootPath, stdio: 'inherit' });
|
|
419
423
|
output.success(`Staged ${files.length} file(s) with git add`);
|
|
420
424
|
} catch {
|
|
421
425
|
output.warning('Could not stage files — run git add manually.');
|
package/cli/index.js
CHANGED
|
@@ -1,53 +1,60 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ship Safe CLI - Module Entry Point
|
|
3
|
-
* ===================================
|
|
4
|
-
*
|
|
5
|
-
* This file exports the CLI commands and agents for programmatic use.
|
|
6
|
-
* For normal CLI usage, run: npx ship-safe
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// ── Core Commands ─────────────────────────────────────────────────────────────
|
|
10
|
-
export { scanCommand } from './commands/scan.js';
|
|
11
|
-
export { checklistCommand } from './commands/checklist.js';
|
|
12
|
-
export { initCommand } from './commands/init.js';
|
|
13
|
-
export { agentCommand } from './commands/agent.js';
|
|
14
|
-
export { depsCommand, runDepsAudit } from './commands/deps.js';
|
|
15
|
-
export { scoreCommand } from './commands/score.js';
|
|
16
|
-
|
|
17
|
-
// ── v4.0 Commands ─────────────────────────────────────────────────────────────
|
|
18
|
-
export { auditCommand } from './commands/audit.js';
|
|
19
|
-
export { redTeamCommand } from './commands/red-team.js';
|
|
20
|
-
export { watchCommand } from './commands/watch.js';
|
|
21
|
-
|
|
22
|
-
// ──
|
|
23
|
-
export {
|
|
24
|
-
|
|
25
|
-
// ──
|
|
26
|
-
export {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
34
|
-
export {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export {
|
|
38
|
-
export {
|
|
39
|
-
export {
|
|
40
|
-
export {
|
|
41
|
-
export {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
export {
|
|
45
|
-
export {
|
|
46
|
-
export {
|
|
47
|
-
export {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export {
|
|
1
|
+
/**
|
|
2
|
+
* Ship Safe CLI - Module Entry Point
|
|
3
|
+
* ===================================
|
|
4
|
+
*
|
|
5
|
+
* This file exports the CLI commands and agents for programmatic use.
|
|
6
|
+
* For normal CLI usage, run: npx ship-safe
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ── Core Commands ─────────────────────────────────────────────────────────────
|
|
10
|
+
export { scanCommand } from './commands/scan.js';
|
|
11
|
+
export { checklistCommand } from './commands/checklist.js';
|
|
12
|
+
export { initCommand } from './commands/init.js';
|
|
13
|
+
export { agentCommand } from './commands/agent.js';
|
|
14
|
+
export { depsCommand, runDepsAudit } from './commands/deps.js';
|
|
15
|
+
export { scoreCommand } from './commands/score.js';
|
|
16
|
+
|
|
17
|
+
// ── v4.0 Commands ─────────────────────────────────────────────────────────────
|
|
18
|
+
export { auditCommand } from './commands/audit.js';
|
|
19
|
+
export { redTeamCommand } from './commands/red-team.js';
|
|
20
|
+
export { watchCommand } from './commands/watch.js';
|
|
21
|
+
|
|
22
|
+
// ── v4.2 Commands ─────────────────────────────────────────────────────────────
|
|
23
|
+
export { doctorCommand } from './commands/doctor.js';
|
|
24
|
+
|
|
25
|
+
// ── v4.3 Commands ─────────────────────────────────────────────────────────────
|
|
26
|
+
export { baselineCommand } from './commands/baseline.js';
|
|
27
|
+
|
|
28
|
+
// ── Patterns ──────────────────────────────────────────────────────────────────
|
|
29
|
+
export { SECRET_PATTERNS, SECURITY_PATTERNS, SKIP_DIRS, SKIP_EXTENSIONS } from './utils/patterns.js';
|
|
30
|
+
|
|
31
|
+
// ── Agent Framework ───────────────────────────────────────────────────────────
|
|
32
|
+
export { BaseAgent, createFinding } from './agents/base-agent.js';
|
|
33
|
+
export { Orchestrator } from './agents/orchestrator.js';
|
|
34
|
+
export { buildOrchestrator } from './agents/index.js';
|
|
35
|
+
|
|
36
|
+
// ── Individual Agents ─────────────────────────────────────────────────────────
|
|
37
|
+
export { ReconAgent } from './agents/recon-agent.js';
|
|
38
|
+
export { InjectionTester } from './agents/injection-tester.js';
|
|
39
|
+
export { AuthBypassAgent } from './agents/auth-bypass-agent.js';
|
|
40
|
+
export { SSRFProber } from './agents/ssrf-prober.js';
|
|
41
|
+
export { SupplyChainAudit } from './agents/supply-chain-agent.js';
|
|
42
|
+
export { ConfigAuditor } from './agents/config-auditor.js';
|
|
43
|
+
export { LLMRedTeam } from './agents/llm-redteam.js';
|
|
44
|
+
export { MobileScanner } from './agents/mobile-scanner.js';
|
|
45
|
+
export { GitHistoryScanner } from './agents/git-history-scanner.js';
|
|
46
|
+
export { CICDScanner } from './agents/cicd-scanner.js';
|
|
47
|
+
export { APIFuzzer } from './agents/api-fuzzer.js';
|
|
48
|
+
export { SupabaseRLSAgent } from './agents/supabase-rls-agent.js';
|
|
49
|
+
|
|
50
|
+
// ── Supporting Modules ────────────────────────────────────────────────────────
|
|
51
|
+
export { ScoringEngine, GRADES, CATEGORIES } from './agents/scoring-engine.js';
|
|
52
|
+
export { SBOMGenerator } from './agents/sbom-generator.js';
|
|
53
|
+
export { PolicyEngine } from './agents/policy-engine.js';
|
|
54
|
+
export { HTMLReporter } from './agents/html-reporter.js';
|
|
55
|
+
|
|
56
|
+
// ── Caching ──────────────────────────────────────────────────────────────────
|
|
57
|
+
export { CacheManager } from './utils/cache-manager.js';
|
|
58
|
+
|
|
59
|
+
// ── LLM Providers ─────────────────────────────────────────────────────────────
|
|
60
|
+
export { createProvider, autoDetectProvider } from './providers/llm-provider.js';
|