improvement(mothership): add file patch tool#3712
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
@cursor review |
PR SummaryHigh Risk Overview Adds PPTX generation + preview flow: Updates workspace file query keys to support Written by Cursor Bugbot for commit 4a537ff. This will update automatically on new commits. Configure here. |
Greptile SummaryThis PR adds a Issues found:
Confidence Score: 2/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant CopilotAI as Copilot AI
participant WorkspaceFileTool as workspace_file tool
participant GenPPTX as generatePptxFromCode
participant Storage as File Storage
participant ServeRoute as /api/files/serve
User->>CopilotAI: "Create a PPTX presentation"
CopilotAI->>WorkspaceFileTool: write(fileName, pptxGenJS code)
WorkspaceFileTool->>GenPPTX: validate (execute code server-side)
Note over GenPPTX: new Function(code) — no sandbox
GenPPTX-->>WorkspaceFileTool: compiled Buffer
WorkspaceFileTool->>Storage: store source as text/x-pptxgenjs
WorkspaceFileTool-->>CopilotAI: { success, fileId, downloadUrl }
CopilotAI->>WorkspaceFileTool: patch(fileId, edits[])
WorkspaceFileTool->>Storage: download current content
WorkspaceFileTool->>WorkspaceFileTool: apply string replacements sequentially
WorkspaceFileTool->>GenPPTX: validate patched code
WorkspaceFileTool->>Storage: re-upload patched source
User->>ServeRoute: GET /api/files/serve/workspace/.../file.pptx
ServeRoute->>Storage: download raw buffer
ServeRoute->>ServeRoute: detect non-ZIP .pptx (source code)
ServeRoute->>GenPPTX: compile(code, workspaceId="")
Note over GenPPTX: workspaceId always "" — getFileBase64 fails
GenPPTX-->>ServeRoute: compiled binary PPTX
ServeRoute-->>User: application/vnd.openxmlformats PPTX
|
| const fn = new Function('pptx', 'getFileBase64', `return (async () => { ${code} })()`) | ||
| await fn(pptx, getFileBase64) |
There was a problem hiding this comment.
Server-side arbitrary code execution via
new Function()
generatePptxFromCode executes AI-supplied (and ultimately user-influenced) code directly on the server with no sandbox:
const fn = new Function('pptx', 'getFileBase64', `return (async () => { ${code} })()`)
await fn(pptx, getFileBase64)In Node.js, new Function() executes in the global scope and has access to process, Buffer, and other runtime globals. Because require itself isn't in scope, an attacker can still pivot via process.mainModule.require (or equivalent) to access fs, child_process, etc. The "validation" call at line 90 (and again at line 154/286) actively executes the untrusted code before storing the file, meaning malicious side-effects (e.g. reading process.env to exfiltrate secrets, spawning child processes) occur on the write/update path too.
The attack chain is: a user prompt-injects a pptxgenjs script → the AI writes it via the workspace_file tool → server immediately runs it.
Consider running compilation inside a Node.js vm.runInNewContext sandbox with a restricted context (no process, no module access) or an isolated worker, rather than executing raw user code in the main process.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Serve route passes undefined workspaceId breaking file references
- Both handleLocalFile and handleCloudProxy now extract workspaceId from the file key using parseWorkspaceFileKey and pass it to compilePptxIfNeeded.
- ✅ Fixed: Server-side code execution via unsandboxed new Function
- Replaced new Function() with vm.createContext() which creates an isolated context exposing only pptx and getFileBase64, blocking access to process and other Node.js globals.
Or push these changes by commenting:
@cursor push cccb0dcbe3
Preview (cccb0dcbe3)
diff --git a/apps/sim/app/api/files/serve/[...path]/route.ts b/apps/sim/app/api/files/serve/[...path]/route.ts
--- a/apps/sim/app/api/files/serve/[...path]/route.ts
+++ b/apps/sim/app/api/files/serve/[...path]/route.ts
@@ -6,6 +6,7 @@
import { generatePptxFromCode } from '@/lib/copilot/tools/server/files/workspace-file'
import { CopilotFiles, isUsingCloudStorage } from '@/lib/uploads'
import type { StorageContext } from '@/lib/uploads/config'
+import { parseWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
import { downloadFile } from '@/lib/uploads/core/storage-service'
import { inferContextFromKey } from '@/lib/uploads/utils/file-utils'
import { verifyFileAccess } from '@/app/api/files/authorization'
@@ -138,10 +139,11 @@
const rawBuffer = await readFile(filePath)
const segment = filename.split('/').pop() || filename
const displayName = stripStorageKeyPrefix(segment)
+ const workspaceId = parseWorkspaceFileKey(filename)
const { buffer: fileBuffer, contentType } = await compilePptxIfNeeded(
rawBuffer,
displayName,
- undefined,
+ workspaceId || undefined,
raw
)
@@ -202,10 +204,11 @@
const segment = cloudKey.split('/').pop() || 'download'
const displayName = stripStorageKeyPrefix(segment)
+ const workspaceId = parseWorkspaceFileKey(cloudKey)
const { buffer: fileBuffer, contentType } = await compilePptxIfNeeded(
rawBuffer,
displayName,
- undefined,
+ workspaceId || undefined,
raw
)
diff --git a/apps/sim/lib/copilot/tools/server/files/workspace-file.ts b/apps/sim/lib/copilot/tools/server/files/workspace-file.ts
--- a/apps/sim/lib/copilot/tools/server/files/workspace-file.ts
+++ b/apps/sim/lib/copilot/tools/server/files/workspace-file.ts
@@ -1,3 +1,4 @@
+import vm from 'node:vm'
import { createLogger } from '@sim/logger'
import PptxGenJS from 'pptxgenjs'
import type { BaseServerTool, ServerToolContext } from '@/lib/copilot/tools/server/base-tool'
@@ -36,8 +37,19 @@
return `data:${mime};base64,${buffer.toString('base64')}`
}
- const fn = new Function('pptx', 'getFileBase64', `return (async () => { ${code} })()`)
- await fn(pptx, getFileBase64)
+ const sandbox = {
+ pptx,
+ getFileBase64,
+ __result: null as unknown,
+ }
+
+ vm.createContext(sandbox)
+
+ const wrappedCode = `(async () => { ${code} })()`
+ const script = new vm.Script(`__result = ${wrappedCode}`, { filename: 'pptx-code.js' })
+ script.runInContext(sandbox)
+ await sandbox.__result
+
const output = await pptx.write({ outputType: 'nodebuffer' })
return output as Buffer
}This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
| } | ||
|
|
||
| const fn = new Function('pptx', 'getFileBase64', `return (async () => { ${code} })()`) | ||
| await fn(pptx, getFileBase64) |
There was a problem hiding this comment.
Server-side code execution via unsandboxed new Function
High Severity
generatePptxFromCode uses new Function() to execute arbitrary JavaScript on the server without any sandboxing. The executed code has access to Node.js globals like process (exposing environment variables, secrets) and other built-ins. Since the code originates from copilot output influenced by user prompts, a crafted prompt could cause the copilot to emit code like process.env.DATABASE_URL or similar, leading to server-side information disclosure or worse.



Summary
Add file patch tool, add pptx capability
Type of Change
Testing
Manual
Checklist