shieldcortex 3.4.29 → 3.4.31
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 +51 -13
- package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
- package/dashboard/.next/standalone/dashboard/.next/required-server-files.json +4 -4
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/{c95cfef94573d615.js → 55858d6b0b93278b.js} +1 -1
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/README.md +2 -2
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/lib/glib-2.0/include/glibconfig.h +9 -8
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64/lib/libvips-cpp.8.17.3.dylib → sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3} +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/package.json +11 -5
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +46 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +221 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +1 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +42 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +30 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-darwin-arm64 → sharp-linux-x64}/package.json +13 -7
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/package.json +46 -0
- package/dashboard/.next/standalone/dashboard/server.js +1 -1
- package/dashboard/.next/standalone/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/package.json +11 -5
- package/dashboard/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +42 -0
- package/dashboard/.next/standalone/node_modules/@img/{sharp-darwin-arm64 → sharp-linux-x64}/package.json +13 -7
- package/dashboard/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +46 -0
- package/dist/api/routes/system.js +35 -0
- package/dist/cli/stats-banner.d.ts +6 -0
- package/dist/cli/stats-banner.js +48 -0
- package/dist/cli/stats-command.d.ts +4 -0
- package/dist/cli/stats-command.js +83 -0
- package/dist/defence/audit/index.d.ts +2 -2
- package/dist/defence/audit/index.js +1 -1
- package/dist/defence/audit/queries.d.ts +12 -0
- package/dist/defence/audit/queries.js +40 -0
- package/dist/index.js +15 -1
- package/dist/server.js +26 -8
- package/dist/setup/openclaw.js +75 -17
- package/package.json +1 -1
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{c51lWSauvNC0BT832gfq4 → Wkh_Gp4JYc_-uQ8K2Ao5-}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{c51lWSauvNC0BT832gfq4 → Wkh_Gp4JYc_-uQ8K2Ao5-}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{c51lWSauvNC0BT832gfq4 → Wkh_Gp4JYc_-uQ8K2Ao5-}/_ssgManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/lib/index.js +0 -0
- /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-darwin-arm64 → sharp-libvips-linux-x64}/versions.json +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@img/sharp-
|
|
2
|
+
"name": "@img/sharp-linux-x64",
|
|
3
3
|
"version": "0.34.5",
|
|
4
|
-
"description": "Prebuilt sharp for use with
|
|
4
|
+
"description": "Prebuilt sharp for use with Linux (glibc) x64",
|
|
5
5
|
"author": "Lovell Fuller <npm@lovell.info>",
|
|
6
6
|
"homepage": "https://sharp.pixelplumbing.com",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/lovell/sharp.git",
|
|
10
|
-
"directory": "npm/
|
|
10
|
+
"directory": "npm/linux-x64"
|
|
11
11
|
},
|
|
12
12
|
"license": "Apache-2.0",
|
|
13
13
|
"funding": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"preferUnplugged": true,
|
|
17
17
|
"optionalDependencies": {
|
|
18
|
-
"@img/sharp-libvips-
|
|
18
|
+
"@img/sharp-libvips-linux-x64": "1.2.4"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"lib"
|
|
@@ -25,16 +25,22 @@
|
|
|
25
25
|
},
|
|
26
26
|
"type": "commonjs",
|
|
27
27
|
"exports": {
|
|
28
|
-
"./sharp.node": "./lib/sharp-
|
|
28
|
+
"./sharp.node": "./lib/sharp-linux-x64.node",
|
|
29
29
|
"./package": "./package.json"
|
|
30
30
|
},
|
|
31
31
|
"engines": {
|
|
32
32
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
|
33
33
|
},
|
|
34
|
+
"config": {
|
|
35
|
+
"glibc": ">=2.26"
|
|
36
|
+
},
|
|
34
37
|
"os": [
|
|
35
|
-
"
|
|
38
|
+
"linux"
|
|
39
|
+
],
|
|
40
|
+
"libc": [
|
|
41
|
+
"glibc"
|
|
36
42
|
],
|
|
37
43
|
"cpu": [
|
|
38
|
-
"
|
|
44
|
+
"x64"
|
|
39
45
|
]
|
|
40
46
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@img/sharp-linuxmusl-x64",
|
|
3
|
+
"version": "0.34.5",
|
|
4
|
+
"description": "Prebuilt sharp for use with Linux (musl) x64",
|
|
5
|
+
"author": "Lovell Fuller <npm@lovell.info>",
|
|
6
|
+
"homepage": "https://sharp.pixelplumbing.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/lovell/sharp.git",
|
|
10
|
+
"directory": "npm/linuxmusl-x64"
|
|
11
|
+
},
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"funding": {
|
|
14
|
+
"url": "https://opencollective.com/libvips"
|
|
15
|
+
},
|
|
16
|
+
"preferUnplugged": true,
|
|
17
|
+
"optionalDependencies": {
|
|
18
|
+
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"lib"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"type": "commonjs",
|
|
27
|
+
"exports": {
|
|
28
|
+
"./sharp.node": "./lib/sharp-linuxmusl-x64.node",
|
|
29
|
+
"./package": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
|
33
|
+
},
|
|
34
|
+
"config": {
|
|
35
|
+
"musl": ">=1.2.2"
|
|
36
|
+
},
|
|
37
|
+
"os": [
|
|
38
|
+
"linux"
|
|
39
|
+
],
|
|
40
|
+
"libc": [
|
|
41
|
+
"musl"
|
|
42
|
+
],
|
|
43
|
+
"cpu": [
|
|
44
|
+
"x64"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { WebSocket } from 'ws';
|
|
2
|
+
import { getLifetimeStats } from '../../defence/audit/queries.js';
|
|
3
|
+
import { getAuditStats } from '../../defence/audit/queries.js';
|
|
4
|
+
import { isDatabaseInitialized } from '../../database/init.js';
|
|
2
5
|
import { getCloudConfig, getCloudSyncControls, getDeviceId, getDeviceName, getDefenceMode, getOpenClawMemoryConfig, isConfigTampered, readRawConfig, setCloudConfig, setCloudSyncControls, setDefenceMode, setOpenClawMemoryConfig, } from '../../cloud/config.js';
|
|
3
6
|
import { getQueueStats } from '../../cloud/sync-queue.js';
|
|
4
7
|
import { getDatabase } from '../../database/init.js';
|
|
@@ -25,10 +28,42 @@ export function registerSystemRoutes(app, deps) {
|
|
|
25
28
|
expiresAt: trial.expiresAt.slice(0, 10), // YYYY-MM-DD
|
|
26
29
|
}
|
|
27
30
|
: null;
|
|
31
|
+
// Stats (best effort — never fail the status endpoint)
|
|
32
|
+
let stats = null;
|
|
33
|
+
try {
|
|
34
|
+
if (isDatabaseInitialized()) {
|
|
35
|
+
const lifetime = getLifetimeStats();
|
|
36
|
+
const h24 = getAuditStats('24h');
|
|
37
|
+
const d7 = getAuditStats('7d');
|
|
38
|
+
stats = {
|
|
39
|
+
lifetime: {
|
|
40
|
+
totalScans: lifetime.totalScans,
|
|
41
|
+
threatsBlocked: lifetime.threatsBlocked,
|
|
42
|
+
quarantined: lifetime.quarantined,
|
|
43
|
+
credentialLeaks: lifetime.credentialLeaks,
|
|
44
|
+
memoriesProtected: lifetime.memoriesProtected,
|
|
45
|
+
},
|
|
46
|
+
last24h: {
|
|
47
|
+
totalScans: h24.totalOperations,
|
|
48
|
+
blocked: h24.blockedCount,
|
|
49
|
+
quarantined: h24.quarantinedCount,
|
|
50
|
+
},
|
|
51
|
+
last7d: {
|
|
52
|
+
totalScans: d7.totalOperations,
|
|
53
|
+
blocked: d7.blockedCount,
|
|
54
|
+
quarantined: d7.quarantinedCount,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Stats unavailable — still return the rest of the status
|
|
61
|
+
}
|
|
28
62
|
res.json({
|
|
29
63
|
tier: license.valid ? license.tier : (trial?.active ? 'pro' : 'free'),
|
|
30
64
|
licenseValid: license.valid,
|
|
31
65
|
trial: trialInfo,
|
|
66
|
+
...(stats ? { stats } : {}),
|
|
32
67
|
});
|
|
33
68
|
}
|
|
34
69
|
catch (error) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats banner — compact one-liner showing what ShieldCortex has protected
|
|
3
|
+
*/
|
|
4
|
+
import type { LifetimeStats } from '../defence/audit/queries.js';
|
|
5
|
+
export declare function formatStatsBanner(stats: LifetimeStats): string | null;
|
|
6
|
+
export declare function printStatsBanner(): Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats banner — compact one-liner showing what ShieldCortex has protected
|
|
3
|
+
*/
|
|
4
|
+
const fmt = new Intl.NumberFormat('en-US');
|
|
5
|
+
function n(num) {
|
|
6
|
+
return fmt.format(num);
|
|
7
|
+
}
|
|
8
|
+
const GREEN = '\x1b[32m';
|
|
9
|
+
const DIM = '\x1b[2m';
|
|
10
|
+
const BOLD = '\x1b[1m';
|
|
11
|
+
const RESET = '\x1b[0m';
|
|
12
|
+
export function formatStatsBanner(stats) {
|
|
13
|
+
// Nothing to show on a fresh install
|
|
14
|
+
if (stats.totalScans === 0)
|
|
15
|
+
return null;
|
|
16
|
+
const parts = [];
|
|
17
|
+
if (stats.threatsBlocked > 0 || stats.quarantined > 0) {
|
|
18
|
+
const blocked = stats.threatsBlocked + stats.quarantined;
|
|
19
|
+
parts.push(`${GREEN}${BOLD}${n(blocked)}${RESET}${DIM} threats blocked${RESET}`);
|
|
20
|
+
}
|
|
21
|
+
if (stats.credentialLeaks > 0) {
|
|
22
|
+
parts.push(`${GREEN}${BOLD}${n(stats.credentialLeaks)}${RESET}${DIM} credential leak${stats.credentialLeaks !== 1 ? 's' : ''} caught${RESET}`);
|
|
23
|
+
}
|
|
24
|
+
if (stats.memoriesProtected > 0) {
|
|
25
|
+
parts.push(`${GREEN}${BOLD}${n(stats.memoriesProtected)}${RESET}${DIM} memories scanned${RESET}`);
|
|
26
|
+
}
|
|
27
|
+
if (parts.length === 0) {
|
|
28
|
+
// Scans happened but nothing notable — show scan count
|
|
29
|
+
parts.push(`${GREEN}${BOLD}${n(stats.totalScans)}${RESET}${DIM} scans completed${RESET}`);
|
|
30
|
+
}
|
|
31
|
+
return `🛡️ ShieldCortex: ${parts.join(`${DIM} · ${RESET}`)}`;
|
|
32
|
+
}
|
|
33
|
+
export async function printStatsBanner() {
|
|
34
|
+
try {
|
|
35
|
+
const { isDatabaseInitialized } = await import('../database/init.js');
|
|
36
|
+
if (!isDatabaseInitialized())
|
|
37
|
+
return;
|
|
38
|
+
const { getLifetimeStats } = await import('../defence/audit/queries.js');
|
|
39
|
+
const stats = getLifetimeStats();
|
|
40
|
+
const banner = formatStatsBanner(stats);
|
|
41
|
+
if (banner) {
|
|
42
|
+
console.error(banner); // stderr so MCP stdout stays clean
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Never crash startup
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shieldcortex stats — detailed security report
|
|
3
|
+
*/
|
|
4
|
+
import { isDatabaseInitialized } from '../database/init.js';
|
|
5
|
+
import { getLifetimeStats } from '../defence/audit/queries.js';
|
|
6
|
+
import { getAuditStats } from '../defence/audit/queries.js';
|
|
7
|
+
import { getLicense } from '../license/store.js';
|
|
8
|
+
import { getTrialStatus } from '../license/trial.js';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
const fmt = new Intl.NumberFormat('en-US');
|
|
13
|
+
function n(num) {
|
|
14
|
+
return fmt.format(num);
|
|
15
|
+
}
|
|
16
|
+
const BOLD = '\x1b[1m';
|
|
17
|
+
const DIM = '\x1b[2m';
|
|
18
|
+
const GREEN = '\x1b[32m';
|
|
19
|
+
const CYAN = '\x1b[36m';
|
|
20
|
+
const RESET = '\x1b[0m';
|
|
21
|
+
function row(label, value, width = 36) {
|
|
22
|
+
const l = label + ':';
|
|
23
|
+
const v = typeof value === 'number' ? n(value) : value;
|
|
24
|
+
return ` ${l.padEnd(width - v.toString().length - 2)}${GREEN}${v}${RESET}`;
|
|
25
|
+
}
|
|
26
|
+
function section(title) {
|
|
27
|
+
return `\n ${BOLD}${title}${RESET}\n ${'─'.repeat(34)}`;
|
|
28
|
+
}
|
|
29
|
+
export async function runStatsCommand() {
|
|
30
|
+
if (!isDatabaseInitialized()) {
|
|
31
|
+
// Try to init the default db first
|
|
32
|
+
try {
|
|
33
|
+
const { initDatabase } = await import('../database/init.js');
|
|
34
|
+
const dbPath = process.env.CLAUDE_MEMORY_DB || join(homedir(), '.shieldcortex', 'memories.db');
|
|
35
|
+
initDatabase(dbPath);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
console.log(`\n No database found. Run ShieldCortex first to start collecting stats.\n`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const lifetime = getLifetimeStats();
|
|
44
|
+
const last24h = getAuditStats('24h');
|
|
45
|
+
const last7d = getAuditStats('7d');
|
|
46
|
+
const licenseFile = join(homedir(), '.shieldcortex', 'license.json');
|
|
47
|
+
const license = getLicense();
|
|
48
|
+
const trial = getTrialStatus(existsSync(licenseFile));
|
|
49
|
+
const tier = license.valid ? license.tier : (trial?.active ? 'pro (trial)' : 'free');
|
|
50
|
+
console.log(`\n ${BOLD}🛡️ ShieldCortex Security Report${RESET} ${DIM}[${tier}]${RESET}`);
|
|
51
|
+
console.log(section('All Time'));
|
|
52
|
+
console.log(row('Total scans', lifetime.totalScans));
|
|
53
|
+
console.log(row('Threats blocked', lifetime.threatsBlocked));
|
|
54
|
+
console.log(row('Quarantined', lifetime.quarantined));
|
|
55
|
+
console.log(row('Credential leaks', lifetime.credentialLeaks));
|
|
56
|
+
console.log(row('Memories protected', lifetime.memoriesProtected));
|
|
57
|
+
console.log(section('Last 24 Hours'));
|
|
58
|
+
console.log(row('Scans', last24h.totalOperations));
|
|
59
|
+
console.log(row('Blocked', last24h.blockedCount));
|
|
60
|
+
console.log(row('Quarantined', last24h.quarantinedCount));
|
|
61
|
+
console.log(section('Last 7 Days'));
|
|
62
|
+
console.log(row('Scans', last7d.totalOperations));
|
|
63
|
+
console.log(row('Blocked', last7d.blockedCount));
|
|
64
|
+
console.log(row('Quarantined', last7d.quarantinedCount));
|
|
65
|
+
const threats = last7d.threatBreakdown;
|
|
66
|
+
const threatEntries = Object.entries(threats).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
67
|
+
if (threatEntries.length > 0) {
|
|
68
|
+
console.log(section('Top Threat Types (7d)'));
|
|
69
|
+
for (const [type, count] of threatEntries) {
|
|
70
|
+
console.log(row(type, count));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const isPro = license.valid || trial?.active;
|
|
74
|
+
if (!isPro) {
|
|
75
|
+
console.log(`\n ${DIM}Upgrade to Pro for custom detection patterns.${RESET}`);
|
|
76
|
+
console.log(` ${CYAN}https://shieldcortex.ai/pricing${RESET}`);
|
|
77
|
+
}
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error(' Failed to load stats:', err.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { logAudit, createContentHash } from './logger.js';
|
|
2
|
-
export { queryAuditLogs, getAuditStats, queryAgentRegistry, queryAgentTimeline, queryAgentOperations } from './queries.js';
|
|
3
|
-
export type { AuditQueryOptions, AuditStats, AgentInfo, AgentTimelinePoint } from './queries.js';
|
|
2
|
+
export { queryAuditLogs, getAuditStats, getLifetimeStats, queryAgentRegistry, queryAgentTimeline, queryAgentOperations } from './queries.js';
|
|
3
|
+
export type { AuditQueryOptions, AuditStats, LifetimeStats, AgentInfo, AgentTimelinePoint } from './queries.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { logAudit, createContentHash } from './logger.js';
|
|
2
|
-
export { queryAuditLogs, getAuditStats, queryAgentRegistry, queryAgentTimeline, queryAgentOperations } from './queries.js';
|
|
2
|
+
export { queryAuditLogs, getAuditStats, getLifetimeStats, queryAgentRegistry, queryAgentTimeline, queryAgentOperations } from './queries.js';
|
|
@@ -68,6 +68,18 @@ export declare function queryAuditLogs(options?: AuditQueryOptions): AuditEntry[
|
|
|
68
68
|
* Get aggregate audit statistics for a time range.
|
|
69
69
|
*/
|
|
70
70
|
export declare function getAuditStats(timeRange: '24h' | '7d' | '30d', project?: string): AuditStats;
|
|
71
|
+
export interface LifetimeStats {
|
|
72
|
+
totalScans: number;
|
|
73
|
+
threatsBlocked: number;
|
|
74
|
+
quarantined: number;
|
|
75
|
+
credentialLeaks: number;
|
|
76
|
+
memoriesProtected: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get all-time aggregate stats from the defence_audit table.
|
|
80
|
+
* Fast COUNT-only queries — no heavy computation.
|
|
81
|
+
*/
|
|
82
|
+
export declare function getLifetimeStats(): LifetimeStats;
|
|
71
83
|
/**
|
|
72
84
|
* Get distinct agents aggregated from audit logs.
|
|
73
85
|
*/
|
|
@@ -106,6 +106,46 @@ export function getAuditStats(timeRange, project) {
|
|
|
106
106
|
threatBreakdown,
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Get all-time aggregate stats from the defence_audit table.
|
|
111
|
+
* Fast COUNT-only queries — no heavy computation.
|
|
112
|
+
*/
|
|
113
|
+
export function getLifetimeStats() {
|
|
114
|
+
const db = getDatabase();
|
|
115
|
+
// Counts by firewall result
|
|
116
|
+
const counts = db.prepare(`
|
|
117
|
+
SELECT firewall_result, COUNT(*) as cnt
|
|
118
|
+
FROM defence_audit
|
|
119
|
+
GROUP BY firewall_result
|
|
120
|
+
`).all();
|
|
121
|
+
let totalScans = 0;
|
|
122
|
+
let threatsBlocked = 0;
|
|
123
|
+
let quarantined = 0;
|
|
124
|
+
let memoriesProtected = 0;
|
|
125
|
+
for (const row of counts) {
|
|
126
|
+
totalScans += row.cnt;
|
|
127
|
+
if (row.firewall_result === 'BLOCK')
|
|
128
|
+
threatsBlocked = row.cnt;
|
|
129
|
+
else if (row.firewall_result === 'QUARANTINE')
|
|
130
|
+
quarantined = row.cnt;
|
|
131
|
+
else if (row.firewall_result === 'ALLOW')
|
|
132
|
+
memoriesProtected = row.cnt;
|
|
133
|
+
}
|
|
134
|
+
// Credential leaks: threat_indicators contains 'credential' (case-insensitive)
|
|
135
|
+
const credRow = db.prepare(`
|
|
136
|
+
SELECT COUNT(*) as cnt
|
|
137
|
+
FROM defence_audit
|
|
138
|
+
WHERE LOWER(threat_indicators) LIKE '%credential%'
|
|
139
|
+
`).get();
|
|
140
|
+
const credentialLeaks = credRow?.cnt ?? 0;
|
|
141
|
+
return {
|
|
142
|
+
totalScans,
|
|
143
|
+
threatsBlocked,
|
|
144
|
+
quarantined,
|
|
145
|
+
credentialLeaks,
|
|
146
|
+
memoriesProtected,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
109
149
|
// ── Agent Query Functions ──
|
|
110
150
|
/**
|
|
111
151
|
* Get distinct agents aggregated from audit logs.
|
package/dist/index.js
CHANGED
|
@@ -415,6 +415,14 @@ async function main() {
|
|
|
415
415
|
catch {
|
|
416
416
|
// Best effort — never let trial messaging crash the CLI
|
|
417
417
|
}
|
|
418
|
+
// Show stats banner (threats blocked etc.) for interactive CLI modes
|
|
419
|
+
try {
|
|
420
|
+
const { printStatsBanner } = await import('./cli/stats-banner.js');
|
|
421
|
+
await printStatsBanner();
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
// Never fail startup over stats
|
|
425
|
+
}
|
|
418
426
|
}
|
|
419
427
|
// Handle --help / -h / help / unknown args
|
|
420
428
|
if (process.argv[2] === '--help' || process.argv[2] === '-h' || process.argv[2] === 'help') {
|
|
@@ -588,6 +596,12 @@ ${bold}DOCS${reset}
|
|
|
588
596
|
await handleLicenseCommand(process.argv.slice(3));
|
|
589
597
|
return;
|
|
590
598
|
}
|
|
599
|
+
// Handle "stats" subcommand — detailed security report
|
|
600
|
+
if (process.argv[2] === 'stats') {
|
|
601
|
+
const { runStatsCommand } = await import('./cli/stats-command.js');
|
|
602
|
+
await runStatsCommand();
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
591
605
|
// Handle "audit" subcommand — full security audit of agent environment
|
|
592
606
|
if (process.argv[2] === 'audit') {
|
|
593
607
|
const { handleAuditCommand } = await import('./cli/audit.js');
|
|
@@ -734,7 +748,7 @@ ${bold}DOCS${reset}
|
|
|
734
748
|
'doctor', 'quickstart', 'setup', 'install', 'migrate', 'uninstall', 'hook',
|
|
735
749
|
'openclaw', 'clawdbot', 'copilot', 'codex', 'service', 'config', 'status',
|
|
736
750
|
'graph', 'license', 'licence', 'audit', 'iron-dome', 'scan', 'cloud',
|
|
737
|
-
'scan-skill', 'scan-skills', 'dashboard', 'api', 'worker',
|
|
751
|
+
'scan-skill', 'scan-skills', 'dashboard', 'api', 'worker', 'stats',
|
|
738
752
|
]);
|
|
739
753
|
const arg = process.argv[2];
|
|
740
754
|
if (arg && !arg.startsWith('-') && !knownCommands.has(arg)) {
|
package/dist/server.js
CHANGED
|
@@ -20,7 +20,7 @@ import { getHighPriorityMemories, getRecentMemories, getRelatedMemories, createM
|
|
|
20
20
|
import { detectContradictions } from './memory/contradiction.js';
|
|
21
21
|
import { handleGraphQuery, handleGraphEntities, handleGraphExplain } from './tools/graph.js';
|
|
22
22
|
import { checkDatabaseSize } from './database/init.js';
|
|
23
|
-
import { queryAuditLogs, getAuditStats } from './defence/audit/index.js';
|
|
23
|
+
import { queryAuditLogs, getAuditStats, getLifetimeStats } from './defence/audit/index.js';
|
|
24
24
|
import { scanExistingMemories } from './defence/scanner/index.js';
|
|
25
25
|
import { resolveSource } from './defence/trust/env-detector.js';
|
|
26
26
|
import { logAudit } from './defence/audit/logger.js';
|
|
@@ -614,16 +614,34 @@ but you can use this tool to check for new contradictions at any time.`, {
|
|
|
614
614
|
timeRange: z.enum(['24h', '7d', '30d']).default('24h'),
|
|
615
615
|
}, { title: 'Defence Statistics', readOnlyHint: true, destructiveHint: false, idempotentHint: true }, async (args) => {
|
|
616
616
|
const stats = getAuditStats(args.timeRange);
|
|
617
|
+
const fmt = new Intl.NumberFormat('en-US');
|
|
618
|
+
const n = (v) => fmt.format(v);
|
|
617
619
|
const lines = [
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
`
|
|
621
|
-
`
|
|
622
|
-
`
|
|
623
|
-
`
|
|
620
|
+
`🛡️ ShieldCortex Defence Stats (${args.timeRange})`,
|
|
621
|
+
`${'─'.repeat(36)}`,
|
|
622
|
+
` Total operations: ${n(stats.totalOperations)}`,
|
|
623
|
+
` Allowed: ${n(stats.allowedCount)}`,
|
|
624
|
+
` Blocked: ${n(stats.blockedCount)}`,
|
|
625
|
+
` Quarantined: ${n(stats.quarantinedCount)}`,
|
|
626
|
+
` Top sources: ${stats.topSources.map(s => `${s.source}(${n(s.count)})`).join(', ') || 'none'}`,
|
|
624
627
|
];
|
|
625
628
|
if (Object.keys(stats.threatBreakdown).length > 0) {
|
|
626
|
-
lines.push(`
|
|
629
|
+
lines.push(` Threat types: ${Object.entries(stats.threatBreakdown).map(([k, v]) => `${k}:${n(v)}`).join(', ')}`);
|
|
630
|
+
}
|
|
631
|
+
// Append lifetime totals
|
|
632
|
+
try {
|
|
633
|
+
const lifetime = getLifetimeStats();
|
|
634
|
+
lines.push('');
|
|
635
|
+
lines.push(`All-Time Totals`);
|
|
636
|
+
lines.push(`${'─'.repeat(36)}`);
|
|
637
|
+
lines.push(` Total scans: ${n(lifetime.totalScans)}`);
|
|
638
|
+
lines.push(` Threats blocked: ${n(lifetime.threatsBlocked)}`);
|
|
639
|
+
lines.push(` Quarantined: ${n(lifetime.quarantined)}`);
|
|
640
|
+
lines.push(` Credential leaks: ${n(lifetime.credentialLeaks)}`);
|
|
641
|
+
lines.push(` Memories protected: ${n(lifetime.memoriesProtected)}`);
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
// Lifetime stats unavailable — not a problem
|
|
627
645
|
}
|
|
628
646
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
629
647
|
});
|
package/dist/setup/openclaw.js
CHANGED
|
@@ -233,15 +233,28 @@ function cleanupLegacyPlugin() {
|
|
|
233
233
|
try {
|
|
234
234
|
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
235
235
|
const config = JSON.parse(raw);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
236
|
+
let changed = false;
|
|
237
|
+
// Remove legacy entries (old format before native installs)
|
|
238
|
+
if (config.plugins?.entries?.['shieldcortex-realtime']) {
|
|
239
|
+
delete config.plugins.entries['shieldcortex-realtime'];
|
|
240
|
+
if (Object.keys(config.plugins.entries).length === 0)
|
|
241
|
+
delete config.plugins.entries;
|
|
242
|
+
changed = true;
|
|
243
|
+
}
|
|
244
|
+
// Remove stale full-path entries from plugins.allow
|
|
245
|
+
// (pre-v2026.3 format used raw file paths instead of plugin IDs)
|
|
246
|
+
if (Array.isArray(config.plugins?.allow)) {
|
|
247
|
+
const before = config.plugins.allow.length;
|
|
248
|
+
config.plugins.allow = config.plugins.allow.filter((e) => !e.includes(PLUGIN_DIR_NAME) || e === PLUGIN_DIR_NAME);
|
|
249
|
+
if (config.plugins.allow.length < before)
|
|
250
|
+
changed = true;
|
|
251
|
+
}
|
|
241
252
|
if (config.plugins && Object.keys(config.plugins).length === 0)
|
|
242
253
|
delete config.plugins;
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
if (changed) {
|
|
255
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
256
|
+
console.log('Cleaned up legacy plugin entries from openclaw.json');
|
|
257
|
+
}
|
|
245
258
|
}
|
|
246
259
|
catch {
|
|
247
260
|
// Non-critical — don't fail the install
|
|
@@ -250,9 +263,10 @@ function cleanupLegacyPlugin() {
|
|
|
250
263
|
function openClawConfigPath() {
|
|
251
264
|
return path.join(resolveUserHome(), '.openclaw', 'openclaw.json');
|
|
252
265
|
}
|
|
253
|
-
function trustLocalPlugin(
|
|
266
|
+
function trustLocalPlugin(installDir, version) {
|
|
254
267
|
const configPath = openClawConfigPath();
|
|
255
268
|
const configDir = path.dirname(configPath);
|
|
269
|
+
const pluginId = PLUGIN_DIR_NAME; // "shieldcortex-realtime"
|
|
256
270
|
try {
|
|
257
271
|
if (!fs.existsSync(configDir)) {
|
|
258
272
|
fs.mkdirSync(configDir, { recursive: true });
|
|
@@ -263,12 +277,33 @@ function trustLocalPlugin(indexPath) {
|
|
|
263
277
|
const allow = Array.isArray(plugins.allow)
|
|
264
278
|
? (plugins.allow ?? [])
|
|
265
279
|
: [];
|
|
266
|
-
|
|
267
|
-
|
|
280
|
+
// Remove any stale full-path entries for this plugin
|
|
281
|
+
const cleaned = allow.filter((e) => !e.includes(PLUGIN_DIR_NAME) || e === pluginId);
|
|
282
|
+
if (!cleaned.includes(pluginId)) {
|
|
283
|
+
cleaned.push(pluginId);
|
|
284
|
+
}
|
|
285
|
+
// Add installs entry so OpenClaw recognises the plugin
|
|
286
|
+
const installs = typeof plugins.installs === 'object' && plugins.installs !== null
|
|
287
|
+
? plugins.installs
|
|
288
|
+
: {};
|
|
289
|
+
installs[pluginId] = {
|
|
290
|
+
source: 'path',
|
|
291
|
+
installPath: installDir,
|
|
292
|
+
version,
|
|
293
|
+
installedAt: new Date().toISOString(),
|
|
294
|
+
};
|
|
295
|
+
// Add entries to enable the plugin
|
|
296
|
+
const entries = typeof plugins.entries === 'object' && plugins.entries !== null
|
|
297
|
+
? plugins.entries
|
|
298
|
+
: {};
|
|
299
|
+
if (!entries[pluginId]) {
|
|
300
|
+
entries[pluginId] = { enabled: true };
|
|
268
301
|
}
|
|
269
302
|
config.plugins = {
|
|
270
303
|
...plugins,
|
|
271
|
-
allow,
|
|
304
|
+
allow: cleaned,
|
|
305
|
+
installs,
|
|
306
|
+
entries,
|
|
272
307
|
};
|
|
273
308
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
274
309
|
return true;
|
|
@@ -383,13 +418,27 @@ function installPlugin() {
|
|
|
383
418
|
catch {
|
|
384
419
|
console.warn(` Warning: ${indexDest} copied but not readable`);
|
|
385
420
|
}
|
|
386
|
-
|
|
387
|
-
|
|
421
|
+
// Read plugin version from manifest
|
|
422
|
+
let pluginVersion = 'unknown';
|
|
423
|
+
try {
|
|
424
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(destDir, 'openclaw.plugin.json'), 'utf-8'));
|
|
425
|
+
pluginVersion = manifest.version ?? pluginVersion;
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
// Fall back to package.json version
|
|
429
|
+
try {
|
|
430
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));
|
|
431
|
+
pluginVersion = pkg.version ?? pluginVersion;
|
|
432
|
+
}
|
|
433
|
+
catch { /* keep "unknown" */ }
|
|
434
|
+
}
|
|
435
|
+
if (trustLocalPlugin(destDir, pluginVersion)) {
|
|
436
|
+
console.log('Registered plugin in OpenClaw config (plugins.allow + installs)');
|
|
388
437
|
console.log(`Installed real-time plugin to ${destDir}`);
|
|
389
438
|
return 'trusted-local-copy';
|
|
390
439
|
}
|
|
391
440
|
else {
|
|
392
|
-
console.warn(' Warning: Could not
|
|
441
|
+
console.warn(' Warning: Could not register plugin in OpenClaw config');
|
|
393
442
|
console.log(`Installed real-time plugin to ${destDir}`);
|
|
394
443
|
return 'untrusted-local-copy';
|
|
395
444
|
}
|
|
@@ -421,10 +470,18 @@ function uninstallPlugin() {
|
|
|
421
470
|
if (fs.existsSync(configPath)) {
|
|
422
471
|
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
423
472
|
const config = JSON.parse(raw);
|
|
473
|
+
const pluginId = PLUGIN_DIR_NAME;
|
|
424
474
|
if (Array.isArray(config.plugins?.allow)) {
|
|
425
|
-
|
|
426
|
-
|
|
475
|
+
// Remove both old file-path entries and new short-name entries
|
|
476
|
+
config.plugins.allow = config.plugins.allow.filter((entry) => !entry.includes(pluginId));
|
|
477
|
+
}
|
|
478
|
+
if (config.plugins?.installs?.[pluginId]) {
|
|
479
|
+
delete config.plugins.installs[pluginId];
|
|
480
|
+
}
|
|
481
|
+
if (config.plugins?.entries?.[pluginId]) {
|
|
482
|
+
delete config.plugins.entries[pluginId];
|
|
427
483
|
}
|
|
484
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
428
485
|
}
|
|
429
486
|
fs.rmSync(destDir, { recursive: true });
|
|
430
487
|
console.log(`Removed real-time plugin from ${destDir}`);
|
|
@@ -457,7 +514,8 @@ function localPluginTrustStatus(pluginPath) {
|
|
|
457
514
|
return 'unknown';
|
|
458
515
|
try {
|
|
459
516
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
460
|
-
|
|
517
|
+
const allow = config.plugins?.allow;
|
|
518
|
+
if (Array.isArray(allow) && (allow.includes(PLUGIN_DIR_NAME) || allow.includes(indexPath))) {
|
|
461
519
|
return 'trusted';
|
|
462
520
|
}
|
|
463
521
|
return 'untrusted';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shieldcortex",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.31",
|
|
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",
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|