claw-dashboard 2.1.0 → 2.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/docs/API.md +1 -2
  3. package/index.js +32 -42
  4. package/package.json +23 -12
  5. package/src/auto-save.js +11 -5
  6. package/src/cli/export-snapshot.js +3 -1
  7. package/src/cli/import-snapshot.js +3 -1
  8. package/src/config.js +9 -15
  9. package/src/errors.js +0 -9
  10. package/src/security.js +6 -2
  11. package/src/snapshot.js +73 -26
  12. package/src/web-server.js +7 -10
  13. package/src/widgets/builtin-widgets.js +0 -94
  14. package/src/widgets/index.js +0 -1
  15. package/.c8rc.json +0 -38
  16. package/.dockerignore +0 -68
  17. package/.github/dependabot.yml +0 -45
  18. package/.github/pull_request_template.md +0 -39
  19. package/.github/workflows/ci.yml +0 -147
  20. package/.github/workflows/release.yml +0 -40
  21. package/.github/workflows/security.yml +0 -84
  22. package/.husky/pre-commit +0 -1
  23. package/.planning/codebase/ARCHITECTURE.md +0 -206
  24. package/.planning/codebase/INTEGRATIONS.md +0 -150
  25. package/.planning/codebase/STACK.md +0 -122
  26. package/.planning/codebase/STRUCTURE.md +0 -201
  27. package/CONTRIBUTING.md +0 -378
  28. package/Dockerfile +0 -54
  29. package/FEATURES.md +0 -307
  30. package/TODO.md +0 -28
  31. package/ai.openclaw.dashboard.plist +0 -35
  32. package/build-cjs.js +0 -127
  33. package/cjs-shim.js +0 -13
  34. package/dist/clawdash +0 -1729
  35. package/dist/clawdash.meta.json +0 -2236
  36. package/dist/widgets.cjs +0 -6511
  37. package/docker-compose.yml +0 -67
  38. package/esbuild.config.js +0 -158
  39. package/eslint.config.js +0 -56
  40. package/examples/plugins/README.md +0 -122
  41. package/examples/plugins/api-status/index.js +0 -294
  42. package/examples/plugins/api-status/plugin.json +0 -19
  43. package/examples/plugins/hello-world/index.js +0 -104
  44. package/examples/plugins/hello-world/plugin.json +0 -15
  45. package/examples/plugins/system-metrics-chart/index.js +0 -339
  46. package/examples/plugins/system-metrics-chart/plugin.json +0 -18
  47. package/examples/plugins/weather-widget/index.js +0 -136
  48. package/examples/plugins/weather-widget/plugin.json +0 -19
  49. package/index.cjs +0 -23005
  50. package/jest.config.js +0 -11
  51. package/scripts/release.js +0 -595
  52. package/src/database.js +0 -734
  53. package/tests/alerts.test.js +0 -437
  54. package/tests/auto-save.test.js +0 -529
  55. package/tests/cache.test.js +0 -317
  56. package/tests/cli.test.js +0 -351
  57. package/tests/command-palette.test.js +0 -320
  58. package/tests/config-processor.test.js +0 -452
  59. package/tests/config-validator.test.js +0 -710
  60. package/tests/config-watcher.test.js +0 -594
  61. package/tests/config.test.js +0 -755
  62. package/tests/database.test.js +0 -438
  63. package/tests/errors.test.js +0 -189
  64. package/tests/example-plugins.test.js +0 -624
  65. package/tests/integration.test.js +0 -1321
  66. package/tests/loading-states.test.js +0 -300
  67. package/tests/manifest-validation-on-load.test.js +0 -671
  68. package/tests/performance-monitor.test.js +0 -190
  69. package/tests/plugin-api-rate-limit.test.js +0 -302
  70. package/tests/plugin-errors.test.js +0 -311
  71. package/tests/plugin-lifecycle-e2e.test.js +0 -1036
  72. package/tests/plugin-reload.test.js +0 -764
  73. package/tests/rate-limiter.test.js +0 -539
  74. package/tests/retry.test.js +0 -308
  75. package/tests/security.test.js +0 -411
  76. package/tests/settings-modal.test.js +0 -226
  77. package/tests/theme-selector.test.js +0 -57
  78. package/tests/utils.js +0 -242
  79. package/tests/utils.test.js +0 -317
  80. package/tests/validate-plugin-cli.test.js +0 -359
  81. package/tests/validation.test.js +0 -837
  82. package/tests/web-server.test.js +0 -646
  83. package/tests/widget-arrange-mode.test.js +0 -183
  84. package/tests/widget-config-hot-reload.test.js +0 -465
  85. package/tests/widget-dependency.test.js +0 -591
  86. package/tests/widget-error-boundary.test.js +0 -749
  87. package/tests/widget-error-isolation.test.js +0 -469
  88. package/tests/widget-integration.test.js +0 -1147
  89. package/tests/widget-refresh-intervals.test.js +0 -284
  90. package/tests/worker-pool.test.js +0 -522
