shieldcortex 4.0.2 → 4.2.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 +61 -0
- package/dist/audit/dependency-scanner.js +394 -0
- package/dist/audit/index.d.ts +1 -0
- package/dist/audit/index.js +1 -0
- package/dist/cli/audit.d.ts +6 -0
- package/dist/cli/audit.js +183 -13
- package/dist/license/gate.d.ts +1 -1
- package/dist/license/gate.js +18 -0
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
* shieldcortex audit --deps --quarantine # scan + quarantine CRITICAL/HIGH
|
|
18
|
+
* shieldcortex audit --deps --clean --force # scan + permanently delete CRITICAL
|
|
19
|
+
* shieldcortex audit --deps --auto-protect # scan + auto-quarantine CRITICAL
|
|
20
|
+
*/
|
|
21
|
+
import type { AuditFinding, ScannerResult } from './types.js';
|
|
22
|
+
/**
|
|
23
|
+
* Compute Levenshtein distance between two strings.
|
|
24
|
+
* Standard dynamic-programming implementation, no external deps.
|
|
25
|
+
*/
|
|
26
|
+
export declare function levenshtein(a: string, b: string): number;
|
|
27
|
+
export interface QuarantineManifest {
|
|
28
|
+
packageName: string;
|
|
29
|
+
version: string;
|
|
30
|
+
reason: string;
|
|
31
|
+
originalPath: string;
|
|
32
|
+
quarantinedAt: string;
|
|
33
|
+
severity: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Quarantine a package by moving it from node_modules to
|
|
37
|
+
* ~/.shieldcortex/quarantine/deps/<pkg>-<timestamp>/.
|
|
38
|
+
*
|
|
39
|
+
* Creates a manifest.json alongside the quarantined files.
|
|
40
|
+
* Only acts on CRITICAL and HIGH findings.
|
|
41
|
+
*
|
|
42
|
+
* @returns The quarantine destination path, or null if not quarantined.
|
|
43
|
+
*/
|
|
44
|
+
export declare function quarantinePackage(finding: AuditFinding, nodeModulesPath: string): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Permanently delete a malicious package from node_modules.
|
|
47
|
+
*
|
|
48
|
+
* Only acts on CRITICAL findings (known malicious packages from blocklist).
|
|
49
|
+
*
|
|
50
|
+
* @returns The package name that was deleted, or null if not deleted.
|
|
51
|
+
*/
|
|
52
|
+
export declare function cleanPackage(finding: AuditFinding, nodeModulesPath: string): string | null;
|
|
53
|
+
export interface DependencyScanOptions {
|
|
54
|
+
/** Absolute path to node_modules directory */
|
|
55
|
+
nodeModulesPath: string;
|
|
56
|
+
}
|
|
57
|
+
export declare function scanDependencies(opts: DependencyScanOptions): ScannerResult;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the node_modules path for a given mode.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveNodeModulesPath(mode: 'local' | 'global' | 'path', customPath?: string): string;
|
|
@@ -0,0 +1,394 @@
|
|
|
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
|
+
* shieldcortex audit --deps --quarantine # scan + quarantine CRITICAL/HIGH
|
|
18
|
+
* shieldcortex audit --deps --clean --force # scan + permanently delete CRITICAL
|
|
19
|
+
* shieldcortex audit --deps --auto-protect # scan + auto-quarantine CRITICAL
|
|
20
|
+
*/
|
|
21
|
+
import { readdirSync, readFileSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'fs';
|
|
22
|
+
import { join, resolve } from 'path';
|
|
23
|
+
import { execSync } from 'child_process';
|
|
24
|
+
import { homedir } from 'os';
|
|
25
|
+
const LEARN_MORE_MALICIOUS = 'https://shieldcortex.ai/docs/threats/supply-chain';
|
|
26
|
+
const LEARN_MORE_TYPOSQUAT = 'https://shieldcortex.ai/docs/threats/typosquatting';
|
|
27
|
+
const LEARN_MORE_POSTINSTALL = 'https://shieldcortex.ai/docs/threats/malicious-scripts';
|
|
28
|
+
// ── 1. Known Malicious Packages ─────────────────────────────────────────────
|
|
29
|
+
const KNOWN_MALICIOUS = [
|
|
30
|
+
// Discovered packages — add more as they are found
|
|
31
|
+
'plain-crypto-js',
|
|
32
|
+
'color-diff-napi',
|
|
33
|
+
'modifiers-napi',
|
|
34
|
+
// axios supply chain imposters (March/April 2026)
|
|
35
|
+
'axios-http',
|
|
36
|
+
'axios-utils',
|
|
37
|
+
// Claude Code typosquatting (31 Mar 2026)
|
|
38
|
+
'claude-code-sdk',
|
|
39
|
+
'claude-code-helper',
|
|
40
|
+
'@anthropic/claude-code-utils',
|
|
41
|
+
// Common malicious patterns seen in the wild
|
|
42
|
+
'event-stream-http',
|
|
43
|
+
'flatmap-stream',
|
|
44
|
+
'getcookies',
|
|
45
|
+
'eslint-scope-backdoor',
|
|
46
|
+
'eslint-config-airbnb-standard',
|
|
47
|
+
'xpc-connection',
|
|
48
|
+
];
|
|
49
|
+
// ── 2. Popular Packages for Typosquat Detection ─────────────────────────────
|
|
50
|
+
const POPULAR_PACKAGES = [
|
|
51
|
+
'axios', 'express', 'lodash', 'react', 'next', 'vue', 'crypto-js',
|
|
52
|
+
'webpack', 'babel', 'typescript', 'eslint', 'prettier', 'jest',
|
|
53
|
+
'mocha', 'chalk', 'commander', 'inquirer', 'dotenv', 'cors',
|
|
54
|
+
'body-parser', 'mongoose', 'sequelize', 'prisma', 'socket.io',
|
|
55
|
+
'jsonwebtoken', 'bcrypt', 'uuid', 'moment', 'dayjs', 'sharp',
|
|
56
|
+
'puppeteer', 'playwright', 'onnxruntime-node', 'better-sqlite3',
|
|
57
|
+
];
|
|
58
|
+
const SUSPICIOUS_PATTERNS = [
|
|
59
|
+
// Downloading payloads
|
|
60
|
+
{ regex: /\bcurl\b/i, label: 'curl (payload download)' },
|
|
61
|
+
{ regex: /\bwget\b/i, label: 'wget (payload download)' },
|
|
62
|
+
{ regex: /\bfetch\s*\(/i, label: 'fetch() (HTTP request)' },
|
|
63
|
+
{ regex: /https?:\/\//i, label: 'HTTP/HTTPS URL' },
|
|
64
|
+
// Executing commands
|
|
65
|
+
{ regex: /\bexec\s*\(/i, label: 'exec() call' },
|
|
66
|
+
{ regex: /\bspawn\s*\(/i, label: 'spawn() call' },
|
|
67
|
+
{ regex: /child_process/i, label: 'child_process import' },
|
|
68
|
+
// OS detection (profiling the victim)
|
|
69
|
+
{ regex: /os\.platform\s*\(/i, label: 'os.platform() (OS detection)' },
|
|
70
|
+
{ regex: /os\.type\s*\(/i, label: 'os.type() (OS detection)' },
|
|
71
|
+
{ regex: /process\.platform/i, label: 'process.platform (OS detection)' },
|
|
72
|
+
// Self-deletion / cleanup
|
|
73
|
+
{ regex: /rm\s+-rf/i, label: 'rm -rf (file deletion)' },
|
|
74
|
+
{ regex: /\bdel\s+\/[sqf]/i, label: 'del /s (Windows deletion)' },
|
|
75
|
+
{ regex: /\bunlink\s*\(/i, label: 'unlink() (file deletion)' },
|
|
76
|
+
// Credential access
|
|
77
|
+
{ regex: /\bprocess\.env\b/i, label: 'process.env (env access)' },
|
|
78
|
+
{ regex: /\$HOME\b|\bHOME\b/, label: '$HOME (home dir access)' },
|
|
79
|
+
{ regex: /\bUSERPROFILE\b/i, label: 'USERPROFILE (Windows home)' },
|
|
80
|
+
{ regex: /\.ssh/i, label: '.ssh (SSH key access)' },
|
|
81
|
+
{ regex: /\.aws/i, label: '.aws (AWS credentials)' },
|
|
82
|
+
{ regex: /\.npmrc/i, label: '.npmrc (npm token access)' },
|
|
83
|
+
];
|
|
84
|
+
// ── Levenshtein Distance ─────────────────────────────────────────────────────
|
|
85
|
+
/**
|
|
86
|
+
* Compute Levenshtein distance between two strings.
|
|
87
|
+
* Standard dynamic-programming implementation, no external deps.
|
|
88
|
+
*/
|
|
89
|
+
export function levenshtein(a, b) {
|
|
90
|
+
if (a === b)
|
|
91
|
+
return 0;
|
|
92
|
+
if (a.length === 0)
|
|
93
|
+
return b.length;
|
|
94
|
+
if (b.length === 0)
|
|
95
|
+
return a.length;
|
|
96
|
+
// Use two-row rolling array to save memory
|
|
97
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
98
|
+
let curr = new Array(b.length + 1);
|
|
99
|
+
for (let i = 1; i <= a.length; i++) {
|
|
100
|
+
curr[0] = i;
|
|
101
|
+
for (let j = 1; j <= b.length; j++) {
|
|
102
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
103
|
+
curr[j] = Math.min(curr[j - 1] + 1, // insertion
|
|
104
|
+
prev[j] + 1, // deletion
|
|
105
|
+
prev[j - 1] + cost);
|
|
106
|
+
}
|
|
107
|
+
// Swap rows
|
|
108
|
+
const _swap = prev;
|
|
109
|
+
prev = curr;
|
|
110
|
+
curr = _swap;
|
|
111
|
+
}
|
|
112
|
+
return prev[b.length];
|
|
113
|
+
}
|
|
114
|
+
function readPackageJson(pkgPath) {
|
|
115
|
+
const pkgJsonPath = join(pkgPath, 'package.json');
|
|
116
|
+
if (!existsSync(pkgJsonPath))
|
|
117
|
+
return null;
|
|
118
|
+
try {
|
|
119
|
+
const raw = readFileSync(pkgJsonPath, 'utf-8');
|
|
120
|
+
return JSON.parse(raw);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* List all top-level package names in a node_modules directory.
|
|
128
|
+
* Handles scoped packages (@org/pkg).
|
|
129
|
+
*/
|
|
130
|
+
function listPackages(nodeModulesPath) {
|
|
131
|
+
if (!existsSync(nodeModulesPath))
|
|
132
|
+
return [];
|
|
133
|
+
let entries;
|
|
134
|
+
try {
|
|
135
|
+
entries = readdirSync(nodeModulesPath);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const packages = [];
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
if (entry.startsWith('.'))
|
|
143
|
+
continue;
|
|
144
|
+
if (entry.startsWith('@')) {
|
|
145
|
+
// Scoped namespace — list sub-entries
|
|
146
|
+
try {
|
|
147
|
+
const scopedEntries = readdirSync(join(nodeModulesPath, entry));
|
|
148
|
+
for (const sub of scopedEntries) {
|
|
149
|
+
if (!sub.startsWith('.')) {
|
|
150
|
+
packages.push(`${entry}/${sub}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch { /* skip */ }
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
packages.push(entry);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return packages;
|
|
161
|
+
}
|
|
162
|
+
// ── Individual Checks ────────────────────────────────────────────────────────
|
|
163
|
+
function checkMalicious(name) {
|
|
164
|
+
return KNOWN_MALICIOUS.includes(name);
|
|
165
|
+
}
|
|
166
|
+
function checkTyposquat(name) {
|
|
167
|
+
// Strip scope for comparison — "@org/axois" → "axois"
|
|
168
|
+
const bare = name.includes('/') ? name.split('/').pop() : name;
|
|
169
|
+
for (const popular of POPULAR_PACKAGES) {
|
|
170
|
+
if (name === popular || bare === popular)
|
|
171
|
+
continue; // exact match, not a typosquat
|
|
172
|
+
const dist = levenshtein(bare, popular);
|
|
173
|
+
if (dist >= 1 && dist <= 2) {
|
|
174
|
+
return popular; // typosquat of this popular package
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
function checkSuspiciousScripts(pkg) {
|
|
180
|
+
const scriptKeys = ['postinstall', 'preinstall', 'install'];
|
|
181
|
+
const matchedLabels = [];
|
|
182
|
+
for (const key of scriptKeys) {
|
|
183
|
+
const script = pkg.scripts?.[key];
|
|
184
|
+
if (!script)
|
|
185
|
+
continue;
|
|
186
|
+
for (const { regex, label } of SUSPICIOUS_PATTERNS) {
|
|
187
|
+
if (regex.test(script) && !matchedLabels.includes(label)) {
|
|
188
|
+
matchedLabels.push(label);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return { matchCount: matchedLabels.length, labels: matchedLabels };
|
|
193
|
+
}
|
|
194
|
+
function checkPackageAge(pkg) {
|
|
195
|
+
const hasPostInstall = !!pkg.scripts?.postinstall ||
|
|
196
|
+
!!pkg.scripts?.preinstall ||
|
|
197
|
+
!!pkg.scripts?.install;
|
|
198
|
+
if (!hasPostInstall)
|
|
199
|
+
return false;
|
|
200
|
+
// Try _time field first (some lockfiles embed it), then parse _resolved for date hints
|
|
201
|
+
const timeStr = pkg._time ?? pkg._resolved;
|
|
202
|
+
if (!timeStr)
|
|
203
|
+
return false;
|
|
204
|
+
// Extract an ISO date if present
|
|
205
|
+
const isoMatch = timeStr.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
|
206
|
+
if (!isoMatch)
|
|
207
|
+
return false;
|
|
208
|
+
const published = new Date(isoMatch[0]);
|
|
209
|
+
if (isNaN(published.getTime()))
|
|
210
|
+
return false;
|
|
211
|
+
const ageMs = Date.now() - published.getTime();
|
|
212
|
+
const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
|
|
213
|
+
return ageMs < sevenDaysMs;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Quarantine a package by moving it from node_modules to
|
|
217
|
+
* ~/.shieldcortex/quarantine/deps/<pkg>-<timestamp>/.
|
|
218
|
+
*
|
|
219
|
+
* Creates a manifest.json alongside the quarantined files.
|
|
220
|
+
* Only acts on CRITICAL and HIGH findings.
|
|
221
|
+
*
|
|
222
|
+
* @returns The quarantine destination path, or null if not quarantined.
|
|
223
|
+
*/
|
|
224
|
+
export function quarantinePackage(finding, nodeModulesPath) {
|
|
225
|
+
// Only quarantine CRITICAL and HIGH
|
|
226
|
+
if (finding.severity !== 'critical' && finding.severity !== 'high') {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
// Extract package name from the finding title heuristically
|
|
230
|
+
// Title examples:
|
|
231
|
+
// "Known malicious package: plain-crypto-js"
|
|
232
|
+
// "Possible typosquat: "axois" → "axios""
|
|
233
|
+
// "Suspicious install script in "bad-pkg""
|
|
234
|
+
const nameMatch = finding.title.match(/^Known malicious package:\s+(.+)$/) ??
|
|
235
|
+
finding.title.match(/^Possible typosquat:\s+"([^"]+)"/) ??
|
|
236
|
+
finding.title.match(/^Suspicious install script in\s+"([^"]+)"/);
|
|
237
|
+
const pkgName = nameMatch?.[1]?.trim();
|
|
238
|
+
if (!pkgName)
|
|
239
|
+
return null;
|
|
240
|
+
// Resolve source path
|
|
241
|
+
const pkgParts = pkgName.split('/');
|
|
242
|
+
const srcPath = resolve(join(nodeModulesPath, ...pkgParts));
|
|
243
|
+
if (!existsSync(srcPath))
|
|
244
|
+
return null;
|
|
245
|
+
// Determine version from package.json
|
|
246
|
+
const pkg = readPackageJson(srcPath);
|
|
247
|
+
const version = pkg?.version ?? 'unknown';
|
|
248
|
+
// Build quarantine destination
|
|
249
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
250
|
+
const safeName = pkgName.replace(/\//g, '__');
|
|
251
|
+
const quarantineBase = join(homedir(), '.shieldcortex', 'quarantine', 'deps');
|
|
252
|
+
const destDir = join(quarantineBase, `${safeName}-${timestamp}`);
|
|
253
|
+
const pkgDestDir = join(destDir, 'pkg');
|
|
254
|
+
mkdirSync(destDir, { recursive: true });
|
|
255
|
+
// Move the package directory
|
|
256
|
+
renameSync(srcPath, pkgDestDir);
|
|
257
|
+
// Write manifest
|
|
258
|
+
const manifest = {
|
|
259
|
+
packageName: pkgName,
|
|
260
|
+
version,
|
|
261
|
+
reason: finding.title,
|
|
262
|
+
originalPath: srcPath,
|
|
263
|
+
quarantinedAt: new Date().toISOString(),
|
|
264
|
+
severity: finding.severity,
|
|
265
|
+
};
|
|
266
|
+
writeFileSync(join(destDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
267
|
+
return destDir;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Permanently delete a malicious package from node_modules.
|
|
271
|
+
*
|
|
272
|
+
* Only acts on CRITICAL findings (known malicious packages from blocklist).
|
|
273
|
+
*
|
|
274
|
+
* @returns The package name that was deleted, or null if not deleted.
|
|
275
|
+
*/
|
|
276
|
+
export function cleanPackage(finding, nodeModulesPath) {
|
|
277
|
+
// Only clean CRITICAL findings
|
|
278
|
+
if (finding.severity !== 'critical') {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
// Extract package name from the finding title
|
|
282
|
+
const nameMatch = finding.title.match(/^Known malicious package:\s+(.+)$/);
|
|
283
|
+
const pkgName = nameMatch?.[1]?.trim();
|
|
284
|
+
if (!pkgName)
|
|
285
|
+
return null;
|
|
286
|
+
const pkgParts = pkgName.split('/');
|
|
287
|
+
const srcPath = resolve(join(nodeModulesPath, ...pkgParts));
|
|
288
|
+
if (!existsSync(srcPath))
|
|
289
|
+
return null;
|
|
290
|
+
rmSync(srcPath, { recursive: true, force: true });
|
|
291
|
+
return pkgName;
|
|
292
|
+
}
|
|
293
|
+
export function scanDependencies(opts) {
|
|
294
|
+
const start = Date.now();
|
|
295
|
+
const { nodeModulesPath } = opts;
|
|
296
|
+
if (!existsSync(nodeModulesPath)) {
|
|
297
|
+
return {
|
|
298
|
+
name: 'Dependency Scanner',
|
|
299
|
+
itemsScanned: 0,
|
|
300
|
+
findings: [],
|
|
301
|
+
durationMs: Date.now() - start,
|
|
302
|
+
skipped: true,
|
|
303
|
+
skipReason: `node_modules not found at ${nodeModulesPath}`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const packageNames = listPackages(nodeModulesPath);
|
|
307
|
+
const findings = [];
|
|
308
|
+
for (const name of packageNames) {
|
|
309
|
+
const pkgPath = join(nodeModulesPath, ...name.split('/'));
|
|
310
|
+
const pkg = readPackageJson(pkgPath);
|
|
311
|
+
// ── 1. Known malicious ──────────────────────────────────────────
|
|
312
|
+
if (checkMalicious(name)) {
|
|
313
|
+
findings.push({
|
|
314
|
+
scanner: 'dependency',
|
|
315
|
+
severity: 'critical',
|
|
316
|
+
title: `Known malicious package: ${name}`,
|
|
317
|
+
description: `"${name}" is a known malicious npm package. Remove it immediately and ` +
|
|
318
|
+
`audit your codebase for any data that may have been exfiltrated.`,
|
|
319
|
+
filePath: join(pkgPath, 'package.json'),
|
|
320
|
+
learnMoreUrl: LEARN_MORE_MALICIOUS,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// ── 2. Typosquat ────────────────────────────────────────────────
|
|
324
|
+
const typosquatOf = checkTyposquat(name);
|
|
325
|
+
if (typosquatOf) {
|
|
326
|
+
findings.push({
|
|
327
|
+
scanner: 'dependency',
|
|
328
|
+
severity: 'high',
|
|
329
|
+
title: `Possible typosquat: "${name}" → "${typosquatOf}"`,
|
|
330
|
+
description: `"${name}" is 1-2 characters away from the popular package "${typosquatOf}". ` +
|
|
331
|
+
`This may be a typosquatting attack. Verify the package is intentional.`,
|
|
332
|
+
filePath: join(pkgPath, 'package.json'),
|
|
333
|
+
learnMoreUrl: LEARN_MORE_TYPOSQUAT,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (!pkg)
|
|
337
|
+
continue;
|
|
338
|
+
// ── 3. Suspicious postinstall ───────────────────────────────────
|
|
339
|
+
const { matchCount, labels } = checkSuspiciousScripts(pkg);
|
|
340
|
+
if (matchCount >= 1) {
|
|
341
|
+
const severity = matchCount >= 2 ? 'high' : 'medium';
|
|
342
|
+
findings.push({
|
|
343
|
+
scanner: 'dependency',
|
|
344
|
+
severity,
|
|
345
|
+
title: `Suspicious install script in "${name}"`,
|
|
346
|
+
description: `"${name}" has an install/postinstall script containing suspicious patterns: ` +
|
|
347
|
+
labels.join(', ') + '. Review before trusting this package.',
|
|
348
|
+
filePath: join(pkgPath, 'package.json'),
|
|
349
|
+
matchedText: labels.slice(0, 5).join(', '),
|
|
350
|
+
learnMoreUrl: LEARN_MORE_POSTINSTALL,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
// ── 4. New package with postinstall ────────────────────────────
|
|
354
|
+
if (checkPackageAge(pkg)) {
|
|
355
|
+
findings.push({
|
|
356
|
+
scanner: 'dependency',
|
|
357
|
+
severity: 'medium',
|
|
358
|
+
title: `Newly published package with install script: "${name}"`,
|
|
359
|
+
description: `"${name}" was published within the last 7 days and has an install/postinstall ` +
|
|
360
|
+
`script. New packages with install scripts are a common supply-chain attack vector.`,
|
|
361
|
+
filePath: join(pkgPath, 'package.json'),
|
|
362
|
+
learnMoreUrl: LEARN_MORE_MALICIOUS,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
name: 'Dependency Scanner',
|
|
368
|
+
itemsScanned: packageNames.length,
|
|
369
|
+
findings,
|
|
370
|
+
durationMs: Date.now() - start,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Resolve the node_modules path for a given mode.
|
|
375
|
+
*/
|
|
376
|
+
export function resolveNodeModulesPath(mode, customPath) {
|
|
377
|
+
switch (mode) {
|
|
378
|
+
case 'global': {
|
|
379
|
+
try {
|
|
380
|
+
const prefix = execSync('npm prefix -g', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
381
|
+
return join(prefix, 'lib', 'node_modules');
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// Fallback common locations
|
|
385
|
+
return join('/usr/local/lib', 'node_modules');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
case 'path':
|
|
389
|
+
return customPath ?? join(process.cwd(), 'node_modules');
|
|
390
|
+
case 'local':
|
|
391
|
+
default:
|
|
392
|
+
return join(process.cwd(), 'node_modules');
|
|
393
|
+
}
|
|
394
|
+
}
|
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, quarantinePackage, cleanPackage } 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, quarantinePackage, cleanPackage } 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,12 @@
|
|
|
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
|
|
15
|
+
* npx shieldcortex audit --deps --quarantine # Scan + quarantine CRITICAL/HIGH
|
|
16
|
+
* npx shieldcortex audit --deps --clean --force # Scan + permanently delete CRITICAL
|
|
17
|
+
* npx shieldcortex audit --deps --auto-protect # Scan + auto-quarantine CRITICAL
|
|
12
18
|
*/
|
|
13
19
|
/**
|
|
14
20
|
* Run the full audit.
|
package/dist/cli/audit.js
CHANGED
|
@@ -9,19 +9,70 @@
|
|
|
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
|
|
15
|
+
* npx shieldcortex audit --deps --quarantine # Scan + quarantine CRITICAL/HIGH
|
|
16
|
+
* npx shieldcortex audit --deps --clean --force # Scan + permanently delete CRITICAL
|
|
17
|
+
* npx shieldcortex audit --deps --auto-protect # Scan + auto-quarantine CRITICAL
|
|
12
18
|
*/
|
|
13
|
-
import {
|
|
19
|
+
import { requireFeature, FeatureGatedError } from '../license/gate.js';
|
|
20
|
+
import { scanMemories, scanMcpConfigs, scanEnvFiles, scanRulesFiles, scanDependencies, resolveNodeModulesPath, quarantinePackage, cleanPackage, formatTerminalReport, formatMarkdownReport, formatJsonReport, calculateGrade, } from '../audit/index.js';
|
|
14
21
|
function parseAuditArgs(args) {
|
|
15
|
-
const options = {
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
const options = {
|
|
23
|
+
format: 'terminal',
|
|
24
|
+
ci: false,
|
|
25
|
+
deps: false,
|
|
26
|
+
depsMode: 'local',
|
|
27
|
+
quarantine: false,
|
|
28
|
+
clean: false,
|
|
29
|
+
autoProtect: false,
|
|
30
|
+
force: false,
|
|
31
|
+
};
|
|
32
|
+
for (let i = 0; i < args.length; i++) {
|
|
33
|
+
const arg = args[i];
|
|
34
|
+
if (arg === '--json') {
|
|
18
35
|
options.format = 'json';
|
|
19
|
-
|
|
36
|
+
}
|
|
37
|
+
else if (arg === '--markdown' || arg === '--md') {
|
|
20
38
|
options.format = 'markdown';
|
|
39
|
+
}
|
|
21
40
|
else if (arg === '--ci') {
|
|
22
41
|
options.ci = true;
|
|
23
42
|
options.format = 'json';
|
|
24
43
|
}
|
|
44
|
+
else if (arg === '--deps') {
|
|
45
|
+
options.deps = true;
|
|
46
|
+
options.depsMode = 'local';
|
|
47
|
+
}
|
|
48
|
+
else if (arg === '--deps-global') {
|
|
49
|
+
options.deps = true;
|
|
50
|
+
options.depsMode = 'global';
|
|
51
|
+
}
|
|
52
|
+
else if (arg === '--deps-path') {
|
|
53
|
+
options.deps = true;
|
|
54
|
+
options.depsMode = 'path';
|
|
55
|
+
const next = args[i + 1];
|
|
56
|
+
if (next && !next.startsWith('--')) {
|
|
57
|
+
options.depsPath = next;
|
|
58
|
+
i++; // consume path argument
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (arg === '--quarantine') {
|
|
62
|
+
options.quarantine = true;
|
|
63
|
+
options.deps = true;
|
|
64
|
+
}
|
|
65
|
+
else if (arg === '--clean') {
|
|
66
|
+
options.clean = true;
|
|
67
|
+
options.deps = true;
|
|
68
|
+
}
|
|
69
|
+
else if (arg === '--auto-protect') {
|
|
70
|
+
options.autoProtect = true;
|
|
71
|
+
options.deps = true;
|
|
72
|
+
}
|
|
73
|
+
else if (arg === '--force') {
|
|
74
|
+
options.force = true;
|
|
75
|
+
}
|
|
25
76
|
}
|
|
26
77
|
return options;
|
|
27
78
|
}
|
|
@@ -31,6 +82,13 @@ function parseAuditArgs(args) {
|
|
|
31
82
|
export async function handleAuditCommand(args) {
|
|
32
83
|
const options = parseAuditArgs(args);
|
|
33
84
|
const start = Date.now();
|
|
85
|
+
// --clean without --force: print warning and exit
|
|
86
|
+
if (options.clean && !options.force && !options.ci) {
|
|
87
|
+
console.error('\x1b[33m⚠ Are you sure? Use --force to confirm.\x1b[0m\n' +
|
|
88
|
+
' --clean permanently deletes CRITICAL packages from node_modules.\n' +
|
|
89
|
+
' Run: shieldcortex audit --deps --clean --force');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
34
92
|
// Get version from package.json
|
|
35
93
|
let version = 'unknown';
|
|
36
94
|
try {
|
|
@@ -56,30 +114,46 @@ export async function handleAuditCommand(args) {
|
|
|
56
114
|
}
|
|
57
115
|
// Run all scanners
|
|
58
116
|
const scanners = [];
|
|
117
|
+
const totalSteps = options.deps ? 5 : 4;
|
|
59
118
|
if (options.format === 'terminal')
|
|
60
|
-
process.stdout.write(
|
|
119
|
+
process.stdout.write(` \x1b[2m[1/${totalSteps}] Memory files...\x1b[0m`);
|
|
61
120
|
const memoryResult = scanMemories();
|
|
62
121
|
scanners.push(memoryResult);
|
|
63
122
|
if (options.format === 'terminal')
|
|
64
|
-
process.stdout.write(`\r \x1b[32m[1
|
|
123
|
+
process.stdout.write(`\r \x1b[32m[1/${totalSteps}]\x1b[0m Memory files \x1b[2m${memoryResult.durationMs}ms\x1b[0m\n`);
|
|
65
124
|
if (options.format === 'terminal')
|
|
66
|
-
process.stdout.write(
|
|
125
|
+
process.stdout.write(` \x1b[2m[2/${totalSteps}] MCP configs...\x1b[0m`);
|
|
67
126
|
const mcpResult = scanMcpConfigs();
|
|
68
127
|
scanners.push(mcpResult);
|
|
69
128
|
if (options.format === 'terminal')
|
|
70
|
-
process.stdout.write(`\r \x1b[32m[2
|
|
129
|
+
process.stdout.write(`\r \x1b[32m[2/${totalSteps}]\x1b[0m MCP configs \x1b[2m${mcpResult.durationMs}ms\x1b[0m\n`);
|
|
71
130
|
if (options.format === 'terminal')
|
|
72
|
-
process.stdout.write(
|
|
131
|
+
process.stdout.write(` \x1b[2m[3/${totalSteps}] Environment secrets...\x1b[0m`);
|
|
73
132
|
const envResult = scanEnvFiles();
|
|
74
133
|
scanners.push(envResult);
|
|
75
134
|
if (options.format === 'terminal')
|
|
76
|
-
process.stdout.write(`\r \x1b[32m[3
|
|
135
|
+
process.stdout.write(`\r \x1b[32m[3/${totalSteps}]\x1b[0m Environment \x1b[2m${envResult.durationMs}ms\x1b[0m\n`);
|
|
77
136
|
if (options.format === 'terminal')
|
|
78
|
-
process.stdout.write(
|
|
137
|
+
process.stdout.write(` \x1b[2m[4/${totalSteps}] Rules files...\x1b[0m`);
|
|
79
138
|
const rulesResult = scanRulesFiles();
|
|
80
139
|
scanners.push(rulesResult);
|
|
81
140
|
if (options.format === 'terminal')
|
|
82
|
-
process.stdout.write(`\r \x1b[32m[4
|
|
141
|
+
process.stdout.write(`\r \x1b[32m[4/${totalSteps}]\x1b[0m Rules files \x1b[2m${rulesResult.durationMs}ms\x1b[0m\n`);
|
|
142
|
+
// Optional: dependency scan
|
|
143
|
+
let nodeModulesPath = '';
|
|
144
|
+
if (options.deps) {
|
|
145
|
+
nodeModulesPath = resolveNodeModulesPath(options.depsMode, options.depsPath);
|
|
146
|
+
if (options.format === 'terminal') {
|
|
147
|
+
process.stdout.write(` \x1b[2m[5/${totalSteps}] Dependencies (${nodeModulesPath})...\x1b[0m`);
|
|
148
|
+
}
|
|
149
|
+
const depsResult = scanDependencies({ nodeModulesPath });
|
|
150
|
+
scanners.push(depsResult);
|
|
151
|
+
if (options.format === 'terminal') {
|
|
152
|
+
process.stdout.write(`\r \x1b[32m[5/${totalSteps}]\x1b[0m Dependencies \x1b[2m${depsResult.durationMs}ms\x1b[0m\n`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (options.format === 'terminal')
|
|
156
|
+
process.stdout.write('\n');
|
|
83
157
|
// Aggregate findings
|
|
84
158
|
const allFindings = scanners.flatMap(s => s.findings);
|
|
85
159
|
// Sort by severity (critical first)
|
|
@@ -117,6 +191,102 @@ export async function handleAuditCommand(args) {
|
|
|
117
191
|
console.log(formatTerminalReport(report));
|
|
118
192
|
break;
|
|
119
193
|
}
|
|
194
|
+
// ── Post-scan actions ────────────────────────────────────────────────────
|
|
195
|
+
const depFindings = allFindings.filter(f => f.scanner === 'dependency');
|
|
196
|
+
if (options.quarantine && depFindings.length > 0) {
|
|
197
|
+
try {
|
|
198
|
+
requireFeature('deps_quarantine');
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
if (e instanceof FeatureGatedError) {
|
|
202
|
+
console.error('\n' + e.message);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
throw e;
|
|
206
|
+
}
|
|
207
|
+
const eligible = depFindings.filter(f => f.severity === 'critical' || f.severity === 'high');
|
|
208
|
+
if (eligible.length === 0) {
|
|
209
|
+
if (options.format === 'terminal') {
|
|
210
|
+
console.log('\n\x1b[32m✓ No CRITICAL or HIGH dependency findings to quarantine.\x1b[0m');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
if (options.format === 'terminal') {
|
|
215
|
+
console.log(`\n\x1b[33m⚠ Quarantining ${eligible.length} package(s)...\x1b[0m`);
|
|
216
|
+
}
|
|
217
|
+
for (const finding of eligible) {
|
|
218
|
+
const dest = quarantinePackage(finding, nodeModulesPath);
|
|
219
|
+
if (dest && options.format === 'terminal') {
|
|
220
|
+
console.log(` \x1b[31m✗\x1b[0m Quarantined: ${finding.title}`);
|
|
221
|
+
console.log(` → ${dest}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (options.clean && depFindings.length > 0) {
|
|
227
|
+
try {
|
|
228
|
+
requireFeature('deps_clean');
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
if (e instanceof FeatureGatedError) {
|
|
232
|
+
console.error('\n' + e.message);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
throw e;
|
|
236
|
+
}
|
|
237
|
+
const criticals = depFindings.filter(f => f.severity === 'critical');
|
|
238
|
+
if (criticals.length === 0) {
|
|
239
|
+
if (options.format === 'terminal') {
|
|
240
|
+
console.log('\n\x1b[32m✓ No CRITICAL dependency findings to clean.\x1b[0m');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
if (options.format === 'terminal') {
|
|
245
|
+
console.log(`\n\x1b[31m🗑 Cleaning ${criticals.length} CRITICAL package(s)...\x1b[0m`);
|
|
246
|
+
}
|
|
247
|
+
for (const finding of criticals) {
|
|
248
|
+
const deleted = cleanPackage(finding, nodeModulesPath);
|
|
249
|
+
if (deleted && options.format === 'terminal') {
|
|
250
|
+
console.log(` \x1b[31m✗\x1b[0m Deleted: ${deleted}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (options.autoProtect && depFindings.length > 0) {
|
|
256
|
+
try {
|
|
257
|
+
requireFeature('deps_auto_protect');
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
if (e instanceof FeatureGatedError) {
|
|
261
|
+
console.error('\n' + e.message);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
throw e;
|
|
265
|
+
}
|
|
266
|
+
const criticals = depFindings.filter(f => f.severity === 'critical');
|
|
267
|
+
const highs = depFindings.filter(f => f.severity === 'high');
|
|
268
|
+
if (criticals.length > 0) {
|
|
269
|
+
if (options.format === 'terminal') {
|
|
270
|
+
console.log(`\n\x1b[33m⚠ Auto-protect: quarantining ${criticals.length} CRITICAL package(s)...\x1b[0m`);
|
|
271
|
+
}
|
|
272
|
+
for (const finding of criticals) {
|
|
273
|
+
const dest = quarantinePackage(finding, nodeModulesPath);
|
|
274
|
+
if (dest && options.format === 'terminal') {
|
|
275
|
+
console.log(` \x1b[31m✗\x1b[0m Quarantined: ${finding.title}`);
|
|
276
|
+
console.log(` → ${dest}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (highs.length > 0 && options.format === 'terminal') {
|
|
281
|
+
console.log(`\n\x1b[33m⚠ Auto-protect: ${highs.length} HIGH finding(s) detected (manual review required):\x1b[0m`);
|
|
282
|
+
for (const finding of highs) {
|
|
283
|
+
console.log(` \x1b[33m!\x1b[0m ${finding.title}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (criticals.length === 0 && highs.length === 0 && options.format === 'terminal') {
|
|
287
|
+
console.log('\n\x1b[32m✓ Auto-protect: no CRITICAL or HIGH dependency findings.\x1b[0m');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
120
290
|
// Exit code
|
|
121
291
|
if (options.ci) {
|
|
122
292
|
// In CI mode: fail on critical or high findings
|
package/dist/license/gate.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* isFeatureEnabled('cloud_sync'); // returns boolean (soft check)
|
|
7
7
|
*/
|
|
8
8
|
import { type LicenseTier } from './keys.js';
|
|
9
|
-
export type GatedFeature = 'custom_injection_patterns' | 'custom_iron_dome_policies' | 'custom_firewall_rules' | 'audit_export' | 'skill_scanner_deep' | 'cloud_sync' | 'team_management' | 'shared_patterns' | 'cortex_learning';
|
|
9
|
+
export type GatedFeature = 'custom_injection_patterns' | 'custom_iron_dome_policies' | 'custom_firewall_rules' | 'audit_export' | 'skill_scanner_deep' | 'cloud_sync' | 'team_management' | 'shared_patterns' | 'cortex_learning' | 'deps_quarantine' | 'deps_clean' | 'deps_auto_protect' | 'deps_global_scan' | 'memory_types' | 'memory_scopes' | 'dream_mode' | 'llm_reranking' | 'positive_feedback';
|
|
10
10
|
export declare class FeatureGatedError extends Error {
|
|
11
11
|
feature: GatedFeature;
|
|
12
12
|
requiredTier: LicenseTier;
|
package/dist/license/gate.js
CHANGED
|
@@ -17,6 +17,15 @@ const FEATURE_TIERS = {
|
|
|
17
17
|
team_management: 'team',
|
|
18
18
|
shared_patterns: 'team',
|
|
19
19
|
cortex_learning: 'pro',
|
|
20
|
+
deps_quarantine: 'pro',
|
|
21
|
+
deps_clean: 'pro',
|
|
22
|
+
deps_auto_protect: 'pro',
|
|
23
|
+
deps_global_scan: 'pro',
|
|
24
|
+
memory_types: 'pro',
|
|
25
|
+
memory_scopes: 'team',
|
|
26
|
+
dream_mode: 'pro',
|
|
27
|
+
llm_reranking: 'pro',
|
|
28
|
+
positive_feedback: 'pro',
|
|
20
29
|
};
|
|
21
30
|
const FEATURE_DESCRIPTIONS = {
|
|
22
31
|
custom_injection_patterns: 'Define up to 50 custom regex patterns for detecting domain-specific threats.',
|
|
@@ -28,6 +37,15 @@ const FEATURE_DESCRIPTIONS = {
|
|
|
28
37
|
team_management: 'Manage team members, invites, and shared security policies.',
|
|
29
38
|
shared_patterns: 'Share custom injection patterns and policies across your team.',
|
|
30
39
|
cortex_learning: 'Systematic mistake learning with pre-flight checks, pattern detection, and rule graduation.',
|
|
40
|
+
deps_quarantine: 'Quarantine malicious packages — move threats to a safe holding area.',
|
|
41
|
+
deps_clean: 'Permanently remove known malicious packages from your project.',
|
|
42
|
+
deps_auto_protect: 'Automated scan + quarantine of critical threats on every install.',
|
|
43
|
+
deps_global_scan: 'Scan global npm installations for supply chain threats.',
|
|
44
|
+
memory_types: 'Typed memories (user/feedback/project/reference) for structured knowledge.',
|
|
45
|
+
memory_scopes: 'Private vs team memory scopes for multi-agent deployments.',
|
|
46
|
+
dream_mode: 'Background memory consolidation — merge duplicates, archive stale, detect contradictions.',
|
|
47
|
+
llm_reranking: 'LLM-powered memory reranking for precision recall.',
|
|
48
|
+
positive_feedback: 'Capture what worked, not just what failed — learn from success.',
|
|
31
49
|
};
|
|
32
50
|
// ── Error class ──────────────────────────────────────────
|
|
33
51
|
export class FeatureGatedError extends Error {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shieldcortex",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.2.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",
|