diff --git a/backend/files.go b/backend/files.go index 015adef..fbc069e 100644 --- a/backend/files.go +++ b/backend/files.go @@ -197,7 +197,6 @@ func handleUploadCardFile(db *DB, workdir string) http.HandlerFunc { } fname := safeFilename(header.Filename) - fileID := newID() storedPath := filepath.Join(dir, randomFilePrefix()+"__"+fname) out, err := os.Create(storedPath) @@ -237,16 +236,12 @@ func handleUploadCardFile(db *DB, workdir string) http.HandlerFunc { actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey) - // Use the random-prefixed path on disk but a stable file id in the DB. cf, err := db.CreateCardFile(cardID, actor, fname, mimeType, storedPath, source, written) if err != nil { os.Remove(storedPath) serverError(w, err) return } - // We generated newID() in CreateCardFile; align the on-disk filename with that id - // is not required since stored_path is what we serve from. - _ = fileID infra.HTTPJSONResponse(w, http.StatusCreated, cf) } diff --git a/frontend/src/components/MessageBody.tsx b/frontend/src/components/MessageBody.tsx index a2154f8..d82a750 100644 --- a/frontend/src/components/MessageBody.tsx +++ b/frontend/src/components/MessageBody.tsx @@ -14,6 +14,24 @@ type Token = ImgToken | LinkToken | TextToken; const TOKEN_RE = /(!\[([^\]\n]*)\]\(([^)\s]+)\))|(\[([^\]\n]+)\]\(([^)\s]+)\))/g; +// Allow only safe URL schemes. Reject javascript:, data:text/html, vbscript:, etc. +// Accepts: absolute http(s), protocol-relative //, and same-origin paths (/...). +function safeURL(url: string): string | null { + const u = url.trim(); + if (u.startsWith("/")) return u; + if (/^https?:\/\//i.test(u)) return u; + return null; +} + +// data: scheme is allowed only when the MIME prefix is image/. +function safeImageURL(url: string): string | null { + const safe = safeURL(url); + if (safe) return safe; + const u = url.trim(); + if (/^data:image\/[a-z0-9.+-]+(;[a-z0-9-]+=[^,]+)*;base64,/i.test(u)) return u; + return null; +} + function tokenize(input: string): Token[] { const out: Token[] = []; let last = 0; @@ -24,9 +42,19 @@ function tokenize(input: string): Token[] { out.push({ kind: "text", value: input.slice(last, m.index) }); } if (m[1]) { - out.push({ kind: "img", alt: m[2] || "", url: m[3] }); + const url = safeImageURL(m[3]); + if (url) { + out.push({ kind: "img", alt: m[2] || "", url }); + } else { + out.push({ kind: "text", value: m[0] }); + } } else if (m[4]) { - out.push({ kind: "link", label: m[5], url: m[6] }); + const url = safeURL(m[6]); + if (url) { + out.push({ kind: "link", label: m[5], url }); + } else { + out.push({ kind: "text", value: m[0] }); + } } last = TOKEN_RE.lastIndex; }