deno-sandbox

Par denoland · skills

À utiliser lors de la création de fonctionnalités qui exécutent du code utilisateur non fiable, du code généré par IA, ou qui nécessitent des environnements d'exécution de code isolés. Couvre le SDK `@deno/sandbox`.

npx skills add https://github.com/denoland/skills --skill deno-sandbox

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() ou sandbox.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 à sable
  • ChildProcess - Représente un processus en cours d'exécution
  • Client - 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");
}

Skills similaires