Add security comments to path handling in various scripts to clarify internal constant usage and mitigate path traversal risks. Update logging in registration and verification processes for improved clarity.
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 2m48s

This commit is contained in:
Torsten Schulz (local)
2026-04-15 20:52:38 +02:00
parent 5f79d220cf
commit edfab28fd3
14 changed files with 46 additions and 6 deletions

View File

@@ -26,8 +26,12 @@ const targetEmail = String(process.argv[2] || 'tsschulz@gmx.net').trim().toLower
function getDataPath(filename) {
const cwd = process.cwd()
if (cwd.endsWith('.output')) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is internal constant in this script (users.json), not user input.
return path.join(cwd, '../server/data', filename)
}
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is internal constant in this script (users.json), not user input.
return path.join(cwd, 'server/data', filename)
}

View File

@@ -4,7 +4,11 @@ import sharp from 'sharp'
const getDataPath = (filename) => {
const cwd = process.cwd()
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is internal constant in this script.
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 internal constant in this script.
return path.join(cwd, 'server/data', filename)
}
@@ -41,6 +45,8 @@ async function fileExists(p) {
}
async function generatePreviewForEntry(entry, size) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// entry.filename originates from internal metadata file, not request parameters.
const original = path.join(GALERIE_DIR, 'originals', entry.filename)
if (!(await fileExists(original))) return { ok: false, reason: 'missing original' }
@@ -48,6 +54,8 @@ async function generatePreviewForEntry(entry, size) {
? entry.previewFilename
: `preview_${entry.filename}`
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// previewFilename is generated from metadata/internal naming conventions.
const preview = path.join(GALERIE_DIR, 'previews', previewFilename)
await sharp(original)

View File

@@ -70,6 +70,8 @@ async function main() {
if (fs.existsSync(internalUploads)) {
pdfFiles = fs.readdirSync(internalUploads).filter(f => f.toLowerCase().endsWith('.pdf'))
.map(f => {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// f comes from fs.readdirSync(internalUploads), not external input.
const filePath = path.join(internalUploads, f)
return { f, mtime: fs.statSync(filePath).mtimeMs, dir: internalUploads }
})

View File

@@ -6,7 +6,11 @@ const allowed = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'])
const getDataPath = (filename) => {
const cwd = process.cwd()
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is internal constant in this script.
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 internal constant in this script.
return path.join(cwd, 'server/data', filename)
}

View File

@@ -13,6 +13,8 @@ if (!KEY) {
}
async function reencryptFile(file) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// file comes from fs.readdir(DIR) and is constrained to *.json below.
const filePath = path.join(DIR, file)
try {
const content = await fs.readFile(filePath, 'utf8')

View File

@@ -133,7 +133,7 @@ export default defineEventHandler(async (event) => {
})
const optionsDuration = Date.now() - optionsStart
console.log(`[DEBUG] Registration options generated (${optionsDuration}ms)`, {
console.log('[DEBUG] Registration options generated', { optionsDurationMs: optionsDuration,
hasChallenge: !!options.challenge,
challengeLength: options.challenge?.length,
rpId: options.rp?.id,
@@ -185,7 +185,7 @@ export default defineEventHandler(async (event) => {
const totalDuration = Date.now() - requestStart
// Debug: Prüfe die vollständige Options-Struktur
console.log(`[DEBUG] Returning options (total: ${totalDuration}ms)`, {
console.log('[DEBUG] Returning options', { totalDurationMs: totalDuration,
registrationId,
optionsKeys: Object.keys(options),
challengeLength: options.challenge?.length,

View File

@@ -161,7 +161,7 @@ export default defineEventHandler(async (event) => {
})
} catch (verifyError) {
const verifyDuration = Date.now() - verifyStart
console.error(`[DEBUG] Verification error (${verifyDuration}ms):`, {
console.error('[DEBUG] Verification error:', { verifyDurationMs: verifyDuration,
error: verifyError,
message: verifyError?.message,
cause: verifyError?.cause?.message,
@@ -175,7 +175,7 @@ export default defineEventHandler(async (event) => {
const verifyDuration = Date.now() - verifyStart
const { verified, registrationInfo } = verification
console.log(`[DEBUG] Verification completed (${verifyDuration}ms)`, {
console.log('[DEBUG] Verification completed', { verifyDurationMs: verifyDuration,
verified,
hasRegistrationInfo: !!registrationInfo,
credentialId: registrationInfo?.credentialID ? 'present' : 'missing',
@@ -246,7 +246,7 @@ export default defineEventHandler(async (event) => {
await writeUsers(users)
const totalDuration = Date.now() - requestStart
console.log(`[DEBUG] User created successfully (total: ${totalDuration}ms)`, {
console.log('[DEBUG] User created successfully', { totalDurationMs: totalDuration,
userId: newUser.id,
email: newUser.email.substring(0, 10) + '...',
hasPasskey: newUser.passkeys?.length > 0,

View File

@@ -94,7 +94,11 @@ 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)
]

View File

@@ -8,7 +8,11 @@ import { readUsers, migrateUserRoles } from '../utils/auth.js'
// filename is always a hardcoded constant ('config.json'), never user input
const getDataPath = (filename) => {
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)
}

View File

@@ -4,7 +4,11 @@ import { getUserFromToken, verifyToken } from '../../../utils/auth.js'
const getDataPath = (filename) => {
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)
}

View File

@@ -35,6 +35,8 @@ 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))) {
throw createError({ statusCode: 400, statusMessage: 'Ungültiger Pfad' })

View File

@@ -110,7 +110,7 @@ function decryptV2GCM(encryptedData, password) {
}
const key = deriveKey(password, salt)
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv)
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH })
decipher.setAuthTag(tag)
const decrypted = Buffer.concat([

View File

@@ -6,6 +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'))
}

View File

@@ -9,8 +9,12 @@ 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)
}
// 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)
}