Skip to content

feat(ai): add code interpreter for AI data analysis#16559

Merged
FelixMalfait merged 11 commits intomainfrom
code-mode-poc
Dec 15, 2025
Merged

feat(ai): add code interpreter for AI data analysis#16559
FelixMalfait merged 11 commits intomainfrom
code-mode-poc

Conversation

@FelixMalfait
Copy link
Copy Markdown
Member

@FelixMalfait FelixMalfait commented Dec 15, 2025

Summary

  • Add code interpreter tool that enables AI to execute Python code for data analysis, CSV processing, and chart generation
  • Support for both local (development) and E2B (sandboxed production) execution drivers
  • Real-time streaming of stdout/stderr and generated files
  • Frontend components for displaying code execution results with expandable sections

Code Quality Improvements

  • Extract getMimeType to shared utility to reduce code duplication between drivers
  • Fix security issue: escape single quotes/backslashes in E2B driver env variable injection
  • Add buildExecutionState helper to reduce duplicated state object construction
  • Add DEFAULT_CODE_INTERPRETER_TIMEOUT_MS constant for consistency
  • Fix lingui linting warning and TypeScript theme errors in frontend

Test Plan

  • Test code interpreter with local driver in development
  • Test code interpreter with E2B driver in production environment
  • Verify streaming output displays correctly in chat UI
  • Verify generated files (charts, CSVs) are uploaded and downloadable
  • Test file upload flow (CSV, Excel) triggers code interpreter

Note

Updates generated i18n catalogs for Polish and pseudo-English, adding strings for code execution/output (code interpreter) and various UI messages, with minor text adjustments.

  • Localization:
    • Generated catalogs: Refresh locales/generated/pl-PL.ts and locales/generated/pseudo-en.ts.
      • Add strings for code execution/output (e.g., code, copy code/output, running/waiting states, download files, generated files, Python code execution).
      • Include new UI texts (errors, prompts, menus) and minor text corrections.
    • No changes to pt-BR; other files unchanged functionally.

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

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

