ship-safe 6.1.1 → 6.2.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 +735 -641
- package/cli/agents/api-fuzzer.js +345 -345
- package/cli/agents/auth-bypass-agent.js +348 -348
- package/cli/agents/base-agent.js +272 -272
- package/cli/agents/cicd-scanner.js +236 -201
- package/cli/agents/config-auditor.js +521 -521
- package/cli/agents/deep-analyzer.js +6 -2
- package/cli/agents/git-history-scanner.js +170 -170
- package/cli/agents/html-reporter.js +568 -568
- package/cli/agents/index.js +84 -84
- package/cli/agents/injection-tester.js +500 -500
- package/cli/agents/llm-redteam.js +251 -251
- package/cli/agents/mobile-scanner.js +231 -231
- package/cli/agents/orchestrator.js +322 -322
- package/cli/agents/pii-compliance-agent.js +301 -301
- package/cli/agents/scoring-engine.js +248 -248
- package/cli/agents/supabase-rls-agent.js +154 -154
- package/cli/agents/supply-chain-agent.js +650 -507
- package/cli/bin/ship-safe.js +452 -426
- package/cli/commands/agent.js +608 -608
- package/cli/commands/audit.js +986 -980
- package/cli/commands/baseline.js +193 -193
- package/cli/commands/ci.js +342 -342
- package/cli/commands/deps.js +516 -516
- package/cli/commands/doctor.js +159 -159
- package/cli/commands/fix.js +218 -218
- package/cli/commands/hooks.js +268 -0
- package/cli/commands/init.js +407 -407
- package/cli/commands/mcp.js +304 -304
- package/cli/commands/red-team.js +7 -1
- package/cli/commands/remediate.js +798 -798
- package/cli/commands/rotate.js +571 -571
- package/cli/commands/scan.js +569 -569
- package/cli/commands/score.js +449 -449
- package/cli/commands/watch.js +281 -281
- package/cli/hooks/patterns.js +313 -0
- package/cli/hooks/post-tool-use.js +140 -0
- package/cli/hooks/pre-tool-use.js +186 -0
- package/cli/index.js +73 -69
- package/cli/providers/llm-provider.js +397 -287
- package/cli/utils/autofix-rules.js +74 -74
- package/cli/utils/cache-manager.js +311 -311
- package/cli/utils/output.js +230 -230
- package/cli/utils/patterns.js +1121 -1121
- package/cli/utils/pdf-generator.js +94 -94
- package/package.json +69 -69
- package/configs/supabase/rls-templates.sql +0 -242
package/cli/commands/doctor.js
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
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); // ship-safe-ignore — module's own path via import.meta.url, not user input
|
|
21
|
-
const __dirname = dirname(__filename);
|
|
22
|
-
const PACKAGE_VERSION = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8')).version;
|
|
23
|
-
|
|
24
|
-
function isNewerVersion(latest, current) {
|
|
25
|
-
const a = latest.split('.').map(Number);
|
|
26
|
-
const b = current.split('.').map(Number);
|
|
27
|
-
for (let i = 0; i < 3; i++) {
|
|
28
|
-
if ((a[i] || 0) > (b[i] || 0)) return true;
|
|
29
|
-
if ((a[i] || 0) < (b[i] || 0)) return false;
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function doctorCommand() {
|
|
35
|
-
console.log();
|
|
36
|
-
console.log(chalk.cyan.bold(' Ship Safe Doctor'));
|
|
37
|
-
console.log(chalk.gray(' ' + '─'.repeat(50)));
|
|
38
|
-
console.log();
|
|
39
|
-
|
|
40
|
-
let allGood = true;
|
|
41
|
-
|
|
42
|
-
// 1. Node.js version
|
|
43
|
-
const nodeVersion = process.version;
|
|
44
|
-
const nodeMajor = parseInt(nodeVersion.slice(1), 10);
|
|
45
|
-
if (nodeMajor >= 18) {
|
|
46
|
-
pass(`Node.js ${nodeVersion} (requires ≥18)`);
|
|
47
|
-
} else {
|
|
48
|
-
fail(`Node.js ${nodeVersion} — requires ≥18`);
|
|
49
|
-
allGood = false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 2. Git
|
|
53
|
-
try {
|
|
54
|
-
const gitVersion = execFileSync('git', ['--version'], { encoding: 'utf-8' }).trim();
|
|
55
|
-
pass(gitVersion.replace('git version ', 'git v'));
|
|
56
|
-
} catch {
|
|
57
|
-
fail('git not found (needed for guard, git-history-scanner)');
|
|
58
|
-
allGood = false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 3. Package manager
|
|
62
|
-
const managers = ['npm', 'yarn', 'pnpm'];
|
|
63
|
-
let foundPm = false;
|
|
64
|
-
for (const pm of managers) {
|
|
65
|
-
try {
|
|
66
|
-
const ver = execFileSync(pm, ['--version'], { encoding: 'utf-8', shell: true }).trim();
|
|
67
|
-
pass(`${pm} v${ver}`);
|
|
68
|
-
foundPm = true;
|
|
69
|
-
break;
|
|
70
|
-
} catch { /* try next */ }
|
|
71
|
-
}
|
|
72
|
-
if (!foundPm) {
|
|
73
|
-
fail('No package manager found (npm/yarn/pnpm)');
|
|
74
|
-
allGood = false;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 4. API keys
|
|
78
|
-
const apiKeys = [
|
|
79
|
-
{ name: 'Anthropic API key', env: 'ANTHROPIC_API_KEY', required: false }, // ship-safe-ignore — env var names in diagnostic check, no key values
|
|
80
|
-
{ name: 'OpenAI API key', env: 'OPENAI_API_KEY', required: false }, // ship-safe-ignore — env var name in diagnostic check, no key value
|
|
81
|
-
{ name: 'Google AI API key', env: 'GOOGLE_API_KEY', required: false },
|
|
82
|
-
];
|
|
83
|
-
for (const key of apiKeys) {
|
|
84
|
-
if (process.env[key.env]) {
|
|
85
|
-
pass(`${key.name} configured`);
|
|
86
|
-
} else {
|
|
87
|
-
info(`${key.name} not set (optional — for AI classification)`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 5. .ship-safeignore
|
|
92
|
-
const cwd = process.cwd();
|
|
93
|
-
const ignorePath = path.join(cwd, '.ship-safeignore');
|
|
94
|
-
if (fs.existsSync(ignorePath)) {
|
|
95
|
-
try {
|
|
96
|
-
const patterns = fs.readFileSync(ignorePath, 'utf-8')
|
|
97
|
-
.split('\n').filter(l => l.trim() && !l.startsWith('#')).length;
|
|
98
|
-
pass(`.ship-safeignore found (${patterns} patterns)`);
|
|
99
|
-
} catch {
|
|
100
|
-
pass('.ship-safeignore found');
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
info('.ship-safeignore not found (run: ship-safe init)');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 6. Cache directory
|
|
107
|
-
const cacheDir = path.join(cwd, '.ship-safe');
|
|
108
|
-
if (fs.existsSync(cacheDir)) {
|
|
109
|
-
try {
|
|
110
|
-
const testFile = path.join(cacheDir, '.doctor-test');
|
|
111
|
-
fs.writeFileSync(testFile, 'test');
|
|
112
|
-
fs.unlinkSync(testFile);
|
|
113
|
-
pass('Cache directory writable');
|
|
114
|
-
} catch {
|
|
115
|
-
fail('Cache directory not writable');
|
|
116
|
-
allGood = false;
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
info('Cache directory does not exist yet (created on first scan)');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 7. Version check
|
|
123
|
-
pass(`ship-safe v${PACKAGE_VERSION}`);
|
|
124
|
-
try {
|
|
125
|
-
const latest = execFileSync('npm', ['view', 'ship-safe', 'version'], {
|
|
126
|
-
encoding: 'utf-8', timeout: 5000, shell: true,
|
|
127
|
-
}).trim();
|
|
128
|
-
if (latest && latest !== PACKAGE_VERSION && isNewerVersion(latest, PACKAGE_VERSION)) {
|
|
129
|
-
const msg = ['v', latest, ' available (current: v', PACKAGE_VERSION, ')'].join('');
|
|
130
|
-
info(msg);
|
|
131
|
-
} else if (latest) {
|
|
132
|
-
pass('Up to date');
|
|
133
|
-
}
|
|
134
|
-
} catch {
|
|
135
|
-
// Skip version check if npm view fails
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
console.log();
|
|
139
|
-
if (allGood) {
|
|
140
|
-
console.log(chalk.green.bold(' All checks passed!'));
|
|
141
|
-
} else {
|
|
142
|
-
console.log(chalk.yellow.bold(' Some checks failed. See above for details.'));
|
|
143
|
-
}
|
|
144
|
-
console.log();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function pass(msg) {
|
|
148
|
-
console.log(chalk.green(' ✔ ') + chalk.white(msg));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function fail(msg) {
|
|
152
|
-
console.log(chalk.red(' ✗ ') + chalk.red(msg));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function info(msg) {
|
|
156
|
-
console.log(chalk.gray(' ○ ') + chalk.gray(msg));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export default doctorCommand;
|
|
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); // ship-safe-ignore — module's own path via import.meta.url, not user input
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
const PACKAGE_VERSION = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8')).version;
|
|
23
|
+
|
|
24
|
+
function isNewerVersion(latest, current) {
|
|
25
|
+
const a = latest.split('.').map(Number);
|
|
26
|
+
const b = current.split('.').map(Number);
|
|
27
|
+
for (let i = 0; i < 3; i++) {
|
|
28
|
+
if ((a[i] || 0) > (b[i] || 0)) return true;
|
|
29
|
+
if ((a[i] || 0) < (b[i] || 0)) return false;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function doctorCommand() {
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(chalk.cyan.bold(' Ship Safe Doctor'));
|
|
37
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)));
|
|
38
|
+
console.log();
|
|
39
|
+
|
|
40
|
+
let allGood = true;
|
|
41
|
+
|
|
42
|
+
// 1. Node.js version
|
|
43
|
+
const nodeVersion = process.version;
|
|
44
|
+
const nodeMajor = parseInt(nodeVersion.slice(1), 10);
|
|
45
|
+
if (nodeMajor >= 18) {
|
|
46
|
+
pass(`Node.js ${nodeVersion} (requires ≥18)`);
|
|
47
|
+
} else {
|
|
48
|
+
fail(`Node.js ${nodeVersion} — requires ≥18`);
|
|
49
|
+
allGood = false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. Git
|
|
53
|
+
try {
|
|
54
|
+
const gitVersion = execFileSync('git', ['--version'], { encoding: 'utf-8' }).trim();
|
|
55
|
+
pass(gitVersion.replace('git version ', 'git v'));
|
|
56
|
+
} catch {
|
|
57
|
+
fail('git not found (needed for guard, git-history-scanner)');
|
|
58
|
+
allGood = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 3. Package manager
|
|
62
|
+
const managers = ['npm', 'yarn', 'pnpm'];
|
|
63
|
+
let foundPm = false;
|
|
64
|
+
for (const pm of managers) {
|
|
65
|
+
try {
|
|
66
|
+
const ver = execFileSync(pm, ['--version'], { encoding: 'utf-8', shell: true }).trim();
|
|
67
|
+
pass(`${pm} v${ver}`);
|
|
68
|
+
foundPm = true;
|
|
69
|
+
break;
|
|
70
|
+
} catch { /* try next */ }
|
|
71
|
+
}
|
|
72
|
+
if (!foundPm) {
|
|
73
|
+
fail('No package manager found (npm/yarn/pnpm)');
|
|
74
|
+
allGood = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. API keys
|
|
78
|
+
const apiKeys = [
|
|
79
|
+
{ name: 'Anthropic API key', env: 'ANTHROPIC_API_KEY', required: false }, // ship-safe-ignore — env var names in diagnostic check, no key values
|
|
80
|
+
{ name: 'OpenAI API key', env: 'OPENAI_API_KEY', required: false }, // ship-safe-ignore — env var name in diagnostic check, no key value
|
|
81
|
+
{ name: 'Google AI API key', env: 'GOOGLE_API_KEY', required: false },
|
|
82
|
+
];
|
|
83
|
+
for (const key of apiKeys) {
|
|
84
|
+
if (process.env[key.env]) {
|
|
85
|
+
pass(`${key.name} configured`);
|
|
86
|
+
} else {
|
|
87
|
+
info(`${key.name} not set (optional — for AI classification)`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 5. .ship-safeignore
|
|
92
|
+
const cwd = process.cwd();
|
|
93
|
+
const ignorePath = path.join(cwd, '.ship-safeignore');
|
|
94
|
+
if (fs.existsSync(ignorePath)) {
|
|
95
|
+
try {
|
|
96
|
+
const patterns = fs.readFileSync(ignorePath, 'utf-8')
|
|
97
|
+
.split('\n').filter(l => l.trim() && !l.startsWith('#')).length;
|
|
98
|
+
pass(`.ship-safeignore found (${patterns} patterns)`);
|
|
99
|
+
} catch {
|
|
100
|
+
pass('.ship-safeignore found');
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
info('.ship-safeignore not found (run: ship-safe init)');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 6. Cache directory
|
|
107
|
+
const cacheDir = path.join(cwd, '.ship-safe');
|
|
108
|
+
if (fs.existsSync(cacheDir)) {
|
|
109
|
+
try {
|
|
110
|
+
const testFile = path.join(cacheDir, '.doctor-test');
|
|
111
|
+
fs.writeFileSync(testFile, 'test');
|
|
112
|
+
fs.unlinkSync(testFile);
|
|
113
|
+
pass('Cache directory writable');
|
|
114
|
+
} catch {
|
|
115
|
+
fail('Cache directory not writable');
|
|
116
|
+
allGood = false;
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
info('Cache directory does not exist yet (created on first scan)');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 7. Version check
|
|
123
|
+
pass(`ship-safe v${PACKAGE_VERSION}`);
|
|
124
|
+
try {
|
|
125
|
+
const latest = execFileSync('npm', ['view', 'ship-safe', 'version'], {
|
|
126
|
+
encoding: 'utf-8', timeout: 5000, shell: true,
|
|
127
|
+
}).trim();
|
|
128
|
+
if (latest && latest !== PACKAGE_VERSION && isNewerVersion(latest, PACKAGE_VERSION)) {
|
|
129
|
+
const msg = ['v', latest, ' available (current: v', PACKAGE_VERSION, ')'].join('');
|
|
130
|
+
info(msg);
|
|
131
|
+
} else if (latest) {
|
|
132
|
+
pass('Up to date');
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// Skip version check if npm view fails
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log();
|
|
139
|
+
if (allGood) {
|
|
140
|
+
console.log(chalk.green.bold(' All checks passed!'));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(chalk.yellow.bold(' Some checks failed. See above for details.'));
|
|
143
|
+
}
|
|
144
|
+
console.log();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function pass(msg) {
|
|
148
|
+
console.log(chalk.green(' ✔ ') + chalk.white(msg));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function fail(msg) {
|
|
152
|
+
console.log(chalk.red(' ✗ ') + chalk.red(msg));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function info(msg) {
|
|
156
|
+
console.log(chalk.gray(' ○ ') + chalk.gray(msg));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default doctorCommand;
|