Skip to content

Commit 7913cd5

Browse files
Improve: Enforce 15MB max file size limit before downloading/viewing sandbox inspect files (#200)
<!-- CURSOR_SUMMARY --> > [!NOTE] > Adds a 15MB max preview limit with pre-read size checks, introduces explicit unreadable states (file_type, too_large), and updates viewer messaging accordingly. > > - **Sandbox Inspect**: > - **Preview size limit**: Introduces `MAX_VIEWABLE_FILE_SIZE_BYTES` (15MB) and pre-checks file size via `files.getInfo` before reading. Oversized files set `unreadable` with `reason: 'too_large'` and include `size`. > - **File content states**: Refactors to explicit types: `TextFileContentState`, `ImageFileContentState`, `UnreadableFileTypeContentState`, `UnreadableTooLargeContentState`. > - **Error mapping**: On read errors and non-text/image blobs, sets `unreadable` with `reason: 'file_type'`. > - **Viewer UI**: > - Renders size-specific message for oversized files and generic message for unreadable type; wires new state variants. > - **Utils**: > - `determineFileContentState` now returns `unreadable` with `reason: 'file_type'` on decode failures. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 254300b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Jakub Novák <[email protected]>
1 parent 1d5fbb7 commit 7913cd5

File tree

4 files changed

+71
-17
lines changed

4 files changed

+71
-17
lines changed

src/features/dashboard/sandbox/inspect/filesystem/store.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,38 @@ import { FilesystemNode } from './types'
1313

1414
enableMapSet()
1515

16+
export const MAX_VIEWABLE_FILE_SIZE_BYTES = 15 * 1024 * 1024 // 15MB
17+
1618
interface FilesystemStatics {
1719
rootPath: string
1820
}
1921

20-
interface ContentFileContentState {
21-
text: string
22+
export interface TextFileContentState {
2223
type: 'text'
24+
text: string
25+
}
26+
27+
export interface ImageFileContentState {
28+
type: 'image'
29+
dataUri: string
2330
}
2431

25-
interface UnreadableFileContentState {
32+
export interface UnreadableFileTypeContentState {
2633
type: 'unreadable'
34+
reason: 'file_type'
2735
}
2836

29-
interface ImageFileContentState {
30-
dataUri: string
31-
type: 'image'
37+
export interface UnreadableTooLargeContentState {
38+
type: 'unreadable'
39+
reason: 'too_large'
40+
size: number
3241
}
3342

3443
export type FileContentState =
35-
| ContentFileContentState
36-
| UnreadableFileContentState
44+
| TextFileContentState
3745
| ImageFileContentState
46+
| UnreadableFileTypeContentState
47+
| UnreadableTooLargeContentState
3848

3949
export type FileContentStateType = FileContentState['type']
4050

src/features/dashboard/sandbox/inspect/sandbox-manager.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import {
55
normalizePath,
66
} from '@/lib/utils/filesystem'
77
import {
8+
FilesystemEventType,
89
type EntryInfo,
910
type FilesystemEvent,
10-
FilesystemEventType,
1111
type Sandbox,
1212
type WatchHandle,
1313
} from 'e2b'
14-
import type { FilesystemStore } from './filesystem/store'
14+
import {
15+
MAX_VIEWABLE_FILE_SIZE_BYTES,
16+
type FilesystemStore,
17+
} from './filesystem/store'
1518
import { FilesystemNode } from './filesystem/types'
1619

1720
export const HANDLED_ERRORS = {
@@ -322,6 +325,20 @@ export class SandboxManager {
322325
try {
323326
state.setLoading(normalizedPath, true)
324327

328+
const fileInfo = await this.sandbox.files.getInfo(normalizedPath, {
329+
user: 'root',
330+
requestTimeoutMs: 10_000,
331+
})
332+
333+
if (fileInfo.size > MAX_VIEWABLE_FILE_SIZE_BYTES) {
334+
state.setFileContent(normalizedPath, {
335+
type: 'unreadable',
336+
reason: 'too_large',
337+
size: fileInfo.size,
338+
})
339+
return
340+
}
341+
325342
const blob = await this.sandbox.files.read(normalizedPath, {
326343
format: 'blob',
327344
requestTimeoutMs: 30_000,
@@ -337,7 +354,10 @@ export class SandboxManager {
337354
console.error(`Failed to read file ${normalizedPath}:`, err)
338355

339356
state.setError(normalizedPath, errorMessage)
340-
state.setFileContent(normalizedPath, { type: 'unreadable' })
357+
state.setFileContent(normalizedPath, {
358+
type: 'unreadable',
359+
reason: 'file_type',
360+
})
341361
} finally {
342362
state.setLoading(normalizedPath, false)
343363
state.setLoaded(normalizedPath, true)

src/features/dashboard/sandbox/inspect/viewer.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import { AnimatePresence } from 'framer-motion'
1010
import { Download } from 'lucide-react'
1111
import { useEffect, useState } from 'react'
1212
import ShikiHighlighter, { Language } from 'react-shiki'
13+
14+
import {
15+
MAX_VIEWABLE_FILE_SIZE_BYTES,
16+
type UnreadableFileTypeContentState,
17+
type UnreadableTooLargeContentState,
18+
} from './filesystem/store'
1319
import SandboxInspectFrame from './frame'
1420
import { useContent } from './hooks/use-content'
1521
import { useFile } from './hooks/use-file'
@@ -117,7 +123,7 @@ function SandboxInspectViewerContent({
117123
) : state.type === 'image' ? (
118124
<ImageContent name={name} dataUri={state.dataUri} />
119125
) : (
120-
<UnreadableContent onDownload={download} />
126+
<UnreadableContent state={state} onDownload={download} />
121127
)}
122128
</SandboxInspectFrame>
123129
)
@@ -148,7 +154,7 @@ function TextContent({
148154
if (content.length === 0) {
149155
return (
150156
<div className="flex h-full flex-col items-center justify-center gap-3">
151-
<span className="text-fg-secondary ">This file is empty.</span>
157+
<span className="text-fg-secondary">This file is empty.</span>
152158
<Button variant="warning" size="sm" onClick={onDownload}>
153159
Download
154160
<Download className="ml-1.5 h-4 w-4" />
@@ -195,14 +201,32 @@ function ImageContent({ name, dataUri }: ImageContentProps) {
195201
)
196202
}
197203

198-
interface UnreadableContent {
204+
interface UnreadableContentProps {
205+
state: UnreadableFileTypeContentState | UnreadableTooLargeContentState
199206
onDownload: () => void
200207
}
201208

202-
function UnreadableContent({ onDownload }: UnreadableContent) {
209+
function UnreadableContent({ state, onDownload }: UnreadableContentProps) {
210+
const maxSizeMB = (MAX_VIEWABLE_FILE_SIZE_BYTES / 1024 / 1024).toFixed(0)
211+
212+
if (state.reason === 'too_large') {
213+
const sizeMB = (state.size / 1024 / 1024).toFixed(1)
214+
return (
215+
<div className="flex h-full flex-col items-center justify-center gap-3">
216+
<span className="text-fg-secondary">
217+
File is too large to preview ({sizeMB} MB). Limit is {maxSizeMB} MB.
218+
</span>
219+
<Button variant="warning" size="sm" onClick={onDownload}>
220+
Download
221+
<Download className="ml-1.5 h-4 w-4" />
222+
</Button>
223+
</div>
224+
)
225+
}
226+
203227
return (
204228
<div className="flex h-full flex-col items-center justify-center gap-3">
205-
<span className="text-fg-secondary ">This file is not readable.</span>
229+
<span className="text-fg-secondary">This file is not readable.</span>
206230
<Button variant="warning" size="sm" onClick={onDownload}>
207231
Download
208232
<Download className="ml-1.5 h-4 w-4" />

src/lib/utils/filesystem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,6 @@ export async function determineFileContentState(
8686
const content = new TextDecoder('utf-8', { fatal: true }).decode(data)
8787
return { type: 'text', text: content }
8888
} catch {
89-
return { type: 'unreadable' }
89+
return { type: 'unreadable', reason: 'file_type' }
9090
}
9191
}

0 commit comments

Comments
 (0)