écrits/tutorial/2026/08
Tutorial3 août 2026·22 min

Isoler l'exécution de code des agents IA avec Microsoft MXC et TypeScript

Apprenez à utiliser Microsoft Execution Containers (MXC) pour isoler le code non fiable exécuté par des agents IA. Ce tutoriel couvre l'installation, la configuration des politiques, l'exécution en bac à sable et l'intégration avec les appels d'outils Claude en TypeScript.

Quand un agent IA invoque un outil qui exécute du code — une commande shell, un sous-processus, ou une écriture sur le système de fichiers — vous avez le choix : faire confiance aveuglément à la sortie du modèle, ou imposer des limites strictes au niveau du système d'exploitation.

Microsoft Execution Containers (MXC), lancé à Build 2026, rend la seconde option pratique. MXC est un moteur de politiques au niveau de l'OS qui contraint précisément ce qu'un processus peut toucher — chemins de fichiers, interfaces réseau, temps CPU — via un schéma JSON unifié et un SDK TypeScript fonctionnant sur Windows, macOS et Linux.

Ce tutoriel vous guide pas à pas pour sécuriser les appels d'outils d'un agent IA avec MXC, de sorte que même la sortie la plus malveillante du modèle ne puisse pas s'échapper de son conteneur.

Note de préversion : MXC est en préversion anticipée en août 2026. Le SDK (@microsoft/mxc-sdk v0.7.0, licence MIT) est stable pour le développement et les usages en production non critiques sur le plan sécurité. L'isolation au niveau VM n'est pas encore en GA — le backend process offre une défense en profondeur, pas une frontière de sécurité absolue.

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 20+ avec npm ou pnpm
  • TypeScript 5.5+ en mode strict
  • Un OS supporté : Windows 11 24H2+, macOS 14+, ou Linux avec noyau 5.15+ (process sandbox) ou Firecracker installé (microVM)
  • À l'aise avec async/await et les génériques TypeScript
  • Une clé API Anthropic pour l'étape d'intégration agent (optionnelle)

Ce que vous allez construire

À la fin de ce tutoriel, vous aurez :

  1. Une classe SandboxExecutor réutilisable propulsée par MXC
  2. Des politiques JSON granulaires restreignant l'accès aux fichiers et au réseau
  3. Des patterns d'exécution en bac à sable unitaire et stateful
  4. Une intégration complète reliant MXC à l'outil bash d'un agent Claude

Étape 1 : Installer le SDK

pnpm add @microsoft/mxc-sdk

Le script postinstall télécharge automatiquement le binaire natif adapté à la plateforme. Sur Linux, il détecte également si Firecracker est disponible pour le backend microvm.

Ajoutez ces options à votre tsconfig.json si elles ne sont pas déjà présentes :

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "target": "ES2022",
    "lib": ["ES2022"],
    "strict": true
  }
}

Étape 2 : Vérifier le support de la plateforme

Interrogez toujours les capacités de la plateforme avant de créer un bac à sable — la disponibilité des backends varie selon l'OS et le noyau :

// src/sandbox/platform.ts
import { getPlatformSupport } from '@microsoft/mxc-sdk';
 
export async function requireSandboxSupport() {
  const support = await getPlatformSupport();
 
  if (support.backends.length === 0) {
    throw new Error(
      `MXC n'est pas disponible sur cette plateforme. ` +
      `OS: ${support.os}, noyau: ${support.kernelVersion}`
    );
  }
 
  // Préférer microvm pour l'isolation maximale, sinon process
  const preferred = support.backends.includes('microvm') ? 'microvm' : 'process';
 
  console.log(`MXC prêt — utilisation du backend "${preferred}"`);
  return { ...support, preferred };
}

Étape 3 : Définir une politique de bac à sable

Les politiques MXC suivent un modèle refus par défaut. Chaque permission doit être accordée explicitement. Voici une politique adaptée aux appels d'outils d'un agent IA :

// src/sandbox/policy.ts
import {
  createConfigFromPolicy,
  getAvailableToolsPolicy,
  getTemporaryFilesPolicy,
  type MxcPolicy,
} from '@microsoft/mxc-sdk';
import * as os from 'node:os';
import * as path from 'node:path';
 
export function buildAgentToolPolicy(projectRoot: string): MxcPolicy {
  const tmpDir = path.join(os.tmpdir(), 'mxc-agent-sandbox');
 
  return {
    network: 'none',
    filesystem: {
      readonlyPaths: [projectRoot],
      readWritePaths: [tmpDir],
      denyPaths: [os.homedir(), '/etc/passwd', '/etc/shadow'],
    },
    process: {
      maxPid: 32,
      allowedExecutables: ['/bin/sh', '/usr/bin/node', '/usr/bin/python3'],
    },
    resources: {
      timeoutSeconds: 30,
      memoryMb: 256,
      cpuPercent: 50,
    },
  };
}
 
