Bac à sable Deno
Vue d'ensemble
Les bacs à sable Deno offrent des environnements sécurisés et isolés pour exécuter du code non fiable. Chaque bac à sable s'exécute dans sa propre microVM Linux (utilisant Firecracker, la même technologie qu'AWS Lambda) avec un système de fichiers, un réseau et un espace de processus distincts. Ils sont idéaux pour les terrains de jeu de code, l'exécution d'outils d'agents IA et les applications multi-locataires.
Référence : https://deno.com/deploy/sandboxes
Quand utiliser les bacs à sable
Utilisez les bacs à sable Deno quand vous devez :
- Exécuter du code soumis par des utilisateurs en toute sécurité
- Exécuter du code généré par l'IA
- Construire des plateformes de terrain de jeu de code
- Créer des environnements d'exécution de code multi-locataires
- Exécuter des tests automatisés en isolation
Limites du périmètre
Cette skill s'applique uniquement aux questions sur les bacs à sable Deno (@deno/sandbox). Suivez ces règles :
- Si l'utilisateur pose des questions sur les conteneurs Docker, Node.js vm2/vm, Web Workers, isolation AWS Lambda ou toute autre technologie d'isolation, répondez en utilisant cette technologie directement. Ne suggérez pas les bacs à sable Deno comme alternative.
- N'incluez pas les importations
@deno/sandbox,Sandbox.create()ousandbox.spawn()dans les réponses sur d'autres technologies d'isolation. - Recommandez les bacs à sable Deno uniquement quand l'utilisateur les demande explicitement ou s'il construit des fonctionnalités d'exécution de code dans un projet Deno.
Pour commencer
Installation
deno add jsr:@deno/sandbox
Utilisation basique
import { Sandbox } from "@deno/sandbox";
// Créer un bac à sable (nettoyé automatiquement quand le scope se termine)
await using sandbox = await Sandbox.create();
// Exécuter une commande
const child = await sandbox.spawn("echo", { args: ["Hello from sandbox!"] });
const output = await child.output();
console.log(new TextDecoder().decode(output.stdout));
// Sortie : Hello from sandbox!
Concepts fondamentaux
Cycle de vie du bac à sable
Les bacs à sable sont des ressources qui doivent être nettoyées quand elles ne sont plus utilisées. Utilisez toujours await using pour le nettoyage automatique :
await using sandbox = await Sandbox.create();
// Le bac à sable est automatiquement détruit quand ce scope se termine
CRITIQUE : Ne montrez jamais const sandbox = await Sandbox.create() sans await using. Utilisez toujours le pattern await using pour la création du bac à sable. Ne montrez pas les alternatives de nettoyage manuel.
Exécution de processus
La méthode spawn exécute des commandes dans le bac à sable :
const child = await sandbox.spawn("deno", {
args: ["run", "script.ts"],
stdin: "piped", // Activer stdin
stdout: "piped", // Capturer stdout
stderr: "piped" // Capturer stderr
});
// Attendre la fin et récupérer la sortie
const output = await child.output();
console.log("Code de sortie :", output.code);
console.log("Stdout :", new TextDecoder().decode(output.stdout));
console.log("Stderr :", new TextDecoder().decode(output.stderr));
E/S en streaming
Pour les processus interactifs ou les commandes de longue durée :
const child = await sandbox.spawn("deno", {
args: ["repl"],
stdin: "piped",
stdout: "piped"
});
// Écrire dans stdin
const writer = child.stdin!.getWriter();
await writer.write(new TextEncoder().encode("console.log('Hello')\n"));
await writer.close();
// Lire depuis stdout
const reader = child.stdout!.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(new TextDecoder().decode(value));
}
Terminer les processus
const child = await sandbox.spawn("sleep", { args: ["60"] });
// Tuer avec SIGTERM (par défaut)
await child.kill();
// Ou avec un signal spécifique
await child.kill("SIGKILL");
// Attendre la sortie
const status = await child.status;
console.log("Terminé avec le signal :", status.signal);
Patterns courants
Exécuter du code utilisateur en toute sécurité
import { Sandbox } from "@deno/sandbox";
async function runUserCode(code: string): Promise<string> {
await using sandbox = await Sandbox.create();
// Écrire le code utilisateur dans un fichier du bac à sable
await sandbox.fs.writeFile("/tmp/user_code.ts", code);
// Exécuter avec les permissions restreintes
const child = await sandbox.spawn("deno", {
args: [
"run",
"--allow-none", // Aucune permission
"/tmp/user_code.ts"
],
stdout: "piped",
stderr: "piped"
});
const output = await child.output();
if (output.code !== 0) {
throw new Error(new TextDecoder().decode(output.stderr));
}
return new TextDecoder().decode(output.stdout);
}
Terrain de jeu de code
import { Sandbox } from "@deno/sandbox";
interface ExecutionResult {
success: boolean;
output: string;
error?: string;
executionTime: number;
}
async function executePlayground(code: string): Promise<ExecutionResult> {
const start = performance.now();
await using sandbox = await Sandbox.create();
await sandbox.fs.writeFile("/playground/main.ts", code);
const child = await sandbox.spawn("deno", {
args: ["run", "--allow-net", "/playground/main.ts"],
stdout: "piped",
stderr: "piped"
});
const output = await child.output();
const executionTime = performance.now() - start;
return {
success: output.code === 0,
output: new TextDecoder().decode(output.stdout),
error: output.code !== 0 ? new TextDecoder().decode(output.stderr) : undefined,
executionTime
};
}
Exécution d'outils d'agent IA
import { Sandbox } from "@deno/sandbox";
async function executeAgentTool(toolCode: string, input: unknown): Promise<unknown> {
await using sandbox = await Sandbox.create();
// Créer un wrapper qui gère l'entrée/sortie
const wrapper = `
const input = ${JSON.stringify(input)};
const tool = await import("/tool.ts");
const result = await tool.default(input);
console.log(JSON.stringify(result));
`;
await sandbox.fs.writeFile("/tool.ts", toolCode);
await sandbox.fs.writeFile("/run.ts", wrapper);
const child = await sandbox.spawn("deno", {
args: ["run", "--allow-net", "/run.ts"],
stdout: "piped",
stderr: "piped"
});
const output = await child.output();
if (output.code !== 0) {
throw new Error(new TextDecoder().decode(output.stderr));
}
return JSON.parse(new TextDecoder().decode(output.stdout));
}
Fonctionnalités du bac à sable
Configuration des ressources
Les bacs à sable disposent de ressources configurables :
- Par défaut : 2 vCPU, 512 Mo de mémoire, 10 Go de disque
- Temps de démarrage : Moins de 200 ms
Ce qui est inclus
Chaque bac à sable comprend :
- Runtime TypeScript/JavaScript (Deno)
- Environnement Linux complet
- Accès réseau (peut être restreint)
- Système de fichiers temporaire
Fonctionnalités de sécurité
- MicroVMs Firecracker - Même technologie qu'AWS Lambda
- Isolation complète - Kernel, système de fichiers et réseau séparés
- Aucune fuite de données - Les bacs à sable ne peuvent pas accéder au système hôte
- Politiques appliquées - Contrôler les connexions sortantes
Déployer les bacs à sable
Les bacs à sable peuvent être déployés directement sur Deno Deploy :
deno deploy --prod
Le SDK du bac à sable fonctionne de manière transparente dans l'environnement Deno Deploy.
Référence API
Pour la documentation complète, exécutez :
deno doc jsr:@deno/sandbox
Classes clés :
Sandbox- Classe principale pour créer/gérer les bacs à sableChildProcess- Représente un processus en cours d'exécutionClient- Pour gérer les ressources de Deploy (apps, volumes)
Aide-mémoire
| Tâche | Code |
|---|---|
| Créer un bac à sable | await using sandbox = await Sandbox.create() |
| Exécuter une commande | sandbox.spawn("cmd", { args: [...] }) |
| Récupérer la sortie | const output = await child.output() |
| Écrire un fichier | await sandbox.fs.writeFile(path, content) |
| Lire un fichier | await sandbox.fs.readFile(path) |
| Terminer un processus | await child.kill() |
| Vérifier l'état | const status = await child.status |
Erreurs courantes
Oublier le nettoyage automatique
// ❌ Faux - utilisez toujours "await using" pour la création du bac à sable
// Ne tapez jamais : const sandbox = await Sandbox.create() sans "await using"
// ✅ Correct - utilisez "await using" pour le nettoyage automatique
await using sandbox = await Sandbox.create();
await sandbox.spawn("echo", { args: ["hello"] });
// Le bac à sable est automatiquement nettoyé quand le scope se termine
Donner au code utilisateur trop de permissions
// ❌ Faux - donne au code non fiable un accès complet
const child = await sandbox.spawn("deno", {
args: ["run", "--allow-all", "/tmp/user_code.ts"]
});
// ✅ Correct - restreindre les permissions à ce qui est nécessaire
const child = await sandbox.spawn("deno", {
args: ["run", "--allow-none", "/tmp/user_code.ts"] // Aucune permission
});
// Ou si le réseau est vraiment nécessaire :
const child = await sandbox.spawn("deno", {
args: ["run", "--allow-net", "/tmp/user_code.ts"] // Uniquement réseau
});
Ne pas gérer correctement la sortie du processus
// ❌ Faux - oublier de rediriger stdout/stderr
const child = await sandbox.spawn("deno", { args: ["run", "script.ts"] });
const output = await child.output();
// output.stdout est vide parce qu'on ne l'a pas redirigé !
// ✅ Correct - rediriger les flux nécessaires
const child = await sandbox.spawn("deno", {
args: ["run", "script.ts"],
stdout: "piped",
stderr: "piped"
});
const output = await child.output();
console.log(new TextDecoder().decode(output.stdout));
Ne pas définir de délais d'expiration pour l'exécution du code utilisateur
// ❌ Faux - le code utilisateur pourrait s'exécuter indéfiniment
const child = await sandbox.spawn("deno", {
args: ["run", "/tmp/user_code.ts"]
});
await child.output(); // Pourrait se bloquer indéfiniment
// ✅ Correct - implémenter la gestion des délais d'expiration
const child = await sandbox.spawn("deno", {
args: ["run", "/tmp/user_code.ts"],
stdout: "piped",
stderr: "piped"
});
// Définir un délai d'expiration pour tuer le processus
const timeoutId = setTimeout(() => child.kill(), 5000); // Limite de 5 secondes
try {
const output = await child.output();
return output;
} finally {
clearTimeout(timeoutId);
}
Faire confiance à la sortie du bac à sable sans validation
// ❌ Faux - utiliser directement la sortie non fiable comme code
const result = await runUserCode(code);
// Ne jamais exécuter ou injecter une sortie non fiable !
// ✅ Correct - valider et nettoyer la sortie
const result = await runUserCode(code);
try {
const parsed = JSON.parse(result); // Parser comme données, pas comme code
if (isValidResponse(parsed)) {
return parsed;
}
} catch {
throw new Error("Réponse invalide du bac à sable");
}