claw-dashboard 2.1.1 → 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 (89) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/docs/API.md +1 -2
  3. package/index.js +31 -41
  4. package/package.json +22 -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/.c8rc.json +0 -38
  14. package/.dockerignore +0 -68
  15. package/.github/dependabot.yml +0 -45
  16. package/.github/pull_request_template.md +0 -39
  17. package/.github/workflows/ci.yml +0 -147
  18. package/.github/workflows/release.yml +0 -40
  19. package/.github/workflows/security.yml +0 -84
  20. package/.husky/pre-commit +0 -1
  21. package/.planning/codebase/ARCHITECTURE.md +0 -206
  22. package/.planning/codebase/INTEGRATIONS.md +0 -150
  23. package/.planning/codebase/STACK.md +0 -122
  24. package/.planning/codebase/STRUCTURE.md +0 -201
  25. package/CONTRIBUTING.md +0 -378
  26. package/Dockerfile +0 -54
  27. package/FEATURES.md +0 -307
  28. package/TODO.md +0 -28
  29. package/ai.openclaw.dashboard.plist +0 -35
  30. package/build-cjs.js +0 -127
  31. package/cjs-shim.js +0 -13
  32. package/dist/clawdash +0 -1729
  33. package/dist/clawdash.meta.json +0 -2236
  34. package/dist/widgets.cjs +0 -6511
  35. package/docker-compose.yml +0 -67
  36. package/esbuild.config.js +0 -158
  37. package/eslint.config.js +0 -56
  38. package/examples/plugins/README.md +0 -122
  39. package/examples/plugins/api-status/index.js +0 -294
  40. package/examples/plugins/api-status/plugin.json +0 -19
  41. package/examples/plugins/hello-world/index.js +0 -104
  42. package/examples/plugins/hello-world/plugin.json +0 -15
  43. package/examples/plugins/system-metrics-chart/index.js +0 -339
  44. package/examples/plugins/system-metrics-chart/plugin.json +0 -18
  45. package/examples/plugins/weather-widget/index.js +0 -136
  46. package/examples/plugins/weather-widget/plugin.json +0 -19
  47. package/index.cjs +0 -23005
  48. package/jest.config.js +0 -11
  49. package/scripts/release.js +0 -595
  50. package/src/database.js +0 -734
  51. package/tests/alerts.test.js +0 -437
  52. package/tests/auto-save.test.js +0 -529
  53. package/tests/cache.test.js +0 -317
  54. package/tests/cli.test.js +0 -351
  55. package/tests/command-palette.test.js +0 -320
  56. package/tests/config-processor.test.js +0 -452
  57. package/tests/config-validator.test.js +0 -710
  58. package/tests/config-watcher.test.js +0 -594
  59. package/tests/config.test.js +0 -755
  60. package/tests/database.test.js +0 -438
  61. package/tests/errors.test.js +0 -189
  62. package/tests/example-plugins.test.js +0 -624
  63. package/tests/integration.test.js +0 -1321
  64. package/tests/loading-states.test.js +0 -300
  65. package/tests/manifest-validation-on-load.test.js +0 -671
  66. package/tests/performance-monitor.test.js +0 -190
  67. package/tests/plugin-api-rate-limit.test.js +0 -302
  68. package/tests/plugin-errors.test.js +0 -311
  69. package/tests/plugin-lifecycle-e2e.test.js +0 -1036
  70. package/tests/plugin-reload.test.js +0 -764
  71. package/tests/rate-limiter.test.js +0 -539
  72. package/tests/removed-dependencies.test.js +0 -74
  73. package/tests/retry.test.js +0 -308
  74. package/tests/security.test.js +0 -411
  75. package/tests/settings-modal.test.js +0 -226
  76. package/tests/theme-selector.test.js +0 -57
  77. package/tests/utils.js +0 -242
  78. package/tests/utils.test.js +0 -317
  79. package/tests/validate-plugin-cli.test.js +0 -359
  80. package/tests/validation.test.js +0 -837
  81. package/tests/web-server.test.js +0 -646
  82. package/tests/widget-arrange-mode.test.js +0 -183
  83. package/tests/widget-config-hot-reload.test.js +0 -465
  84. package/tests/widget-dependency.test.js +0 -591
  85. package/tests/widget-error-boundary.test.js +0 -749
  86. package/tests/widget-error-isolation.test.js +0 -469
  87. package/tests/widget-integration.test.js +0 -1147
  88. package/tests/widget-refresh-intervals.test.js +0 -284
  89. 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