export async function buildComposedPolicy() {
  const toolsPolicy = await getAvailableToolsPolicy();
  const tempPolicy = await getTemporaryFilesPolicy();
 
  return {
    ...toolsPolicy,
    ...tempPolicy,
    network: 'none' as const,
  };
}

Les denyPaths ont la priorité sur readonlyPaths et readWritePaths, ce qui permet d'autoriser largement puis d'exclure les emplacements sensibles.

Étape 4 : Exécution en une seule passe

Pour les commandes qui se terminent en un seul appel, utilisez spawnSandboxFromConfig. Il crée le conteneur, exécute la commande et tout démantèle automatiquement :

// src/sandbox/exec.ts
import { spawnSandboxFromConfig, createConfigFromPolicy } from '@microsoft/mxc-sdk';
import { buildAgentToolPolicy } from './policy.js';
 
export interface ExecResult {
  exitCode: number;
  stdout: string;
  stderr: string;
  durationMs: number;
}
 
export async function execInSandbox(
  command: string,
  args: string[],
  projectRoot: string,
): Promise<ExecResult> {
  const policy = buildAgentToolPolicy(projectRoot);
  const config = await createConfigFromPolicy(policy);
 
  const start = Date.now();
 
  const result = await spawnSandboxFromConfig(config, {
    command,
    args,
    cwd: projectRoot,
    env: {
      HOME: '/tmp',
      PATH: '/usr/bin:/bin',
    },
  });
 
  return {
    exitCode: result.exitCode,
    stdout: result.stdout,
    stderr: result.stderr,
    durationMs: Date.now() - start,
  };
}

Test de fumée rapide :

import { execInSandbox } from './src/sandbox/exec.js';
 
const result = await execInSandbox('node', ['--version'], process.cwd());
console.log(result.stdout.trim()); // v26.x.x
console.log('code de sortie:', result.exitCode); // 0

Si la commande dépasse timeoutSeconds, spawnSandboxFromConfig lève MxcTimeoutError. Si elle tente d'accéder à un chemin refusé, le noyau bloque le syscall et le processus reçoit EACCES.

Étape 5 : Cycle de vie stateful du bac à sable

Quand un agent doit exécuter plusieurs commandes en séquence — installer une dépendance, puis lancer des tests — créer un nouveau conteneur pour chaque commande gaspille 100 à 500 ms par appel. MXC expose un cycle de vie explicite pour la réutilisation :

// src/sandbox/session.ts
import {
  createConfigFromPolicy,
  type MxcSandbox,
} from '@microsoft/mxc-sdk';
import { buildAgentToolPolicy } from './policy.js';
 
export class SandboxSession {
  private sandbox: MxcSandbox | null = null;
 
  constructor(private readonly projectRoot: string) {}
 
  async open() {
    const policy = buildAgentToolPolicy(this.projectRoot);
    const config = await createConfigFromPolicy(policy);
 
    this.sandbox = await config.provision();
    await this.sandbox.start();
  }
 
  async exec(command: string, args: string[] = []) {
    if (!this.sandbox) throw new Error('Session non ouverte');
 
    return this.sandbox.exec({
      command,
      args,
      cwd: this.projectRoot,
      env: { HOME: '/tmp', PATH: '/usr/bin:/bin' },
    });
  }
 
  async close() {
    if (!this.sandbox) return;
    await this.sandbox.stop();
    await this.sandbox.deprovision();
    this.sandbox = null;
  }
}

Commandes séquentielles dans un conteneur unique :

const session = new SandboxSession(process.cwd());
await session.open();
 
try {
  const install = await session.exec('npm', ['ci', '--ignore-scripts']);
  console.log('sortie install:', install.exitCode);
 
  const tests = await session.exec('npm', ['test']);
  console.log(tests.stdout);
} finally {
  await session.close();
}

Étape 6 : Intégration avec les outils Claude

Le scénario le plus précieux est de brancher MXC sur un agent IA qui peut exécuter du code arbitraire. L'exemple suivant utilise le SDK TypeScript d'Anthropic avec un outil bash sécurisé :

// src/agent/sandbox-agent.ts
import Anthropic from '@anthropic-ai/sdk';
import { execInSandbox } from '../sandbox/exec.js';
 
const client = new Anthropic();
 
const BASH_TOOL: Anthropic.Tool = {
  name: 'bash',
  description: "Exécuter une commande shell et retourner stdout et stderr.",
  input_schema: {
    type: 'object' as const,
    properties: {
      command: {
        type: 'string',
        description: 'La commande shell à exécuter.',
      },
    },
    required: ['command'],
  },
};
 
