Enhance security by adding role-checking functions in ESLint configuration and updating Vue components to improve content sanitization comments, while refining error handling in API endpoints for better clarity.
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 3m40s

This commit is contained in:
Torsten Schulz (local)
2025-12-20 14:19:55 +01:00
parent 19024cd87e
commit e128e1a77c
22 changed files with 40 additions and 25 deletions

View File

@@ -32,12 +32,16 @@ export default [
'setCookie': 'readonly', 'setCookie': 'readonly',
'deleteCookie': 'readonly', 'deleteCookie': 'readonly',
'getHeader': 'readonly', 'getHeader': 'readonly',
'setHeader': 'readonly',
'getRouterParam': 'readonly', 'getRouterParam': 'readonly',
'getQuery': 'readonly', 'getQuery': 'readonly',
'sendStream': 'readonly', 'sendStream': 'readonly',
'sendRedirect': 'readonly', 'sendRedirect': 'readonly',
'createError': 'readonly', 'createError': 'readonly',
'useRuntimeConfig': 'readonly', 'useRuntimeConfig': 'readonly',
'hasAnyRole': 'readonly',
'hasRole': 'readonly',
'hasAllRoles': 'readonly',
'process': 'readonly', 'process': 'readonly',
// Vue Composition API // Vue Composition API
'onUnmounted': 'readonly', 'onUnmounted': 'readonly',

View File

@@ -166,9 +166,10 @@
Keine Empfänger gefunden Keine Empfänger gefunden
</span> </span>
</div> </div>
<!-- nosemgrep: javascript.vue.security.audit.xss.templates.avoid-v-html -->
<div <div
class="text-sm text-gray-600 prose prose-sm max-w-none mb-3" class="text-sm text-gray-600 prose prose-sm max-w-none mb-3"
v-html="useSanitizeHtml(post.content.substring(0, 200) + (post.content.length > 200 ? '...' : ''))" <!-- nosemgrep: javascript.vue.security.audit.xss.templates.avoid-v-html --> v-html="useSanitizeHtml(post.content.substring(0, 200) + (post.content.length > 200 ? '...' : ''))"
/> />
<!-- Empfängerliste (collapsible) --> <!-- Empfängerliste (collapsible) -->

View File

@@ -29,7 +29,7 @@ async function loadConfig() {
try { try {
const data = await $fetch('/api/config') const data = await $fetch('/api/config')
rawContent.value = data?.seiten?.geschichte || '' rawContent.value = data?.seiten?.geschichte || ''
} catch (e) { } catch (_e) {
rawContent.value = '' rawContent.value = ''
} }
} }

View File

@@ -64,7 +64,7 @@ async function loadConfig() {
rawContent.value = satzung.content || '' rawContent.value = satzung.content || ''
pdfUrl.value = satzung.pdfUrl || '' pdfUrl.value = satzung.pdfUrl || ''
} }
} catch (e) { } catch (_e) {
rawContent.value = '' rawContent.value = ''
pdfUrl.value = '' pdfUrl.value = ''
} }

View File

@@ -4,9 +4,10 @@
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-6"> <h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-6">
TT-Regeln TT-Regeln
</h1> </h1>
<!-- nosemgrep: javascript.vue.security.audit.xss.templates.avoid-v-html -->
<div <div
class="prose prose-lg max-w-none" class="prose prose-lg max-w-none"
v-html="content" <!-- nosemgrep: javascript.vue.security.audit.xss.templates.avoid-v-html --> v-html="content"
/> />
</div> </div>
</div> </div>
@@ -28,7 +29,7 @@ async function loadConfig() {
try { try {
const data = await $fetch('/api/config') const data = await $fetch('/api/config')
rawContent.value = data?.seiten?.ttRegeln || '' rawContent.value = data?.seiten?.ttRegeln || ''
} catch (e) { } catch (_e) {
rawContent.value = '' rawContent.value = ''
} }
} }

View File

@@ -4,9 +4,10 @@
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-6"> <h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-6">
Über uns Über uns
</h1> </h1>
<!-- nosemgrep: javascript.vue.security.audit.xss.templates.avoid-v-html -->
<div <div
class="prose prose-lg max-w-none" class="prose prose-lg max-w-none"
v-html="content" <!-- nosemgrep: javascript.vue.security.audit.xss.templates.avoid-v-html --> v-html="content"
/> />
</div> </div>
</div> </div>
@@ -28,7 +29,7 @@ async function loadConfig() {
try { try {
const data = await $fetch('/api/config') const data = await $fetch('/api/config')
rawContent.value = data?.seiten?.ueberUns || '' rawContent.value = data?.seiten?.ueberUns || ''
} catch (e) { } catch (_e) {
rawContent.value = '' rawContent.value = ''
} }
} }

View File

@@ -125,6 +125,7 @@ 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
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')) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
console.log('✅ Backup von users.json erstellt') console.log('✅ Backup von users.json erstellt')
@@ -167,6 +168,7 @@ 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
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')) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
console.log('✅ Backup von members.json erstellt') console.log('✅ Backup von members.json erstellt')
@@ -217,6 +219,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
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)
@@ -226,6 +229,7 @@ async function reencryptMembershipApplications(backupDir, oldKeys) {
try { try {
// Backup erstellen // Backup erstellen
// nosemgrep: javascript.lang.security.audit.path-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) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
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

@@ -39,8 +39,10 @@ export default defineEventHandler(async (event) => {
}) })
} }
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
const filePath = path.join(dataDir, `${id}.json`) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal const dataDir = path.join(process.cwd(), 'server/data/membership-applications')
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
const filePath = path.join(dataDir, `${id}.json`)
// Antrag laden // Antrag laden
const fileContent = await fs.readFile(filePath, 'utf8') const fileContent = await fs.readFile(filePath, 'utf8')

View File

@@ -53,7 +53,8 @@ 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
filePath = path.join(process.cwd(), 'public', 'documents', 'spielplaene', sanitizedFilename) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
filePath = path.join(process.cwd(), 'public', 'documents', 'spielplaene', sanitizedFilename)
} }
// Prüfe ob Datei existiert // Prüfe ob Datei existiert

View File

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