shieldcortex 4.1.0 → 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.
@@ -14,13 +14,42 @@
14
14
  * shieldcortex audit --deps # scan ./node_modules
15
15
  * shieldcortex audit --deps-global # scan global npm prefix
16
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
17
20
  */
18
- import type { ScannerResult } from './types.js';
21
+ import type { AuditFinding, ScannerResult } from './types.js';
19
22
  /**
20
23
  * Compute Levenshtein distance between two strings.
21
24
  * Standard dynamic-programming implementation, no external deps.
22
25
  */
23
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;
24
53
  export interface DependencyScanOptions {
25
54
  /** Absolute path to node_modules directory */
26
55
  nodeModulesPath: string;
@@ -14,10 +14,14 @@
14
14
  * shieldcortex audit --deps # scan ./node_modules
15
15
  * shieldcortex audit --deps-global # scan global npm prefix
16
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
17
20
  */
18
- import { readdirSync, readFileSync, existsSync } from 'fs';
19
- import { join } from 'path';
21
+ import { readdirSync, readFileSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'fs';
22
+ import { join, resolve } from 'path';
20
23
  import { execSync } from 'child_process';
24
+ import { homedir } from 'os';
21
25
  const LEARN_MORE_MALICIOUS = 'https://shieldcortex.ai/docs/threats/supply-chain';
22
26
  const LEARN_MORE_TYPOSQUAT = 'https://shieldcortex.ai/docs/threats/typosquatting';
23
27
  const LEARN_MORE_POSTINSTALL = 'https://shieldcortex.ai/docs/threats/malicious-scripts';
@@ -208,6 +212,84 @@ function checkPackageAge(pkg) {
208
212
  const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
209
213
  return ageMs < sevenDaysMs;
210
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
+ }
211
293
  export function scanDependencies(opts) {
212
294
  const start = Date.now();
213
295
  const { nodeModulesPath } = opts;
@@ -8,7 +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
+ export { scanDependencies, resolveNodeModulesPath, quarantinePackage, cleanPackage } from './dependency-scanner.js';
12
12
  export { formatTerminalReport, formatMarkdownReport, formatJsonReport } from './report-formatter.js';
13
13
  export { calculateGrade } from './types.js';
14
14
  export type { AuditFinding, AuditReport, AuditGrade, AuditSeverity, ScannerResult, } from './types.js';
@@ -8,6 +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
+ export { scanDependencies, resolveNodeModulesPath, quarantinePackage, cleanPackage } from './dependency-scanner.js';
12
12
  export { formatTerminalReport, formatMarkdownReport, formatJsonReport } from './report-formatter.js';
13
13
  export { calculateGrade } from './types.js';
@@ -12,6 +12,9 @@
12
12
  * npx shieldcortex audit --deps # Also scan ./node_modules for malicious packages
13
13
  * npx shieldcortex audit --deps-global # Also scan global npm node_modules
14
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
15
18
  */
16
19
  /**
17
20
  * Run the full audit.
package/dist/cli/audit.js CHANGED
@@ -12,14 +12,22 @@
12
12
  * npx shieldcortex audit --deps # Also scan ./node_modules for malicious packages
13
13
  * npx shieldcortex audit --deps-global # Also scan global npm node_modules
14
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
15
18
  */
16
- import { scanMemories, scanMcpConfigs, scanEnvFiles, scanRulesFiles, scanDependencies, resolveNodeModulesPath, formatTerminalReport, formatMarkdownReport, formatJsonReport, calculateGrade, } from '../audit/index.js';
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';
17
21
  function parseAuditArgs(args) {
18
22
  const options = {
19
23
  format: 'terminal',
20
24
  ci: false,
21
25
  deps: false,
22
26
  depsMode: 'local',
27
+ quarantine: false,
28
+ clean: false,
29
+ autoProtect: false,
30
+ force: false,
23
31
  };
24
32
  for (let i = 0; i < args.length; i++) {
25
33
  const arg = args[i];
@@ -50,6 +58,21 @@ function parseAuditArgs(args) {
50
58
  i++; // consume path argument
51
59
  }
52
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
+ }
53
76
  }
54
77
  return options;
55
78
  }
@@ -59,6 +82,13 @@ function parseAuditArgs(args) {
59
82
  export async function handleAuditCommand(args) {
60
83
  const options = parseAuditArgs(args);
61
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
+ }
62
92
  // Get version from package.json
63
93
  let version = 'unknown';
64
94
  try {
@@ -110,8 +140,9 @@ export async function handleAuditCommand(args) {
110
140
  if (options.format === 'terminal')
111
141
  process.stdout.write(`\r \x1b[32m[4/${totalSteps}]\x1b[0m Rules files \x1b[2m${rulesResult.durationMs}ms\x1b[0m\n`);
112
142
  // Optional: dependency scan
143
+ let nodeModulesPath = '';
113
144
  if (options.deps) {
114
- const nodeModulesPath = resolveNodeModulesPath(options.depsMode, options.depsPath);
145
+ nodeModulesPath = resolveNodeModulesPath(options.depsMode, options.depsPath);
115
146
  if (options.format === 'terminal') {
116
147
  process.stdout.write(` \x1b[2m[5/${totalSteps}] Dependencies (${nodeModulesPath})...\x1b[0m`);
117
148
  }
@@ -160,6 +191,102 @@ export async function handleAuditCommand(args) {
160
191
  console.log(formatTerminalReport(report));
161
192
  break;
162
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
+ }
163
290
  // Exit code
164
291
  if (options.ci) {
165
292
  // In CI mode: fail on critical or high findings
@@ -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;
@@ -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.1.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",