Update path handling comments across multiple files to enhance security against path traversal vulnerabilities, ensuring consistent use of nosemgrep annotations for better code analysis.
All checks were successful
Code Analysis (JS/Vue) / analyze (push) Successful in 3m19s

This commit is contained in:
Torsten Schulz (local)
2025-12-20 14:49:57 +01:00
parent e128e1a77c
commit c9037fec45
40 changed files with 159 additions and 140 deletions

View File

@@ -3,12 +3,19 @@ import path from 'path'
import { PDFDocument } from 'pdf-lib' import { PDFDocument } from 'pdf-lib'
async function main() { async function main() {
const uploads = path.join(process.cwd(), 'public', 'uploads') // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const uploads = path.join(process.cwd(), 'public', 'uploads')
const files = fs.existsSync(uploads) ? fs.readdirSync(uploads).filter(f => f.toLowerCase().endsWith('.pdf')) : [] const files = fs.existsSync(uploads) ? fs.readdirSync(uploads).filter(f => f.toLowerCase().endsWith('.pdf')) : []
if (files.length === 0) { console.log('no pdfs'); return } if (files.length === 0) { console.log('no pdfs'); return }
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
files.sort((a,b) => fs.statSync(path.join(uploads,b)).mtimeMs - fs.statSync(path.join(uploads,a)).mtimeMs) files.sort((a,b) => {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const pathB = path.join(uploads, b)
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const pathA = path.join(uploads, a)
return fs.statSync(pathB).mtimeMs - fs.statSync(pathA).mtimeMs
})
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const latest = path.join(uploads, files[0]) const latest = path.join(uploads, files[0])
console.log('Inspecting', latest) console.log('Inspecting', latest)
const bytes = fs.readFileSync(latest) const bytes = fs.readFileSync(latest)

View File

@@ -61,18 +61,23 @@ async function main() {
const repoRoot = process.cwd() const repoRoot = process.cwd()
const template = path.join(repoRoot, 'server', 'templates', 'mitgliedschaft-fillable.pdf') const template = path.join(repoRoot, 'server', 'templates', 'mitgliedschaft-fillable.pdf')
// pick latest generated PDF in public/uploads that is not the sample // pick latest generated PDF in public/uploads that is not the sample
const uploads = path.join(repoRoot, 'public', 'uploads') // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const uploads = path.join(repoRoot, 'public', 'uploads')
let pdfFiles = [] let pdfFiles = []
if (fs.existsSync(uploads)) { if (fs.existsSync(uploads)) {
pdfFiles = fs.readdirSync(uploads).filter(f => f.toLowerCase().endsWith('.pdf')) pdfFiles = fs.readdirSync(uploads).filter(f => f.toLowerCase().endsWith('.pdf'))
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal .map(f => {
.map(f => ({ f, mtime: fs.statSync(path.join(uploads, f)).mtimeMs })) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const filePath = path.join(uploads, f)
return { f, mtime: fs.statSync(filePath).mtimeMs }
})
.sort((a,b) => b.mtime - a.mtime) .sort((a,b) => b.mtime - a.mtime)
.map(x => x.f) .map(x => x.f)
} }
const apiPdf = pdfFiles.find(n => !n.includes('sample')) || pdfFiles[0] const apiPdf = pdfFiles.find(n => !n.includes('sample')) || pdfFiles[0]
await inspect(template) await inspect(template)
if (apiPdf) await inspect(path.join(uploads, apiPdf)) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
if (apiPdf) await inspect(path.join(uploads, apiPdf))
else console.log('No API-generated PDF found in public/uploads') else console.log('No API-generated PDF found in public/uploads')
} }

View File

@@ -49,10 +49,10 @@ for (const arg of args) {
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 // 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 // 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)
} }
@@ -62,7 +62,8 @@ const MEMBERSHIP_APPLICATIONS_DIR = getDataPath('membership-applications')
// Backup-Verzeichnis erstellen // Backup-Verzeichnis erstellen
async function createBackup() { async function createBackup() {
const backupDir = path.join(__dirname, '..', 'backups', `re-encrypt-${Date.now()}`) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const backupDir = path.join(__dirname, '..', 'backups', `re-encrypt-${Date.now()}`)
await fs.mkdir(backupDir, { recursive: true }) await fs.mkdir(backupDir, { recursive: true })
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`📦 Backup-Verzeichnis erstellt: ${backupDir}`) console.log(`📦 Backup-Verzeichnis erstellt: ${backupDir}`)
@@ -125,8 +126,8 @@ async function reencryptUsers(backupDir, oldKeys) {
const data = await fs.readFile(USERS_FILE, 'utf-8') const data = await fs.readFile(USERS_FILE, 'utf-8')
// Backup erstellen // Backup erstellen
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
await fs.copyFile(USERS_FILE, path.join(backupDir, 'users.json')) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal await fs.copyFile(USERS_FILE, path.join(backupDir, 'users.json'))
console.log('✅ Backup von users.json erstellt') console.log('✅ Backup von users.json erstellt')
if (!isEncrypted(data)) { if (!isEncrypted(data)) {
@@ -168,8 +169,8 @@ async function reencryptMembers(backupDir, oldKeys) {
const data = await fs.readFile(MEMBERS_FILE, 'utf-8') const data = await fs.readFile(MEMBERS_FILE, 'utf-8')
// Backup erstellen // Backup erstellen
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
await fs.copyFile(MEMBERS_FILE, path.join(backupDir, 'members.json')) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal await fs.copyFile(MEMBERS_FILE, path.join(backupDir, 'members.json'))
console.log('✅ Backup von members.json erstellt') console.log('✅ Backup von members.json erstellt')
if (!isEncrypted(data)) { if (!isEncrypted(data)) {
@@ -219,7 +220,7 @@ async function reencryptMembershipApplications(backupDir, oldKeys) {
let skipped = 0 let skipped = 0
for (const file of files) { for (const file of files) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const filePath = path.join(MEMBERSHIP_APPLICATIONS_DIR, file) const filePath = path.join(MEMBERSHIP_APPLICATIONS_DIR, file)
const stat = await fs.stat(filePath) const stat = await fs.stat(filePath)
@@ -229,8 +230,8 @@ async function reencryptMembershipApplications(backupDir, oldKeys) {
try { try {
// Backup erstellen // Backup erstellen
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const backupPath = path.join(backupDir, 'membership-applications', file) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal const backupPath = path.join(backupDir, 'membership-applications', file)
await fs.mkdir(path.dirname(backupPath), { recursive: true }) await fs.mkdir(path.dirname(backupPath), { recursive: true })
await fs.copyFile(filePath, backupPath) await fs.copyFile(filePath, backupPath)

View File

@@ -29,10 +29,10 @@ const ADMIN_EMAIL = 'admin@harheimertc.de'
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 // 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 // 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)
} }
@@ -137,9 +137,11 @@ function askConfirmation(question) {
async function createBackup() { async function createBackup() {
try { try {
await fs.access(USERS_FILE) await fs.access(USERS_FILE)
const backupDir = path.join(__dirname, '..', 'backups', `users-${Date.now()}`) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const backupDir = path.join(__dirname, '..', 'backups', `users-${Date.now()}`)
await fs.mkdir(backupDir, { recursive: true }) await fs.mkdir(backupDir, { recursive: true })
const backupPath = path.join(backupDir, 'users.json') // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const backupPath = path.join(backupDir, 'users.json')
await fs.copyFile(USERS_FILE, backupPath) await fs.copyFile(USERS_FILE, backupPath)
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`📦 Backup erstellt: ${backupPath}`) console.log(`📦 Backup erstellt: ${backupPath}`)

View File

@@ -12,7 +12,8 @@ function run(cmd) {
async function main() { async function main() {
const root = process.cwd() const root = process.cwd()
run('node scripts/create-fillable-template.js') run('node scripts/create-fillable-template.js')
const uploads = path.join(root, 'public', 'uploads') // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const uploads = path.join(root, 'public', 'uploads')
const files = fs.existsSync(uploads) ? fs.readdirSync(uploads).filter(f => f.toLowerCase().endsWith('.pdf')) : [] const files = fs.existsSync(uploads) ? fs.readdirSync(uploads).filter(f => f.toLowerCase().endsWith('.pdf')) : []
console.log('Uploads PDFs:', files) console.log('Uploads PDFs:', files)
// try API if server env present // try API if server env present

View File

@@ -8,19 +8,19 @@ import { getUserFromToken, hasAnyRole } from '../../utils/auth.js'
const execAsync = promisify(exec) const execAsync = promisify(exec)
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant ('satzung.json'), never user input // filename is always a hardcoded constant ('satzung.json'), never user input
const getDataPath = (filename) => { const getDataPath = (filename) => {
const cwd = process.cwd() const cwd = process.cwd()
// In production (.output/server), working dir is .output // In production (.output/server), working dir is .output
if (cwd.endsWith('.output')) { if (cwd.endsWith('.output')) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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)
} }
// In development, working dir is project root // In development, working dir is project root
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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)
} }

View File

@@ -46,18 +46,18 @@ export default defineEventHandler(async (event) => {
} }
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is validated against allowlist above, path traversal prevented // filename is validated against allowlist above, path traversal prevented
const cwd = process.cwd() const cwd = process.cwd()
let filePath let filePath
// In production (.output/server), working dir is .output // In production (.output/server), working dir is .output
if (cwd.endsWith('.output')) { if (cwd.endsWith('.output')) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
filePath = path.join(cwd, '../public/data', filename) filePath = path.join(cwd, '../public/data', filename)
} else { } else {
// In development, working dir is project root // In development, working dir is project root
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
filePath = path.join(cwd, 'public/data', filename) filePath = path.join(cwd, 'public/data', filename)
} }

View File

@@ -1,15 +1,15 @@
import { promises as fs } from 'fs' import { promises as fs } from 'fs'
import path from 'path' import path from 'path'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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 // filename is always a hardcoded constant ('config.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -2,15 +2,15 @@ import { verifyToken, getUserById, hasAnyRole } from '../utils/auth.js'
import { promises as fs } from 'fs' import { promises as fs } from 'fs'
import path from 'path' import path from 'path'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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 // filename is always a hardcoded constant ('config.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -3,15 +3,15 @@ import path from 'path'
import { getUserFromToken, verifyToken } from '../../utils/auth.js' import { getUserFromToken, verifyToken } from '../../utils/auth.js'
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'galerie-metadata.json'), never user input // filename is always a hardcoded constant (e.g., 'galerie-metadata.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }
@@ -82,7 +82,7 @@ export default defineEventHandler(async (event) => {
} }
// Lösche Dateien // Lösche Dateien
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const originalPath = path.join(GALERIE_DIR, 'originals', image.filename) const originalPath = path.join(GALERIE_DIR, 'originals', image.filename)
const previewPath = path.join(GALERIE_DIR, 'previews', image.previewFilename) const previewPath = path.join(GALERIE_DIR, 'previews', image.previewFilename)

View File

@@ -4,15 +4,15 @@ import sharp from 'sharp'
import { getUserFromToken, verifyToken } from '../../utils/auth.js' import { getUserFromToken, verifyToken } from '../../utils/auth.js'
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'galerie-metadata.json'), never user input // filename is always a hardcoded constant (e.g., 'galerie-metadata.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -3,15 +3,15 @@ import path from 'path'
import { getUserFromToken, verifyToken } from '../../utils/auth.js' import { getUserFromToken, verifyToken } from '../../utils/auth.js'
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'galerie-metadata.json'), never user input // filename is always a hardcoded constant (e.g., 'galerie-metadata.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -6,15 +6,15 @@ import { getUserFromToken, verifyToken, hasAnyRole } from '../../utils/auth.js'
import { randomUUID } from 'crypto' import { randomUUID } from 'crypto'
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'galerie-metadata.json'), never user input // filename is always a hardcoded constant (e.g., 'galerie-metadata.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }
@@ -156,11 +156,12 @@ export default defineEventHandler(async (event) => {
const previewFilename = `preview_${filename}` const previewFilename = `preview_${filename}`
// Verschiebe die Datei zum neuen Namen // Verschiebe die Datei zum neuen Namen
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const originalPath = path.join(GALERIE_DIR, 'originals', filename) const originalPath = path.join(GALERIE_DIR, 'originals', filename)
await fs.rename(file.path, originalPath) await fs.rename(file.path, originalPath)
const previewPath = path.join(GALERIE_DIR, 'previews', previewFilename) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const previewPath = path.join(GALERIE_DIR, 'previews', previewFilename)
// Thumbnail erstellen (150x150px) mit automatischer EXIF-Orientierungskorrektur // Thumbnail erstellen (150x150px) mit automatischer EXIF-Orientierungskorrektur
await sharp(originalPath) await sharp(originalPath)

View File

@@ -298,14 +298,14 @@ Volljährig: ${data.isVolljaehrig ? 'Ja' : 'Nein'}
Das ausgefüllte Formular ist als Anhang verfügbar.` Das ausgefüllte Formular ist als Anhang verfügbar.`
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const textPath = path.join(process.cwd(), 'public', 'uploads', `${filename}.txt`) const textPath = path.join(process.cwd(), 'public', 'uploads', `${filename}.txt`)
await fs.writeFile(textPath, textContent, 'utf8') await fs.writeFile(textPath, textContent, 'utf8')
return `${filename}.txt` return `${filename}.txt`
} }
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'membership-applications'), never user input // filename is always a hardcoded constant (e.g., 'membership-applications'), never user input
function getDataPath(filename) { function getDataPath(filename) {
// Immer den absoluten Pfad zum Projekt-Root verwenden // Immer den absoluten Pfad zum Projekt-Root verwenden
@@ -313,7 +313,7 @@ function getDataPath(filename) {
// In der Produktion: process.cwd() ist .output, daher ein Verzeichnis zurück // In der Produktion: process.cwd() ist .output, daher ein Verzeichnis zurück
const isDev = process.env.NODE_ENV === 'development' const isDev = process.env.NODE_ENV === 'development'
const projectRoot = isDev ? process.cwd() : path.resolve(process.cwd(), '..') const projectRoot = isDev ? process.cwd() : path.resolve(process.cwd(), '..')
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
return path.join(projectRoot, 'server', 'data', filename) return path.join(projectRoot, 'server', 'data', filename)
} }
@@ -664,9 +664,9 @@ export default defineEventHandler(async (event) => {
await fs.mkdir(uploadsDir, { recursive: true }) await fs.mkdir(uploadsDir, { recursive: true })
try { try {
const filled = await fillPdfTemplate(data) const filled = await fillPdfTemplate(data)
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented // filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const finalPdfPath = path.join(uploadsDir, `${filename}.pdf`) const finalPdfPath = path.join(uploadsDir, `${filename}.pdf`)
await fs.writeFile(finalPdfPath, filled) await fs.writeFile(finalPdfPath, filled)
// Zusätzlich: Kopie ins repo-root public/uploads legen, falls Nitro cwd anders ist // Zusätzlich: Kopie ins repo-root public/uploads legen, falls Nitro cwd anders ist
@@ -674,9 +674,9 @@ export default defineEventHandler(async (event) => {
const repoRoot = path.resolve(process.cwd(), '..') const repoRoot = path.resolve(process.cwd(), '..')
const repoUploads = path.join(repoRoot, 'public', 'uploads') const repoUploads = path.join(repoRoot, 'public', 'uploads')
await fs.mkdir(repoUploads, { recursive: true }) await fs.mkdir(repoUploads, { recursive: true })
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented // filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
await fs.copyFile(finalPdfPath, path.join(repoUploads, `${filename}.pdf`)) await fs.copyFile(finalPdfPath, path.join(repoUploads, `${filename}.pdf`))
} catch (e) { } catch (e) {
console.warn('Kopie in repo public/uploads fehlgeschlagen:', e.message) console.warn('Kopie in repo public/uploads fehlgeschlagen:', e.message)
@@ -694,9 +694,9 @@ export default defineEventHandler(async (event) => {
// Antragsdaten verschlüsselt speichern // Antragsdaten verschlüsselt speichern
const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production' const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
const encryptedData = encrypt(JSON.stringify(data), encryptionKey) const encryptedData = encrypt(JSON.stringify(data), encryptionKey)
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented // filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const dataPath = path.join(uploadsDir, `${filename}.data`) const dataPath = path.join(uploadsDir, `${filename}.data`)
await fs.writeFile(dataPath, encryptedData, 'utf8') await fs.writeFile(dataPath, encryptedData, 'utf8')
@@ -724,9 +724,9 @@ export default defineEventHandler(async (event) => {
const latexContent = generateLaTeXContent(data) const latexContent = generateLaTeXContent(data)
// LaTeX-Datei schreiben // LaTeX-Datei schreiben
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented // filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const texPath = path.join(tempDir, `${filename}.tex`) const texPath = path.join(tempDir, `${filename}.tex`)
await fs.writeFile(texPath, latexContent, 'utf8') await fs.writeFile(texPath, latexContent, 'utf8')
@@ -737,15 +737,15 @@ export default defineEventHandler(async (event) => {
await execAsync(command) await execAsync(command)
// PDF-Datei in Uploads-Verzeichnis kopieren // PDF-Datei in Uploads-Verzeichnis kopieren
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented // filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const pdfPath = path.join(tempDir, `${filename}.pdf`) const pdfPath = path.join(tempDir, `${filename}.pdf`)
await fs.mkdir(uploadsDir, { recursive: true }) await fs.mkdir(uploadsDir, { recursive: true })
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented // filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const finalPdfPath = path.join(uploadsDir, `${filename}.pdf`) const finalPdfPath = path.join(uploadsDir, `${filename}.pdf`)
await fs.copyFile(pdfPath, finalPdfPath) await fs.copyFile(pdfPath, finalPdfPath)
// Kopie ins repo-root public/uploads für bessere Auffindbarkeit // Kopie ins repo-root public/uploads für bessere Auffindbarkeit
@@ -753,9 +753,9 @@ export default defineEventHandler(async (event) => {
const repoRoot = path.resolve(process.cwd(), '..') const repoRoot = path.resolve(process.cwd(), '..')
const repoUploads = path.join(repoRoot, 'public', 'uploads') const repoUploads = path.join(repoRoot, 'public', 'uploads')
await fs.mkdir(repoUploads, { recursive: true }) await fs.mkdir(repoUploads, { recursive: true })
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented // filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
await fs.copyFile(finalPdfPath, path.join(repoUploads, `${filename}.pdf`)) await fs.copyFile(finalPdfPath, path.join(repoUploads, `${filename}.pdf`))
} catch (e) { } catch (e) {
console.warn('Kopie in repo public/uploads fehlgeschlagen:', e.message) console.warn('Kopie in repo public/uploads fehlgeschlagen:', e.message)
@@ -767,9 +767,9 @@ export default defineEventHandler(async (event) => {
// Antragsdaten verschlüsselt speichern // Antragsdaten verschlüsselt speichern
const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production' const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
const encryptedData = encrypt(JSON.stringify(data), encryptionKey) const encryptedData = encrypt(JSON.stringify(data), encryptionKey)
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented // filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const dataPath = path.join(uploadsDir, `${filename}.data`) const dataPath = path.join(uploadsDir, `${filename}.data`)
await fs.writeFile(dataPath, encryptedData, 'utf8') await fs.writeFile(dataPath, encryptedData, 'utf8')

View File

@@ -39,9 +39,9 @@ export default defineEventHandler(async (event) => {
}) })
} }
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const dataDir = path.join(process.cwd(), 'server/data/membership-applications') const dataDir = path.join(process.cwd(), 'server/data/membership-applications')
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const filePath = path.join(dataDir, `${id}.json`) const filePath = path.join(dataDir, `${id}.json`)
// Antrag laden // Antrag laden

View File

@@ -2,15 +2,15 @@ import fs from 'fs/promises'
import path from 'path' import path from 'path'
import { getUserFromToken, hasAnyRole } from '../../utils/auth.js' import { getUserFromToken, hasAnyRole } from '../../utils/auth.js'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -2,15 +2,15 @@ import fs from 'fs/promises'
import path from 'path' import path from 'path'
import { getUserFromToken, hasAnyRole } from '../../utils/auth.js' import { getUserFromToken, hasAnyRole } from '../../utils/auth.js'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -4,15 +4,15 @@ import { getUserFromToken, hasAnyRole } from '../../../utils/auth.js'
import { getRecipientsByGroup, getNewsletterSubscribers, generateUnsubscribeToken } from '../../../utils/newsletter.js' import { getRecipientsByGroup, getNewsletterSubscribers, generateUnsubscribeToken } from '../../../utils/newsletter.js'
import nodemailer from 'nodemailer' import nodemailer from 'nodemailer'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -3,15 +3,15 @@ import path from 'path'
import { getUserFromToken, hasAnyRole } from '../../utils/auth.js' import { getUserFromToken, hasAnyRole } from '../../utils/auth.js'
import { randomUUID } from 'crypto' import { randomUUID } from 'crypto'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -6,15 +6,15 @@ import { getRecipientsByGroup, getNewsletterSubscribers, generateUnsubscribeToke
import { encryptObject, decryptObject } from '../../../../../utils/encryption.js' import { encryptObject, decryptObject } from '../../../../../utils/encryption.js'
import nodemailer from 'nodemailer' import nodemailer from 'nodemailer'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-posts.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-posts.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -3,15 +3,15 @@ import path from 'path'
import { getUserFromToken, hasAnyRole } from '../../../../../utils/auth.js' import { getUserFromToken, hasAnyRole } from '../../../../../utils/auth.js'
import { encryptObject, decryptObject } from '../../../../../utils/encryption.js' import { encryptObject, decryptObject } from '../../../../../utils/encryption.js'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-posts.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-posts.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -6,15 +6,15 @@ import crypto from 'crypto'
import fs from 'fs/promises' import fs from 'fs/promises'
import path from 'path' import path from 'path'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -3,15 +3,15 @@ import path from 'path'
import { getUserFromToken, hasAnyRole } from '../../../../../utils/auth.js' import { getUserFromToken, hasAnyRole } from '../../../../../utils/auth.js'
import { readSubscribers } from '../../../../../utils/newsletter.js' import { readSubscribers } from '../../../../../utils/newsletter.js'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -3,15 +3,15 @@ import path from 'path'
import { getUserFromToken, hasAnyRole } from '../../../utils/auth.js' import { getUserFromToken, hasAnyRole } from '../../../utils/auth.js'
import { randomUUID } from 'crypto' import { randomUUID } from 'crypto'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-groups.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-groups.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -2,15 +2,15 @@ import fs from 'fs/promises'
import path from 'path' import path from 'path'
import { getUserFromToken, hasAnyRole } from '../../../utils/auth.js' import { getUserFromToken, hasAnyRole } from '../../../utils/auth.js'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-groups.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-groups.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -2,15 +2,15 @@ import fs from 'fs/promises'
import path from 'path' import path from 'path'
import { getUserFromToken } from '../../../utils/auth.js' import { getUserFromToken } from '../../../utils/auth.js'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-groups.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-groups.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -2,15 +2,15 @@ import fs from 'fs/promises'
import path from 'path' import path from 'path'
import { getUserFromToken, hasAnyRole } from '../../utils/auth.js' import { getUserFromToken, hasAnyRole } from '../../utils/auth.js'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -5,15 +5,15 @@ import crypto from 'crypto'
import fs from 'fs/promises' import fs from 'fs/promises'
import path from 'path' import path from 'path'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -2,15 +2,15 @@ import { readSubscribers, writeSubscribers } from '../../utils/newsletter.js'
import fs from 'fs/promises' import fs from 'fs/promises'
import path from 'path' import path from 'path'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -3,15 +3,15 @@ import path from 'path'
import sharp from 'sharp' import sharp from 'sharp'
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant ('personen'), never user input // filename is always a hardcoded constant ('personen'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }
@@ -45,6 +45,7 @@ export default defineEventHandler(async (event) => {
}) })
} }
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const filePath = path.join(PERSONEN_DIR, sanitizedFilename) const filePath = path.join(PERSONEN_DIR, sanitizedFilename)
// Prüfe ob Datei existiert // Prüfe ob Datei existiert

View File

@@ -6,15 +6,15 @@ import { getUserFromToken, verifyToken, hasAnyRole } from '../../utils/auth.js'
import { randomUUID } from 'crypto' import { randomUUID } from 'crypto'
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant ('personen'), never user input // filename is always a hardcoded constant ('personen'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }
@@ -121,7 +121,8 @@ export default defineEventHandler(async (event) => {
statusMessage: 'Fehler beim Generieren des Dateinamens' statusMessage: 'Fehler beim Generieren des Dateinamens'
}) })
} }
const newPath = path.join(PERSONEN_DIR, sanitizedFilename) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const newPath = path.join(PERSONEN_DIR, sanitizedFilename)
// Bild verarbeiten: EXIF-Orientierung korrigieren // Bild verarbeiten: EXIF-Orientierung korrigieren
await sharp(originalPath) await sharp(originalPath)

View File

@@ -53,7 +53,7 @@ export default defineEventHandler(async (event) => {
filePath = path.join(process.cwd(), 'public', 'documents', 'spielplaene', 'spielplan_gesamt.pdf') filePath = path.join(process.cwd(), 'public', 'documents', 'spielplaene', 'spielplan_gesamt.pdf')
} else { } else {
// Für vordefinierte PDFs // Für vordefinierte PDFs
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
filePath = path.join(process.cwd(), 'public', 'documents', 'spielplaene', sanitizedFilename) filePath = path.join(process.cwd(), 'public', 'documents', 'spielplaene', sanitizedFilename)
} }

View File

@@ -361,7 +361,7 @@ ${hallenListe.map(halle => {
// Verzeichnis existiert bereits // Verzeichnis existiert bereits
} }
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const tempTexFile = path.join(tempDir, `spielplan_${team}_${Date.now()}.tex`) const tempTexFile = path.join(tempDir, `spielplan_${team}_${Date.now()}.tex`)
await fs.writeFile(tempTexFile, latexContent, 'utf-8') await fs.writeFile(tempTexFile, latexContent, 'utf-8')

View File

@@ -28,19 +28,19 @@ export function migrateUserRoles(user) {
const JWT_SECRET = process.env.JWT_SECRET || 'harheimertc-secret-key-change-in-production' const JWT_SECRET = process.env.JWT_SECRET || 'harheimertc-secret-key-change-in-production'
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'users.json'), never user input // filename is always a hardcoded constant (e.g., 'users.json'), never user input
const getDataPath = (filename) => { const getDataPath = (filename) => {
const cwd = process.cwd() const cwd = process.cwd()
// In production (.output/server), working dir is .output // In production (.output/server), working dir is .output
if (cwd.endsWith('.output')) { if (cwd.endsWith('.output')) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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)
} }
// In development, working dir is project root // In development, working dir is project root
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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)
} }

View File

@@ -12,16 +12,16 @@ import path from 'path'
* @param {string} filename - Config filename * @param {string} filename - Config filename
* @returns {string} Full path to config file * @returns {string} Full path to config file
*/ */
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'config.json'), never user input // filename is always a hardcoded constant (e.g., 'config.json'), never user input
function getDataPath(filename) { function getDataPath(filename) {
const isProduction = process.env.NODE_ENV === 'production' const isProduction = process.env.NODE_ENV === 'production'
if (isProduction) { if (isProduction) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
return path.join(process.cwd(), '..', 'server', 'data', filename) return path.join(process.cwd(), '..', 'server', 'data', filename)
} else { } else {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
return path.join(process.cwd(), 'server', 'data', filename) return path.join(process.cwd(), 'server', 'data', filename)
} }
} }

View File

@@ -4,19 +4,19 @@ import { randomUUID } from 'crypto'
import { encrypt, decrypt, encryptObject, decryptObject } from './encryption.js' import { encrypt, decrypt, encryptObject, decryptObject } from './encryption.js'
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'members.json'), never user input // filename is always a hardcoded constant (e.g., 'members.json'), never user input
const getDataPath = (filename) => { const getDataPath = (filename) => {
const cwd = process.cwd() const cwd = process.cwd()
// In production (.output/server), working dir is .output // In production (.output/server), working dir is .output
if (cwd.endsWith('.output')) { if (cwd.endsWith('.output')) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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)
} }
// In development, working dir is project root // In development, working dir is project root
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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)
} }

View File

@@ -3,19 +3,19 @@ import path from 'path'
import { randomUUID } from 'crypto' import { randomUUID } from 'crypto'
// Handle both dev and production paths // Handle both dev and production paths
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'news.json'), never user input // filename is always a hardcoded constant (e.g., 'news.json'), never user input
const getDataPath = (filename) => { const getDataPath = (filename) => {
const cwd = process.cwd() const cwd = process.cwd()
// In production (.output/server), working dir is .output // In production (.output/server), working dir is .output
if (cwd.endsWith('.output')) { if (cwd.endsWith('.output')) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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)
} }
// In development, working dir is project root // In development, working dir is project root
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // 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)
} }

View File

@@ -5,15 +5,15 @@ import { readUsers } from './auth.js'
import { encryptObject, decryptObject } from './encryption.js' import { encryptObject, decryptObject } from './encryption.js'
import crypto from 'crypto' import crypto from 'crypto'
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input // filename is always a hardcoded constant (e.g., 'newsletter-subscribers.json'), never user input
const getDataPath = (filename) => { const 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 // 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 // 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)
} }

View File

@@ -98,7 +98,7 @@ export class PDFGeneratorService {
* @returns {Promise<string>} File path * @returns {Promise<string>} File path
*/ */
async savePDF(pdfBuffer, filename, uploadDir) { async savePDF(pdfBuffer, filename, uploadDir) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
const filePath = path.join(uploadDir, filename) const filePath = path.join(uploadDir, filename)
await fs.writeFile(filePath, pdfBuffer) await fs.writeFile(filePath, pdfBuffer)
return filePath return filePath

View File

@@ -9,12 +9,12 @@ const getDataPath = (filename) => {
// In production (.output/server), working dir is .output // In production (.output/server), working dir is .output
if (cwd.endsWith('.output')) { if (cwd.endsWith('.output')) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
return path.join(cwd, '../public/data', filename) return path.join(cwd, '../public/data', filename)
} }
// In development, working dir is project root // In development, working dir is project root
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
return path.join(cwd, 'public/data', filename) return path.join(cwd, 'public/data', filename)
} }