fix: harden store writes, strict schemas, null cfg

This commit is contained in:
smartass committed 2026-06-11 23:26:54 +05:00
1 parent 46906e3f5b
commit 51aa6e3258
7 files changed
+96 -26

No files matched your search

+6
View File
@@ -36,6 +36,12 @@ export const readConfigFile = (path: string | undefined): ConnectionConfig[] =>
console.error('dbmole: config file ' + path + ' is not valid JSON, ignoring')
return []
}
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
console.error(
'dbmole: config file ' + path + ' must contain { "connections": [...] }, ignoring'
)
return []
}
const connections = (parsed as { connections?: unknown }).connections
if (!Array.isArray(connections)) {
console.error(
+20 -5
View File
@@ -1,4 +1,4 @@
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { dirname, join } from 'node:path'
import { type ConnectionConfig, connectionConfigSchema } from './types.js'
@@ -47,10 +47,25 @@ let writeSeq = 0
export const writeStore = (path: string, connections: ConnectionConfig[]): void => {
mkdirSync(dirname(path), { recursive: true, mode: 0o700 })
writeSeq += 1
const tmp = path + '.' + process.pid + '.' + writeSeq + '.tmp'
const items = connections.map((c) => JSON.stringify(c, null, 4))
const content = items.length === 0 ? '[]\n' : '[\n' + items.join(',\n') + '\n]\n'
writeFileSync(tmp, content, { mode: 0o600 })
renameSync(tmp, path)
let tmp: string
for (;;) {
writeSeq += 1
tmp = path + '.' + process.pid + '.' + writeSeq + '.tmp'
try {
writeFileSync(tmp, content, { mode: 0o600, flag: 'wx' })
break
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
throw error
}
}
}
try {
renameSync(tmp, path)
} catch (error) {
rmSync(tmp, { force: true })
throw error
}
}
+25 -21
View File
@@ -1,27 +1,31 @@
import * as z from 'zod'
export const sshConfigSchema = z.object({
host: z.string().min(1),
port: z.number().int().positive().default(22),
user: z.string().min(1),
password: z.string().optional(),
privateKey: z.string().optional(),
privateKeyPath: z.string().optional(),
passphrase: z.string().optional(),
agent: z.boolean().optional()
})
export const sshConfigSchema = z
.object({
host: z.string().min(1),
port: z.number().int().positive().default(22),
user: z.string().min(1),
password: z.string().optional(),
privateKey: z.string().optional(),
privateKeyPath: z.string().optional(),
passphrase: z.string().optional(),
agent: z.boolean().optional()
})
.strict()
export const connectionConfigSchema = z.object({
name: z.string().regex(/^[a-zA-Z0-9_-]+$/, 'name must match [a-zA-Z0-9_-]+'),
type: z.enum(['postgres', 'mysql']),
host: z.string().min(1),
port: z.number().int().positive().optional(),
user: z.string().min(1),
password: z.string().optional(),
database: z.string().optional(),
readonly: z.boolean().default(false),
ssh: sshConfigSchema.optional()
})
export const connectionConfigSchema = z
.object({
name: z.string().regex(/^[a-zA-Z0-9_-]+$/, 'name must match [a-zA-Z0-9_-]+'),
type: z.enum(['postgres', 'mysql']),
host: z.string().min(1),
port: z.number().int().positive().optional(),
user: z.string().min(1),
password: z.string().optional(),
database: z.string().optional(),
readonly: z.boolean().default(false),
ssh: sshConfigSchema.optional()
})
.strict()
export type SshConfig = z.infer<typeof sshConfigSchema>
export type ConnectionConfig = z.infer<typeof connectionConfigSchema>