fix(0128): XSS scheme allowlist + drop dead fileID
review findings: - MessageBody: only http(s) and relative paths allowed for links; data:image/* allowed for inline images. Rejects javascript:, data:text/html, vbscript: which would execute via <a href>. Unsafe matches fall back to plain text. - files.go: remove unused fileID var generated then discarded.
This commit is contained in:
@@ -197,7 +197,6 @@ func handleUploadCardFile(db *DB, workdir string) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fname := safeFilename(header.Filename)
|
fname := safeFilename(header.Filename)
|
||||||
fileID := newID()
|
|
||||||
storedPath := filepath.Join(dir, randomFilePrefix()+"__"+fname)
|
storedPath := filepath.Join(dir, randomFilePrefix()+"__"+fname)
|
||||||
|
|
||||||
out, err := os.Create(storedPath)
|
out, err := os.Create(storedPath)
|
||||||
@@ -237,16 +236,12 @@ func handleUploadCardFile(db *DB, workdir string) http.HandlerFunc {
|
|||||||
|
|
||||||
actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
|
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)
|
cf, err := db.CreateCardFile(cardID, actor, fname, mimeType, storedPath, source, written)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Remove(storedPath)
|
os.Remove(storedPath)
|
||||||
serverError(w, err)
|
serverError(w, err)
|
||||||
return
|
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)
|
infra.HTTPJSONResponse(w, http.StatusCreated, cf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,24 @@ type Token = ImgToken | LinkToken | TextToken;
|
|||||||
|
|
||||||
const TOKEN_RE = /(!\[([^\]\n]*)\]\(([^)\s]+)\))|(\[([^\]\n]+)\]\(([^)\s]+)\))/g;
|
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[] {
|
function tokenize(input: string): Token[] {
|
||||||
const out: Token[] = [];
|
const out: Token[] = [];
|
||||||
let last = 0;
|
let last = 0;
|
||||||
@@ -24,9 +42,19 @@ function tokenize(input: string): Token[] {
|
|||||||
out.push({ kind: "text", value: input.slice(last, m.index) });
|
out.push({ kind: "text", value: input.slice(last, m.index) });
|
||||||
}
|
}
|
||||||
if (m[1]) {
|
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]) {
|
} 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;
|
last = TOKEN_RE.lastIndex;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user