package/CHANGELOG.md CHANGED
@@ -5,7 +5,35 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [2.2.0] - 2026-07-28
9
+
10
+ ### Security
11
+ - Fixed all npm audit vulnerabilities (brace-expansion, js-yaml, @babel/core)
12
+ - Added brace-expansion override to force safe version across transitive deps
13
+ - Enabled Dependabot security updates on repository
14
+
15
+ ### Dependencies
16
+ - Updated eslint 10.0.3 → 10.8.0
17
+ - Updated jest 30.3.0 → 30.4.2
18
+ - Updated systeminformation 5.31.7 → 5.33.1
19
+ - Updated c8 11 → 12
20
+ - Updated lint-staged 16 → 17
21
+
22
+ ### Improvements
23
+ - Bumped minimum Node.js version from 18 to 20
24
+ - Updated CI matrix to test on Node 20, 22, 24
25
+ - Tightened ESLint rules (no-unused-vars, no-undef, prefer-const, no-var, no-console as warnings)
26
+ - Raised coverage thresholds (statements/lines 25→35, branches 30→35, functions 4→20)
27
+ - Removed dead CJS files (index.cjs, build-cjs.js, cjs-shim.js)
28
+ - Removed stale files (test.js, node_trace.1.log, start.sh)
29
+ - Added node_trace.*.log to .gitignore
30
+
31
+ ### Lean/Clean Refactor (from v2.1.1+)
32
+ - Removed sql.js dep + full historical DB module (backlog only; stores no longer called from refresh/start/web paths; WASM/init/DB-file overhead eliminated for lean runtime).
33
+ - ESM-only: dropped CJS require conditions in exports, build:cjs script, and active dual-build in release (ci unaffected). "files" + .npmignore hygiene to exclude dead (src/database.js, index.cjs, build-cjs*, dist/widgets.cjs).
34
+ - Synced tests (config, errors, integration), config, errors, docs, and database stub for removed surface. Full suite green for core.
35
+ - Core TUI, retained features (snapshots, --web, plugins, perf, etc.), visuals, and controls 100% preserved.
36
+ - Pre-existing snapshot path validation and web defaults noted (addressed with comments + minimal containment; no scope creep into general security refactor).
9
37
 
10
38
  ## [2.0.0] - 2026-02-28
11
39
 
package/docs/API.md CHANGED
@@ -626,7 +626,6 @@ Returns a JSON-serializable representation of the error.
626
626
  | `AuthError` | `AUTH_ERROR` | Authentication failures |
627
627
  | `NetworkError` | `NETWORK_ERROR` | Network issues |
628
628
  | `UIError` | `UI_ERROR` | UI/rendering errors |
629
- | `DatabaseError` | `DATABASE_ERROR` | Database operations |
630
629
  | `ValidationError` | `VALIDATION_ERROR` | Input validation |
631
630
  | `TimeoutError` | `TIMEOUT_ERROR` | Operation timeouts |
632
631
 
@@ -668,7 +667,7 @@ import { ERROR_CODES } from './src/errors.js';
668
667
  // Available codes:
669
668
  // CONFIG_ERROR, SETTINGS_ERROR, GATEWAY_ERROR, SESSION_ERROR,
670
669
  // DATA_FETCH_ERROR, AUTH_ERROR, NETWORK_ERROR, UI_ERROR,
