diff --git a/src/main/runtime/rpc/methods/files.test.ts b/src/main/runtime/rpc/methods/files.test.ts index d3c4edaea5a..70187103315 100644 --- a/src/main/runtime/rpc/methods/files.test.ts +++ b/src/main/runtime/rpc/methods/files.test.ts @@ -391,6 +391,10 @@ describe('file RPC methods', () => { [ 'non-string content', { worktree: 'id:wt-1', relativePath: 'assets/logo.png', contentBase64: 0 } + ], + [ + 'malformed content', + { worktree: 'id:wt-1', relativePath: 'assets/logo.png', contentBase64: '!!!!' } ] ])('rejects a base64 write with %s', async (_name, params) => { const runtime = { @@ -456,6 +460,15 @@ describe('file RPC methods', () => { contentBase64: 0, append: true } + ], + [ + 'malformed content', + { + worktree: 'id:wt-1', + relativePath: 'assets/video.mov', + contentBase64: '!!!!', + append: true + } ] ])('rejects a base64 chunk write with %s (inherits the schema)', async (_name, params) => { const runtime = { diff --git a/src/main/runtime/rpc/methods/files.ts b/src/main/runtime/rpc/methods/files.ts index fbcf31936de..b4ba19db69f 100644 --- a/src/main/runtime/rpc/methods/files.ts +++ b/src/main/runtime/rpc/methods/files.ts @@ -4,6 +4,13 @@ import { defineMethod, defineStreamingMethod, type RpcAnyMethod } from '../core' import { createFileWatchEventBatcher } from './file-watch-event-batcher' let filesWatchSubscriptionSeq = 0 +const RUNTIME_FILE_BASE64_PATTERN = /^[A-Za-z0-9+/]*={0,2}$/ + +function isValidRuntimeFileBase64(value: unknown): value is string { + return ( + typeof value === 'string' && value.length % 4 !== 1 && RUNTIME_FILE_BASE64_PATTERN.test(value) + ) +} const WorktreeSelector = z.object({ worktree: z @@ -43,6 +50,9 @@ const FileWriteBase64 = FileOpen.extend({ contentBase64: z .unknown() .refine((v): v is string => typeof v === 'string', { message: 'Missing file content' }) + // Why: Buffer.from(..., 'base64') accepts malformed input by dropping + // invalid bytes, which can silently create empty or corrupt uploaded files. + .refine(isValidRuntimeFileBase64, 'File content must be base64') }) const FileWriteBase64Chunk = FileWriteBase64.extend({