-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add TypeScript sandbox service for Bun #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: add TypeScript sandbox service for Bun #19
Conversation
4b49c65 to
c26b292
Compare
| loader: "ts", | ||
| target: "browser", // Use browser target for clean output | ||
| trimUnusedImports: true | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Bun transpiler output incompatible with executor wrappers
The BunTranspilerLive with target: "browser" preserves ES module syntax (export default), but the executors wrap transpiled code inside functions where export statements are syntactically invalid. This causes a syntax error at runtime. The Sucrase transpiler correctly converts ES modules to CommonJS (exports.default = ...) using transforms: ["typescript", "imports"], but the Bun transpiler lacks equivalent configuration. BunProductionLayer and BunFastLayer would fail to execute any user code using exports.
Additional Locations (2)
| return await exported(ctx); | ||
| } | ||
| return exported; | ||
| \`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Template literal interpolation breaks with backticks in user code
The user's transpiled JavaScript is interpolated into a template literal using ${javascript}. If the user code contains backticks (template literals like `hello ${name}`), the backtick will terminate the outer template literal prematurely, causing syntax errors or potentially allowing code injection. Valid user code that uses template literals will break when executed. The javascript string needs to be escaped or the injection mechanism should use string concatenation or JSON.stringify instead of template literal interpolation.
Additional Locations (1)
a2c922a to
c1b104f
Compare
| ] | ||
| const propName = node.property?.type === "Identifier" | ||
| ? node.property.name | ||
| : (node.property?.type === "Literal" ? node.property.value : null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Template literal property access bypasses security validation
The MemberExpression handler only checks for Identifier and Literal property types when detecting dangerous properties like constructor or __proto__. Template literals without expressions (e.g., obj[`constructor`]) are parsed as TemplateLiteral AST nodes, not Literal nodes. This causes propName to be null, bypassing the security check entirely. An attacker could write [][`constructor`][`constructor`]("return process.env")() to access the Function constructor and execute arbitrary code, defeating the sandbox security for the unsafe executor.
| target: "browser", | ||
| trimUnusedImports: true | ||
| }) | ||
| return transpiler.transformSync(typescript) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Bun transpiler keeps ESM syntax breaking executor
The Bun transpiler with target: "browser" preserves ESM syntax (export default), but the executor wraps code inside a function and expects CommonJS-style exports (module.exports). When running in Bun, the transpiled code like export default (ctx) => ... will cause a SyntaxError because export statements are invalid inside a function body. The tests pass only because Vitest runs in Node where Sucrase is used, which converts ESM to CommonJS. Production Bun execution would fail.
Additional Locations (1)
| const module = { exports: {} }; | ||
| const exports = module.exports; | ||
| ${javascript} | ||
| const exported = module.exports.default || module.exports; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Falsy default exports return incorrect value
The expression module.exports.default || module.exports uses the || operator, which returns module.exports when module.exports.default is a falsy value like 0, false, "", or null. This causes the executor to return the entire module object instead of the intended exported value. Using nullish coalescing (??) or an explicit property existence check would correctly handle falsy exports.
Implements a sandboxed TypeScript execution system using Effect-TS:
- Core types: ParentContext, ExecutionResult, CompiledModule, SandboxConfig
- Error types: ValidationError, TranspilationError, ExecutionError, TimeoutError
- Services: Transpiler, CodeValidator, SandboxExecutor, TypeScriptSandbox
- Implementations:
- Acorn-based code validator (static analysis for security)
- Sucrase transpiler (fast dev transpilation)
- Bun native transpiler (production, Bun-only)
- Unsafe executor (eval-based, no isolation)
- Bun Worker executor (true process isolation)
- Pre-composed layers: DevFastLayer, DevSafeLayer, BunProductionLayer
- Comprehensive test suite (20 tests)
User code contract:
export default async (ctx) => {
const result = await ctx.callbacks.fetchData("key")
return { value: ctx.data.multiplier * 2, fetched: result }
}
- Block .constructor, __proto__, and prototype manipulation properties - Add Effect.async cleanup functions to prevent resource leaks on interruption - Add safeResume wrapper to prevent double-calling resume callbacks - Add comprehensive security bypass tests for constructor chain attacks - Document static analysis limitations for dynamic computed properties
- Use Layer.mergeAll for idiomatic layer composition - Add Effect.fn tracing to compile/run methods for observability - Enhance errors with SandboxErrorTypeId for runtime type guards - Add cause tracking via Schema.Defect for error chains - Add computed message getters with location info - Add TypeScript feature tests: generics, enums, classes, unions - Test invalid TypeScript produces useful TranspilationError
- executor-unsafe: Replace Effect.async + Promise.race with Effect.gen + Effect.try + Effect.tryPromise + Effect.timeoutFail (93 → 50 lines) - executor-bun-worker: Extract cleanup/cleanupAll helpers to reduce duplication - errors: Remove _message + override getter pattern, use message directly (matches simpler pattern in src/errors.ts) - Remove TypeId boilerplate and isSandboxError predicate (unused) Net reduction: ~100 lines while improving readability
Effect.timeoutFail doesn't work correctly when the underlying Promise never resolves - the interrupt signal isn't observed. Promise.race at the JavaScript level properly handles this case.
- Rename src/sandbox → src/code-mode - Remove bun worker executor (unnecessary complexity) - Remove multiple layer compositions (single CodeModeLive) - Add comprehensive README.md explaining architecture - Transpiler falls back to sucrase for Node/vitest compatibility The validator exists for SECURITY, not type-checking: - Blocks imports/require (no filesystem access) - Blocks eval/Function constructor (no sandbox escape) - Blocks prototype chain attacks (__proto__, .constructor) - Enforces globals allowlist (no process, Bun access)
Add TypeChecker service using TypeScript compiler API for optional type checking before transpilation. Key changes: - New TypeChecker service with configurable compilerOptions and preamble - Preamble allows injecting ctx type definitions checked but not transpiled - Line numbers in errors exclude preamble lines for accurate reporting - TypeCheckError with diagnostics array (message, line, column, code) - Simplified ctx structure: flat object instead of ctx.callbacks/ctx.data - Type checking disabled by default for backwards compatibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Plan for integrating TypeScript sandbox with actor architecture: - New event types (CodeBlockStart, CodeExecutionResult, TypeCheckResult) - OpenTUI Code component with Tree-Sitter syntax highlighting - Feed reducer extension for code block rendering - Execution service wrapping CodeMode 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
c1afbe0 to
500a090
Compare
Implements a sandboxed TypeScript execution system using Effect-TS:
User code contract:
export default async (ctx) => {
const result = await ctx.callbacks.fetchData("key")
return { value: ctx.data.multiplier * 2, fetched: result }
}
Note
Introduce a modular TypeScript sandbox (
code-mode/) with Bun/Sucrase transpilation, acorn-based security validation, TS compiler type-checking, eval-based execution with timeouts, and comprehensive tests, plus docs and minimal deps.src/code-mode/):CodeModecomposite (compile/run) coordinatingTypeChecker→Transpiler→Validator→Executor; defaultCodeModeLivelayer.Transpiler: Bun.Transpiler (if available) with Sucrase fallback.Validator: acorn + AST walk; blocks imports, eval/new Function, constructor/proto chains, undeclared globals.TypeChecker: TypeScript compiler API with preamble support; surfaces diagnostics with adjusted line numbers.Executor: eval-based execution with injectedctxand timeout handling.types.ts(ExecutionResult,CompiledModule, configs) and rich error classes (ValidationError,TypeCheckError,ExecutionError,TimeoutError,SecurityViolation).code-mode.test.tscovering valid flows, TS features, security blocks, timeouts, compile-once reuse, and type-checking scenarios.src/code-mode/README.mdusage/architecture.docs/todo/code-mode-integration-plan.mdfor MiniAgent/OpenTUI.acorn,acorn-walk,sucrase(and lockfile updates).Written by Cursor Bugbot for commit 500a090. This will update automatically on new commits. Configure here.