fix: security hardening and cleanup from community PR cherry-picks

- Add HTML sanitizer for markdown rendering (XSS prevention)
- Switch service worker to network-first caching (deploys take effect immediately)
- Sanitize Content-Disposition filenames (header injection prevention)
- Expose session.muxName getter, replace unsafe `as any` cast
- Static import for execFile, update CLAUDE.md keyboard shortcuts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
arkon
2026-04-11 07:20:09 +02:00
co-authored by Claude Opus 4.6
parent f3cbe9bca6
commit b4a808adcf
6 changed files with 49 additions and 14 deletions
+3 -1
View File
@@ -293,7 +293,9 @@ export function registerFileRoutes(app: FastifyInstance, ctx: SessionPort): void
const content = await fs.readFile(resolvedPath);
if (download === 'true') {
const basename = filePath!.split('/').pop() || 'download';
const rawBasename = filePath!.split('/').pop() || 'download';
// Sanitize filename for Content-Disposition header (prevent header injection)
const basename = rawBasename.replace(/["\\\r\n]/g, '_');
reply.raw.writeHead(200, {
'Content-Type': mimeTypes[ext] || 'application/octet-stream',
'Content-Disposition': `attachment; filename="${basename}"`,
+2 -2
View File
@@ -7,6 +7,7 @@
import { FastifyInstance } from 'fastify';
import { join, dirname } from 'node:path';
import { existsSync, statSync, mkdirSync, writeFileSync } from 'node:fs';
import { execFile } from 'node:child_process';
import fs from 'node:fs/promises';
import {
ApiErrorCode,
@@ -545,13 +546,12 @@ export function registerSessionRoutes(
}
const session = findSessionOrFail(ctx, id);
const muxName = (session as any)._muxSession?.muxName;
const muxName = session.muxName;
if (!muxName) {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'No tmux session');
}
try {
const { execFile } = await import('child_process');
await new Promise<void>((resolve, reject) => {
execFile('tmux', ['send-keys', '-H', '-t', muxName, ...hex], { timeout: 5000 }, (err) => {
if (err) reject(err);