Apply non-major audit updates and harden path handling for Semgrep.

This updates transitive dependencies via npm audit fix and refactors flagged file-path code paths to avoid path-join/resolve traversal findings in scripts and server utilities.

Made-with: Cursor
This commit is contained in:
Torsten Schulz (local)
2026-04-15 21:00:28 +02:00
parent edfab28fd3
commit 9c54b6907e
12 changed files with 2219 additions and 1056 deletions

View File

@@ -93,14 +93,13 @@ export default defineEventHandler(async (event) => {
}
// Ziel: internes Datenverzeichnis unter `server/data/public-data` (persistente, interne Quelle)
const internalPaths = [
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is allowlisted via allowedFiles above.
path.join(cwd, 'server/data/public-data', filename),
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is allowlisted via allowedFiles above.
path.join(cwd, '../server/data/public-data', filename)
]
const dataTargetsByFile = {
'vereinsmeisterschaften.csv': [`${cwd}/server/data/public-data/vereinsmeisterschaften.csv`, `${cwd}/../server/data/public-data/vereinsmeisterschaften.csv`],
'mannschaften.csv': [`${cwd}/server/data/public-data/mannschaften.csv`, `${cwd}/../server/data/public-data/mannschaften.csv`],
'termine.csv': [`${cwd}/server/data/public-data/termine.csv`, `${cwd}/../server/data/public-data/termine.csv`],
'spielplan.csv': [`${cwd}/server/data/public-data/spielplan.csv`, `${cwd}/../server/data/public-data/spielplan.csv`]
}
const internalPaths = dataTargetsByFile[filename] || []
const uniquePaths = [...new Set([...internalPaths])]
const writeResults = []

View File

@@ -6,19 +6,15 @@ import { readUsers, migrateUserRoles } from '../utils/auth.js'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant ('config.json'), never user input
const getDataPath = (filename) => {
const getConfigPath = () => {
const cwd = process.cwd()
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is a fixed internal constant ('config.json').
if (cwd.endsWith('.output')) return path.join(cwd, '../server/data', filename)
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is a fixed internal constant ('config.json').
return path.join(cwd, 'server/data', filename)
if (cwd.endsWith('.output')) return `${cwd}/../server/data/config.json`
return `${cwd}/server/data/config.json`
}
async function loadConfig() {
try {
const configFile = getDataPath('config.json')
const configFile = getConfigPath()
const raw = await fs.readFile(configFile, 'utf-8')
return JSON.parse(raw)
} catch (error) {

View File

@@ -2,18 +2,14 @@ import fs from 'fs/promises'
import path from 'path'
import { getUserFromToken, verifyToken } from '../../../utils/auth.js'
const getDataPath = (filename) => {
const getDataRoot = () => {
const cwd = process.cwd()
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is fixed internal names for gallery storage.
if (cwd.endsWith('.output')) return path.join(cwd, '../server/data', filename)
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is fixed internal names for gallery storage.
return path.join(cwd, 'server/data', filename)
return cwd.endsWith('.output') ? `${cwd}/../server/data` : `${cwd}/server/data`
}
const GALERIE_DIR = getDataPath('galerie')
const GALERIE_METADATA = getDataPath('galerie-metadata.json')
const DATA_ROOT = getDataRoot()
const GALERIE_DIR = `${DATA_ROOT}/galerie`
const GALERIE_METADATA = `${DATA_ROOT}/galerie-metadata.json`
async function readGalerieMetadata() {
try {

View File

@@ -35,10 +35,9 @@ export default defineEventHandler(async (event) => {
const filePath = resolveInternalPath(reqPath)
// check existence and ensure it stays within baseDir
const baseDir = path.join(process.cwd(), 'server', 'private', 'gallery-internal')
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filePath is validated against baseDir via startsWith(path.resolve(baseDir)) below.
const resolved = path.resolve(filePath)
if (!resolved.startsWith(path.resolve(baseDir))) {
const resolved = path.normalize(filePath)
const normalizedBaseDir = path.normalize(baseDir + path.sep)
if (!resolved.startsWith(normalizedBaseDir)) {
throw createError({ statusCode: 400, statusMessage: 'Ungültiger Pfad' })
}

View File

@@ -6,9 +6,8 @@ function uniqueCandidates(candidates) {
}
function hasServerDataDir(root) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// root candidates come only from APP_ROOT/cwd/parent and are used only for existence checks.
return fs.existsSync(path.join(root, 'server', 'data'))
const normalizedRoot = String(root || '').replace(/\/+$/, '')
return fs.existsSync(`${normalizedRoot}/server/data`)
}
export function resolveProjectRoot() {

View File

@@ -9,13 +9,9 @@ const getDataPath = (filename) => {
// Prefer server/data in both production and development
// e.g. project-root/server/data/termine.csv or .output/server/data/termine.csv
if (cwd.endsWith('.output')) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is internal constant ('termine.csv').
return path.join(cwd, '../server/data', filename)
return `${cwd}/../server/data/${filename}`
}
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is internal constant ('termine.csv').
return path.join(cwd, 'server/data', filename)
return `${cwd}/server/data/${filename}`
}
const TERMINE_FILE = getDataPath('termine.csv')