export async function runSandboxedAgent(userPrompt: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: 'user', content: userPrompt },
  ];
 
  while (true) {
    const response = await client.messages.create({
      model: 'claude-sonnet-5',
      max_tokens: 4096,
      tools: [BASH_TOOL],
      messages,
    });
 
    messages.push({ role: 'assistant', content: response.content });
 
    if (response.stop_reason === 'end_turn') {
      const text = response.content.find(b => b.type === 'text');
      return text?.type === 'text' ? text.text : '';
    }
 
    if (response.stop_reason !== 'tool_use') break;
 
    const toolResults: Anthropic.ToolResultBlockParam[] = [];
 
    for (const block of response.content) {
      if (block.type !== 'tool_use') continue;
 
      const input = block.input as { command: string };
 
      try {
        const result = await execInSandbox(
          '/bin/sh',
          ['-c', input.command],
          process.cwd(),
        );
 
        const output =
          result.stdout + (result.stderr ? `\nSTDERR: ${result.stderr}` : '');
 
        toolResults.push({
          type: 'tool_result',
          tool_use_id: block.id,
          content: output || `(code de sortie ${result.exitCode})`,
        });
      } catch (err) {
        toolResults.push({
          type: 'tool_result',
          tool_use_id: block.id,
          is_error: true,
          content: err instanceof Error ? err.message : 'Erreur inconnue du bac à sable',
        });
      }
    }
 
    messages.push({ role: 'user', content: toolResults });
  }
 
  return '';
}

Chaque commande shell émise par le modèle est interceptée, encapsulée dans un conteneur MXC et exécutée avec network: 'none' et un délai de 30 secondes, avant que le résultat ne revienne au modèle.

Étape 7 : Gestion des erreurs

MXC lève des classes d'erreurs nommées que vous pouvez intercepter individuellement :

import {
  MxcTimeoutError,
  MxcPolicyViolationError,
  MxcBackendUnavailableError,
} from '@microsoft/mxc-sdk';
 
try {
  await execInSandbox('/bin/sh', ['-c', 'sleep 60'], process.cwd());
} catch (err) {
  if (err instanceof MxcTimeoutError) {
    console.error('Délai dépassé après', err.timeoutMs, 'ms');
  } else if (err instanceof MxcPolicyViolationError) {
    console.error('Violation de politique — ressource refusée :', err.deniedResource);
  } else if (err instanceof MxcBackendUnavailableError) {
    console.error('Aucun backend MXC disponible sur cet hôte');
  } else {
    throw err;
  }
}

Dans une boucle agent, transformez MxcTimeoutError et MxcPolicyViolationError en résultats d'outils avec is_error: true et un message clair pour que le modèle puisse se remettre sans répéter indéfiniment la commande bloquée.

Étape 8 : Considérations de production

Choisissez le bon backend. Le backend process démarre en moins de 10 ms et fonctionne partout, mais partage le noyau de l'hôte. Le backend microvm sur Linux fournit une isolation niveau VM via Firecracker — le choix approprié pour les sorties réellement non fiables.

Commencez par la politique la plus restrictive. Démarrez avec network: 'none' et une liste allowedExecutables couvrant exactement ce dont vous avez besoin.

Observabilité. Journalisez durationMs, le code de sortie et la commande brute dans votre système de traçage. Des clusters de violations de politique sont des signaux précoces de tentatives d'injection de prompt.

Dépannage

MxcBackendUnavailableError sous Linux — Installez Firecracker et définissez FIRECRACKER_BIN=/usr/local/bin/firecracker, ou demandez explicitement le backend process avec backend: 'process'.

Le bac à sable se termine immédiatement avec le code 1 — Inspectez stderr. La cause la plus fréquente est que le binaire cible n'est pas dans allowedExecutables. Ajoutez son chemin absolu.

Latence élevée au premier appel — Le backend microvm démarre une VM Firecracker en 200 à 500 ms. Utilisez SandboxSession (étape 5) pour maintenir la VM active sur plusieurs appels d'outils.

macOS : dialogues de permission — MXC utilise Sandbox.framework sur macOS. Si Gatekeeper bloque le binaire natif, exécutez :

xattr -d com.apple.quarantine ./node_modules/@microsoft/mxc-sdk/bin/mxc-host

Pour aller plus loin

  • Consultez la référence complète du schéma de politique dans le dépôt MXC sur GitHub
  • Combinez MXC avec l'intégration Sentry pour Next.js pour tracer les exécutions en bac à sable dans votre pipeline d'observabilité
  • Pour les agents nécessitant un accès réseau légitime, réglez network: 'loopback' et routez tout le trafic sortant via un proxy local audité

Conclusion

Microsoft MXC vous offre une frontière imposée par le système d'exploitation entre la sortie du modèle et votre environnement hôte. Avec quelques dizaines de lignes de TypeScript, chaque appel d'outil de votre agent s'exécute dans un conteneur qui ne peut pas lire de fichiers sensibles, contacter l'extérieur ou consommer des ressources sans limite. Ce même SDK passera de la défense en profondeur à une frontière de sécurité stricte au niveau VM sans modifier une seule ligne de code dès que le backend microvm sera disponible en GA.