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.
- package/README.md +748 -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 +85 -84
- package/cli/agents/injection-tester.js +500 -500
- package/cli/agents/legal-risk-agent.js +302 -0
- 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 +464 -426
- package/cli/commands/agent.js +608 -608
- package/cli/commands/audit.js +1006 -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/legal.js +158 -0
- 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
|
@@ -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;
|