shieldcortex 4.0.1 → 4.1.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/dist/audit/dependency-scanner.d.ts +32 -0
- package/dist/audit/dependency-scanner.js +312 -0
- package/dist/audit/index.d.ts +1 -0
- package/dist/audit/index.js +1 -0
- package/dist/cli/audit.d.ts +3 -0
- package/dist/cli/audit.js +56 -13
- package/package.json +1 -1
- package/scripts/postinstall.mjs +3 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans node_modules for:
|
|
5
|
+
* 1. Known malicious packages (CRITICAL)
|
|
6
|
+
* 2. Typosquat packages — Levenshtein distance 1-2 from popular packages (HIGH)
|
|
7
|
+
* 3. Suspicious postinstall scripts — network, exec, credential access patterns (HIGH/MEDIUM)
|
|
8
|
+
* 4. New packages with postinstall scripts published < 7 days ago (MEDIUM)
|
|
9
|
+
*
|
|
10
|
+
* Inspired by the axios 1.14.1 supply chain attack and the Claude Code
|
|
11
|
+
* typosquatting attacks discovered 31 Mar 2026.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* shieldcortex audit --deps # scan ./node_modules
|
|
15
|
+
* shieldcortex audit --deps-global # scan global npm prefix
|
|
16
|
+
* shieldcortex audit --deps-path /some/path # scan specific path
|
|
17
|
+
*/
|
|
18
|
+
import type { ScannerResult } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Compute Levenshtein distance between two strings.
|
|
21
|
+
* Standard dynamic-programming implementation, no external deps.
|
|
22
|
+
*/
|
|
23
|
+
export declare function levenshtein(a: string, b: string): number;
|
|
24
|
+
export interface DependencyScanOptions {
|
|
25
|
+
/** Absolute path to node_modules directory */
|
|
26
|
+
nodeModulesPath: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function scanDependencies(opts: DependencyScanOptions): ScannerResult;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the node_modules path for a given mode.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveNodeModulesPath(mode: 'local' | 'global' | 'path', customPath?: string): string;
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans node_modules for:
|
|
5
|
+
* 1. Known malicious packages (CRITICAL)
|
|
6
|
+
* 2. Typosquat packages — Levenshtein distance 1-2 from popular packages (HIGH)
|
|
7
|
+
* 3. Suspicious postinstall scripts — network, exec, credential access patterns (HIGH/MEDIUM)
|
|
8
|
+
* 4. New packages with postinstall scripts published < 7 days ago (MEDIUM)
|
|
9
|
+
*
|
|
10
|
+
* Inspired by the axios 1.14.1 supply chain attack and the Claude Code
|
|
11
|
+
* typosquatting attacks discovered 31 Mar 2026.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* shieldcortex audit --deps # scan ./node_modules
|
|
15
|
+
* shieldcortex audit --deps-global # scan global npm prefix
|
|
16
|
+
* shieldcortex audit --deps-path /some/path # scan specific path
|
|
17
|
+
*/
|
|
18
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
import { execSync } from 'child_process';
|
|
21
|
+
const LEARN_MORE_MALICIOUS = 'https://shieldcortex.ai/docs/threats/supply-chain';
|
|
22
|
+
const LEARN_MORE_TYPOSQUAT = 'https://shieldcortex.ai/docs/threats/typosquatting';
|
|
23
|
+
const LEARN_MORE_POSTINSTALL = 'https://shieldcortex.ai/docs/threats/malicious-scripts';
|
|
24
|
+
// ── 1. Known Malicious Packages ─────────────────────────────────────────────
|
|
25
|
+
const KNOWN_MALICIOUS = [
|
|
26
|
+
// Discovered packages — add more as they are found
|
|
27
|
+
'plain-crypto-js',
|
|
28
|
+
'color-diff-napi',
|
|
29
|
+
'modifiers-napi',
|
|
30
|
+
// axios supply chain imposters (March/April 2026)
|
|
31
|
+
'axios-http',
|
|
32
|
+
'axios-utils',
|
|
33
|
+
// Claude Code typosquatting (31 Mar 2026)
|
|
34
|
+
'claude-code-sdk',
|
|
35
|
+
'claude-code-helper',
|
|
36
|
+
'@anthropic/claude-code-utils',
|
|
37
|
+
// Common malicious patterns seen in the wild
|
|
38
|
+
'event-stream-http',
|
|
39
|
+
'flatmap-stream',
|
|
40
|
+
'getcookies',
|
|
41
|
+
'eslint-scope-backdoor',
|
|
42
|
+
'eslint-config-airbnb-standard',
|
|
43
|
+
'xpc-connection',
|
|
44
|
+
];
|
|
45
|
+
// ── 2. Popular Packages for Typosquat Detection ─────────────────────────────
|
|
46
|
+
const POPULAR_PACKAGES = [
|
|
47
|
+
'axios', 'express', 'lodash', 'react', 'next', 'vue', 'crypto-js',
|
|
48
|
+
'webpack', 'babel', 'typescript', 'eslint', 'prettier', 'jest',
|
|
49
|
+
'mocha', 'chalk', 'commander', 'inquirer', 'dotenv', 'cors',
|
|
50
|
+
'body-parser', 'mongoose', 'sequelize', 'prisma', 'socket.io',
|
|
51
|
+
'jsonwebtoken', 'bcrypt', 'uuid', 'moment', 'dayjs', 'sharp',
|
|
52
|
+
'puppeteer', 'playwright', 'onnxruntime-node', 'better-sqlite3',
|
|
53
|
+
];
|
|
54
|
+
const SUSPICIOUS_PATTERNS = [
|
|
55
|
+
// Downloading payloads
|
|
56
|
+
{ regex: /\bcurl\b/i, label: 'curl (payload download)' },
|
|
57
|
+
{ regex: /\bwget\b/i, label: 'wget (payload download)' },
|
|
58
|
+
{ regex: /\bfetch\s*\(/i, label: 'fetch() (HTTP request)' },
|
|
59
|
+
{ regex: /https?:\/\//i, label: 'HTTP/HTTPS URL' },
|
|
60
|
+
// Executing commands
|
|
61
|
+
{ regex: /\bexec\s*\(/i, label: 'exec() call' },
|
|
62
|
+
{ regex: /\bspawn\s*\(/i, label: 'spawn() call' },
|
|
63
|
+
{ regex: /child_process/i, label: 'child_process import' },
|
|
64
|
+
// OS detection (profiling the victim)
|
|
65
|
+
{ regex: /os\.platform\s*\(/i, label: 'os.platform() (OS detection)' },
|
|
66
|
+
{ regex: /os\.type\s*\(/i, label: 'os.type() (OS detection)' },
|
|
67
|
+
{ regex: /process\.platform/i, label: 'process.platform (OS detection)' },
|
|
68
|
+
// Self-deletion / cleanup
|
|
69
|
+
{ regex: /rm\s+-rf/i, label: 'rm -rf (file deletion)' },
|
|
70
|
+
{ regex: /\bdel\s+\/[sqf]/i, label: 'del /s (Windows deletion)' },
|
|
71
|
+
{ regex: /\bunlink\s*\(/i, label: 'unlink() (file deletion)' },
|
|
72
|
+
// Credential access
|
|
73
|
+
{ regex: /\bprocess\.env\b/i, label: 'process.env (env access)' },
|
|
74
|
+
{ regex: /\$HOME\b|\bHOME\b/, label: '$HOME (home dir access)' },
|
|
75
|
+
{ regex: /\bUSERPROFILE\b/i, label: 'USERPROFILE (Windows home)' },
|
|
76
|
+
{ regex: /\.ssh/i, label: '.ssh (SSH key access)' },
|
|
77
|
+
{ regex: /\.aws/i, label: '.aws (AWS credentials)' },
|
|
78
|
+
{ regex: /\.npmrc/i, label: '.npmrc (npm token access)' },
|
|
79
|
+
];
|
|
80
|
+
// ── Levenshtein Distance ─────────────────────────────────────────────────────
|
|
81
|
+
/**
|
|
82
|
+
* Compute Levenshtein distance between two strings.
|
|
83
|
+
* Standard dynamic-programming implementation, no external deps.
|
|
84
|
+
*/
|
|
85
|
+
export function levenshtein(a, b) {
|
|
86
|
+
if (a === b)
|
|
87
|
+
return 0;
|
|
88
|
+
if (a.length === 0)
|
|
89
|
+
return b.length;
|
|
90
|
+
if (b.length === 0)
|
|
91
|
+
return a.length;
|
|
92
|
+
// Use two-row rolling array to save memory
|
|
93
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
94
|
+
let curr = new Array(b.length + 1);
|
|
95
|
+
for (let i = 1; i <= a.length; i++) {
|
|
96
|
+
curr[0] = i;
|
|
97
|
+
for (let j = 1; j <= b.length; j++) {
|
|
98
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
99
|
+
curr[j] = Math.min(curr[j - 1] + 1, // insertion
|
|
100
|
+
prev[j] + 1, // deletion
|
|
101
|
+
prev[j - 1] + cost);
|
|
102
|
+
}
|
|
103
|
+
// Swap rows
|
|
104
|
+
const _swap = prev;
|
|
105
|
+
prev = curr;
|
|
106
|
+
curr = _swap;
|
|
107
|
+
}
|
|
108
|
+
return prev[b.length];
|
|
109
|
+
}
|
|
110
|
+
function readPackageJson(pkgPath) {
|
|
111
|
+
const pkgJsonPath = join(pkgPath, 'package.json');
|
|
112
|
+
if (!existsSync(pkgJsonPath))
|
|
113
|
+
return null;
|
|
114
|
+
try {
|
|
115
|
+
const raw = readFileSync(pkgJsonPath, 'utf-8');
|
|
116
|
+
return JSON.parse(raw);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* List all top-level package names in a node_modules directory.
|
|
124
|
+
* Handles scoped packages (@org/pkg).
|
|
125
|
+
*/
|
|
126
|
+
function listPackages(nodeModulesPath) {
|
|
127
|
+
if (!existsSync(nodeModulesPath))
|
|
128
|
+
return [];
|
|
129
|
+
let entries;
|
|
130
|
+
try {
|
|
131
|
+
entries = readdirSync(nodeModulesPath);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
const packages = [];
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
if (entry.startsWith('.'))
|
|
139
|
+
continue;
|
|
140
|
+
if (entry.startsWith('@')) {
|
|
141
|
+
// Scoped namespace — list sub-entries
|
|
142
|
+
try {
|
|
143
|
+
const scopedEntries = readdirSync(join(nodeModulesPath, entry));
|
|
144
|
+
for (const sub of scopedEntries) {
|
|
145
|
+
if (!sub.startsWith('.')) {
|
|
146
|
+
packages.push(`${entry}/${sub}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch { /* skip */ }
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
packages.push(entry);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return packages;
|
|
157
|
+
}
|
|
158
|
+
// ── Individual Checks ────────────────────────────────────────────────────────
|
|
159
|
+
function checkMalicious(name) {
|
|
160
|
+
return KNOWN_MALICIOUS.includes(name);
|
|
161
|
+
}
|
|
162
|
+
function checkTyposquat(name) {
|
|
163
|
+
// Strip scope for comparison — "@org/axois" → "axois"
|
|
164
|
+
const bare = name.includes('/') ? name.split('/').pop() : name;
|
|
165
|
+
for (const popular of POPULAR_PACKAGES) {
|
|
166
|
+
if (name === popular || bare === popular)
|
|
167
|
+
continue; // exact match, not a typosquat
|
|
168
|
+
const dist = levenshtein(bare, popular);
|
|
169
|
+
if (dist >= 1 && dist <= 2) {
|
|
170
|
+
return popular; // typosquat of this popular package
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function checkSuspiciousScripts(pkg) {
|
|
176
|
+
const scriptKeys = ['postinstall', 'preinstall', 'install'];
|
|
177
|
+
const matchedLabels = [];
|
|
178
|
+
for (const key of scriptKeys) {
|
|
179
|
+
const script = pkg.scripts?.[key];
|
|
180
|
+
if (!script)
|
|
181
|
+
continue;
|
|
182
|
+
for (const { regex, label } of SUSPICIOUS_PATTERNS) {
|
|
183
|
+
if (regex.test(script) && !matchedLabels.includes(label)) {
|
|
184
|
+
matchedLabels.push(label);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { matchCount: matchedLabels.length, labels: matchedLabels };
|
|
189
|
+
}
|
|
190
|
+
function checkPackageAge(pkg) {
|
|
191
|
+
const hasPostInstall = !!pkg.scripts?.postinstall ||
|
|
192
|
+
!!pkg.scripts?.preinstall ||
|
|
193
|
+
!!pkg.scripts?.install;
|
|
194
|
+
if (!hasPostInstall)
|
|
195
|
+
return false;
|
|
196
|
+
// Try _time field first (some lockfiles embed it), then parse _resolved for date hints
|
|
197
|
+
const timeStr = pkg._time ?? pkg._resolved;
|
|
198
|
+
if (!timeStr)
|
|
199
|
+
return false;
|
|
200
|
+
// Extract an ISO date if present
|
|
201
|
+
const isoMatch = timeStr.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
|
202
|
+
if (!isoMatch)
|
|
203
|
+
return false;
|
|
204
|
+
const published = new Date(isoMatch[0]);
|
|
205
|
+
if (isNaN(published.getTime()))
|
|
206
|
+
return false;
|
|
207
|
+
const ageMs = Date.now() - published.getTime();
|
|
208
|
+
const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
|
|
209
|
+
return ageMs < sevenDaysMs;
|
|
210
|
+
}
|
|
211
|
+
export function scanDependencies(opts) {
|
|
212
|
+
const start = Date.now();
|
|
213
|
+
const { nodeModulesPath } = opts;
|
|
214
|
+
if (!existsSync(nodeModulesPath)) {
|
|
215
|
+
return {
|
|
216
|
+
name: 'Dependency Scanner',
|
|
217
|
+
itemsScanned: 0,
|
|
218
|
+
findings: [],
|
|
219
|
+
durationMs: Date.now() - start,
|
|
220
|
+
skipped: true,
|
|
221
|
+
skipReason: `node_modules not found at ${nodeModulesPath}`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const packageNames = listPackages(nodeModulesPath);
|
|
225
|
+
const findings = [];
|
|
226
|
+
for (const name of packageNames) {
|
|
227
|
+
const pkgPath = join(nodeModulesPath, ...name.split('/'));
|
|
228
|
+
const pkg = readPackageJson(pkgPath);
|
|
229
|
+
// ── 1. Known malicious ──────────────────────────────────────────
|
|
230
|
+
if (checkMalicious(name)) {
|
|
231
|
+
findings.push({
|
|
232
|
+
scanner: 'dependency',
|
|
233
|
+
severity: 'critical',
|
|
234
|
+
title: `Known malicious package: ${name}`,
|
|
235
|
+
description: `"${name}" is a known malicious npm package. Remove it immediately and ` +
|
|
236
|
+
`audit your codebase for any data that may have been exfiltrated.`,
|
|
237
|
+
filePath: join(pkgPath, 'package.json'),
|
|
238
|
+
learnMoreUrl: LEARN_MORE_MALICIOUS,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
// ── 2. Typosquat ────────────────────────────────────────────────
|
|
242
|
+
const typosquatOf = checkTyposquat(name);
|
|
243
|
+
if (typosquatOf) {
|
|
244
|
+
findings.push({
|
|
245
|
+
scanner: 'dependency',
|
|
246
|
+
severity: 'high',
|
|
247
|
+
title: `Possible typosquat: "${name}" → "${typosquatOf}"`,
|
|
248
|
+
description: `"${name}" is 1-2 characters away from the popular package "${typosquatOf}". ` +
|
|
249
|
+
`This may be a typosquatting attack. Verify the package is intentional.`,
|
|
250
|
+
filePath: join(pkgPath, 'package.json'),
|
|
251
|
+
learnMoreUrl: LEARN_MORE_TYPOSQUAT,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (!pkg)
|
|
255
|
+
continue;
|
|
256
|
+
// ── 3. Suspicious postinstall ───────────────────────────────────
|
|
257
|
+
const { matchCount, labels } = checkSuspiciousScripts(pkg);
|
|
258
|
+
if (matchCount >= 1) {
|
|
259
|
+
const severity = matchCount >= 2 ? 'high' : 'medium';
|
|
260
|
+
findings.push({
|
|
261
|
+
scanner: 'dependency',
|
|
262
|
+
severity,
|
|
263
|
+
title: `Suspicious install script in "${name}"`,
|
|
264
|
+
description: `"${name}" has an install/postinstall script containing suspicious patterns: ` +
|
|
265
|
+
labels.join(', ') + '. Review before trusting this package.',
|
|
266
|
+
filePath: join(pkgPath, 'package.json'),
|
|
267
|
+
matchedText: labels.slice(0, 5).join(', '),
|
|
268
|
+
learnMoreUrl: LEARN_MORE_POSTINSTALL,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
// ── 4. New package with postinstall ────────────────────────────
|
|
272
|
+
if (checkPackageAge(pkg)) {
|
|
273
|
+
findings.push({
|
|
274
|
+
scanner: 'dependency',
|
|
275
|
+
severity: 'medium',
|
|
276
|
+
title: `Newly published package with install script: "${name}"`,
|
|
277
|
+
description: `"${name}" was published within the last 7 days and has an install/postinstall ` +
|
|
278
|
+
`script. New packages with install scripts are a common supply-chain attack vector.`,
|
|
279
|
+
filePath: join(pkgPath, 'package.json'),
|
|
280
|
+
learnMoreUrl: LEARN_MORE_MALICIOUS,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
name: 'Dependency Scanner',
|
|
286
|
+
itemsScanned: packageNames.length,
|
|
287
|
+
findings,
|
|
288
|
+
durationMs: Date.now() - start,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Resolve the node_modules path for a given mode.
|
|
293
|
+
*/
|
|
294
|
+
export function resolveNodeModulesPath(mode, customPath) {
|
|
295
|
+
switch (mode) {
|
|
296
|
+
case 'global': {
|
|
297
|
+
try {
|
|
298
|
+
const prefix = execSync('npm prefix -g', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
299
|
+
return join(prefix, 'lib', 'node_modules');
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// Fallback common locations
|
|
303
|
+
return join('/usr/local/lib', 'node_modules');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
case 'path':
|
|
307
|
+
return customPath ?? join(process.cwd(), 'node_modules');
|
|
308
|
+
case 'local':
|
|
309
|
+
default:
|
|
310
|
+
return join(process.cwd(), 'node_modules');
|
|
311
|
+
}
|
|
312
|
+
}
|
package/dist/audit/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { scanMemories } from './memory-scanner.js';
|
|
|
8
8
|
export { scanMcpConfigs } from './mcp-config-scanner.js';
|
|
9
9
|
export { scanEnvFiles } from './env-scanner.js';
|
|
10
10
|
export { scanRulesFiles } from './rules-file-scanner.js';
|
|
11
|
+
export { scanDependencies, resolveNodeModulesPath } from './dependency-scanner.js';
|
|
11
12
|
export { formatTerminalReport, formatMarkdownReport, formatJsonReport } from './report-formatter.js';
|
|
12
13
|
export { calculateGrade } from './types.js';
|
|
13
14
|
export type { AuditFinding, AuditReport, AuditGrade, AuditSeverity, ScannerResult, } from './types.js';
|
package/dist/audit/index.js
CHANGED
|
@@ -8,5 +8,6 @@ export { scanMemories } from './memory-scanner.js';
|
|
|
8
8
|
export { scanMcpConfigs } from './mcp-config-scanner.js';
|
|
9
9
|
export { scanEnvFiles } from './env-scanner.js';
|
|
10
10
|
export { scanRulesFiles } from './rules-file-scanner.js';
|
|
11
|
+
export { scanDependencies, resolveNodeModulesPath } from './dependency-scanner.js';
|
|
11
12
|
export { formatTerminalReport, formatMarkdownReport, formatJsonReport } from './report-formatter.js';
|
|
12
13
|
export { calculateGrade } from './types.js';
|
package/dist/cli/audit.d.ts
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* npx shieldcortex audit --json # JSON output
|
|
10
10
|
* npx shieldcortex audit --markdown # Markdown output
|
|
11
11
|
* npx shieldcortex audit --ci # CI mode (exit code reflects grade)
|
|
12
|
+
* npx shieldcortex audit --deps # Also scan ./node_modules for malicious packages
|
|
13
|
+
* npx shieldcortex audit --deps-global # Also scan global npm node_modules
|
|
14
|
+
* npx shieldcortex audit --deps-path /p # Also scan specific node_modules path
|
|
12
15
|
*/
|
|
13
16
|
/**
|
|
14
17
|
* Run the full audit.
|
package/dist/cli/audit.js
CHANGED
|
@@ -9,19 +9,47 @@
|
|
|
9
9
|
* npx shieldcortex audit --json # JSON output
|
|
10
10
|
* npx shieldcortex audit --markdown # Markdown output
|
|
11
11
|
* npx shieldcortex audit --ci # CI mode (exit code reflects grade)
|
|
12
|
+
* npx shieldcortex audit --deps # Also scan ./node_modules for malicious packages
|
|
13
|
+
* npx shieldcortex audit --deps-global # Also scan global npm node_modules
|
|
14
|
+
* npx shieldcortex audit --deps-path /p # Also scan specific node_modules path
|
|
12
15
|
*/
|
|
13
|
-
import { scanMemories, scanMcpConfigs, scanEnvFiles, scanRulesFiles, formatTerminalReport, formatMarkdownReport, formatJsonReport, calculateGrade, } from '../audit/index.js';
|
|
16
|
+
import { scanMemories, scanMcpConfigs, scanEnvFiles, scanRulesFiles, scanDependencies, resolveNodeModulesPath, formatTerminalReport, formatMarkdownReport, formatJsonReport, calculateGrade, } from '../audit/index.js';
|
|
14
17
|
function parseAuditArgs(args) {
|
|
15
|
-
const options = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
const options = {
|
|
19
|
+
format: 'terminal',
|
|
20
|
+
ci: false,
|
|
21
|
+
deps: false,
|
|
22
|
+
depsMode: 'local',
|
|
23
|
+
};
|
|
24
|
+
for (let i = 0; i < args.length; i++) {
|
|
25
|
+
const arg = args[i];
|
|
26
|
+
if (arg === '--json') {
|
|
18
27
|
options.format = 'json';
|
|
19
|
-
|
|
28
|
+
}
|
|
29
|
+
else if (arg === '--markdown' || arg === '--md') {
|
|
20
30
|
options.format = 'markdown';
|
|
31
|
+
}
|
|
21
32
|
else if (arg === '--ci') {
|
|
22
33
|
options.ci = true;
|
|
23
34
|
options.format = 'json';
|
|
24
35
|
}
|
|
36
|
+
else if (arg === '--deps') {
|
|
37
|
+
options.deps = true;
|
|
38
|
+
options.depsMode = 'local';
|
|
39
|
+
}
|
|
40
|
+
else if (arg === '--deps-global') {
|
|
41
|
+
options.deps = true;
|
|
42
|
+
options.depsMode = 'global';
|
|
43
|
+
}
|
|
44
|
+
else if (arg === '--deps-path') {
|
|
45
|
+
options.deps = true;
|
|
46
|
+
options.depsMode = 'path';
|
|
47
|
+
const next = args[i + 1];
|
|
48
|
+
if (next && !next.startsWith('--')) {
|
|
49
|
+
options.depsPath = next;
|
|
50
|
+
i++; // consume path argument
|
|
51
|
+
}
|
|
52
|
+
}
|
|
25
53
|
}
|
|
26
54
|
return options;
|
|
27
55
|
}
|
|
@@ -56,30 +84,45 @@ export async function handleAuditCommand(args) {
|
|
|
56
84
|
}
|
|
57
85
|
// Run all scanners
|
|
58
86
|
const scanners = [];
|
|
87
|
+
const totalSteps = options.deps ? 5 : 4;
|
|
59
88
|
if (options.format === 'terminal')
|
|
60
|
-
process.stdout.write(
|
|
89
|
+
process.stdout.write(` \x1b[2m[1/${totalSteps}] Memory files...\x1b[0m`);
|
|
61
90
|
const memoryResult = scanMemories();
|
|
62
91
|
scanners.push(memoryResult);
|
|
63
92
|
if (options.format === 'terminal')
|
|
64
|
-
process.stdout.write(`\r \x1b[32m[1
|
|
93
|
+
process.stdout.write(`\r \x1b[32m[1/${totalSteps}]\x1b[0m Memory files \x1b[2m${memoryResult.durationMs}ms\x1b[0m\n`);
|
|
65
94
|
if (options.format === 'terminal')
|
|
66
|
-
process.stdout.write(
|
|
95
|
+
process.stdout.write(` \x1b[2m[2/${totalSteps}] MCP configs...\x1b[0m`);
|
|
67
96
|
const mcpResult = scanMcpConfigs();
|
|
68
97
|
scanners.push(mcpResult);
|
|
69
98
|
if (options.format === 'terminal')
|
|
70
|
-
process.stdout.write(`\r \x1b[32m[2
|
|
99
|
+
process.stdout.write(`\r \x1b[32m[2/${totalSteps}]\x1b[0m MCP configs \x1b[2m${mcpResult.durationMs}ms\x1b[0m\n`);
|
|
71
100
|
if (options.format === 'terminal')
|
|
72
|
-
process.stdout.write(
|
|
101
|
+
process.stdout.write(` \x1b[2m[3/${totalSteps}] Environment secrets...\x1b[0m`);
|
|
73
102
|
const envResult = scanEnvFiles();
|
|
74
103
|
scanners.push(envResult);
|
|
75
104
|
if (options.format === 'terminal')
|
|
76
|
-
process.stdout.write(`\r \x1b[32m[3
|
|
105
|
+
process.stdout.write(`\r \x1b[32m[3/${totalSteps}]\x1b[0m Environment \x1b[2m${envResult.durationMs}ms\x1b[0m\n`);
|
|
77
106
|
if (options.format === 'terminal')
|
|
78
|
-
process.stdout.write(
|
|
107
|
+
process.stdout.write(` \x1b[2m[4/${totalSteps}] Rules files...\x1b[0m`);
|
|
79
108
|
const rulesResult = scanRulesFiles();
|
|
80
109
|
scanners.push(rulesResult);
|
|
81
110
|
if (options.format === 'terminal')
|
|
82
|
-
process.stdout.write(`\r \x1b[32m[4
|
|
111
|
+
process.stdout.write(`\r \x1b[32m[4/${totalSteps}]\x1b[0m Rules files \x1b[2m${rulesResult.durationMs}ms\x1b[0m\n`);
|
|
112
|
+
// Optional: dependency scan
|
|
113
|
+
if (options.deps) {
|
|
114
|
+
const nodeModulesPath = resolveNodeModulesPath(options.depsMode, options.depsPath);
|
|
115
|
+
if (options.format === 'terminal') {
|
|
116
|
+
process.stdout.write(` \x1b[2m[5/${totalSteps}] Dependencies (${nodeModulesPath})...\x1b[0m`);
|
|
117
|
+
}
|
|
118
|
+
const depsResult = scanDependencies({ nodeModulesPath });
|
|
119
|
+
scanners.push(depsResult);
|
|
120
|
+
if (options.format === 'terminal') {
|
|
121
|
+
process.stdout.write(`\r \x1b[32m[5/${totalSteps}]\x1b[0m Dependencies \x1b[2m${depsResult.durationMs}ms\x1b[0m\n`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (options.format === 'terminal')
|
|
125
|
+
process.stdout.write('\n');
|
|
83
126
|
// Aggregate findings
|
|
84
127
|
const allFindings = scanners.flatMap(s => s.findings);
|
|
85
128
|
// Sort by severity (critical first)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shieldcortex",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Trustworthy memory and security for AI agents. Recall debugging, review queue, OpenClaw session capture, and memory poisoning defence for Claude Code, Codex, OpenClaw, LangChain, and MCP agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - SHIELDCORTEX_SKIP_AUTO_OPENCLAW=1 is set
|
|
10
10
|
* - Running as a local/dev install (npm_config_global !== 'true')
|
|
11
11
|
*/
|
|
12
|
-
import { existsSync, copyFileSync, mkdirSync, readdirSync } from 'fs';
|
|
12
|
+
import { existsSync, copyFileSync, mkdirSync, readdirSync, readFileSync } from 'fs';
|
|
13
13
|
import { join, dirname } from 'path';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
import { spawnSync } from 'child_process';
|
|
@@ -28,8 +28,8 @@ function isDockerEnvironment() {
|
|
|
28
28
|
if (process.env.DOCKER === 'true' || process.env.DOCKER === '1') return true;
|
|
29
29
|
if (process.env.container === 'docker') return true;
|
|
30
30
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const cgroup = readFileSync('/proc/1/cgroup', 'utf8');
|
|
32
|
+
if (cgroup.includes('docker') || cgroup.includes('kubepods') || cgroup.includes('containerd')) return true;
|
|
33
33
|
} catch { /* ignore */ }
|
|
34
34
|
return false;
|
|
35
35
|
}
|