ship-safe 6.1.1 → 6.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.
Files changed (49) hide show
  1. package/README.md +748 -641
  2. package/cli/agents/api-fuzzer.js +345 -345
  3. package/cli/agents/auth-bypass-agent.js +348 -348
  4. package/cli/agents/base-agent.js +272 -272
  5. package/cli/agents/cicd-scanner.js +236 -201
  6. package/cli/agents/config-auditor.js +521 -521
  7. package/cli/agents/deep-analyzer.js +6 -2
  8. package/cli/agents/git-history-scanner.js +170 -170
  9. package/cli/agents/html-reporter.js +568 -568
  10. package/cli/agents/index.js +85 -84
  11. package/cli/agents/injection-tester.js +500 -500
  12. package/cli/agents/legal-risk-agent.js +302 -0
  13. package/cli/agents/llm-redteam.js +251 -251
  14. package/cli/agents/mobile-scanner.js +231 -231
  15. package/cli/agents/orchestrator.js +322 -322
  16. package/cli/agents/pii-compliance-agent.js +301 -301
  17. package/cli/agents/scoring-engine.js +248 -248
  18. package/cli/agents/supabase-rls-agent.js +154 -154
  19. package/cli/agents/supply-chain-agent.js +650 -507
  20. package/cli/bin/ship-safe.js +464 -426
  21. package/cli/commands/agent.js +608 -608
  22. package/cli/commands/audit.js +1006 -980
  23. package/cli/commands/baseline.js +193 -193
  24. package/cli/commands/ci.js +342 -342
  25. package/cli/commands/deps.js +516 -516
  26. package/cli/commands/doctor.js +159 -159
  27. package/cli/commands/fix.js +218 -218
  28. package/cli/commands/hooks.js +268 -0
  29. package/cli/commands/init.js +407 -407
  30. package/cli/commands/legal.js +158 -0
  31. package/cli/commands/mcp.js +304 -304
  32. package/cli/commands/red-team.js +7 -1
  33. package/cli/commands/remediate.js +798 -798
  34. package/cli/commands/rotate.js +571 -571
  35. package/cli/commands/scan.js +569 -569
  36. package/cli/commands/score.js +449 -449
  37. package/cli/commands/watch.js +281 -281
  38. package/cli/hooks/patterns.js +313 -0
  39. package/cli/hooks/post-tool-use.js +140 -0
  40. package/cli/hooks/pre-tool-use.js +186 -0
  41. package/cli/index.js +73 -69
  42. package/cli/providers/llm-provider.js +397 -287
  43. package/cli/utils/autofix-rules.js +74 -74
  44. package/cli/utils/cache-manager.js +311 -311
  45. package/cli/utils/output.js +230 -230
  46. package/cli/utils/patterns.js +1121 -1121
  47. package/cli/utils/pdf-generator.js +94 -94
  48. package/package.json +69 -69
  49. package/configs/supabase/rls-templates.sql +0 -242
