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.
@@ -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
+ ]
@@ -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
  // -----------------------------------------------------------------------------
@@ -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
+ }
@@ -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 = next;
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.1",
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": {