12 issues found across 85 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (all 12 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/types/code-interpreter-input.type.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/types/code-interpreter-input.type.ts:1">
P2: Inconsistent pattern: This file manually defines types instead of inferring them from the Zod schema (`CodeInterpreterInputZodSchema`) like other tool type files do. This could lead to type drift if the schema changes. Follow the pattern from `http-request-input.type.ts` and `send-email-input.type.ts`.</violation>
</file>

<file name="packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/utils/extract-code-interpreter-files.util.ts">

<violation number="1" location="packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/utils/extract-code-interpreter-files.util.ts:81">
P2: Hardcoded path `/home/user/` should be extracted to a named constant shared with the code interpreter execution environment to ensure consistency.</violation>
</file>

<file name="packages/twenty-server/src/engine/core-modules/code-interpreter/drivers/e2b.driver.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/code-interpreter/drivers/e2b.driver.ts:49">
P1: Security: Environment variable key is not escaped, allowing potential Python code injection. While the value is properly escaped, the key is inserted directly into the Python code template. If `context.env` keys come from untrusted sources, an attacker could inject arbitrary Python code.</violation>
</file>

<file name="packages/twenty-server/src/engine/core-modules/tool-provider/services/tool-provider.service.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/tool-provider/services/tool-provider.service.ts:176">
P2: The `executionContext` is missing `userId`, `userWorkspaceId`, and `onCodeExecutionUpdate` fields that `CodeInterpreterTool` uses. Without `onCodeExecutionUpdate`, real-time streaming of code execution output won&#39;t work when tools are obtained through this service. Consider extending `ToolSpecification` to include these fields or documenting that this code path doesn&#39;t support streaming.</violation>
</file>

<file name="packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts">

<violation number="1" location="packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts:113">
P2: Missing `CODE_INTERPRETER_TOOL` in `TOOL_PERMISSION_FLAGS` constant. This new permission is added to defaults but not registered as a tool permission in `constants/tool-permission-flags.ts`. As a result, `isToolPermission()` will return `false`, causing the permission check to use `canUpdateAllSettings` instead of `canAccessAllTools`, which is inconsistent with other tool permissions like `SEND_EMAIL_TOOL` and `HTTP_REQUEST_TOOL`. Add `&#39;CODE_INTERPRETER_TOOL&#39;` to the `TOOL_PERMISSION_FLAGS` array.</violation>
</file>

<file name="packages/twenty-server/src/engine/core-modules/code-interpreter/drivers/local.driver.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/code-interpreter/drivers/local.driver.ts:41">
P1: Path traversal vulnerability: `file.filename` is not sanitized before joining with `workDir`. If `filename` contains `../` sequences, files can be written outside the sandbox directory. Consider using `path.basename()` to strip directory components:
```js
await fs.writeFile(join(workDir, path.basename(file.filename)), file.content);
```</violation>
</file>

<file name="packages/twenty-front/src/modules/ai/components/CodeExecutionDisplay.tsx">

<violation number="1" location="packages/twenty-front/src/modules/ai/components/CodeExecutionDisplay.tsx:341">
P2: Using `filename` as the React key may cause issues if files with duplicate names are generated. Consider using `file.url` which is more likely to be unique.</violation>

<violation number="2" location="packages/twenty-front/src/modules/ai/components/CodeExecutionDisplay.tsx:344">
P2: Consider adding an `onError` handler to `StyledPreviewImage` to gracefully fall back to the file icon if the image fails to load.</violation>
</file>

<file name="packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts:537">
P2: `@IsOptional()` should be removed from `E2B_API_KEY`. The factory code throws if this key is missing when E2B is selected, so validation should catch this at startup rather than failing at runtime. Other required conditional keys (like `AUTH_GOOGLE_CLIENT_SECRET`, `BILLING_STRIPE_API_KEY`) don&#39;t use `@IsOptional()`.</violation>
</file>

<file name="packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx">

<violation number="1" location="packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx:163">
P2: Tool-level error (`errorText`) is not displayed when code_interpreter execution fails. If the tool fails at a higher level (timeout, service error), users will see &quot;Failed&quot; status without any error message. Consider passing `errorText` to handle this case, e.g., `stderr={codeOutput?.result?.stderr || errorText || &#39;&#39;}`.</violation>
</file>

<file name="packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.ts:285">
P2: Missing timeout for HTTP request. If the remote server is unresponsive, this could hang indefinitely. Consider adding a timeout option.</violation>

<violation number="2" location="packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.ts:358">
P1: Potential path traversal vulnerability: `file.filename` is used directly in file path construction without sanitization. Consider sanitizing the filename to remove path separators and traversal sequences (e.g., `path.basename(file.filename)` or a stricter sanitization function).</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

@@ -0,0 +1,9 @@
export type CodeInterpreterFileInput = {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 15, 2025

Choose a reason for hiding this comment

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

P2: Inconsistent pattern: This file manually defines types instead of inferring them from the Zod schema (CodeInterpreterInputZodSchema) like other tool type files do. This could lead to type drift if the schema changes. Follow the pattern from http-request-input.type.ts and send-email-input.type.ts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/types/code-interpreter-input.type.ts, line 1:

<comment>Inconsistent pattern: This file manually defines types instead of inferring them from the Zod schema (`CodeInterpreterInputZodSchema`) like other tool type files do. This could lead to type drift if the schema changes. Follow the pattern from `http-request-input.type.ts` and `send-email-input.type.ts`.</comment>

<file context>
@@ -0,0 +1,9 @@
+export type CodeInterpreterFileInput = {
+  filename: string;
+  url: string;
</file context>
Fix with Cubic


newParts.push({
type: 'text',
text: `\n\n[Files available for code interpreter at /home/user/:\n${fileList}]\n\nUse the code_interpreter tool to analyze these files.`,
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 15, 2025

Choose a reason for hiding this comment

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

P2: Hardcoded path /home/user/ should be extracted to a named constant shared with the code interpreter execution environment to ensure consistency.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/utils/extract-code-interpreter-files.util.ts, line 81:

<comment>Hardcoded path `/home/user/` should be extracted to a named constant shared with the code interpreter execution environment to ensure consistency.</comment>

<file context>
@@ -0,0 +1,95 @@
+
+      newParts.push({
+        type: &#39;text&#39;,
+        text: `\n\n[Files available for code interpreter at /home/user/:\n${fileList}]\n\nUse the code_interpreter tool to analyze these files.`,
+      });
+    }
</file context>
Fix with Cubic

.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'");

return `os.environ['${key}'] = '${escapedValue}'`;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 15, 2025

Choose a reason for hiding this comment

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

P1: Security: Environment variable key is not escaped, allowing potential Python code injection. While the value is properly escaped, the key is inserted directly into the Python code template. If context.env keys come from untrusted sources, an attacker could inject arbitrary Python code.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/core-modules/code-interpreter/drivers/e2b.driver.ts, line 49:

<comment>Security: Environment variable key is not escaped, allowing potential Python code injection. While the value is properly escaped, the key is inserted directly into the Python code template. If `context.env` keys come from untrusted sources, an attacker could inject arbitrary Python code.</comment>

<file context>
@@ -0,0 +1,109 @@
+                .replace(/\\/g, &#39;\\\\&#39;)
+                .replace(/&#39;/g, &quot;\\&#39;&quot;);
+
+              return `os.environ[&#39;${key}&#39;] = &#39;${escapedValue}&#39;`;
+            })
+            .join(&#39;\n&#39;)}\n\n`
</file context>
Fix with Cubic


private async getActionTools(spec: ToolSpecification): Promise<ToolSet> {
const tools: ToolSet = {};
const executionContext = { workspaceId: spec.workspaceId };
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 15, 2025

Choose a reason for hiding this comment

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

P2: The executionContext is missing userId, userWorkspaceId, and onCodeExecutionUpdate fields that CodeInterpreterTool uses. Without onCodeExecutionUpdate, real-time streaming of code execution output won't work when tools are obtained through this service. Consider extending ToolSpecification to include these fields or documenting that this code path doesn't support streaming.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/core-modules/tool-provider/services/tool-provider.service.ts, line 176:

<comment>The `executionContext` is missing `userId`, `userWorkspaceId`, and `onCodeExecutionUpdate` fields that `CodeInterpreterTool` uses. Without `onCodeExecutionUpdate`, real-time streaming of code execution output won&#39;t work when tools are obtained through this service. Consider extending `ToolSpecification` to include these fields or documenting that this code path doesn&#39;t support streaming.</comment>

<file context>
@@ -164,6 +173,7 @@ export class ToolProviderService {
 
   private async getActionTools(spec: ToolSpecification): Promise&lt;ToolSet&gt; {
     const tools: ToolSet = {};
+    const executionContext = { workspaceId: spec.workspaceId };
 
     for (const [toolType, { tool, flag }] of this.actionTools) {
</file context>
Fix with Cubic

<CodeExecutionDisplay
code={codeInput?.code ?? ''}
stdout={codeOutput?.result?.stdout ?? ''}
stderr={codeOutput?.result?.stderr ?? ''}
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 15, 2025

Choose a reason for hiding this comment

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

P2: Tool-level error (errorText) is not displayed when code_interpreter execution fails. If the tool fails at a higher level (timeout, service error), users will see "Failed" status without any error message. Consider passing errorText to handle this case, e.g., stderr={codeOutput?.result?.stderr || errorText || ''}.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx, line 163:

<comment>Tool-level error (`errorText`) is not displayed when code_interpreter execution fails. If the tool fails at a higher level (timeout, service error), users will see &quot;Failed&quot; status without any error message. Consider passing `errorText` to handle this case, e.g., `stderr={codeOutput?.result?.stderr || errorText || &#39;&#39;}`.</comment>

<file context>
@@ -141,6 +142,32 @@ export const ToolStepRenderer = ({ toolPart }: { toolPart: ToolUIPart }) =&gt; {
+      &lt;CodeExecutionDisplay
+        code={codeInput?.code ?? &#39;&#39;}
+        stdout={codeOutput?.result?.stdout ?? &#39;&#39;}
+        stderr={codeOutput?.result?.stderr ?? &#39;&#39;}
+        exitCode={codeOutput?.result?.exitCode}
+        files={codeOutput?.result?.files}
</file context>
Suggested change
stderr={codeOutput?.result?.stderr ?? ''}
stderr={codeOutput?.result?.stderr || errorText || ''}

✅ Addressed in 4b90a1d

}

// Handle HTTP URLs
const response = await this.httpService.axiosRef.get(file.url, {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 15, 2025

Choose a reason for hiding this comment

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

P2: Missing timeout for HTTP request. If the remote server is unresponsive, this could hang indefinitely. Consider adding a timeout option.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/code-interpreter-tool.ts, line 285:

<comment>Missing timeout for HTTP request. If the remote server is unresponsive, this could hang indefinitely. Consider adding a timeout option.</comment>

<file context>
@@ -0,0 +1,435 @@
+        }
+
+        // Handle HTTP URLs
+        const response = await this.httpService.axiosRef.get(file.url, {
+          responseType: &#39;arraybuffer&#39;,
+        });
</file context>

✅ Addressed in 4b90a1d

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Dec 15, 2025

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:35397

This environment will automatically shut down when the PR is closed or after 5 hours.

- Extract getMimeType to shared utility to reduce code duplication
- Fix security issue: escape single quotes in E2B driver env variable injection
- Add CodeInterpreterTool to ToolProviderService for consistency
- Create buildExecutionState helper to reduce duplicated state object construction
- Add DEFAULT_CODE_INTERPRETER_TIMEOUT_MS constant
- Fix lingui warning by extracting filename variable
- Fix TypeScript errors: use theme.background.transparent.success/danger
- Remove trailing empty lines from new files
Copy link
Copy Markdown

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

),
);
}
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Async callback causes race condition with file uploads

The onResult callback is defined as async but the StreamCallbacks interface specifies onResult as returning void. The drivers call callbacks?.onResult?.(outputFile) synchronously without awaiting, so the async upload runs in the background while execution continues. By the time uploadOutputFiles is called on line 189, streamedFiles may be incomplete because the async uploads haven't finished. This can cause duplicate file uploads and inconsistent streaming state.

Additional Locations (1)

Fix in Cursor Fix in Web

// Allow requests to the server's own URL (for internal file downloads)
// but block all other private/internal IPs to prevent SSRF attacks
const isInternalFileUrl = file.url.startsWith(serverUrl);
const adapter = isInternalFileUrl ? undefined : getSecureAdapter();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: SSRF protection bypass via URL prefix matching

The SSRF protection check uses file.url.startsWith(serverUrl) to determine if a URL is internal, but this string prefix matching is insufficient. If SERVER_URL is https://app.example.com and an attacker crafts a URL like https://app.example.com.attacker.com/malicious, the startsWith check returns true, bypassing the secure adapter. The request then goes to the attacker-controlled domain without SSRF protection. The check should parse both URLs and compare hostnames to ensure they match exactly.

Fix in Cursor Fix in Web

Comment on lines +80 to +86
const content = await sbx.files.read(
`/home/user/output/${file.name}`,
);

const outputFile: OutputFile = {
filename: file.name,
content: Buffer.from(content),

This comment was marked as outdated.

for (const file of files ?? []) {
const arrayBuffer = new Uint8Array(file.content).buffer;

await sbx.files.write(`/home/user/${file.filename}`, arrayBuffer);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: E2B driver lacks path traversal protection for input files

The E2BDriver directly interpolates file.filename into the file path without sanitization, unlike the LocalDriver which properly uses basename(file.filename) to strip path traversal characters. A malicious filename like ../../../etc/passwd would be written to /home/user/../../../etc/passwd instead of /home/user/passwd. While E2B runs in a sandbox, this creates inconsistent behavior between development and production environments and could allow writing files to unexpected locations within the sandbox.

Additional Locations (1)

Fix in Cursor Fix in Web

Comment on lines +331 to +335
sub: userId ?? workspaceId,
type: JwtTokenTypeEnum.ACCESS,
workspaceId,
userId: userId ?? workspaceId,
userWorkspaceId: userWorkspaceId ?? workspaceId,

This comment was marked as outdated.

return `os.environ['${key}'] = '${escapedValue}'`;
})
.join('\n')}\n\n`
: '';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Environment variable key not escaped in Python code generation

The PR description states this fixes "security issue: escape single quotes/backslashes in E2B driver env variable injection", but the fix is incomplete. While escapedValue properly escapes the environment variable value, the key variable is interpolated directly into the Python code without any escaping on line 88. If an environment variable key contains a single quote or bracket (e.g., test'] = 'x'; malicious_code(); os.environ['y), it could break out of the string literal and inject arbitrary Python code. The ExecutionContext interface allows arbitrary string keys via Record<string, string>, so both keys and values need escaping for the fix to be complete.

Fix in Cursor Fix in Web

@FelixMalfait FelixMalfait merged commit 2e104c8 into main Dec 15, 2025
72 of 74 checks passed
@FelixMalfait FelixMalfait deleted the code-mode-poc branch December 15, 2025 15:11
@twenty-eng-sync
Copy link
Copy Markdown

Hey @FelixMalfait! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

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