mcp-nervous-system 1.4.0 → 1.5.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.
Files changed (2) hide show
  1. package/index.js +217 -6
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -129,7 +129,7 @@ const MCP_VERSION = '2024-11-05';
129
129
  // Server info
130
130
  const SERVER_INFO = {
131
131
  name: 'nervous-system',
132
- version: '1.4.0'
132
+ version: '1.5.0'
133
133
  };
134
134
 
135
135
  // ============================================================
@@ -138,7 +138,7 @@ const SERVER_INFO = {
138
138
 
139
139
  const FRAMEWORK = {
140
140
  name: 'The Nervous System',
141
- version: '1.4.0',
141
+ version: '1.5.0',
142
142
  author: 'Arthur Palyan',
143
143
  tagline: 'Anthropic built the brain. Arthur built the nervous system that keeps it from hurting itself.',
144
144
  problem: 'LLMs lose context between sessions, loop on problems instead of dispatching, silently fail without progress notes, edit protected files, drift from the real problem, and solve instead of asking.',
@@ -616,6 +616,27 @@ const TOOLS = [
616
616
  }
617
617
  }
618
618
  }
619
+ },
620
+ // NEW: Security Audit
621
+ {
622
+ name: 'security_audit',
623
+ annotations: { title: 'Security Audit', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
624
+ description: 'Scans for security vulnerabilities - hardcoded passwords in HTML, exposed API keys, missing TLS, missing rate limiting, exposed bot tokens, and insecure file permissions.',
625
+ inputSchema: { type: 'object', properties: {} }
626
+ },
627
+ // NEW: Auto Propagate
628
+ {
629
+ name: 'auto_propagate',
630
+ annotations: { title: 'Auto Propagate', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
631
+ description: 'Runs all 3 propagators (role, version, content) and reports what changed vs what was already current. Ensures all downstream files match source-of-truth values.',
632
+ inputSchema: { type: 'object', properties: {} }
633
+ },
634
+ // NEW: Session Close
635
+ {
636
+ name: 'session_close',
637
+ annotations: { title: 'Session Close', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
638
+ description: 'One-call session close. Runs drift_audit scope=full, then all 3 propagators. Returns combined results. The end-of-session button.',
639
+ inputSchema: { type: 'object', properties: {} }
619
640
  }
620
641
  ];
621
642
 
@@ -976,7 +997,7 @@ function auditWebsite() {
976
997
  // Source of truth values
977
998
  const pkgFile = '/root/github-repos/mcp-nervous-system/package.json';
978
999
  const pkg = safeReadJSON(pkgFile);
979
- const expectedVersion = pkg ? pkg.version : '1.4.0';
1000
+ const expectedVersion = pkg ? pkg.version : '1.5.0';
980
1001
  const actualToolCount = TOOLS.length;
981
1002
  const actualResourceCount = RESOURCES.length;
982
1003
 
@@ -1184,6 +1205,177 @@ function runDriftAudit(scope) {
1184
1205
  };
1185
1206
  }
1186
1207
 
1208
+ // ============================================================
1209
+ // SECURITY AUDIT ENGINE
1210
+ // ============================================================
1211
+
1212
+ function runSecurityAudit() {
1213
+ const vulnerabilities = [];
1214
+ let checksPassed = 0;
1215
+
1216
+ // 1. Scan HTML files for hardcoded passwords/secrets
1217
+ const htmlDir = '/root/family-home/';
1218
+ const secretPatterns = [
1219
+ /Liarzhek1\$/g,
1220
+ /Levelsofself1\$/g,
1221
+ /\d{10}:AA[A-Za-z0-9_-]{30,}/g,
1222
+ /sk-ant-[a-zA-Z0-9_-]+/g,
1223
+ /npm_[A-Za-z0-9]{20,}/g,
1224
+ /ghp_[A-Za-z0-9]{20,}/g,
1225
+ /BOT_TOKEN\s*[:=]\s*['"][^'"]+['"]/gi,
1226
+ /PAPA_FULL\s*[:=]\s*['"][^'"]+['"]/gi,
1227
+ /PAPA_READ\s*[:=]\s*['"][^'"]+['"]/gi
1228
+ ];
1229
+ try {
1230
+ const htmlFiles = fs.readdirSync(htmlDir).filter(f => f.endsWith('.html'));
1231
+ for (const hf of htmlFiles) {
1232
+ const content = safeReadFile(htmlDir + hf);
1233
+ if (!content) continue;
1234
+ let fileClean = true;
1235
+ for (const pat of secretPatterns) {
1236
+ pat.lastIndex = 0;
1237
+ const matches = content.match(pat);
1238
+ if (matches && matches.length > 0) {
1239
+ vulnerabilities.push({ type: 'hardcoded_secret', file: hf, pattern: pat.source, count: matches.length });
1240
+ fileClean = false;
1241
+ }
1242
+ }
1243
+ if (fileClean) checksPassed++;
1244
+ }
1245
+ } catch (e) {
1246
+ vulnerabilities.push({ type: 'scan_error', file: 'html_scan', detail: e.message });
1247
+ }
1248
+
1249
+ // 2. Check auth endpoints use server-side validation
1250
+ const serverContent = safeReadFile('/root/family-home/server.js');
1251
+ if (serverContent) {
1252
+ if (serverContent.includes('getSessionFromReq') || serverContent.includes('getAccessLevel')) {
1253
+ checksPassed++;
1254
+ } else {
1255
+ vulnerabilities.push({ type: 'missing_server_auth', file: 'server.js', detail: 'No server-side auth validation found' });
1256
+ }
1257
+ }
1258
+
1259
+ // 3. Verify GUEST_HIDDEN_FILES covers sensitive files
1260
+ if (serverContent) {
1261
+ const sensitiveFiles = ['api-credentials.json', 'family-roles.json', 'system-config.json', 'llm-providers.json'];
1262
+ for (const sf of sensitiveFiles) {
1263
+ if (serverContent.includes('"' + sf + '"') || serverContent.includes("'" + sf + "'")) {
1264
+ checksPassed++;
1265
+ } else {
1266
+ vulnerabilities.push({ type: 'unhidden_sensitive_file', file: sf, detail: 'Not in GUEST_HIDDEN_FILES' });
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ // 4. Check Caddy TLS
1272
+ const caddyContent = safeReadFile('/etc/caddy/Caddyfile');
1273
+ if (caddyContent) {
1274
+ if (caddyContent.includes('tls') || caddyContent.includes('https://') || caddyContent.includes('100levelup.com')) {
1275
+ checksPassed++;
1276
+ } else {
1277
+ vulnerabilities.push({ type: 'missing_tls', file: 'Caddyfile', detail: 'No TLS configuration found' });
1278
+ }
1279
+ }
1280
+
1281
+ // 5. Check bridge rate limiting
1282
+ if (serverContent && serverContent.includes('rate') || fs.existsSync('/root/rate-limit.js') || fs.existsSync('/root/bridge-ratelimit.js')) {
1283
+ checksPassed++;
1284
+ } else {
1285
+ vulnerabilities.push({ type: 'missing_rate_limit', file: 'bridge', detail: 'No rate limiting found for bridge' });
1286
+ }
1287
+
1288
+ // 6. Check bot tokens not in public HTML
1289
+ try {
1290
+ const htmlFiles = fs.readdirSync(htmlDir).filter(f => f.endsWith('.html'));
1291
+ let tokenFound = false;
1292
+ for (const hf of htmlFiles) {
1293
+ const content = safeReadFile(htmlDir + hf);
1294
+ if (!content) continue;
1295
+ const tokenMatch = content.match(/\d{10}:AA[A-Za-z0-9_-]{30,}/g);
1296
+ if (tokenMatch) {
1297
+ vulnerabilities.push({ type: 'exposed_bot_token', file: hf, count: tokenMatch.length });
1298
+ tokenFound = true;
1299
+ }
1300
+ }
1301
+ if (!tokenFound) checksPassed++;
1302
+ } catch (e) {}
1303
+
1304
+ // 7. Check api-credentials.json permissions
1305
+ try {
1306
+ const credFile = '/root/family-data/api-credentials.json';
1307
+ if (fs.existsSync(credFile)) {
1308
+ const stats = fs.statSync(credFile);
1309
+ const mode = (stats.mode & 0o777).toString(8);
1310
+ if (mode === '600') {
1311
+ checksPassed++;
1312
+ } else {
1313
+ vulnerabilities.push({ type: 'insecure_permissions', file: 'api-credentials.json', detail: 'Mode is ' + mode + ', should be 600' });
1314
+ }
1315
+ } else {
1316
+ checksPassed++; // No creds file = no risk
1317
+ }
1318
+ } catch (e) {}
1319
+
1320
+ // 8. Check for Telegram tokens, API keys, npm tokens in family-home
1321
+ try {
1322
+ const allFiles = fs.readdirSync(htmlDir);
1323
+ const dangerPatterns = [
1324
+ { name: 'telegram_token', pat: /\d{10}:AA[A-Za-z0-9_-]{30,}/g },
1325
+ { name: 'anthropic_key', pat: /sk-ant-[a-zA-Z0-9_-]{20,}/g },
1326
+ { name: 'npm_token', pat: /npm_[A-Za-z0-9]{20,}/g }
1327
+ ];
1328
+ for (const f of allFiles) {
1329
+ if (f.endsWith('.html') || f.endsWith('.js') || f.endsWith('.json')) {
1330
+ const content = safeReadFile(htmlDir + f);
1331
+ if (!content) continue;
1332
+ for (const dp of dangerPatterns) {
1333
+ dp.pat.lastIndex = 0;
1334
+ const m = content.match(dp.pat);
1335
+ if (m) {
1336
+ vulnerabilities.push({ type: 'exposed_' + dp.name, file: f, count: m.length });
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ checksPassed++;
1342
+ } catch (e) {}
1343
+
1344
+ return {
1345
+ status: vulnerabilities.length === 0 ? 'secure' : 'vulnerabilities_found',
1346
+ vulnerability_count: vulnerabilities.length,
1347
+ checks_passed: checksPassed,
1348
+ vulnerabilities
1349
+ };
1350
+ }
1351
+
1352
+ // ============================================================
1353
+ // AUTO PROPAGATE ENGINE
1354
+ // ============================================================
1355
+
1356
+ function runAutoPropagators() {
1357
+ const results = [];
1358
+ const scripts = [
1359
+ { name: 'role', path: '/root/family-workers/role-propagator.js' },
1360
+ { name: 'version', path: '/root/family-workers/version-propagator.js' },
1361
+ { name: 'content', path: '/root/family-workers/content-propagator.js' }
1362
+ ];
1363
+ for (const script of scripts) {
1364
+ try {
1365
+ const out = execSync('node ' + script.path + ' 2>&1', { timeout: 15000 }).toString();
1366
+ const current = out.indexOf('Already current') !== -1;
1367
+ results.push({ propagator: script.name, status: current ? 'current' : 'updated', output: out.trim().substring(0, 500) });
1368
+ } catch (e) {
1369
+ results.push({ propagator: script.name, status: 'error', error: e.message.substring(0, 200) });
1370
+ }
1371
+ }
1372
+ return {
1373
+ timestamp: new Date().toISOString(),
1374
+ propagators_run: results.length,
1375
+ results
1376
+ };
1377
+ }
1378
+
1187
1379
  // ============================================================
1188
1380
  // Handle tool calls
1189
1381
  // ============================================================
@@ -1286,6 +1478,25 @@ function handleToolCall(name, args) {
1286
1478
  return runDriftAudit(scope);
1287
1479
  }
1288
1480
 
1481
+ case 'security_audit': {
1482
+ return runSecurityAudit();
1483
+ }
1484
+
1485
+ case 'auto_propagate': {
1486
+ return runAutoPropagators();
1487
+ }
1488
+
1489
+ case 'session_close': {
1490
+ const driftResult = runDriftAudit('full');
1491
+ const propagateResult = runAutoPropagators();
1492
+ return {
1493
+ timestamp: new Date().toISOString(),
1494
+ drift_audit: driftResult,
1495
+ propagation: propagateResult,
1496
+ summary: driftResult.drift_count === 0 ? 'Session clean - no drifts, propagators run' : `${driftResult.drift_count} drifts found - review before closing`
1497
+ };
1498
+ }
1499
+
1289
1500
  default:
1290
1501
  return { error: 'Unknown tool' };
1291
1502
  }
@@ -1407,7 +1618,7 @@ const server = http.createServer((req, res) => {
1407
1618
  // Health check
1408
1619
  if (req.method === 'GET' && url.pathname === '/health') {
1409
1620
  res.writeHead(200, { 'Content-Type': 'application/json' });
1410
- res.end(JSON.stringify({ status: 'ok', service: 'nervous-system-mcp', version: '1.4.0', protocol: MCP_VERSION }));
1621
+ res.end(JSON.stringify({ status: 'ok', service: 'nervous-system-mcp', version: '1.5.0', protocol: MCP_VERSION }));
1411
1622
  return;
1412
1623
  }
1413
1624
 
@@ -1524,7 +1735,7 @@ const server = http.createServer((req, res) => {
1524
1735
  res.writeHead(200, { 'Content-Type': 'application/json' });
1525
1736
  res.end(JSON.stringify({
1526
1737
  name: 'The Nervous System MCP Server',
1527
- version: '1.4.0',
1738
+ version: '1.5.0',
1528
1739
  protocol: MCP_VERSION,
1529
1740
  description: 'LLM behavioral enforcement framework. 7 core rules, preflight checks, session handoffs, worklogs, violation logging, kill switch, hash-chained audit, and forced reflection cycles. Built by Arthur Palyan.',
1530
1741
  endpoints: {
@@ -1546,7 +1757,7 @@ const server = http.createServer((req, res) => {
1546
1757
  migrateExistingViolations();
1547
1758
 
1548
1759
  server.listen(PORT, '127.0.0.1', () => {
1549
- console.error(`[MCP Server] Nervous System v1.4.0 running on port ${PORT}`);
1760
+ console.error(`[MCP Server] Nervous System v1.5.0 running on port ${PORT}`);
1550
1761
  console.error(`[MCP Server] SSE: /sse | HTTP: /mcp | Health: /health | Kill: POST /kill | Audit: GET /audit/verify | Dispatches: GET /dispatches`);
1551
1762
  console.error(`[MCP Server] Protocol: ${MCP_VERSION}`);
1552
1763
  console.error(`[MCP Server] Tools: ${TOOLS.length} (including kill switch, audit chain, dispatch, drift audit)`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-nervous-system",
3
- "version": "1.4.0",
4
- "description": "The Nervous System - LLM Behavioral Enforcement Framework. 7 mechanically enforced rules, 12 tools including kill switch, audit chain, dispatch, and drift audit. MCP server for Claude Desktop, Claude Code, and any MCP-compatible client.",
3
+ "version": "1.5.0",
4
+ "description": "The Nervous System - LLM Behavioral Enforcement Framework. 7 mechanically enforced rules, 15 tools including kill switch, audit chain, dispatch, drift audit, security audit, and session close. MCP server for Claude Desktop, Claude Code, and any MCP-compatible client.",
5
5
  "main": "server.js",
6
6
  "bin": {
7
7
  "mcp-nervous-system": "./stdio.js"