Skip to content

improvement(mothership): add file patch tool#3712

Open
Sg312 wants to merge 15 commits intostagingfrom
improvement/mothership-file-patches
Open

improvement(mothership): add file patch tool#3712
Sg312 wants to merge 15 commits intostagingfrom
improvement/mothership-file-patches

Conversation

@Sg312
Copy link
Collaborator

@Sg312 Sg312 commented Mar 22, 2026

Summary

Add file patch tool, add pptx capability

Type of Change

  • New feature

Testing

Manual

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link

vercel bot commented Mar 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 23, 2026 1:50am

Request Review

@Sg312
Copy link
Collaborator Author

Sg312 commented Mar 22, 2026

@cursor review

@cursor
Copy link

cursor bot commented Mar 22, 2026

PR Summary

High Risk
Adds server/client execution of stored pptxgenjs code (via new Function) and serves compiled .pptx binaries, which is security- and stability-sensitive and could impact file serving performance. Also expands Copilot’s ability to modify files via the new patch operation, increasing blast radius if misused or buggy.

Overview
Enables Copilot to patch existing workspace files via a new workspace_file tool patch operation that applies search/replace edits, with schema updates to accept edits.

Adds PPTX generation + preview flow: .pptx files written/updated by Copilot are stored as text/x-pptxgenjs source (validated by compiling with pptxgenjs), the file-serve API auto-compiles these sources into real .pptx binaries unless raw=1 is requested, and the workspace FileViewer gains a PPTX preview that renders slides client-side using pptxviewjs with a small in-memory slide cache.

Updates workspace file query keys to support text/raw/binary modes (including a new useWorkspaceFileBinary hook) and adjusts invalidation to target the shared contentFile key, plus expands supported attachment/readable MIME types to include PPTX and text/x-pptxgenjs.

Written by Cursor Bugbot for commit 4a537ff. This will update automatically on new commits. Configure here.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 22, 2026

Greptile Summary

This PR adds a patch operation to the workspace_file copilot tool (find-and-replace edits on stored text files) and introduces first-class support for PPTX presentations: the AI can write/update/patch pptxgenjs source code that is compiled on-demand both at write-time (for validation) and at serve-time to produce a binary .pptx. A new PptxPreview component renders slides in the file viewer, and streaming PPTX preview is wired into the mothership chat panel.

Issues found:

  • P0 — Server-side code execution (workspace-file.ts line 39): generatePptxFromCode runs AI-supplied JavaScript on the server using new Function() with no sandboxing. Node.js globals such as process, Buffer, and indirect require access via process.mainModule are reachable, making this a vector for secret exfiltration or worse if a user can prompt-inject a malicious pptxgenjs script. The validation call on write/update/patch actively executes the code, so side-effects happen before any error check.

  • P1 — workspaceId is always empty at serve time (route.ts line 36–40): Both handleLocalFile and handleCloudProxy pass undefined as the workspaceId to compilePptxIfNeeded, which resolves to ''. Any PPTX that calls getFileBase64(fileId) will hit getWorkspaceFile('', fileId), find nothing, and throw—breaking the serve path for image-embedded presentations. The workspaceId is already encoded in the storage key (workspace/{workspaceId}/...) and can be extracted with the existing parseWorkspaceFileKey utility.

  • P1 — Incomplete escape sequences in extractFileContent (resource-content.tsx line 436–447): The streaming-content extractor only handles \\n, \\t, \\\", \\\\—silently dropping \r, \b, \f, \/, and \uXXXX Unicode escapes. This can corrupt content (especially code) during streaming preview.

Confidence Score: 2/5

  • Not safe to merge as-is: the server-side new Function() execution is an unmitigated RCE risk that should be sandboxed before this feature ships.
  • The patch operation itself is clean, the React/query-layer changes are solid, and the workflow-reload fix is a good improvement. However, the core PPTX compilation mechanism executes arbitrary user-influenced JavaScript in the main Node.js process with no isolation. Until that is addressed (vm sandbox, worker thread, or equivalent), the feature carries meaningful production security risk. The workspaceId omission and incomplete escape handling are independently blocking for correct behavior.
  • apps/sim/lib/copilot/tools/server/files/workspace-file.ts (RCE), apps/sim/app/api/files/serve/[...path]/route.ts (workspaceId), apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx (escape sequences)

Important Files Changed

Filename Overview
apps/sim/lib/copilot/tools/server/files/workspace-file.ts Adds patch operation and pptxgenjs code generation; the generatePptxFromCode helper executes user-supplied code server-side via new Function() with no sandboxing — a critical RCE risk.
apps/sim/app/api/files/serve/[...path]/route.ts Adds on-the-fly PPTX compilation when serving files; workspaceId is always passed as undefined to compilePptxIfNeeded, breaking getFileBase64 for presentations that embed workspace images.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx Adds PptxPreview component with slide rendering, streaming PPTX preview, and a small in-memory LRU cache; client-side new Function() usage is lower risk than server-side but same pattern.
apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx Introduces streaming PPTX preview and extractFileContent helper; the helper incompletely handles JSON escape sequences which can corrupt streamed content.
apps/sim/lib/copilot/vfs/file-reader.ts Adds text/x-pptxgenjs to readable types and re-orders the readable-type check before parseable-extension check; logic is correct, previously unreachable null return now explicit.

Sequence Diagram

sequenceDiagram
    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
Loading

Comments Outside Diff (1)

  1. apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx, line 436-447 (link)

    P1 Incomplete escape-sequence handling in extractFileContent

    The function manually unescapes only four sequences (\\n, \\t, \\\", \\\\) from what is a partially-received JSON string:

    return rest
      .replace(/\\n/g, '\n')
      .replace(/\\t/g, '\t')
      .replace(/\\"/g, '"')
      .replace(/\\\\/g, '\\')

    This silently drops \r, \b, \f, \/, and \uXXXX Unicode escapes. Any streaming content containing those sequences (e.g. code with \u0000 or Windows-style line endings \r\n) will be displayed corrupted or passed incorrectly to PptxGenJS, potentially causing render errors.

    Consider using a small incremental JSON decoder / partial-JSON parser, or at minimum adding the missing cases:

    return rest
      .replace(/\\r/g, '\r')
      .replace(/\\b/g, '\b')
      .replace(/\\f/g, '\f')
      .replace(/\\\//g, '/')
      .replace(/\\u([0-9a-fA-F]{4})/g, (_, h) => String.fromCharCode(parseInt(h, 16)))
      // existing replacements...

Reviews (1): Last reviewed commit: "Fix lint" | Re-trigger Greptile

Comment on lines +39 to +40
const fn = new Function('pptx', 'getFileBase64', `return (async () => { ${code} })()`)
await fn(pptx, getFileBase64)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 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.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

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.

Create PR

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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant