Operational gaps in the paste-image endpoint introduced in #84 — non-security, but worth filing so they don't get lost.
1. No rate-limit and no GC for .claude-images/
The endpoint enforces a 10MB per-request cap (src/web/server.ts:929-936) but no aggregate cap, no per-IP token-bucket, and cleanup only runs when killMux=true on session destroy. Soft-closed sessions leave their image dirs on disk indefinitely. An authenticated user (or anyone if CODEMAN_PASSWORD is unset and the instance is tunneled) can loop 10MB POSTs to fill the disk.
Fix: add a per-IP token-bucket on /api/sessions/:id/paste-image (e.g., 30/min, 100MB/hr); reject if du -s .claude-images/ exceeds a per-session cap; periodic GC of files older than 7 days even without killMux=true.
2. Filename collision on same-millisecond uploads
paste-${Date.now()}${ext} (src/web/routes/session-routes.ts:~1545) collides under concurrent paste from two browser tabs. Last-write-wins overwrites the prior file silently, and the response's path still references the (now-overwritten) name — the first uploader's terminal @-mentions a file whose contents are the second uploader's image.
Fix: append randomBytes(8).toString('hex') (or use randomUUID()) so concurrent uploads never collide.
3. Bracketed-paste bypass on Ctrl+V
The Ctrl+V interceptor in src/web/public/terminal-ui.js:86-92 swallows all paste events and re-sends text via sendInput(), which strips xterm's bracketed-paste markers. Claude CLI / bash can no longer distinguish "typed" from "pasted" text — relevant to prompt-injection defences in Claude Code, which treat bracketed-paste content differently.
Fix: detect "no image" early and return true from customKeyEventHandler so xterm's native paste flow handles the text path with bracketed-paste preserved.
Operational gaps in the paste-image endpoint introduced in #84 — non-security, but worth filing so they don't get lost.
1. No rate-limit and no GC for
.claude-images/The endpoint enforces a 10MB per-request cap (
src/web/server.ts:929-936) but no aggregate cap, no per-IP token-bucket, and cleanup only runs whenkillMux=trueon session destroy. Soft-closed sessions leave their image dirs on disk indefinitely. An authenticated user (or anyone ifCODEMAN_PASSWORDis unset and the instance is tunneled) can loop 10MB POSTs to fill the disk.Fix: add a per-IP token-bucket on
/api/sessions/:id/paste-image(e.g., 30/min, 100MB/hr); reject ifdu -s .claude-images/exceeds a per-session cap; periodic GC of files older than 7 days even withoutkillMux=true.2. Filename collision on same-millisecond uploads
paste-${Date.now()}${ext}(src/web/routes/session-routes.ts:~1545) collides under concurrent paste from two browser tabs. Last-write-wins overwrites the prior file silently, and the response'spathstill references the (now-overwritten) name — the first uploader's terminal@-mentionsa file whose contents are the second uploader's image.Fix: append
randomBytes(8).toString('hex')(or userandomUUID()) so concurrent uploads never collide.3. Bracketed-paste bypass on Ctrl+V
The Ctrl+V interceptor in
src/web/public/terminal-ui.js:86-92swallows all paste events and re-sends text viasendInput(), which strips xterm's bracketed-paste markers. Claude CLI / bash can no longer distinguish "typed" from "pasted" text — relevant to prompt-injection defences in Claude Code, which treat bracketed-paste content differently.Fix: detect "no image" early and return
truefromcustomKeyEventHandlerso xterm's native paste flow handles the text path with bracketed-paste preserved.