fix(security): harden season file paths for semgrep
All checks were successful
Code Analysis and Production Deploy / analyze (push) Successful in 2m45s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Successful in 2m1s

This commit is contained in:
Torsten Schulz (local)
2026-05-20 18:03:29 +02:00
parent 11ff823fe2
commit f883d45452
2 changed files with 33 additions and 14 deletions

View File

@@ -1,11 +1,35 @@
import { promises as fs } from 'fs'
import path from 'path'
import { getCurrentSeasonSlug, validateSeasonSlug } from '../utils/spielplan-data.js'
function normalizeSeasonFilename(season) {
return `mannschaften_${season}.csv`
}
function isAllowedFilename(filename) {
return filename === 'mannschaften.csv' || /^mannschaften_\d{2}--\d{2}\.csv$/.test(String(filename || ''))
}
function buildCsvCandidates(cwd, filename) {
const safeFilename = String(filename || '').trim()
if (!isAllowedFilename(safeFilename)) {
throw createError({
statusCode: 400,
statusMessage: 'Ungueltiger Dateiname fuer Mannschaften'
})
}
return [
`${cwd}/server/data/public-data/${safeFilename}`,
`${cwd}/../server/data/public-data/${safeFilename}`,
`${cwd}/.output/server/data/${safeFilename}`,
`${cwd}/server/data/${safeFilename}`,
`${cwd}/.output/public/data/${safeFilename}`,
`${cwd}/public/data/${safeFilename}`,
`${cwd}/../.output/public/data/${safeFilename}`,
`${cwd}/../public/data/${safeFilename}`
]
}
async function exists(p) {
try {
await fs.access(p)
@@ -37,16 +61,7 @@ export default defineEventHandler(async (event) => {
// then legacy locations.
const candidates = []
for (const filename of candidateFileNames) {
candidates.push(
path.join(cwd, 'server/data/public-data', filename),
path.join(cwd, '../server/data/public-data', filename),
path.join(cwd, '.output/server/data', filename),
path.join(cwd, 'server/data', filename),
path.join(cwd, '.output/public/data', filename),
path.join(cwd, 'public/data', filename),
path.join(cwd, '../.output/public/data', filename),
path.join(cwd, '../public/data', filename)
)
candidates.push(...buildCsvCandidates(cwd, filename))
}
let csvPath = null

View File

@@ -1,5 +1,4 @@
import { promises as fs } from 'fs'
import path from 'path'
import { getServerDataPath, getProjectPath } from './paths.js'
import { getSpieljahrForDate } from './spielplan-import.js'
import { parseDelimitedLine } from './spielplan-data.js'
@@ -132,6 +131,11 @@ function extractSeasonSlugFromUrl(url) {
return match?.[1] || null
}
function sanitizeSeasonSlug(value) {
const seasonSlug = String(value || '').trim()
return /^\d{2}--\d{2}$/.test(seasonSlug) ? seasonSlug : null
}
async function resolveMannschaftenCsvPath() {
const candidates = [
getServerDataPath('public-data', 'mannschaften.csv'),
@@ -226,8 +230,8 @@ export async function importLeagueTables(options = {}) {
}
const fallbackSeasonSlug = teams.length > 0 ? extractSeasonSlugFromUrl(teams[0].tableUrl) : null
const seasonSlug = fallbackSeasonSlug || season.seasonSlug
const outputFile = path.join(OUTPUT_DIR, `tables_${seasonSlug}.json`)
const seasonSlug = sanitizeSeasonSlug(fallbackSeasonSlug) || sanitizeSeasonSlug(season.seasonSlug) || getSpieljahrForDate(today).seasonSlug
const outputFile = `${OUTPUT_DIR}/tables_${seasonSlug}.json`
const payload = {
format: 'harheimertc.tables.v1',