dev #41
@@ -20,8 +20,10 @@ const FILES = {
|
|||||||
function getDataPath(filename) {
|
function getDataPath(filename) {
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
if (cwd.endsWith('.output')) {
|
if (cwd.endsWith('.output')) {
|
||||||
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
||||||
return path.join(cwd, '../server/data', filename)
|
return path.join(cwd, '../server/data', filename)
|
||||||
}
|
}
|
||||||
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
||||||
return path.join(cwd, 'server/data', filename)
|
return path.join(cwd, 'server/data', filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,21 @@ function buildBackupName(date = new Date()) {
|
|||||||
return `${date.toISOString().replace(/[:.]/g, '-')}-${randomSuffix}.bak`
|
return `${date.toISOString().replace(/[:.]/g, '-')}-${randomSuffix}.bak`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveDataFileBackupPath(backupDir, backupName) {
|
||||||
|
if (typeof backupName !== 'string' || !backupName.endsWith('.bak') || path.basename(backupName) !== backupName) {
|
||||||
|
throw new Error('Ungueltiger Backup-Dateiname')
|
||||||
|
}
|
||||||
|
|
||||||
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
||||||
|
const resolvedBackupDir = path.resolve(backupDir)
|
||||||
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
||||||
|
const resolvedBackupPath = path.resolve(resolvedBackupDir, backupName)
|
||||||
|
if (path.dirname(resolvedBackupPath) !== resolvedBackupDir) {
|
||||||
|
throw new Error('Backup-Datei liegt ausserhalb des Backup-Ordners')
|
||||||
|
}
|
||||||
|
return resolvedBackupPath
|
||||||
|
}
|
||||||
|
|
||||||
async function ensureDirectory(dirPath) {
|
async function ensureDirectory(dirPath) {
|
||||||
await fs.mkdir(dirPath, { recursive: true })
|
await fs.mkdir(dirPath, { recursive: true })
|
||||||
}
|
}
|
||||||
@@ -55,10 +70,11 @@ async function rotateOldBackups(backupDir, maxBackups) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const toDelete = backups.slice(0, overflowCount)
|
const toDelete = backups.slice(0, overflowCount)
|
||||||
await Promise.all(toDelete.map((name) => fs.unlink(path.join(backupDir, name)).catch(() => {})))
|
await Promise.all(toDelete.map((name) => fs.unlink(resolveDataFileBackupPath(backupDir, name)).catch(() => {})))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBackupDirectoryForDataFile(filePath) {
|
export function getBackupDirectoryForDataFile(filePath) {
|
||||||
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
||||||
const resolvedPath = path.resolve(filePath)
|
const resolvedPath = path.resolve(filePath)
|
||||||
return path.join(getBackupRoot(), sanitizeFileKey(resolvedPath))
|
return path.join(getBackupRoot(), sanitizeFileKey(resolvedPath))
|
||||||
}
|
}
|
||||||
@@ -77,6 +93,7 @@ export async function writeDataFileWithRotation(filePath, content, {
|
|||||||
encoding = 'utf-8',
|
encoding = 'utf-8',
|
||||||
maxBackups = DEFAULT_MAX_BACKUPS
|
maxBackups = DEFAULT_MAX_BACKUPS
|
||||||
} = {}) {
|
} = {}) {
|
||||||
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
||||||
const resolvedPath = path.resolve(filePath)
|
const resolvedPath = path.resolve(filePath)
|
||||||
await ensureDirectory(path.dirname(resolvedPath))
|
await ensureDirectory(path.dirname(resolvedPath))
|
||||||
|
|
||||||
@@ -100,7 +117,7 @@ export async function writeDataFileWithRotation(filePath, content, {
|
|||||||
if (existingContent !== null) {
|
if (existingContent !== null) {
|
||||||
const backupDir = getBackupDirectoryForDataFile(resolvedPath)
|
const backupDir = getBackupDirectoryForDataFile(resolvedPath)
|
||||||
await ensureDirectory(backupDir)
|
await ensureDirectory(backupDir)
|
||||||
backupPath = path.join(backupDir, buildBackupName())
|
backupPath = resolveDataFileBackupPath(backupDir, buildBackupName())
|
||||||
await fs.copyFile(resolvedPath, backupPath)
|
await fs.copyFile(resolvedPath, backupPath)
|
||||||
await rotateOldBackups(backupDir, maxBackups)
|
await rotateOldBackups(backupDir, maxBackups)
|
||||||
}
|
}
|
||||||
@@ -116,9 +133,10 @@ export async function writeDataFileWithRotation(filePath, content, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function restoreDataFileBackup(filePath, backupName, options = {}) {
|
export async function restoreDataFileBackup(filePath, backupName, options = {}) {
|
||||||
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
||||||
const resolvedPath = path.resolve(filePath)
|
const resolvedPath = path.resolve(filePath)
|
||||||
const backupDir = getBackupDirectoryForDataFile(resolvedPath)
|
const backupDir = getBackupDirectoryForDataFile(resolvedPath)
|
||||||
const sourceBackupPath = path.join(backupDir, backupName)
|
const sourceBackupPath = resolveDataFileBackupPath(backupDir, backupName)
|
||||||
const backupContent = await fs.readFile(sourceBackupPath, 'utf-8')
|
const backupContent = await fs.readFile(sourceBackupPath, 'utf-8')
|
||||||
|
|
||||||
return writeDataFileWithRotation(resolvedPath, backupContent, options)
|
return writeDataFileWithRotation(resolvedPath, backupContent, options)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import path from 'path'
|
|||||||
import { promises as fs } from 'fs'
|
import { promises as fs } from 'fs'
|
||||||
import {
|
import {
|
||||||
getBackupDirectoryForDataFile,
|
getBackupDirectoryForDataFile,
|
||||||
|
resolveDataFileBackupPath,
|
||||||
listDataFileBackups,
|
listDataFileBackups,
|
||||||
restoreDataFileBackup,
|
restoreDataFileBackup,
|
||||||
writeDataFileWithRotation
|
writeDataFileWithRotation
|
||||||
@@ -60,7 +61,7 @@ describe('Data file rotation utility', () => {
|
|||||||
expect(backups.length).toBe(1)
|
expect(backups.length).toBe(1)
|
||||||
|
|
||||||
const backupDir = getBackupDirectoryForDataFile(dataFile)
|
const backupDir = getBackupDirectoryForDataFile(dataFile)
|
||||||
const backupContent = await fs.readFile(path.join(backupDir, backups[0]), 'utf-8')
|
const backupContent = await fs.readFile(resolveDataFileBackupPath(backupDir, backups[0]), 'utf-8')
|
||||||
expect(backupContent).toBe('v1')
|
expect(backupContent).toBe('v1')
|
||||||
|
|
||||||
const currentContent = await fs.readFile(dataFile, 'utf-8')
|
const currentContent = await fs.readFile(dataFile, 'utf-8')
|
||||||
@@ -91,7 +92,7 @@ describe('Data file rotation utility', () => {
|
|||||||
|
|
||||||
const backupDir = getBackupDirectoryForDataFile(dataFile)
|
const backupDir = getBackupDirectoryForDataFile(dataFile)
|
||||||
const backupContents = await Promise.all(
|
const backupContents = await Promise.all(
|
||||||
backups.map((name) => fs.readFile(path.join(backupDir, name), 'utf-8'))
|
backups.map((name) => fs.readFile(resolveDataFileBackupPath(backupDir, name), 'utf-8'))
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(backupContents).toEqual(expect.arrayContaining(['v2', 'v3']))
|
expect(backupContents).toEqual(expect.arrayContaining(['v2', 'v3']))
|
||||||
@@ -109,7 +110,7 @@ describe('Data file rotation utility', () => {
|
|||||||
const resolved = await Promise.all(
|
const resolved = await Promise.all(
|
||||||
beforeRestoreBackups.map(async (name) => ({
|
beforeRestoreBackups.map(async (name) => ({
|
||||||
name,
|
name,
|
||||||
content: await fs.readFile(path.join(backupDir, name), 'utf-8')
|
content: await fs.readFile(resolveDataFileBackupPath(backupDir, name), 'utf-8')
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
const v1Entry = resolved.find((entry) => entry.content === 'v1')
|
const v1Entry = resolved.find((entry) => entry.content === 'v1')
|
||||||
@@ -124,7 +125,7 @@ describe('Data file rotation utility', () => {
|
|||||||
|
|
||||||
const afterRestoreBackups = await listDataFileBackups(dataFile)
|
const afterRestoreBackups = await listDataFileBackups(dataFile)
|
||||||
const afterContents = await Promise.all(
|
const afterContents = await Promise.all(
|
||||||
afterRestoreBackups.map((name) => fs.readFile(path.join(backupDir, name), 'utf-8'))
|
afterRestoreBackups.map((name) => fs.readFile(resolveDataFileBackupPath(backupDir, name), 'utf-8'))
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(afterContents).toContain('v3')
|
expect(afterContents).toContain('v3')
|
||||||
|
|||||||
Reference in New Issue
Block a user