671
- // DATABASE_ERROR, VALIDATION_ERROR, TIMEOUT_ERROR, DASHBOARD_ERROR
670
+ // VALIDATION_ERROR, TIMEOUT_ERROR, DASHBOARD_ERROR
672
671
  ```
673
672
 
674
673
  ---
package/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import blessed from 'blessed';
4
- import contrib from 'blessed-contrib';
5
4
  import si from 'systeminformation';
6
5
  import { exec } from 'child_process';
7
6
  import { promisify } from 'util';
@@ -10,7 +9,7 @@ import http from 'http';
10
9
  import os from 'os';
11
10
  import fs from 'fs';
12
11
  import { fileURLToPath } from 'url';
13
- import { dirname, join, resolve } from 'path';
12
+ import { dirname, join, resolve, sep } from 'path';
14
13
  import logger from './src/logger.js';
15
14
  import {
16
15
  cycleTheme, getCurrentTheme, loadTheme, saveTheme, getThemeName,
@@ -21,11 +20,10 @@ import retry from './src/retry.js';
21
20
  import config, { DASHBOARD_VERSION, WIDGET_SIZES, WIDGET_SIZE_PRESETS } from './src/config.js';
22
21
  import validation from './src/validation.js';
23
22
  import cache from './src/cache.js';
24
- import database from './src/database.js';
25
23
  import { setSecurePermissionsSync } from './src/security.js';
26
24
  import { showSplashScreen } from './src/splash.js';
27
25
  import { showFirstRunHints } from './src/hints.js';
28
- import { DashboardError, ConfigError, SettingsError, GatewayError, SessionError, DataFetchError, AuthError, NetworkError, UIError, DatabaseError, ValidationError, TimeoutError, getErrorCode } from './src/errors.js';
26
+ import { DashboardError, ConfigError, SettingsError, GatewayError, SessionError, DataFetchError, AuthError, NetworkError, UIError, ValidationError, TimeoutError, getErrorCode } from './src/errors.js';
29
27
  import { ConfigWatcher, watchSettingsFile } from './src/config-watcher.js';
30
28
  import { PluginReloadManager } from './src/plugin-reload.js';
31
29
  import { WidgetLoader } from './src/widgets/widget-loader.js';
@@ -60,7 +58,6 @@ import {
60
58
  generateSnapshotFilename,
61
59
  getSnapshotsDirectory,
62
60
  listSnapshots,
63
- deleteSnapshot,
64
61
  getSnapshotSummary,
65
62
  } from './src/snapshot.js';
66
63
  import {
@@ -80,7 +77,7 @@ const __filename = fileURLToPath(import.meta.url);
80
77
  const __dirname = dirname(__filename);
81
78
  const execAsync = promisify(exec);
82
79
 
83
- // Safe file path validation
80
+ // Safe file path validation (used for settings/export paths; allows any subpath under home or listed dirs; contrast to validatePluginPath used by snapshots which adds char/hidden filters - see the allowedHidden list and hidden check comment in security.js + export/import/delete comments in snapshot.js for snapshot path rules)
84
81
  function validateFilePath(filePath, allowedDirs = []) {
85
82
  try {
86
83
  // Handle null/undefined input
@@ -88,8 +85,10 @@ function validateFilePath(filePath, allowedDirs = []) {
88
85
  return { valid: false, resolvedPath: filePath, error: "Invalid file path" };
89
86
  }
90
87
 
91
- // Replace leading ~ with home directory before resolving
92
- const normalizedPath = filePath.startsWith('~')
88
+ // Replace leading ~ with home directory before resolving (robust for '~/...' and '~...' )
89
+ const normalizedPath = filePath.startsWith('~/')
90
+ ? join(os.homedir(), filePath.slice(2))
91
+ : filePath.startsWith('~')
93
92
  ? join(os.homedir(), filePath.slice(1))
94
93
  : filePath;
95
94
 
@@ -106,8 +105,8 @@ function validateFilePath(filePath, allowedDirs = []) {
106
105
  const homeDir = os.homedir();
107
106
  const defaultAllowedDirs = [
108
107
  homeDir,
109
- homeDir + "/.openclaw",
110
- homeDir + "/.openclaw/agents",
108
+ join(homeDir, '.openclaw'),
109
+ join(homeDir, '.openclaw', 'agents'),
111
110
  "/tmp"
112
111
  ];
113
112
 
@@ -116,7 +115,7 @@ function validateFilePath(filePath, allowedDirs = []) {
116
115
  // Check if resolved path is within any allowed directory
117
116
  const isAllowed = allAllowedDirs.some(allowedDir => {
118
117
  const resolvedAllowed = resolve(allowedDir);
119
- return resolvedPath.startsWith(resolvedAllowed + "/") || resolvedPath === resolvedAllowed;
118
+ return resolvedPath.startsWith(resolvedAllowed + sep) || resolvedPath === resolvedAllowed;
120
119
  });
121
120
 
122
121
  if (!isAllowed) {
@@ -841,8 +840,7 @@ class Dashboard {
841
840
  this.sessionSearchQuery = this.settings.sessionSearchQuery || '';
842
841
  this.isSearchMode = false;
843
842
  this.filteredSessions = [];
844
- // Widget navigation state - index into focusableWidgets array
845
- this.focusedWidgetIndex = 0;
843
+ // Widget navigation state - index into focusableWidgets array (set to -1 for no initial focus)
846
844
  this.focusableWidgets = [];
847
845
  // Restore search filter if query was persisted
848
846
  if (this.sessionSearchQuery) {
@@ -862,7 +860,6 @@ class Dashboard {
862
860
  this.corruptedSessionsCount = 0;
863
861
  this.corruptedSessionsWarningShown = false;
864
862
  // Widget focus navigation state
865
- this.focusableWidgets = [];
866
863
  this.focusedWidgetIndex = -1; // -1 means no widget focused (normal mode)
867
864
  // Widget arrangement mode state
868
865
  this.isWidgetArrangeMode = false;
@@ -2703,7 +2700,7 @@ class Dashboard {
2703
2700
  }
2704
2701
 
2705
2702
  exportDashboard() {
2706
- const exportDir = this.settings.exportDirectory || os.homedir() + '/.openclaw/exports';
2703
+ const exportDir = this.settings.exportDirectory || join(os.homedir(), '.openclaw', 'exports');
2707
2704
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
2708
2705
  const format = this.settings.exportFormat || 'json';
2709
2706
  const filename = `dashboard-${timestamp}.${format}`;
@@ -2717,7 +2714,7 @@ class Dashboard {
2717
2714
  return;
2718
2715
  }
2719
2716
  const validatedExportDir = pathValidation.resolvedPath;
2720
- const filepath = validatedExportDir + '/' + filename;
2717
+ const filepath = join(validatedExportDir, filename);
2721
2718
 
2722
2719
  try {
2723
2720
  // Create export directory if it doesn't exist
@@ -2815,7 +2812,7 @@ class Dashboard {
2815
2812
 
2816
2813
  const snapshotDir = getSnapshotsDirectory();
2817
2814
  const filename = generateSnapshotFilename('dashboard');
2818
- const filepath = `${snapshotDir}/${filename}`;
2815
+ const filepath = join(snapshotDir, filename);
2819
2816
 
2820
2817
  const result = exportSnapshotToFile(snapshot, filepath);
2821
2818
 
@@ -2871,8 +2868,10 @@ class Dashboard {
2871
2868
  this.w.snapshotPrompt.input('Enter snapshot file path:', '', (err, value) => {
2872
2869
  if (!err && value && value.trim()) {
2873
2870
  let filePath = value.trim();
2874
- if (filePath.startsWith('~')) {
2875
- filePath = os.homedir() + filePath.substring(1);
2871
+ if (filePath.startsWith('~/')) {
2872
+ filePath = join(os.homedir(), filePath.slice(2));
2873
+ } else if (filePath.startsWith('~')) {
2874
+ filePath = join(os.homedir(), filePath.slice(1));
2876
2875
  }
2877
2876
  this.loadAndApplySnapshot(filePath);
2878
2877
  }
@@ -3817,12 +3816,12 @@ class Dashboard {
3817
3816
  break;
3818
3817
  case 11: // Cycle export directory: ~/.openclaw/exports -> ~/Downloads -> ~/Desktop -> Custom
3819
3818
  const exportDirs = [
3820
- os.homedir() + '/.openclaw/exports',
3821
- os.homedir() + '/Downloads',
3822
- os.homedir() + '/Desktop',
3819
+ join(os.homedir(), '.openclaw', 'exports'),
3820
+ join(os.homedir(), 'Downloads'),
3821
+ join(os.homedir(), 'Desktop'),
3823
3822
  'custom'
3824
3823
  ];
3825
- const currentExportDir = this.settings.exportDirectory || os.homedir() + '/.openclaw/exports';
3824
+ const currentExportDir = this.settings.exportDirectory || join(os.homedir(), '.openclaw', 'exports');
3826
3825
  let currentDirIdx = exportDirs.indexOf(currentExportDir);
3827
3826
  if (currentDirIdx === -1) {
3828
3827
  // Current dir is custom, start from beginning
@@ -3846,8 +3845,10 @@ class Dashboard {
3846
3845
  this.w.customPathPrompt.input('Enter custom export path (~ for home):', currentExportDir, (err, value) => {
3847
3846
  if (!err && value && value.trim()) {
3848
3847
  let customPath = value.trim();
3849
- if (customPath.startsWith('~')) {
3850
- customPath = os.homedir() + customPath.substring(1);
3848
+ if (customPath.startsWith('~/')) {
3849
+ customPath = join(os.homedir(), customPath.slice(2));
3850
+ } else if (customPath.startsWith('~')) {
3851
+ customPath = join(os.homedir(), customPath.slice(1));
3851
3852
  }
3852
3853
  const pathValidation = validateFilePath(customPath);
3853
3854
  if (pathValidation.valid) {
@@ -4620,28 +4621,20 @@ class Dashboard {
4620
4621
  async start(updateProgress) {
4621
4622
  const progress = updateProgress || (() => {});
4622
4623
 
4623
- // Initialize the database
4624
- progress(15, 'Initializing database...');
4625
- await database.initDatabase();
4626
-
4627
- // Clean up old data (older than 30 days) on startup
4628
- progress(25, 'Cleaning up old data...');
4629
- database.cleanupOldData(30);
4630
-
4631
4624
  // Initialize gateway manager with settings
4632
- progress(35, 'Initializing gateway...');
4625
+ progress(15, 'Initializing gateway...');
4633
4626
  gatewayManager.init(this.settings);
4634
4627
 
4635
4628
  // Start performance monitoring
4636
- progress(45, 'Starting performance monitor...');
4629
+ progress(30, 'Starting performance monitor...');
4637
4630
  performanceMonitor.start();
4638
4631
 
4639
4632
  // Wire up worker pool for metrics tracking
4640
- progress(55, 'Starting workers...');
4633
+ progress(45, 'Starting workers...');
4641
4634
  setWorkerPool(workerPool);
4642
4635
 
4643
4636
  // Start watching for config hot-reload
4644
- progress(65, 'Setting up config watcher...');
4637
+ progress(60, 'Setting up config watcher...');
4645
4638
  this.startConfigWatcher();
4646
4639
 
4647
4640
  // Start watching for plugin hot-reload if --watch flag is set
@@ -5136,7 +5129,7 @@ class Dashboard {
5136
5129
  }
5137
5130
  }
5138
5131
  } catch (err) {
5139
- // eslint-disable-next-line no-console
5132
+
5140
5133
  logger.error('Session fetch error:', err.message);
5141
5134
  this.data.sessions = this.data.sessions || [];
5142
5135
  this.data.openclaw = { gateway: { reachable: false } };
@@ -5217,8 +5210,6 @@ class Dashboard {
5217
5210
  });
5218
5211
 
5219
5212
  this.render();
5220
- // Store metrics in database for historical tracking
5221
- database.storeMetricsSnapshot(this.data);
5222
5213
  } catch (e) {}
5223
5214
  }
5224
5215
 
@@ -5738,8 +5729,6 @@ class WebDashboard extends Dashboard {
5738
5729
  }
5739
5730
 
5740
5731
  // Initialize data fetching (reuse parent's start() logic but without UI)
5741
- await database.initDatabase();
5742
- database.cleanupOldData(30);
5743
5732
  gatewayManager.init(this.settings);
5744
5733
  performanceMonitor.start();
5745
5734
 
@@ -5974,5 +5963,6 @@ async function main() {
5974
5963
  // Run main
5975
5964
  main().catch(err => {
5976
5965
  console.error('Fatal error:', err);
5966
+ if (err.stack) console.error('Stack:', err.stack);
5977
5967
  process.exit(1);
5978
5968
  });
package/package.json CHANGED
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "name": "claw-dashboard",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "A beautiful console dashboard for monitoring OpenClaw instances — inspired by htop/btop/mactop",
5
5
  "main": "index.js",
6
6
  "exports": {
7
7
  ".": {
8
- "import": "./index.js",
9
- "require": "./index.cjs"
8
+ "import": "./index.js"
10
9
  },
11
10
  "./widgets": {
12
- "import": "./src/widgets/index.js",
13
- "require": "./dist/widgets.cjs"
11
+ "import": "./src/widgets/index.js"
14
12
  },
15
13
  "./package.json": "./package.json"
16
14
  },
@@ -20,6 +18,19 @@
20
18
  "man": [
21
19
  "./man/clawdash.1"
22
20
  ],
21
+ "files": [
22
+ "index.js",
23
+ "src/**/*.js",
24
+ "src/**/*.ts",
25
+ "!src/database.js",
26
+ "man/clawdash.1",
27
+ "docs/*.md",
28
+ "schemas/*.json",
29
+ "README.md",
30
+ "LICENSE",
31
+ "CHANGELOG.md",
32
+ "package.json"
33
+ ],
23
34
  "type": "module",
24
35
  "scripts": {
25
36
  "start": "node index.js",
@@ -31,7 +42,6 @@
31
42
  "coverage:clean": "rm -rf coverage",
32
43
  "build": "node esbuild.config.js",
33
44
  "build:dev": "node esbuild.config.js --dev",
34
- "build:cjs": "node build-cjs.js",
35
45
  "lint": "eslint src/ --ignore-pattern 'src/workers/**'",
36
46
  "release": "node scripts/release.js",
37
47
  "release:sign": "node scripts/release.js --sign",
@@ -57,22 +67,22 @@
57
67
  "author": "Kevin Smith",
58
68
  "license": "MIT",
59
69
  "engines": {
60
- "node": ">=18"
70
+ "node": ">=20"
61
71
  },
62
72
  "dependencies": {
63
73
  "@pm2/blessed": "^0.1.81",
74
+ "blessed": "npm:@pm2/blessed@^0.1.81",
64
75
  "chalk": "^5.3.0",
65
- "sql.js": "^1.14.0",
66
76
  "systeminformation": "^5.21.22"
67
77
  },
68
78
  "devDependencies": {
69
79
  "@eslint/js": "^10.0.1",
70
- "c8": "^11.0.0",
71
- "esbuild": "^0.27.3",
80
+ "c8": "^12.0.0",
81
+ "esbuild": "^0.28.1",
72
82
  "eslint": "^10.0.2",
73
83
  "husky": "^9.1.7",
74
84
  "jest": "^30.2.0",
75
- "lint-staged": "^16.3.0"
85
+ "lint-staged": "^17.2.0"
76
86
  },
77
87
  "lint-staged": {
78
88
  "src/**/*.{js,mjs,cjs}": [
@@ -84,6 +94,7 @@
84
94
  "xml2js": "^0.6.2",
85
95
  "form-data": "^4.0.0",
86
96
  "qs": "^6.14.1",
87
- "tough-cookie": "^5.0.0"
97
+ "tough-cookie": "^5.0.0",
98
+ "brace-expansion": "^5.0.8"
88
99
  }
89
100
  }
package/src/auto-save.js CHANGED
@@ -22,15 +22,21 @@ function validateFilePath(filePath) {
22
22
  }
23
23
 
24
24
  // Expand tilde to home directory
25
- const resolvedPath = filePath.startsWith('~')
25
+ const resolvedPath = filePath.startsWith('~/')
26
+ ? path.join(os.homedir(), filePath.slice(2))
27
+ : filePath.startsWith('~')
26
28
  ? path.join(os.homedir(), filePath.slice(1))
27
29
  : path.resolve(filePath);
28
30
 
29
- // Check for path traversal - allow home directory and temp directories
31
+ // Check for path traversal - allow home directory and temp directories (sep + === to prevent prefix collisions e.g. /home/userfoo; matches index/snapshot/security style)
30
32
  const homeDir = os.homedir();
31
33
  const tempDirs = ['/tmp', os.tmpdir()];
32
- const isInAllowedDir = resolvedPath.startsWith(homeDir) ||
33
- tempDirs.some(tmpDir => resolvedPath.startsWith(tmpDir));
34
+ const resolvedHome = path.resolve(homeDir);
35
+ const isInAllowedDir = resolvedPath.startsWith(resolvedHome + path.sep) || resolvedPath === resolvedHome ||
36
+ tempDirs.some(tmpDir => {
37
+ const rtmp = path.resolve(tmpDir);
38
+ return resolvedPath.startsWith(rtmp + path.sep) || resolvedPath === rtmp;
39
+ });
34
40
  if (!isInAllowedDir) {
35
41
  return { valid: false, error: 'Path must be within home or temp directory' };
36
42
  }
@@ -354,7 +360,7 @@ export class AutoSaveManager {
354
360
  }
355
361
 
356
362
  // Ensure directory exists
357
- const dir = pathValidation.resolvedPath.substring(0, pathValidation.resolvedPath.lastIndexOf('/'));
363
+ const dir = path.dirname(pathValidation.resolvedPath);
358
364
  if (!fs.existsSync(dir)) {
359
365
  fs.mkdirSync(dir, { recursive: true });
360
366
  }
@@ -82,7 +82,9 @@ Examples:
82
82
  let filePath;
83
83
  if (outputPath) {
84
84
  let resolvedPath = outputPath;
85
- if (outputPath.startsWith('~')) {
85
+ if (outputPath.startsWith('~/')) {
86
+ resolvedPath = join(os.homedir(), outputPath.slice(2));
87
+ } else if (outputPath.startsWith('~')) {
86
88
  resolvedPath = join(os.homedir(), outputPath.slice(1));
87
89
  }
88
90
  filePath = resolve(resolvedPath);
@@ -117,7 +117,9 @@ Examples:
117
117
  try {
118
118
  // Resolve the path
119
119
  let resolvedPath = filePath;
120
- if (filePath.startsWith('~')) {
120
+ if (filePath.startsWith('~/')) {
121
+ resolvedPath = join(os.homedir(), filePath.slice(2));
122
+ } else if (filePath.startsWith('~')) {
121
123
  resolvedPath = join(os.homedir(), filePath.slice(1));
122
124
  }
123
125
  resolvedPath = resolve(resolvedPath);
package/src/config.js CHANGED
@@ -101,17 +101,6 @@ export const CACHE_CONFIG = {
101
101
  container: { ttl: CACHE_TTL.CONTAINER },
102
102
  };
103
103
 
104
- // ============================================================================
105
- // DATABASE SETTINGS
106
- // ============================================================================
107
-
108
- export const DATABASE = {
109
- PATH: os.homedir() + '/.openclaw/dashboard-history.db',
110
- SAVE_INTERVAL_MS: 30000, // Save every 30 seconds
111
- CLEANUP_INTERVAL_MS: 60 * 60 * 1000, // Cleanup every hour
112
- DEFAULT_RETENTION_DAYS: 30,
113
- };
114
-
115
104
  // ============================================================================
116
105
  // CHECKSUM VERIFICATION SETTINGS
117
106
  // ============================================================================
@@ -375,6 +364,12 @@ export const WORKER_OVERLOAD = {
375
364
 
376
365
  export const WEB = {
377
366
  DEFAULT_PORT: 18790, // Default port for web interface
367
+ // SECURITY NOTE (pre-existing defaults; --web is for remote/trusted use only):
368
+ // - HOST '0.0.0.0' binds all interfaces (consider '127.0.0.1' for localhost-only).
369
+ // - CORS '*' allows all (restrict to specific origins in production).
370
+ // - AUTH.ENABLED=false (opt-in only; enable + keys for production).
371
+ // Health endpoint intentionally unauthed. See web-server.js and README for guidance.
372
+ // These were not changed in lean trim to avoid UX break for existing --web users.
378
373
  HOST: '0.0.0.0', // Bind to all interfaces by default
379
374
  CORS_ORIGIN: '*', // CORS origin (restrict in production)
380
375
  REQUEST_TIMEOUT: 30000, // Request timeout in milliseconds
@@ -396,8 +391,8 @@ export const WEB = {
396
391
  },
397
392
  // CORS configuration
398
393
  CORS: {
399
- // Production: specify allowed origins as array (e.g., ['https://example.com'])
400
- // Development: use '*' to allow all origins
394
+ // SECURITY: Production: specify allowed origins as array (e.g., ['https://example.com'])
395
+ // Development: use '*' to allow all origins (default here; see note on WEB.HOST/AUTH)
401
396
  ALLOWED_ORIGINS: '*', // Default to allow all (restrict in production)
402
397
  ALLOWED_METHODS: ['GET', 'POST', 'OPTIONS'],
403
398
  ALLOWED_HEADERS: ['Content-Type', 'Authorization'],
@@ -406,7 +401,7 @@ export const WEB = {
406
401
  },
407
402
  // Authentication configuration
408
403
  AUTH: {
409
- ENABLED: false, // Disabled by default (enable explicitly)
404
+ ENABLED: false, // Disabled by default - must explicitly enable (see WEB security note above)
410
405
  HEADER_NAME: 'Authorization', // HTTP header for API key
411
406
  SCHEME: 'Bearer', // Auth scheme (Bearer, ApiKey, etc.)
412
407
  KEY_PREFIX: 'cd_', // Prefix for auto-generated API keys
@@ -656,7 +651,6 @@ export default {
656
651
  UI,
657
652
  CACHE_TTL,
658
653
  CACHE_CONFIG,
659
- DATABASE,
660
654
  RETRY,
661
655
  DEFAULT_RETRY_OPTIONS,
662
656
  AUTO_RETRY,
package/src/errors.js CHANGED
@@ -94,14 +94,6 @@ export class UIError extends DashboardError {
94
94
  }
95
95
  }
96
96
 
97
- // Database errors
98
- export class DatabaseError extends DashboardError {
99
- constructor(message, details = {}) {
100
- super(message, 'DATABASE_ERROR', details);
101
- this.name = 'DatabaseError';
102
- }
103
- }
104
-
105
97
  // Validation errors
106
98
  export class ValidationError extends DashboardError {
107
99
  constructor(message, details = {}) {
@@ -147,7 +139,6 @@ export const ERROR_CODES = {
147
139
  AUTH_ERROR: 'AUTH_ERROR',
148
140
  NETWORK_ERROR: 'NETWORK_ERROR',
149
141
  UI_ERROR: 'UI_ERROR',
150
- DATABASE_ERROR: 'DATABASE_ERROR',
151
142
  VALIDATION_ERROR: 'VALIDATION_ERROR',
152
143
  TIMEOUT_ERROR: 'TIMEOUT_ERROR',
153
144
  CHECKSUM_ERROR: 'CHECKSUM_ERROR',
package/src/security.js CHANGED
@@ -394,8 +394,12 @@ function validatePluginPath(inputPath, options = {}) {
394
394
 
395
395
  // Check for hidden files/directories (starting with .)
396
396
  if (part.startsWith('.') && part !== '.' && part !== '..') {
397
- // Allow specific hidden files like .gitkeep but not arbitrary ones
398
- const allowedHidden = ['.gitkeep', '.gitignore', '.npmignore'];
397
+ // Allow specific hidden files like .gitkeep but not arbitrary ones.
398
+ // '.openclaw' whitelisted for standard ~/.openclaw/snapshots + /plugins flows (PATHS.OPENCLAW_DIR etc.)
399
+ // and user arbitrary paths under dot-dirs; other dot components still rejected for security.
400
+ // This rule (via shared validatePluginPath) applies to snapshot export/import (even with allowAbsolute:true, e.g. ~/.config/.secret/snap.json or /tmp/.foo/bar rejected); delete uses allowedDirs.
401
+ // See the validateFilePath comment in index.js (settings uses different/broader rules under ~), and export/import/delete comments in snapshot.js.
402
+ const allowedHidden = ['.gitkeep', '.gitignore', '.npmignore', '.openclaw'];
399
403
  if (!allowedHidden.includes(part)) {
400
404
  return { valid: false, path: null, error: `Hidden files/directories are not allowed: ${part}` };
401
405
  }