ship-safe 9.2.2 → 9.2.4
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 +2 -1
- package/cli/agents/mcp-security-agent.js +1 -1
- package/cli/bin/ship-safe.js +9 -0
- package/cli/commands/audit.js +12 -0
- package/cli/commands/ci.js +2 -2
- package/cli/commands/share.js +71 -0
- package/cli/commands/shell.js +7 -0
- package/package.json +1 -1
- package/cli/.ship-safe/context.json +0 -8157
- package/cli/.ship-safe/history.json +0 -190
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ $ ship-safe
|
|
|
98
98
|
███████╗██╗ ██╗██╗██████╗ ███████╗ █████╗ ███████╗███████╗
|
|
99
99
|
...
|
|
100
100
|
|
|
101
|
-
v9.2.
|
|
101
|
+
v9.2.3 · DeepSeek · ~/my-project
|
|
102
102
|
|
|
103
103
|
/scan to find issues · /agent to fix them · /help for more
|
|
104
104
|
|
|
@@ -113,6 +113,7 @@ shipsafe ›
|
|
|
113
113
|
| `/show <n>` | Full detail on finding n |
|
|
114
114
|
| `/plan <n>` | Preview fix plan for finding n (no writes) |
|
|
115
115
|
| `/undo [--all]` | Revert the last fix (or all fixes) |
|
|
116
|
+
| `/share` | Publish scan report as a public URL (7 days) |
|
|
116
117
|
| `/diff` | Show git working-tree diff |
|
|
117
118
|
| `/provider <name>` | Switch LLM provider mid-session |
|
|
118
119
|
| `/quit` | Exit (also `Ctrl-D` or `Ctrl-C`) |
|
|
@@ -64,7 +64,7 @@ const PATTERNS = [
|
|
|
64
64
|
{
|
|
65
65
|
rule: 'MCP_STDIO_NO_SANDBOX',
|
|
66
66
|
title: 'MCP: stdio Transport Without Sandbox',
|
|
67
|
-
regex: /(?:StdioServerTransport|
|
|
67
|
+
regex: /(?:StdioServerTransport|new\s+StdioTransport|transport\s*[:=]\s*['"]stdio['"]|StdioClientTransport)/g,
|
|
68
68
|
severity: 'medium',
|
|
69
69
|
cwe: 'CWE-269',
|
|
70
70
|
owasp: 'A04:2021',
|
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
|
@@ -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();
|
package/cli/commands/ci.js
CHANGED
|
@@ -241,10 +241,10 @@ function buildSARIF(findings, rootPath) {
|
|
|
241
241
|
locations: [{
|
|
242
242
|
physicalLocation: {
|
|
243
243
|
artifactLocation: {
|
|
244
|
-
uri: path.relative(rootPath, f.file).replace(/\\/g, '/'),
|
|
244
|
+
uri: path.relative(rootPath, f.file).replace(/\\/g, '/').replace(/\[/g, '%5B').replace(/\]/g, '%5D'),
|
|
245
245
|
uriBaseId: '%SRCROOT%',
|
|
246
246
|
},
|
|
247
|
-
region: { startLine: f.line, startColumn: f.column || 1 },
|
|
247
|
+
region: { startLine: Math.max(1, f.line || 1), startColumn: f.column || 1 },
|
|
248
248
|
},
|
|
249
249
|
}],
|
|
250
250
|
})),
|
|
@@ -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 };
|
|
@@ -320,6 +321,11 @@ async function handleSlashCommand(line, state, options) {
|
|
|
320
321
|
return true;
|
|
321
322
|
}
|
|
322
323
|
|
|
324
|
+
case 'share': {
|
|
325
|
+
await shareCommand(state.root);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
323
329
|
case 'provider': {
|
|
324
330
|
const name = args[0];
|
|
325
331
|
if (!name) {
|
|
@@ -428,6 +434,7 @@ function printHelp() {
|
|
|
428
434
|
console.log(' /plan <n> Preview a fix plan for finding <n> (no writes)');
|
|
429
435
|
console.log(' /agent [--plan-only] Run the interactive fix loop');
|
|
430
436
|
console.log(' /undo [--all] Revert the last fix (or all)');
|
|
437
|
+
console.log(' /share Publish scan report as a public URL (7 days)');
|
|
431
438
|
console.log(' /diff [path] Show git working-tree diff');
|
|
432
439
|
console.log(' /git <args> Pass through to git (status, log, stash, ...)');
|
|
433
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.4",
|
|
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": {
|