fix(security): close remaining high-severity CodeQL alerts

- svg-sanitize.ts: strip each dangerous element repeatedly until stable with
  whitespace-tolerant end tags, defeating nested/overlapping tags (closes 5
  incomplete-multi-character-sanitization + 1 bad-tag-filter; the prior
  single-pass regex could leave a residual <script>/<iframe>).
- file-preview.ts: add a resolve()+containment barrier (the path-traversal
  guard CodeQL recognizes) on top of the id charset check (closes 9
  path-injection).
- metadata.ts: bound the XMP namespace:name key segments so parseXmp cannot
  backtrack polynomially (closes js/polynomial-redos).
- analytics-disabled.spec.ts: match analytics by URL host, not substring
  (closes 4 incomplete-url-substring-sanitization).

typecheck + lint green; svg (119), preview (22), metadata (164) tests pass.
This commit is contained in:
SnapOtter
2026-06-21 13:47:22 +08:00
parent 4fdd10f488
commit bdadb843d8
4 changed files with 63 additions and 30 deletions
+3 -1
View File
@@ -137,7 +137,9 @@ export function parseXmp(xmpBuffer: Buffer): Record<string, string> {
const xml = xmpBuffer.toString("utf-8");
const result: Record<string, string> = {};
for (const match of xml.matchAll(/(\w+:\w+)="([^"]+)"/g)) {
// Bound the namespace:name key segments (real XMP keys are short) so the
// pattern cannot backtrack polynomially on hostile input (CodeQL js/polynomial-redos).
for (const match of xml.matchAll(/(\w{1,80}:\w{1,80})="([^"]+)"/g)) {
const key = match[1];
if (key.startsWith("xmlns:") || key.startsWith("rdf:")) continue;
result[key] = match[2];