@@ -9,7 +9,7 @@ import http from 'http';
9
9
  import os from 'os';
10
10
  import fs from 'fs';
11
11
  import { fileURLToPath } from 'url';
12
- import { dirname, join, resolve } from 'path';
12
+ import { dirname, join, resolve, sep } from 'path';
13
13
  import logger from './src/logger.js';
14
14
  import {
15
15
  cycleTheme, getCurrentTheme, loadTheme, saveTheme, getThemeName,
@@ -20,11 +20,10 @@ import retry from './src/retry.js';
20
20
  import config, { DASHBOARD_VERSION, WIDGET_SIZES, WIDGET_SIZE_PRESETS } from './src/config.js';
21
21
  import validation from './src/validation.js';
22
22
  import cache from './src/cache.js';
23
- import database from './src/database.js';
24
23
  import { setSecurePermissionsSync } from './src/security.js';
25
24
  import { showSplashScreen } from './src/splash.js';
26
25
  import { showFirstRunHints } from './src/hints.js';
27
- 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';
28
27
  import { ConfigWatcher, watchSettingsFile } from './src/config-watcher.js';
29
28
  import { PluginReloadManager } from './src/plugin-reload.js';
30
29
  import { WidgetLoader } from './src/widgets/widget-loader.js';
@@ -59,7 +58,6 @@ import {
59
58
  generateSnapshotFilename,
60
59
  getSnapshotsDirectory,
61
60
  listSnapshots,
62
- deleteSnapshot,
63
61
  getSnapshotSummary,
64
62
  } from './src/snapshot.js';
65
63
  import {
@@ -79,7 +77,7 @@ const __filename = fileURLToPath(import.meta.url);
79
77
  const __dirname = dirname(__filename);
80
78
  const execAsync = promisify(exec);
81
79
 
82
- // 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)
83
81
  function validateFilePath(filePath, allowedDirs = []) {
84
82
  try {
85
83
  // Handle null/undefined input
@@ -87,8 +85,10 @@ function validateFilePath(filePath, allowedDirs = []) {
87
85
  return { valid: false, resolvedPath: filePath, error: "Invalid file path" };
88
86
  }
89
87
 
90
- // Replace leading ~ with home directory before resolving
91
- 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('~')
92
92
  ? join(os.homedir(), filePath.slice(1))
93
93
  : filePath;
94
94
 
@@ -105,8 +105,8 @@ function validateFilePath(filePath, allowedDirs = []) {
105
105
  const homeDir = os.homedir();
106
106
  const defaultAllowedDirs = [
107
107
  homeDir,
108
- homeDir + "/.openclaw",
109
- homeDir + "/.openclaw/agents",
108
+ join(homeDir, '.openclaw'),
109
+ join(homeDir, '.openclaw', 'agents'),
110
110
  "/tmp"
111
111
  ];
112
112
 
@@ -115,7 +115,7 @@ function validateFilePath(filePath, allowedDirs = []) {
115
115
  // Check if resolved path is within any allowed directory
116
116
  const isAllowed = allAllowedDirs.some(allowedDir => {
117
117
  const resolvedAllowed = resolve(allowedDir);
118
- return resolvedPath.startsWith(resolvedAllowed + "/") || resolvedPath === resolvedAllowed;
118
+ return resolvedPath.startsWith(resolvedAllowed + sep) || resolvedPath === resolvedAllowed;
119
119
  });
120
120
 
121
121
  if (!isAllowed) {
@@ -840,8 +840,7 @@ class Dashboard {
840
840
  this.sessionSearchQuery = this.settings.sessionSearchQuery || '';
841
841
  this.isSearchMode = false;
842
842
  this.filteredSessions = [];
843
- // Widget navigation state - index into focusableWidgets array
844
- this.focusedWidgetIndex = 0;
843
+ // Widget navigation state - index into focusableWidgets array (set to -1 for no initial focus)
845
844
  this.focusableWidgets = [];
846
845
  // Restore search filter if query was persisted
847
846
  if (this.sessionSearchQuery) {
@@ -861,7 +860,6 @@ class Dashboard {
861
860
  this.corruptedSessionsCount = 0;
862
861
  this.corruptedSessionsWarningShown = false;
863
862
  // Widget focus navigation state
864
- this.focusableWidgets = [];
865
863
  this.focusedWidgetIndex = -1; // -1 means no widget focused (normal mode)
866
864
  // Widget arrangement mode state
867
865
  this.isWidgetArrangeMode = false;
@@ -2702,7 +2700,7 @@ class Dashboard {
2702
2700
  }
2703
2701
 
2704
2702
  exportDashboard() {
2705
- const exportDir = this.settings.exportDirectory || os.homedir() + '/.openclaw/exports';
2703
+ const exportDir = this.settings.exportDirectory || join(os.homedir(), '.openclaw', 'exports');
2706
2704
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
2707
2705
  const format = this.settings.exportFormat || 'json';
2708
2706
  const filename = `dashboard-${timestamp}.${format}`;
@@ -2716,7 +2714,7 @@ class Dashboard {
2716
2714
  return;
2717
2715
  }
2718
2716
  const validatedExportDir = pathValidation.resolvedPath;
2719
- const filepath = validatedExportDir + '/' + filename;
2717
+ const filepath = join(validatedExportDir, filename);
2720
2718
 
2721
2719
  try {
2722
2720
  // Create export directory if it doesn't exist
@@ -2814,7 +2812,7 @@ class Dashboard {
2814
2812
 
2815
2813
  const snapshotDir = getSnapshotsDirectory();
2816
2814
  const filename = generateSnapshotFilename('dashboard');
2817
- const filepath = `${snapshotDir}/${filename}`;
2815
+ const filepath = join(snapshotDir, filename);
2818
2816
 
2819
2817
  const result = exportSnapshotToFile(snapshot, filepath);
2820
2818
 
@@ -2870,8 +2868,10 @@ class Dashboard {
2870
2868
  this.w.snapshotPrompt.input('Enter snapshot file path:', '', (err, value) => {
2871
2869
  if (!err && value && value.trim()) {
2872
2870
  let filePath = value.trim();
2873
- if (filePath.startsWith('~')) {
2874
- 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));
2875
2875
  }
2876
2876
  this.loadAndApplySnapshot(filePath);
2877
2877
  }
@@ -3816,12 +3816,12 @@ class Dashboard {
3816
3816
  break;
3817
3817
  case 11: // Cycle export directory: ~/.openclaw/exports -> ~/Downloads -> ~/Desktop -> Custom
3818
3818
  const exportDirs = [
3819
- os.homedir() + '/.openclaw/exports',
3820
- os.homedir() + '/Downloads',
3821
- os.homedir() + '/Desktop',
3819
+ join(os.homedir(), '.openclaw', 'exports'),
3820
+ join(os.homedir(), 'Downloads'),
3821
+ join(os.homedir(), 'Desktop'),
3822
3822
  'custom'
3823
3823
  ];
3824
- const currentExportDir = this.settings.exportDirectory || os.homedir() + '/.openclaw/exports';
3824
+ const currentExportDir = this.settings.exportDirectory || join(os.homedir(), '.openclaw', 'exports');
3825
3825
  let currentDirIdx = exportDirs.indexOf(currentExportDir);
3826
3826
  if (currentDirIdx === -1) {
3827
3827
  // Current dir is custom, start from beginning
@@ -3845,8 +3845,10 @@ class Dashboard {
3845
3845
  this.w.customPathPrompt.input('Enter custom export path (~ for home):', currentExportDir, (err, value) => {
3846
3846
  if (!err && value && value.trim()) {
3847
3847
  let customPath = value.trim();
3848
- if (customPath.startsWith('~')) {
3849
- 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));
3850
3852
  }
3851
3853
  const pathValidation = validateFilePath(customPath);
3852
3854
  if (pathValidation.valid) {
@@ -4619,28 +4621,20 @@ class Dashboard {
4619
4621
  async start(updateProgress) {
4620
4622
  const progress = updateProgress || (() => {});
4621
4623
 
4622
- // Initialize the database
4623
- progress(15, 'Initializing database...');
4624
- await database.initDatabase();
4625
-
4626
- // Clean up old data (older than 30 days) on startup
4627
- progress(25, 'Cleaning up old data...');
4628
- database.cleanupOldData(30);
4629
-
4630
4624
  // Initialize gateway manager with settings
4631
- progress(35, 'Initializing gateway...');
4625
+ progress(15, 'Initializing gateway...');
4632
4626
  gatewayManager.init(this.settings);
4633
4627
 
4634
4628
  // Start performance monitoring
4635
- progress(45, 'Starting performance monitor...');
4629
+ progress(30, 'Starting performance monitor...');
4636
4630
  performanceMonitor.start();
4637
4631
 
4638
4632
  // Wire up worker pool for metrics tracking
4639
- progress(55, 'Starting workers...');
4633
+ progress(45, 'Starting workers...');
4640
4634
  setWorkerPool(workerPool);
4641
4635
 
4642
4636
  // Start watching for config hot-reload
4643
- progress(65, 'Setting up config watcher...');
4637
+ progress(60, 'Setting up config watcher...');
4644
4638
  this.startConfigWatcher();
4645
4639
 
4646
4640
  // Start watching for plugin hot-reload if --watch flag is set
@@ -5135,7 +5129,7 @@ class Dashboard {
5135
5129
  }
5136
5130
  }
5137
5131
  } catch (err) {
5138
- // eslint-disable-next-line no-console
5132
+
5139
5133
  logger.error('Session fetch error:', err.message);
5140
5134
  this.data.sessions = this.data.sessions || [];
5141
5135
  this.data.openclaw = { gateway: { reachable: false } };
@@ -5216,8 +5210,6 @@ class Dashboard {
5216
5210
  });
5217
5211
 
5218
5212
  this.render();
5219
- // Store metrics in database for historical tracking
5220
- database.storeMetricsSnapshot(this.data);
5221
5213
  } catch (e) {}
5222
5214
  }
5223
5215
 
@@ -5737,8 +5729,6 @@ class WebDashboard extends Dashboard {
5737
5729
  }
5738
5730
 
5739
5731
  // Initialize data fetching (reuse parent's start() logic but without UI)
5740
- await database.initDatabase();
5741
- database.cleanupOldData(30);
5742
5732
  gatewayManager.init(this.settings);
5743
5733
  performanceMonitor.start();
5744
5734
 
package/package.json CHANGED
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "name": "claw-dashboard",
3
- "version": "2.1.1",
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,23 +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",
64
74
  "blessed": "npm:@pm2/blessed@^0.1.81",
65
75
  "chalk": "^5.3.0",
66
- "sql.js": "^1.14.0",
67
76
  "systeminformation": "^5.21.22"
68
77
  },
69
78
  "devDependencies": {
70
79
  "@eslint/js": "^10.0.1",
71
- "c8": "^11.0.0",
72
- "esbuild": "^0.27.3",
80
+ "c8": "^12.0.0",
81
+ "esbuild": "^0.28.1",
73
82
  "eslint": "^10.0.2",
74
83
  "husky": "^9.1.7",
75
84
  "jest": "^30.2.0",
76
- "lint-staged": "^16.3.0"
85
+ "lint-staged": "^17.2.0"
77
86
  },
78
87
  "lint-staged": {
79
88
  "src/**/*.{js,mjs,cjs}": [
@@ -85,6 +94,7 @@
85
94
  "xml2js": "^0.6.2",
86
95
  "form-data": "^4.0.0",
87
96
  "qs": "^6.14.1",
88
- "tough-cookie": "^5.0.0"
97
+ "tough-cookie": "^5.0.0",
98
+ "brace-expansion": "^5.0.8"
89
99
  }
90
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
  }