shieldcortex 3.4.28 → 3.4.30
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/admin.js +14 -2
- package/dist/api/routes/system.js +63 -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/database/init.js +4 -4
- 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 +69 -2
- package/dist/license/cli.js +50 -22
- package/dist/license/index.d.ts +1 -1
- package/dist/license/index.js +1 -1
- package/dist/license/store.d.ts +4 -1
- package/dist/license/store.js +38 -15
- package/dist/license/trial.d.ts +48 -0
- package/dist/license/trial.js +128 -0
- 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/{oqbBQnPvKRkfU3j_7cE7c → 5yxbHrMFACE5ILOtSpx42}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{oqbBQnPvKRkfU3j_7cE7c → 5yxbHrMFACE5ILOtSpx42}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{oqbBQnPvKRkfU3j_7cE7c → 5yxbHrMFACE5ILOtSpx42}/_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
|
+
}
|
package/dist/api/routes/admin.js
CHANGED
|
@@ -2,9 +2,12 @@ import { getDatabase } from '../../database/init.js';
|
|
|
2
2
|
import { getCloudConfig } from '../../cloud/config.js';
|
|
3
3
|
import { queryAgentOperations, queryAgentRegistry, queryAgentTimeline, queryAuditLogs, getAuditStats } from '../../defence/audit/queries.js';
|
|
4
4
|
import { approveQuarantineItem, approveQuarantineItems, rejectQuarantineItem, rejectQuarantineItems, } from '../../defence/quarantine/review.js';
|
|
5
|
-
import { getLicense, activateLicense, deactivateLicense } from '../../license/store.js';
|
|
5
|
+
import { getLicense, getLicenseTier, getTrialStatus, activateLicense, deactivateLicense } from '../../license/store.js';
|
|
6
6
|
import { listFeatures } from '../../license/gate.js';
|
|
7
7
|
import { validateOnceNow } from '../../license/validate.js';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
8
11
|
export function registerAdminRoutes(app, deps) {
|
|
9
12
|
const { brainWorker, requireNotLocked, requireProFeature, requireIronDomeAction } = deps;
|
|
10
13
|
app.get('/api/v1/audit', (req, res) => {
|
|
@@ -278,13 +281,22 @@ export function registerAdminRoutes(app, deps) {
|
|
|
278
281
|
app.get('/api/license/status', (_req, res) => {
|
|
279
282
|
try {
|
|
280
283
|
const info = getLicense();
|
|
284
|
+
const licenseFile = join(process.env.SHIELDCORTEX_CONFIG_DIR || join(homedir(), '.shieldcortex'), 'license.json');
|
|
285
|
+
const trial = getTrialStatus(existsSync(licenseFile));
|
|
281
286
|
res.json({
|
|
282
|
-
tier:
|
|
287
|
+
tier: getLicenseTier(),
|
|
283
288
|
valid: info.valid,
|
|
284
289
|
email: info.email,
|
|
285
290
|
expiresAt: info.expiresAt?.toISOString() ?? null,
|
|
286
291
|
daysUntilExpiry: info.daysUntilExpiry,
|
|
287
292
|
teamId: info.teamId,
|
|
293
|
+
trial: trial
|
|
294
|
+
? {
|
|
295
|
+
active: trial.active,
|
|
296
|
+
daysRemaining: trial.daysRemaining,
|
|
297
|
+
expiresAt: trial.expiresAt,
|
|
298
|
+
}
|
|
299
|
+
: null,
|
|
288
300
|
features: listFeatures(),
|
|
289
301
|
});
|
|
290
302
|
}
|
|
@@ -1,12 +1,75 @@
|
|
|
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';
|
|
5
8
|
import { getRequiredTier, isFeatureEnabled } from '../../license/gate.js';
|
|
9
|
+
import { getLicense } from '../../license/store.js';
|
|
10
|
+
import { getTrialStatus } from '../../license/trial.js';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { homedir } from 'os';
|
|
6
14
|
import { getControlStatus, isKillSwitchActive, pause, resume } from '../control.js';
|
|
7
15
|
import { checkForUpdates, getCurrentVersion, getRunningVersion, performUpdate, scheduleRestart, } from '../version.js';
|
|
8
16
|
export function registerSystemRoutes(app, deps) {
|
|
9
17
|
const { broadcast, clients, requireIronDomeAction } = deps;
|
|
18
|
+
app.get('/api/system/status', (_req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const licenseFile = join(homedir(), '.shieldcortex', 'license.json');
|
|
21
|
+
const licenseFileExists = existsSync(licenseFile);
|
|
22
|
+
const license = getLicense();
|
|
23
|
+
const trial = getTrialStatus(licenseFileExists);
|
|
24
|
+
const trialInfo = trial
|
|
25
|
+
? {
|
|
26
|
+
active: trial.active,
|
|
27
|
+
daysRemaining: trial.daysRemaining,
|
|
28
|
+
expiresAt: trial.expiresAt.slice(0, 10), // YYYY-MM-DD
|
|
29
|
+
}
|
|
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
|
+
}
|
|
62
|
+
res.json({
|
|
63
|
+
tier: license.valid ? license.tier : (trial?.active ? 'pro' : 'free'),
|
|
64
|
+
licenseValid: license.valid,
|
|
65
|
+
trial: trialInfo,
|
|
66
|
+
...(stats ? { stats } : {}),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
res.status(500).json({ error: error.message });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
10
73
|
app.get('/api/control/status', (_req, res) => {
|
|
11
74
|
try {
|
|
12
75
|
res.json(getControlStatus());
|
|
@@ -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
|
+
}
|
package/dist/database/init.js
CHANGED
|
@@ -266,12 +266,12 @@ function attemptDumpRecovery(dbPath) {
|
|
|
266
266
|
return null;
|
|
267
267
|
// Back up the corrupt file
|
|
268
268
|
const backupPath = backupCorruptDatabase(dbPath);
|
|
269
|
-
console.
|
|
269
|
+
console.error(`[database] Backed up corrupt database to: ${backupPath}`);
|
|
270
270
|
// Create fresh database and import the dump
|
|
271
271
|
const freshDb = new Database(dbPath);
|
|
272
272
|
try {
|
|
273
273
|
freshDb.exec(dumpOutput);
|
|
274
|
-
console.
|
|
274
|
+
console.error('[database] Successfully recovered data via dump/reimport.');
|
|
275
275
|
return freshDb;
|
|
276
276
|
}
|
|
277
277
|
catch {
|
|
@@ -330,7 +330,7 @@ function attemptFtsRecovery(database) {
|
|
|
330
330
|
database.exec(`INSERT INTO memories_fts(memories_fts) VALUES('rebuild')`);
|
|
331
331
|
const postRepairIntegrity = runIntegrityCheck(database);
|
|
332
332
|
if (postRepairIntegrity === 'ok') {
|
|
333
|
-
console.
|
|
333
|
+
console.error('[database] Successfully rebuilt memories_fts after integrity failure.');
|
|
334
334
|
return true;
|
|
335
335
|
}
|
|
336
336
|
console.warn(`[database] FTS rebuild did not fully repair integrity: ${postRepairIntegrity}`);
|
|
@@ -374,7 +374,7 @@ export function initDatabase(dbPath) {
|
|
|
374
374
|
// Store path for size monitoring
|
|
375
375
|
currentDbPath = expandedPath;
|
|
376
376
|
acquireStartupLock(expandedPath);
|
|
377
|
-
console.
|
|
377
|
+
console.error(`[database] Startup runtime=${resolveRuntimeInfo().kind} db=${expandedPath} wal=${existsSync(expandedPath + '-wal')} shm=${existsSync(expandedPath + '-shm')}`);
|
|
378
378
|
const healthyBackups = listHealthyBackups(expandedPath);
|
|
379
379
|
// Wrap the initial open in try/catch to handle corrupt files gracefully
|
|
380
380
|
let database;
|
|
@@ -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
|
@@ -363,6 +363,67 @@ async function isLocalDashboardRunning() {
|
|
|
363
363
|
async function main() {
|
|
364
364
|
// Warn if npx is serving a stale cached version
|
|
365
365
|
checkVersionStaleness();
|
|
366
|
+
const parsedArgs = parseArgs();
|
|
367
|
+
// ── Trial welcome / expiry warning ──────────────────────
|
|
368
|
+
// Only show for interactive CLI commands (not MCP server mode — stdout must stay clean for JSON-RPC)
|
|
369
|
+
if (process.argv[2] && parsedArgs.mode !== 'mcp') {
|
|
370
|
+
try {
|
|
371
|
+
const { existsSync } = await import('fs');
|
|
372
|
+
const { join } = await import('path');
|
|
373
|
+
const { homedir } = await import('os');
|
|
374
|
+
const { getTrialStatus } = await import('./license/trial.js');
|
|
375
|
+
const { acknowledgeTrialWelcome } = await import('./license/trial.js');
|
|
376
|
+
const { getLicense } = await import('./license/store.js');
|
|
377
|
+
const licenseFile = join(homedir(), '.shieldcortex', 'license.json');
|
|
378
|
+
const licenseFileExists = existsSync(licenseFile);
|
|
379
|
+
const activeLicense = getLicense();
|
|
380
|
+
// Only show trial messages when no paid license is active
|
|
381
|
+
if (!activeLicense.valid) {
|
|
382
|
+
const trial = getTrialStatus(licenseFileExists);
|
|
383
|
+
if (trial?.justCreated) {
|
|
384
|
+
// First ever run — show welcome message
|
|
385
|
+
const expiryDate = new Date(trial.expiresAt).toLocaleDateString();
|
|
386
|
+
const bold = '\x1b[1m';
|
|
387
|
+
const reset = '\x1b[0m';
|
|
388
|
+
const green = '\x1b[32m';
|
|
389
|
+
const cyan = '\x1b[36m';
|
|
390
|
+
const yellow = '\x1b[33m';
|
|
391
|
+
console.log(`\n${bold}🎁 Welcome to ShieldCortex!${reset}\n`);
|
|
392
|
+
console.log(`You have a ${yellow}14-day Pro trial${reset} — all Pro features are unlocked.\n`);
|
|
393
|
+
console.log(` ${green}✓${reset} Custom injection patterns (up to 50)`);
|
|
394
|
+
console.log(` ${green}✓${reset} Custom Iron Dome policies`);
|
|
395
|
+
console.log(` ${green}✓${reset} Custom firewall rules`);
|
|
396
|
+
console.log(` ${green}✓${reset} Audit export (JSON/CSV)`);
|
|
397
|
+
console.log(` ${green}✓${reset} Skill scanner deep mode`);
|
|
398
|
+
console.log(`\nYour trial expires on ${bold}${expiryDate}${reset}. Upgrade anytime at:`);
|
|
399
|
+
console.log(` ${cyan}https://shieldcortex.ai/pricing${reset}`);
|
|
400
|
+
console.log(`\nRun: shieldcortex license status\n`);
|
|
401
|
+
acknowledgeTrialWelcome();
|
|
402
|
+
}
|
|
403
|
+
else if (trial?.active && trial.daysRemaining <= 3) {
|
|
404
|
+
// Trial expiring soon — show warning
|
|
405
|
+
const yellow = '\x1b[33m';
|
|
406
|
+
const cyan = '\x1b[36m';
|
|
407
|
+
const reset = '\x1b[0m';
|
|
408
|
+
const days = trial.daysRemaining;
|
|
409
|
+
console.error(`${yellow}⚠️ Pro trial expires in ${days} day${days !== 1 ? 's' : ''}. Upgrade to keep Pro features:${reset}`);
|
|
410
|
+
console.error(` shieldcortex license activate <key>`);
|
|
411
|
+
console.error(` ${cyan}https://shieldcortex.ai/pricing${reset}\n`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// Best effort — never let trial messaging crash the CLI
|
|
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
|
+
}
|
|
426
|
+
}
|
|
366
427
|
// Handle --help / -h / help / unknown args
|
|
367
428
|
if (process.argv[2] === '--help' || process.argv[2] === '-h' || process.argv[2] === 'help') {
|
|
368
429
|
const bold = '\x1b[1m';
|
|
@@ -535,6 +596,12 @@ ${bold}DOCS${reset}
|
|
|
535
596
|
await handleLicenseCommand(process.argv.slice(3));
|
|
536
597
|
return;
|
|
537
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
|
+
}
|
|
538
605
|
// Handle "audit" subcommand — full security audit of agent environment
|
|
539
606
|
if (process.argv[2] === 'audit') {
|
|
540
607
|
const { handleAuditCommand } = await import('./cli/audit.js');
|
|
@@ -681,7 +748,7 @@ ${bold}DOCS${reset}
|
|
|
681
748
|
'doctor', 'quickstart', 'setup', 'install', 'migrate', 'uninstall', 'hook',
|
|
682
749
|
'openclaw', 'clawdbot', 'copilot', 'codex', 'service', 'config', 'status',
|
|
683
750
|
'graph', 'license', 'licence', 'audit', 'iron-dome', 'scan', 'cloud',
|
|
684
|
-
'scan-skill', 'scan-skills', 'dashboard', 'api', 'worker',
|
|
751
|
+
'scan-skill', 'scan-skills', 'dashboard', 'api', 'worker', 'stats',
|
|
685
752
|
]);
|
|
686
753
|
const arg = process.argv[2];
|
|
687
754
|
if (arg && !arg.startsWith('-') && !knownCommands.has(arg)) {
|
|
@@ -689,7 +756,7 @@ ${bold}DOCS${reset}
|
|
|
689
756
|
console.error(`Run 'shieldcortex --help' for a list of commands.`);
|
|
690
757
|
process.exit(1);
|
|
691
758
|
}
|
|
692
|
-
const { dbPath, mode } =
|
|
759
|
+
const { dbPath, mode } = parsedArgs;
|
|
693
760
|
let dashboardProcess = null;
|
|
694
761
|
if (mode === 'api') {
|
|
695
762
|
// API mode only - for dashboard visualization
|