From bf19cba4d3826b52c95a583fe52c2dcacd09a433 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Sun, 5 Jul 2026 17:22:23 +0200 Subject: [PATCH] fix: guard body null su PUT contenuti, formato tag e suffisso random su upload immagine --- src/pages/api/admin/content/[tag].ts | 1 + src/pages/api/admin/content/[tag]/image.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/api/admin/content/[tag].ts b/src/pages/api/admin/content/[tag].ts index 66a8503..753b7c7 100644 --- a/src/pages/api/admin/content/[tag].ts +++ b/src/pages/api/admin/content/[tag].ts @@ -10,6 +10,7 @@ const json = (status: number, body: object) => export const PUT: APIRoute = async ({ params, request, locals }) => { let data: Record; try { data = await request.json(); } catch { return json(400, { error: 'Dati non validi.' }); } + if (typeof data !== 'object' || data === null) return json(400, { error: 'Dati non validi.' }); const r = applyContentUpdate(getDb(), params.tag ?? '', data, locals.user!.username); if (!r.ok) return json(r.status, { error: r.error }); invalidateContentCache(); diff --git a/src/pages/api/admin/content/[tag]/image.ts b/src/pages/api/admin/content/[tag]/image.ts index da8d9db..a312705 100644 --- a/src/pages/api/admin/content/[tag]/image.ts +++ b/src/pages/api/admin/content/[tag]/image.ts @@ -1,6 +1,7 @@ import type { APIRoute } from 'astro'; import { writeFile, mkdir } from 'node:fs/promises'; import { join } from 'node:path'; +import { randomBytes } from 'node:crypto'; import { getDb } from '../../../../../lib/db'; import { getEnv } from '../../../../../lib/env'; import { invalidateContentCache } from '../../../../../lib/content'; @@ -10,7 +11,12 @@ export const prerender = false; const json = (status: number, body: object) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } }); +// Difesa in profondità: oggi i tag arrivano solo dal seed, ma il nome file deriva +// dal tag — vincolarne il formato evita sorprese se in futuro i tag diventano dinamici. +const TAG_RE = /^[a-z0-9][a-z0-9.-]*$/; + function findImageTag(tag: string): { ok: true } | { ok: false; status: number; error: string } { + if (!TAG_RE.test(tag)) return { ok: false, status: 400, error: 'Tag non valido.' }; const row = getDb().prepare('SELECT type FROM content_blocks WHERE tag = ?').get(tag) as { type: string } | undefined; if (!row) return { ok: false, status: 404, error: 'Tag inesistente.' }; if (row.type !== 'image') return { ok: false, status: 400, error: 'Il tag non è di tipo immagine.' }; @@ -26,7 +32,7 @@ export const POST: APIRoute = async ({ params, request, locals }) => { if (!checked.ok) return json(400, { error: checked.error }); const dir = join(getEnv('UPLOADS_DIR', 'uploads'), 'content'); await mkdir(dir, { recursive: true }); - const name = `${tag.replaceAll('.', '-')}-${Date.now()}${checked.ext}`; + const name = `${tag.replaceAll('.', '-')}-${Date.now()}-${randomBytes(4).toString('hex')}${checked.ext}`; await writeFile(join(dir, name), Buffer.from(await checked.file.arrayBuffer())); getDb().prepare( `UPDATE content_blocks SET value = ?, updated_at = datetime('now'), updated_by = ? WHERE tag = ?`