From 198eaa0242436a99dfb6babf76394b7aafadf336 Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Thu, 16 Jul 2026 17:31:40 -0700 Subject: [PATCH] Prevent malformed config from being overwritten --- src/datastore/DataStore.ts | 23 ++++++++++++++++++++- tests/DataStore.test.ts | 42 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/DataStore.test.ts diff --git a/src/datastore/DataStore.ts b/src/datastore/DataStore.ts index 96efef8..18e0b0d 100644 --- a/src/datastore/DataStore.ts +++ b/src/datastore/DataStore.ts @@ -38,6 +38,24 @@ const CURRENT_THEME = 'currentTheme' const DEFAULT_CAPTAIN_ROOT_DOMAIN = 'captain.localhost' +export function validateConfigFile(configPath: string) { + if (!fs.pathExistsSync(configPath)) { + return + } + + try { + JSON.parse(fs.readFileSync(configPath, 'utf8')) + } catch (error) { + if (error instanceof SyntaxError) { + throw new Error( + `Cannot start CapRover because ${configPath} contains invalid JSON. Fix the file or restore it from a backup, then restart CapRover.` + ) + } + + throw error + } +} + const DEFAULT_NGINX_BASE_CONFIG = fs .readFileSync(__dirname + '/../../template/base-nginx-conf.ejs') .toString() @@ -69,11 +87,14 @@ class DataStore { private projectsDataStore: ProjectsDataStore constructor(namespace: string) { + const configPath = `${CaptainConstants.captainDataDirectory}/config-${namespace}.json` + validateConfigFile(configPath) + const data = new Configstore( `captain-store-${namespace}`, // This value seems to be unused {}, { - configPath: `${CaptainConstants.captainDataDirectory}/config-${namespace}.json`, + configPath, } ) diff --git a/tests/DataStore.test.ts b/tests/DataStore.test.ts new file mode 100644 index 0000000..4e361b9 --- /dev/null +++ b/tests/DataStore.test.ts @@ -0,0 +1,42 @@ +import fs = require('fs-extra') +import os = require('os') +import path = require('path') +import { validateConfigFile } from '../src/datastore/DataStore' + +describe('DataStore config validation', () => { + let tempDirectory: string + + beforeEach(() => { + tempDirectory = fs.mkdtempSync( + path.join(os.tmpdir(), 'caprover-config-') + ) + }) + + afterEach(() => { + fs.removeSync(tempDirectory) + }) + + test('does not modify a malformed config file', () => { + const configPath = path.join(tempDirectory, 'config-captain.json') + const malformedConfig = '{"namespace":"captain",}' + fs.writeFileSync(configPath, malformedConfig) + + expect(() => validateConfigFile(configPath)).toThrow( + `Cannot start CapRover because ${configPath} contains invalid JSON.` + ) + expect(fs.readFileSync(configPath, 'utf8')).toBe(malformedConfig) + }) + + test('accepts a valid config file', () => { + const configPath = path.join(tempDirectory, 'config-captain.json') + fs.writeJsonSync(configPath, { namespace: 'captain' }) + + expect(() => validateConfigFile(configPath)).not.toThrow() + }) + + test('accepts a missing config file on first startup', () => { + const configPath = path.join(tempDirectory, 'config-captain.json') + + expect(() => validateConfigFile(configPath)).not.toThrow() + }) +})