|
| 1 | +/** |
| 2 | + * @title Spin up a sandbox to run untrusted code |
| 3 | + * @difficulty beginner |
| 4 | + * @tags sandbox |
| 5 | + * @run -A --env-file <url> |
| 6 | + * @resource {https://docs.deno.com/sandbox/} Deno Deploy Sandbox |
| 7 | + * @group Deno Deploy |
| 8 | + * |
| 9 | + * Deno Deploy's Sandbox API lets you create secure microVMs to run untrusted code safely. |
| 10 | + * In this example, we ask Claude to generate a Deno script that fetches the current Bitcoin price, |
| 11 | + * then run that script inside a sandbox with strict resource limits and no access to the host environment. |
| 12 | + */ |
| 13 | + |
| 14 | +// Import the Anthropic SDK and the Deno Sandbox SDK. |
| 15 | +import Anthropic from "npm:@anthropic-ai/sdk"; |
| 16 | +import { Sandbox } from "jsr:@deno/sandbox"; |
| 17 | + |
| 18 | +// Create an Anthropic client. |
| 19 | +// It automatically picks up ANTHROPIC_API_KEY from your environment. |
| 20 | +const client = new Anthropic(); |
| 21 | + |
| 22 | +// Ask Claude to write some code for us wrapped in a markdown code block. |
| 23 | +const response = await client.messages.create({ |
| 24 | + model: "claude-opus-4-6", |
| 25 | + max_tokens: 1024, |
| 26 | + messages: [{ |
| 27 | + role: "user", |
| 28 | + content: |
| 29 | + "Write a Deno script that fetches the current Bitcoin price from the CoinGecko API and prints it.", |
| 30 | + }], |
| 31 | +}); |
| 32 | + |
| 33 | +// Take the generated code out of Claude's response. |
| 34 | +// Check it's a text block, then strip the markdown |
| 35 | +// fences to get the raw source. |
| 36 | +const firstBlock = response.content[0]; |
| 37 | +if (firstBlock.type !== "text") { |
| 38 | + throw new Error(`Unexpected content type: ${firstBlock.type}`); |
| 39 | +} |
| 40 | +const generatedCode = extractCode(firstBlock.text); |
| 41 | + |
| 42 | +// Create a sandbox . |
| 43 | +await using sandbox = await Sandbox.create(); |
| 44 | + |
| 45 | +// Write the AI-generated code into the sandbox filesystem. |
| 46 | +await sandbox.fs.writeTextFile("/tmp/generated.ts", generatedCode); |
| 47 | + |
| 48 | +// Run the code inside the sandbox with Deno. |
| 49 | +// stdout and stderr are piped so we can capture and display them. |
| 50 | +const child = await sandbox.spawn("deno", { |
| 51 | + args: [ |
| 52 | + "run", |
| 53 | + "--allow-net=api.coingecko.com", // Only the specific host we expect |
| 54 | + "/tmp/generated.ts", |
| 55 | + ], |
| 56 | + stdout: "piped", |
| 57 | + stderr: "piped", |
| 58 | +}); |
| 59 | + |
| 60 | +// AI-generated code could accidentally (or maliciously) loop forever — |
| 61 | +// this ensures we kill the process after 10 seconds no matter what. |
| 62 | +const timeout = setTimeout(() => child.kill(), 10_000); |
| 63 | + |
| 64 | +// Wait for the process to finish and print the results. |
| 65 | +// output.stdoutText / stderrText are pre-decoded UTF-8 strings. |
| 66 | +// output.status.success is true only if the exit code was 0. |
| 67 | +try { |
| 68 | + const output = await child.output(); |
| 69 | + console.log("Output:\n", output.stdoutText ?? "No output"); |
| 70 | + if (!output.status.success) { |
| 71 | + console.error("Error:\n", output.stderrText ?? "No error output"); |
| 72 | + } |
| 73 | +} finally { |
| 74 | + clearTimeout(timeout); |
| 75 | +} |
| 76 | + |
| 77 | +// Helper: extract the first code block from a markdown string. |
| 78 | +// Falls back to returning the raw text if no fences are found. |
| 79 | +function extractCode(text: string): string { |
| 80 | + const match = text.match( |
| 81 | + /```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/, |
| 82 | + ); |
| 83 | + return match ? match[1] : text; |
| 84 | +} |
0 commit comments