Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request 'fix: security — auth on DELETE, forms ReDoS, slides img XSS' (#240) from fix/security-qa-batch into main

scott 713aaa9c 68d1119f

+17 -4
+8
server/index.ts
··· 35 35 expires_at: string | null; 36 36 deleted_at: string | null; 37 37 tags: string | null; 38 + owner: string | null; 38 39 created_at: string; 39 40 updated_at: string; 40 41 } ··· 443 444 }); 444 445 445 446 app.delete('/api/documents/:id', (req: Request<{ id: string }>, res: Response) => { 447 + const doc = stmts.getOne.get(req.params.id) as DocumentListRow | undefined; 448 + if (!doc) { res.status(404).json({ error: 'Not found' }); return; } 449 + // Only the document owner (or unauthenticated legacy docs with no owner) can permanently delete 450 + if (doc.owner && req.tsUser?.login !== doc.owner) { 451 + res.status(403).json({ error: 'Only the document owner can delete' }); 452 + return; 453 + } 446 454 stmts.deleteDoc.run(req.params.id); 447 455 res.json({ ok: true }); 448 456 });
+5 -3
src/forms/form-builder.ts
··· 218 218 } 219 219 } 220 220 221 - // Custom validation pattern 222 - if (question.validationPattern) { 221 + // Custom validation pattern (length-limited to prevent ReDoS) 222 + if (question.validationPattern && question.validationPattern.length <= 200) { 223 223 try { 224 - if (!new RegExp(question.validationPattern).test(str)) return 'Invalid format'; 224 + const re = new RegExp(question.validationPattern); 225 + // Test against a truncated string to bound worst-case backtracking 226 + if (!re.test(str.slice(0, 1000))) return 'Invalid format'; 225 227 } catch { 226 228 // Invalid pattern, skip 227 229 }
+4 -1
src/slides/main.ts
··· 205 205 div.innerHTML = renderShapeSVG(el.shapeType || 'rectangle', el.width, el.height, fill); 206 206 } else if (el.type === 'image') { 207 207 const img = document.createElement('img'); 208 - img.src = el.content || ''; 208 + const imgUrl = el.content || ''; 209 + // Only allow safe URL schemes to prevent javascript: XSS 210 + const isSafe = /^(https?:|data:|blob:)/i.test(imgUrl) || imgUrl === ''; 211 + img.src = isSafe ? imgUrl : ''; 209 212 img.style.cssText = 'width:100%;height:100%;object-fit:contain;'; 210 213 img.alt = ''; 211 214 div.appendChild(img);