ship-safe 9.2.1 → 9.2.3
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 +86 -713
- package/cli/.ship-safe/context.json +8157 -0
- package/cli/.ship-safe/history.json +190 -0
- package/cli/bin/ship-safe.js +9 -0
- package/cli/commands/audit.js +13 -1
- package/cli/commands/share.js +71 -0
- package/cli/commands/shell.js +12 -1
- package/package.json +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"timestamp": "2026-04-24T05:18:07.805Z",
|
|
4
|
+
"score": 13,
|
|
5
|
+
"grade": "F",
|
|
6
|
+
"totalFindings": 533,
|
|
7
|
+
"totalDepVulns": 0,
|
|
8
|
+
"categoryScores": {
|
|
9
|
+
"secrets": {
|
|
10
|
+
"deduction": 15,
|
|
11
|
+
"counts": {
|
|
12
|
+
"critical": 5,
|
|
13
|
+
"high": 0,
|
|
14
|
+
"medium": 2,
|
|
15
|
+
"low": 0
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"injection": {
|
|
19
|
+
"deduction": 15,
|
|
20
|
+
"counts": {
|
|
21
|
+
"critical": 24,
|
|
22
|
+
"high": 47,
|
|
23
|
+
"medium": 22,
|
|
24
|
+
"low": 0
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"deps": {
|
|
28
|
+
"deduction": 0,
|
|
29
|
+
"counts": {
|
|
30
|
+
"critical": 0,
|
|
31
|
+
"high": 0,
|
|
32
|
+
"medium": 0,
|
|
33
|
+
"low": 0
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"auth": {
|
|
37
|
+
"deduction": 15,
|
|
38
|
+
"counts": {
|
|
39
|
+
"critical": 5,
|
|
40
|
+
"high": 18,
|
|
41
|
+
"medium": 10,
|
|
42
|
+
"low": 0
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"config": {
|
|
46
|
+
"deduction": 8,
|
|
47
|
+
"counts": {
|
|
48
|
+
"critical": 3,
|
|
49
|
+
"high": 16,
|
|
50
|
+
"medium": 12,
|
|
51
|
+
"low": 2
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"supply-chain": {
|
|
55
|
+
"deduction": 12,
|
|
56
|
+
"counts": {
|
|
57
|
+
"critical": 3,
|
|
58
|
+
"high": 4,
|
|
59
|
+
"medium": 0,
|
|
60
|
+
"low": 0
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"api": {
|
|
64
|
+
"deduction": 10,
|
|
65
|
+
"counts": {
|
|
66
|
+
"critical": 3,
|
|
67
|
+
"high": 21,
|
|
68
|
+
"medium": 5,
|
|
69
|
+
"low": 6
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"llm": {
|
|
73
|
+
"deduction": 12,
|
|
74
|
+
"counts": {
|
|
75
|
+
"critical": 23,
|
|
76
|
+
"high": 178,
|
|
77
|
+
"medium": 124,
|
|
78
|
+
"low": 0
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"suppressions": {
|
|
83
|
+
"total": 94,
|
|
84
|
+
"rules": {
|
|
85
|
+
"suppression": 1,
|
|
86
|
+
"_unspecified": 78,
|
|
87
|
+
"annotations": 2,
|
|
88
|
+
"on": 4,
|
|
89
|
+
"comments": 1,
|
|
90
|
+
"RULE_NAME": 1,
|
|
91
|
+
"comment": 5,
|
|
92
|
+
"as": 2
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"timestamp": "2026-04-25T19:29:18.418Z",
|
|
98
|
+
"score": 13,
|
|
99
|
+
"grade": "F",
|
|
100
|
+
"totalFindings": 541,
|
|
101
|
+
"totalDepVulns": 0,
|
|
102
|
+
"categoryScores": {
|
|
103
|
+
"secrets": {
|
|
104
|
+
"deduction": 15,
|
|
105
|
+
"counts": {
|
|
106
|
+
"critical": 6,
|
|
107
|
+
"high": 0,
|
|
108
|
+
"medium": 2,
|
|
109
|
+
"low": 0
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"injection": {
|
|
113
|
+
"deduction": 15,
|
|
114
|
+
"counts": {
|
|
115
|
+
"critical": 24,
|
|
116
|
+
"high": 47,
|
|
117
|
+
"medium": 22,
|
|
118
|
+
"low": 0
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"deps": {
|
|
122
|
+
"deduction": 0,
|
|
123
|
+
"counts": {
|
|
124
|
+
"critical": 0,
|
|
125
|
+
"high": 0,
|
|
126
|
+
"medium": 0,
|
|
127
|
+
"low": 0
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"auth": {
|
|
131
|
+
"deduction": 15,
|
|
132
|
+
"counts": {
|
|
133
|
+
"critical": 5,
|
|
134
|
+
"high": 18,
|
|
135
|
+
"medium": 10,
|
|
136
|
+
"low": 0
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"config": {
|
|
140
|
+
"deduction": 8,
|
|
141
|
+
"counts": {
|
|
142
|
+
"critical": 3,
|
|
143
|
+
"high": 16,
|
|
144
|
+
"medium": 12,
|
|
145
|
+
"low": 2
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"supply-chain": {
|
|
149
|
+
"deduction": 12,
|
|
150
|
+
"counts": {
|
|
151
|
+
"critical": 3,
|
|
152
|
+
"high": 4,
|
|
153
|
+
"medium": 0,
|
|
154
|
+
"low": 0
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"api": {
|
|
158
|
+
"deduction": 10,
|
|
159
|
+
"counts": {
|
|
160
|
+
"critical": 3,
|
|
161
|
+
"high": 21,
|
|
162
|
+
"medium": 5,
|
|
163
|
+
"low": 6
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"llm": {
|
|
167
|
+
"deduction": 12,
|
|
168
|
+
"counts": {
|
|
169
|
+
"critical": 23,
|
|
170
|
+
"high": 165,
|
|
171
|
+
"medium": 144,
|
|
172
|
+
"low": 0
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"suppressions": {
|
|
177
|
+
"total": 94,
|
|
178
|
+
"rules": {
|
|
179
|
+
"_unspecified": 78,
|
|
180
|
+
"suppression": 1,
|
|
181
|
+
"annotations": 2,
|
|
182
|
+
"on": 4,
|
|
183
|
+
"comments": 1,
|
|
184
|
+
"RULE_NAME": 1,
|
|
185
|
+
"comment": 5,
|
|
186
|
+
"as": 2
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
]
|
package/cli/bin/ship-safe.js
CHANGED
|
@@ -31,6 +31,7 @@ import { rotateCommand } from '../commands/rotate.js';
|
|
|
31
31
|
import { agentCommand } from '../commands/agent.js';
|
|
32
32
|
import { agentFixCommand } from '../commands/agent-fix.js';
|
|
33
33
|
import { undoCommand } from '../commands/undo.js';
|
|
34
|
+
import { shareCommand } from '../commands/share.js';
|
|
34
35
|
import { shellCommand } from '../commands/shell.js';
|
|
35
36
|
import { depsCommand } from '../commands/deps.js';
|
|
36
37
|
import { scoreCommand } from '../commands/score.js';
|
|
@@ -217,6 +218,14 @@ program
|
|
|
217
218
|
.option('--dry-run', 'Show what would be reverted without writing anything')
|
|
218
219
|
.action(undoCommand);
|
|
219
220
|
|
|
221
|
+
// -----------------------------------------------------------------------------
|
|
222
|
+
// SHARE COMMAND
|
|
223
|
+
// -----------------------------------------------------------------------------
|
|
224
|
+
program
|
|
225
|
+
.command('share [path]')
|
|
226
|
+
.description('Publish your latest scan report as a public URL (valid 7 days)')
|
|
227
|
+
.action(shareCommand);
|
|
228
|
+
|
|
220
229
|
// -----------------------------------------------------------------------------
|
|
221
230
|
// SHELL COMMAND
|
|
222
231
|
// -----------------------------------------------------------------------------
|
package/cli/commands/audit.js
CHANGED
|
@@ -327,7 +327,7 @@ export async function auditCommand(targetPath = '.', options = {}) {
|
|
|
327
327
|
);
|
|
328
328
|
|
|
329
329
|
// ── AI Classification (optional, with LLM cache) ───────────────────────
|
|
330
|
-
if (options.ai !== false) {
|
|
330
|
+
if (options.ai !== false && !options.noAi) {
|
|
331
331
|
const provider = autoDetectProvider(absolutePath, {
|
|
332
332
|
provider: options.provider,
|
|
333
333
|
baseUrl: options.baseUrl,
|
|
@@ -499,6 +499,18 @@ export async function auditCommand(targetPath = '.', options = {}) {
|
|
|
499
499
|
printComparison(scoringEngine, absolutePath, scoreResult);
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
+
// ── Upgrade prompt ────────────────────────────────────────────────────
|
|
503
|
+
const criticalCount = filteredFindings.filter(f => f.severity === 'critical').length;
|
|
504
|
+
const highCount = filteredFindings.filter(f => f.severity === 'high').length;
|
|
505
|
+
const fixable = criticalCount + highCount;
|
|
506
|
+
if (fixable > 0 && scoreResult.score < 80) {
|
|
507
|
+
console.log();
|
|
508
|
+
console.log(chalk.yellow(' ┌─────────────────────────────────────────────────────────┐'));
|
|
509
|
+
console.log(chalk.yellow(' │') + chalk.white.bold(` ${fixable} critical/high finding${fixable === 1 ? '' : 's'} — fix them automatically`) + chalk.yellow(' │'));
|
|
510
|
+
console.log(chalk.yellow(' │') + chalk.cyan(' npx ship-safe agent .') + chalk.gray(' · ') + chalk.cyan('shipsafecli.com/pricing') + chalk.yellow(' │'));
|
|
511
|
+
console.log(chalk.yellow(' └─────────────────────────────────────────────────────────┘'));
|
|
512
|
+
}
|
|
513
|
+
|
|
502
514
|
console.log();
|
|
503
515
|
console.log(chalk.cyan('═'.repeat(60)));
|
|
504
516
|
console.log();
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Share Command — Publish a scan report as a public URL
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ship-safe share [path]
|
|
6
|
+
*
|
|
7
|
+
* Reads the latest scan from .ship-safe/history.json (or runs a fresh scan),
|
|
8
|
+
* uploads it to shipsafecli.com, and returns a shareable link valid for 7 days.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import ora from 'ora';
|
|
15
|
+
|
|
16
|
+
const SHARE_ENDPOINT = 'https://shipsafecli.com/api/share';
|
|
17
|
+
|
|
18
|
+
export async function shareCommand(targetPath = '.', options = {}) {
|
|
19
|
+
const root = path.resolve(targetPath);
|
|
20
|
+
|
|
21
|
+
// ── Load last scan from history ──────────────────────────────────────────
|
|
22
|
+
const historyPath = path.join(root, '.ship-safe', 'history.json');
|
|
23
|
+
let report = null;
|
|
24
|
+
|
|
25
|
+
if (fs.existsSync(historyPath)) {
|
|
26
|
+
try {
|
|
27
|
+
const history = JSON.parse(fs.readFileSync(historyPath, 'utf8'));
|
|
28
|
+
const entries = Array.isArray(history) ? history : [history];
|
|
29
|
+
if (entries.length > 0) report = entries[entries.length - 1];
|
|
30
|
+
} catch {
|
|
31
|
+
// fall through
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!report) {
|
|
36
|
+
console.log(chalk.yellow(' No scan found. Run a scan first:'));
|
|
37
|
+
console.log(chalk.gray(' npx ship-safe audit .'));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const spinner = ora({ text: 'Uploading report...', color: 'cyan' }).start();
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(SHARE_ENDPOINT, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
score: report.score ?? null,
|
|
49
|
+
grade: report.grade ?? null,
|
|
50
|
+
repo: report.rootPath ? path.basename(report.rootPath) : null,
|
|
51
|
+
findings: report.totalFindings ?? 0,
|
|
52
|
+
report,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
throw new Error(`Server returned ${res.status}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { url } = await res.json();
|
|
61
|
+
spinner.succeed(chalk.green('Report shared!'));
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(chalk.white(' Share URL: ') + chalk.cyan.bold(url));
|
|
64
|
+
console.log(chalk.gray(' Link expires in 7 days.'));
|
|
65
|
+
console.log();
|
|
66
|
+
} catch (err) {
|
|
67
|
+
spinner.fail(chalk.red('Failed to share report'));
|
|
68
|
+
console.log(chalk.gray(` ${err.message}`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
package/cli/commands/shell.js
CHANGED
|
@@ -29,6 +29,7 @@ import { autoDetectProvider } from '../providers/llm-provider.js';
|
|
|
29
29
|
import { auditCommand } from './audit.js';
|
|
30
30
|
import { agentFixCommand } from './agent-fix.js';
|
|
31
31
|
import { undoCommand } from './undo.js';
|
|
32
|
+
import { shareCommand } from './share.js';
|
|
32
33
|
import * as output from '../utils/output.js';
|
|
33
34
|
|
|
34
35
|
const SEV_RANK = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
|
|
@@ -292,13 +293,16 @@ async function handleSlashCommand(line, state, options) {
|
|
|
292
293
|
case 'agent':
|
|
293
294
|
case 'fix': {
|
|
294
295
|
// Hand off to agent command. Pass through caller options + any inline flags.
|
|
296
|
+
// Forward the active provider key so /provider switches take effect.
|
|
295
297
|
const opts = { ...options };
|
|
298
|
+
if (state.providerKey) opts.provider = state.providerKey;
|
|
296
299
|
for (const a of args) {
|
|
297
300
|
if (a === '--plan-only') opts.planOnly = true;
|
|
298
301
|
if (a === '--allow-dirty') opts.allowDirty = true;
|
|
299
302
|
if (a === '--branch') opts.branch = true;
|
|
300
303
|
if (a === '--pr') opts.pr = true;
|
|
301
304
|
if (a.startsWith('--severity=')) opts.severity = a.slice('--severity='.length);
|
|
305
|
+
if (a.startsWith('--provider=')) opts.provider = a.slice('--provider='.length);
|
|
302
306
|
}
|
|
303
307
|
try {
|
|
304
308
|
await agentFixCommand(state.root, opts);
|
|
@@ -317,6 +321,11 @@ async function handleSlashCommand(line, state, options) {
|
|
|
317
321
|
return true;
|
|
318
322
|
}
|
|
319
323
|
|
|
324
|
+
case 'share': {
|
|
325
|
+
await shareCommand(state.root);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
320
329
|
case 'provider': {
|
|
321
330
|
const name = args[0];
|
|
322
331
|
if (!name) {
|
|
@@ -328,7 +337,8 @@ async function handleSlashCommand(line, state, options) {
|
|
|
328
337
|
if (!next) {
|
|
329
338
|
console.log(chalk.yellow(` Could not load provider "${name}" — is the API key set?`));
|
|
330
339
|
} else {
|
|
331
|
-
state.provider
|
|
340
|
+
state.provider = next;
|
|
341
|
+
state.providerKey = name; // keep the string so /agent can forward it
|
|
332
342
|
console.log(chalk.green(` Provider switched to ${next.name}.`));
|
|
333
343
|
}
|
|
334
344
|
return true;
|
|
@@ -424,6 +434,7 @@ function printHelp() {
|
|
|
424
434
|
console.log(' /plan <n> Preview a fix plan for finding <n> (no writes)');
|
|
425
435
|
console.log(' /agent [--plan-only] Run the interactive fix loop');
|
|
426
436
|
console.log(' /undo [--all] Revert the last fix (or all)');
|
|
437
|
+
console.log(' /share Publish scan report as a public URL (7 days)');
|
|
427
438
|
console.log(' /diff [path] Show git working-tree diff');
|
|
428
439
|
console.log(' /git <args> Pass through to git (status, log, stash, ...)');
|
|
429
440
|
console.log(' /provider <name> Switch LLM provider');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ship-safe",
|
|
3
|
-
"version": "9.2.
|
|
3
|
+
"version": "9.2.3",
|
|
4
4
|
"description": "AI-powered multi-agent security platform. 23 agents scan 80+ attack classes including AI integration supply chain (Vercel-class attacks), Hermes Agent deployments (ASI-01–ASI-10), tool registry poisoning, function-call injection, skill permission drift, and agent attestation. Ship Safe × Hermes Agent.",
|
|
5
5
|
"main": "cli/index.js",
|
|
6
6
|
"bin": {
|