@@ -0,0 +1,302 @@
1
+ /**
2
+ * LegalRiskAgent
3
+ * ==============
4
+ *
5
+ * Scans project dependency manifests for packages that carry legal risk:
6
+ * active DMCA takedowns, known leaked-source derivatives, IP disputes,
7
+ * or license violations.
8
+ *
9
+ * This is a separate threat category from security IOCs — the danger is
10
+ * not malware, but legal liability for shipping the dependency.
11
+ *
12
+ * Supported manifests:
13
+ * npm/yarn/pnpm → package.json
14
+ * Python → requirements.txt, pyproject.toml
15
+ * Rust → Cargo.toml
16
+ * Go → go.mod
17
+ *
18
+ * USAGE:
19
+ * ship-safe legal .
20
+ * ship-safe audit . --include-legal
21
+ */
22
+
23
+ import fs from 'fs';
24
+ import path from 'path';
25
+ import { BaseAgent, createFinding } from './base-agent.js';
26
+
27
+ // =============================================================================
28
+ // LEGALLY RISKY PACKAGES
29
+ // Format: { name, versions, ecosystem, risk, severity, detail, references }
30
+ //
31
+ // versions: array of specific bad versions, or '*' for all versions
32
+ // ecosystem: 'npm' | 'pypi' | 'cargo' | 'go' | '*'
33
+ // risk: 'dmca' | 'ip-dispute' | 'leaked-source' | 'license-violation'
34
+ // =============================================================================
35
+ export const LEGALLY_RISKY_PACKAGES = [
36
+ // ---------------------------------------------------------------------------
37
+ // Claude Code source leak (March 31 2026)
38
+ // Anthropic's Claude Code source was accidentally leaked. Several repos
39
+ // appeared immediately; Anthropic filed DMCA takedowns but derivatives
40
+ // remain online. Shipping any of these exposes you to IP liability.
41
+ // ---------------------------------------------------------------------------
42
+ {
43
+ name: 'claw-code',
44
+ versions: '*',
45
+ ecosystem: 'npm',
46
+ risk: 'dmca',
47
+ severity: 'high',
48
+ detail:
49
+ 'Derived from leaked Anthropic Claude Code source (March 2026). ' +
50
+ 'Anthropic has filed DMCA takedown notices. Shipping this package ' +
51
+ 'may expose your project to IP infringement liability.',
52
+ references: [
53
+ 'https://cybernews.com/security/anthropic-claude-code-source-leak/',
54
+ 'https://venturebeat.com/technology/claude-codes-source-code-appears-to-have-leaked-heres-what-we-know',
55
+ ],
56
+ },
57
+ {
58
+ name: 'claw-code-js',
59
+ versions: '*',
60
+ ecosystem: 'npm',
61
+ risk: 'leaked-source',
62
+ severity: 'high',
63
+ detail:
64
+ 'JavaScript port derived from the leaked Anthropic Claude Code source (March 2026). ' +
65
+ 'Under active DMCA enforcement. Contains Anthropic proprietary IP.',
66
+ references: [
67
+ 'https://cybernews.com/tech/claude-code-leak-spawns-fastest-github-repo/',
68
+ ],
69
+ },
70
+ {
71
+ name: 'claude-code-oss',
72
+ versions: '*',
73
+ ecosystem: 'npm',
74
+ risk: 'leaked-source',
75
+ severity: 'high',
76
+ detail:
77
+ 'Open-source mirror of the leaked Claude Code source (March 2026). ' +
78
+ 'Despite "open-source" branding, the underlying code is Anthropic proprietary IP ' +
79
+ 'and DMCA takedowns are in progress.',
80
+ references: [
81
+ 'https://cybernews.com/security/anthropic-claude-code-source-leak/',
82
+ ],
83
+ },
84
+ // ---------------------------------------------------------------------------
85
+ // License violations — well-known cases
86
+ // ---------------------------------------------------------------------------
87
+ {
88
+ name: 'faker',
89
+ versions: ['6.6.6'],
90
+ ecosystem: 'npm',
91
+ risk: 'license-violation',
92
+ severity: 'medium',
93
+ detail:
94
+ 'faker@6.6.6 was deliberately sabotaged by its maintainer (January 2022). ' +
95
+ 'The package prints an infinite loop of gibberish. Replaced by @faker-js/faker ' +
96
+ 'which is community-maintained under MIT.',
97
+ references: [
98
+ 'https://www.bleepingcomputer.com/news/security/dev-corrupts-npm-libs-colors-and-faker-breaking-thousands-of-apps/',
99
+ ],
100
+ },
101
+ {
102
+ name: 'colors',
103
+ versions: ['1.4.44-liberty-2'],
104
+ ecosystem: 'npm',
105
+ risk: 'license-violation',
106
+ severity: 'medium',
107
+ detail:
108
+ 'colors@1.4.44-liberty-2 was a malicious release by the maintainer that ' +
109
+ 'deliberately printed an infinite loop. Use colors@1.4.0 or the maintained fork.',
110
+ references: [
111
+ 'https://www.bleepingcomputer.com/news/security/dev-corrupts-npm-libs-colors-and-faker-breaking-thousands-of-apps/',
112
+ ],
113
+ },
114
+ ];
115
+
116
+ // =============================================================================
117
+ // AGENT
118
+ // =============================================================================
119
+
120
+ export class LegalRiskAgent extends BaseAgent {
121
+ constructor() {
122
+ super('LegalRiskAgent', 'Legal risk audit: DMCA, IP disputes, leaked source in dependencies', 'legal');
123
+ }
124
+
125
+ async analyze(context) {
126
+ const { rootPath } = context;
127
+ const findings = [];
128
+
129
+ findings.push(...this._scanNpm(rootPath));
130
+ findings.push(...this._scanPython(rootPath));
131
+ findings.push(...this._scanCargo(rootPath));
132
+ findings.push(...this._scanGoMod(rootPath));
133
+
134
+ return findings;
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // npm / yarn / pnpm — package.json
139
+ // ---------------------------------------------------------------------------
140
+ _scanNpm(rootPath) {
141
+ const findings = [];
142
+ const pkgPath = path.join(rootPath, 'package.json');
143
+ if (!fs.existsSync(pkgPath)) return findings;
144
+
145
+ let pkg;
146
+ try {
147
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
148
+ } catch {
149
+ return findings;
150
+ }
151
+
152
+ const allDeps = {
153
+ ...(pkg.dependencies || {}),
154
+ ...(pkg.devDependencies || {}),
155
+ ...(pkg.optionalDependencies || {}),
156
+ ...(pkg.peerDependencies || {}),
157
+ };
158
+
159
+ for (const [name, version] of Object.entries(allDeps)) {
160
+ const entry = LEGALLY_RISKY_PACKAGES.find(
161
+ e => e.name === name && (e.ecosystem === 'npm' || e.ecosystem === '*')
162
+ );
163
+ if (!entry) continue;
164
+
165
+ const bare = String(version).replace(/^[\^~>=<]+/, '').trim();
166
+ const versionMatches =
167
+ entry.versions === '*' || entry.versions.includes(bare);
168
+
169
+ if (versionMatches) {
170
+ findings.push(this._makeFinding(pkgPath, name, bare, entry));
171
+ }
172
+ }
173
+
174
+ return findings;
175
+ }
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // Python — requirements.txt
179
+ // ---------------------------------------------------------------------------
180
+ _scanPython(rootPath) {
181
+ const findings = [];
182
+ const reqPath = path.join(rootPath, 'requirements.txt');
183
+ if (!fs.existsSync(reqPath)) return findings;
184
+
185
+ const lines = (this.readFile(reqPath) || '').split('\n');
186
+ for (let i = 0; i < lines.length; i++) {
187
+ const line = lines[i].trim();
188
+ if (!line || line.startsWith('#')) continue;
189
+
190
+ // Match: package==version or package>=version etc., or bare package name
191
+ const m = line.match(/^([\w.-]+)\s*(?:[=<>!~]=?\s*([\d.\w]+))?/);
192
+ if (!m) continue;
193
+ const [, name, version = '*'] = m;
194
+
195
+ const entry = LEGALLY_RISKY_PACKAGES.find(
196
+ e => e.name.toLowerCase() === name.toLowerCase() &&
197
+ (e.ecosystem === 'pypi' || e.ecosystem === '*')
198
+ );
199
+ if (!entry) continue;
200
+
201
+ const versionMatches =
202
+ entry.versions === '*' || entry.versions.includes(version);
203
+ if (versionMatches) {
204
+ findings.push(this._makeFinding(reqPath, name, version, entry));
205
+ }
206
+ }
207
+
208
+ return findings;
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Rust — Cargo.toml
213
+ // ---------------------------------------------------------------------------
214
+ _scanCargo(rootPath) {
215
+ const findings = [];
216
+ const cargoPath = path.join(rootPath, 'Cargo.toml');
217
+ if (!fs.existsSync(cargoPath)) return findings;
218
+
219
+ const content = this.readFile(cargoPath) || '';
220
+ // Match lines like: package-name = "1.2.3" or package-name = { version = "1.2.3" }
221
+ const depPattern = /^\s*([\w-]+)\s*=\s*(?:"([\d.\w^~>=<*]+)"|{[^}]*version\s*=\s*"([\d.\w^~>=<*]+)")/gm;
222
+ let match;
223
+ while ((match = depPattern.exec(content)) !== null) {
224
+ const name = match[1];
225
+ const version = (match[2] || match[3] || '*').replace(/^[\^~>=<]+/, '').trim();
226
+
227
+ const entry = LEGALLY_RISKY_PACKAGES.find(
228
+ e => e.name === name && (e.ecosystem === 'cargo' || e.ecosystem === '*')
229
+ );
230
+ if (!entry) continue;
231
+
232
+ const versionMatches = entry.versions === '*' || entry.versions.includes(version);
233
+ if (versionMatches) {
234
+ findings.push(this._makeFinding(cargoPath, name, version, entry));
235
+ }
236
+ }
237
+
238
+ return findings;
239
+ }
240
+
241
+ // ---------------------------------------------------------------------------
242
+ // Go — go.mod
243
+ // ---------------------------------------------------------------------------
244
+ _scanGoMod(rootPath) {
245
+ const findings = [];
246
+ const goModPath = path.join(rootPath, 'go.mod');
247
+ if (!fs.existsSync(goModPath)) return findings;
248
+
249
+ const lines = (this.readFile(goModPath) || '').split('\n');
250
+ for (const line of lines) {
251
+ const m = line.trim().match(/^([\w./\-]+)\s+(v[\d.]+)/);
252
+ if (!m) continue;
253
+ const [, name, version] = m;
254
+
255
+ const entry = LEGALLY_RISKY_PACKAGES.find(
256
+ e => e.name === name && (e.ecosystem === 'go' || e.ecosystem === '*')
257
+ );
258
+ if (!entry) continue;
259
+
260
+ const bare = version.replace(/^v/, '');
261
+ const versionMatches = entry.versions === '*' || entry.versions.includes(bare);
262
+ if (versionMatches) {
263
+ findings.push(this._makeFinding(goModPath, name, bare, entry));
264
+ }
265
+ }
266
+
267
+ return findings;
268
+ }
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // Finding factory
272
+ // ---------------------------------------------------------------------------
273
+ _makeFinding(file, name, version, entry) {
274
+ const riskLabel = {
275
+ dmca: 'DMCA Takedown',
276
+ 'ip-dispute': 'IP Dispute',
277
+ 'leaked-source': 'Leaked Source',
278
+ 'license-violation': 'License Violation',
279
+ }[entry.risk] || entry.risk;
280
+
281
+ const versionStr = version === '*' ? '(any version)' : `@${version}`;
282
+
283
+ return createFinding({
284
+ file,
285
+ line: 0,
286
+ severity: entry.severity,
287
+ category: 'legal',
288
+ rule: `LEGAL_RISK_${entry.risk.toUpperCase().replace(/-/g, '_')}`,
289
+ title: `[${riskLabel}] ${name}${versionStr}`,
290
+ description: entry.detail,
291
+ matched: version === '*' ? name : `${name}@${version}`,
292
+ confidence: 'high',
293
+ fix:
294
+ `Remove ${name} from your dependencies. ` +
295
+ (entry.references.length > 0
296
+ ? `See: ${entry.references[0]}`
297
+ : ''),
298
+ });
299
+ }
300
+ }
301
+
302
+ export default LegalRiskAgent;