Diwall MCP

Français
Télécharger v0.0.2

Comment configurer son poste pour qu'un modèle ne lise pas ses mots de passe ?

C'est la configuration du poste qui décide, non le serveur. Les trois niveaux (intransigeant, normal, inconscient) côte à côte avec ce que chacun ferme et ce qu'il coûte, puis les étapes dans l'ordre avec la sortie réelle de chaque commande, et la commande qui vérifie le résultat.

Sommaire

La réponse courte : c’est la configuration de votre poste qui décide

Qu’un modèle puisse lire vos mots de passe se règle par la configuration de votre poste, non par le serveur. Le serveur ferme les voies qu’il peut fermer : la valeur d’un identifiant n’apparaît jamais dans un résultat d’outil, dans le journal ni sur une ligne de commande ; elle ne se tape que sur l’origine à laquelle elle est liée ; un paiement, une suppression ou un changement de secret attendent votre confirmation. Un modèle qui a aussi un shell, c’est autre chose. Il atteint tout ce que son compte atteint : s’il tourne sous le compte qui détient votre répertoire chiffré, il liste les fichiers qui s’y trouvent, parle au navigateur sur son port de débogage et réécrit le profil auquel le serveur obéit. Aucun contrôle à l’intérieur du serveur ne peut refuser ce que le système autorise.

Une règle écrite dans un fichier de configuration, un modèle qui a un shell peut la modifier. Un refus du système d’exploitation, parce que le fichier appartient à un autre compte, non. Vous choisissez ce que vous voulez que le système refuse, parmi trois niveaux : intransigeant, normal et inconscient (déconseillé). Les étapes les construisent dans l’ordre inverse, du poste que vous avez au plus strict. Cette page met les trois côte à côte, puis donne, pour chacun, les étapes dans l’ordre, chaque commande avec sa sortie réelle, et la vérification qui dit où en est votre poste.

En bref : seul le niveau intransigeant ferme la capture de ce que vous tapez et sudo. Le niveau normal ferme le répertoire chiffré, le profil du navigateur, le port de débogage et le journal, mais pas le compte sous lequel tourne le modèle. Le niveau inconscient ne ferme rien : c’est l’état d’un poste où rien n’a été choisi.

Les trois niveaux côte à côte

Un niveau est une propriété du poste, non un réglage du serveur. Il dépend du compte qui détient le répertoire chiffré, le navigateur et le serveur, et du compte sous lequel tourne le modèle.

NiveauSous votre compteSous diwall-mcpSous agent
Intransigeantvotre écran, les fenêtres de confirmation, l’administrationle répertoire chiffré, le profil du navigateur, le navigateur, le serveurle client MCP et le modèle
Normalle client MCP et le modèle, votre écran, les fenêtres de confirmationle répertoire chiffré, le profil du navigateur, le navigateur, le serveurrien
Inconscient (déconseillé)toutrienrien
NiveauFermeLaisse ouvertCoûte
Intransigeantles dix voies : neuf par le noyau, la voie 4 par le serveuraucuneun compte de plus (agent), dans le répertoire personnel duquel finit tout ce que le modèle écrit
Normalles voies 1, 2, 3, 5, 6 et 8 par le noyau, la voie 4 par le serveurles voies 7, 9 et 10un compte (diwall-mcp), une unité systemd et une règle de pare-feu
Inconscient (déconseillé)rien par le noyauneuf voies sur dix ; la dixième est sans objetrien
  • Intransigeant déplace le client et le modèle vers un troisième compte. Il n’atteint ni votre écran, ni vos fichiers de démarrage, ni vos droits d’administrateur.
  • Normal déplace le répertoire chiffré, le navigateur et le serveur vers un second compte. Il arrête un modèle négligent ou trompé, et l’injection ordinaire, qui passent par le répertoire chiffré. Il n’arrête pas un modèle conçu pour nuire : celui-là tourne sous votre compte et atteint votre shell et sudo.
  • Inconscient est le poste que donne l’installation si vous ne faites rien de plus : le modèle et le répertoire chiffré partagent un seul compte. Le noyau ne ferme rien ; seules des règles tiennent (la configuration du client, les contrôles du serveur).

Les dix voies qu’un modèle peut prendre vers un secret

Une voie est fermée par le noyau quand le système refuse quoi que le modèle tente, fermée par le serveur quand le code du serveur la refuse et que le modèle ne peut modifier ni ce code ni sa configuration, et ouverte sinon. Le niveau d’un poste est celui de sa voie la plus ouverte.

VoieCe que tente un modèle qui a un shellIntransigeantNormalInconscient (déconseillé)
1. Lire le répertoire chiffré : identifiants, journal, captures, téléchargementsls et cat dans le point de montagenoyaunoyauouverte
2. Lire le profil du navigateur : les cookies des sessions ouverteslire le dossier du profilnoyaunoyauouverte
3. Piloter le navigateur sans le serveur, par son port de débogagese connecter au port 9222noyaunoyauouverte
4. Se faire passer pour le navigateur : écouter sur le port avant lui, pour que le serveur s’y attache et y tape un mot de passeouvrir un écouteur sur le port, puis appeler statusserveurserveursans objet
5. Changer ce que le serveur s’autorise : profil de l’opérateur, unité, commande du serveurécrire le profil et l’uniténoyaunoyauouverte
6. Effacer ou réécrire sa trace dans le journalécrire dans le dossier du journalnoyaunoyauouverte
7. Répondre à la fenêtre de confirmation à votre placexdotool, /dev/uinput, le socket de votre écrannoyauouverteouverte
8. Lire la mémoire du serveur, du navigateur ou de gocryptfs/proc/<pid>/mem, gdb -pnoyaunoyauouverte
9. Capter un mot de passe que vous tapezécrire un alias dans les fichiers de démarrage de votre shell, lire les événements du claviernoyauouverteouverte
10. Devenir administrateursudo -n true, sudo -l, injecter des frappes dans votre terminalnoyauouverteouverte

Les voies 7, 9 et 10 restent ouvertes au niveau normal parce que le modèle tourne sous le compte qui possède votre écran, vos fichiers de démarrage et vos droits sudo. Au niveau intransigeant, l’écran refuse le compte du modèle, les fichiers de démarrage et les événements du clavier lui sont fermés, et il n’a pas de sudo. Les voies 7 et 9, et la commande xhost employée plus bas, dépendent du serveur d’affichage de votre session. Elles ont été mesurées deux fois : sous X11, dans une session KDE, et sous Wayland, dans une session GNOME ; la section sur une session Wayland, plus bas, dit ce qui y diffère.

Ce que tout niveau conserve

Le serveur fait ceci à tous les niveaux, parce que son code le fait :

  • la valeur d’un identifiant n’est jamais dans un résultat d’outil, dans le journal ni sur une ligne de commande ;
  • un identifiant ne se tape que sur sa propre origine ;
  • un paiement, une suppression ou un changement de secret attendent votre confirmation.

Ces contrôles protègent d’une fuite par les outils. Ils ne protègent pas d’un modèle qui a un shell sous le compte du répertoire chiffré, ce pour quoi les niveaux existent. Un niveau protège la valeur d’un secret, non l’usage d’une session ouverte dans le navigateur dédié : le modèle peut se servir de cette session, dans les limites de la liste rouge, à tous les niveaux (Limites, Brancher un client MCP).

Vérifier son niveau : le rapport d’isolation

mcp-server-diwall-browser-setup isolation se lance depuis le compte du modèle. Il essaie chacune des dix voies par l’essai du tableau ci-dessus, n’écrit rien, ne demande aucun mot de passe, ne lance aucun sudo et ne lit aucun contenu du serveur. Pour chaque voie il dit fermée par le noyau, fermée par le serveur, ouverte, sans objet ou non mesurable d’ici, puis le niveau atteint. La sous-commande arrive avec la version 0.0.2 du serveur.

  • Sans argument, il prend diwall-mcp pour compte du serveur si ce compte existe, et votre compte sinon. isolation suivi d’un nom de compte prend ce compte à la place ; --port PORT désigne un autre port de débogage ; --json imprime les verdicts pour un script.
  • Une voie qui demande le répertoire chiffré n’est pas mesurable tant que le répertoire n’est pas monté, et la voie 6 n’est pas mesurable avant que le serveur ait tourné une fois, parce que le dossier du journal n’existe pas encore : le rapport liste une telle voie et ne la compte pas comme fermée.
  • La voie 4 est essayée par le code installé du serveur sur une table simulée où le compte du modèle écoute, non par un écouteur réel. La voie 10 est essayée sans sudo : groupes, règles sudoers lisibles et interrupteur de l’injection dans le terminal. Une règle que seul root peut lire est invisible depuis le compte du modèle, et le rapport le dit.

À propos des commandes et de leurs sorties

Les commandes ont été jouées sur deux postes Debian 13, un niveau après l’autre, puis défaites dans l’ordre inverse : l’un avec une session KDE sous X11, qui a donné les sorties des trois niveaux, l’autre avec une session GNOME 48.7 sous Wayland, qui a rejoué les mêmes commandes et donné les sorties de la section sur une session Wayland. Les sorties sont copiées de ces passages, avec des remplacements : votre compte s’écrit alice, son répertoire personnel ~, et le dossier d’essai du poste Wayland /var/tmp/test. Les lignes de progression d’apt et les paragraphes explicatifs que l’assistant d’installation affiche autour des invites de mot de passe sont omis. Les commandes du système ont tourné avec LC_ALL=C pour que leurs messages se lisent partout de la même façon ; les vôtres suivent votre langue, et les numéros de version affichés sont ceux de ce qui était installé ce jour-là. Les messages de l’assistant d’installation sont en français. Les lignes de trace qu’un shell écrit pour une commande lancée dans un sous-shell ($$) sont omises des sorties Wayland.

Toutes les commandes prennent les mêmes valeurs d’exemple : l’opérateur est le compte sous lequel vous êtes connecté (alice dans les sorties, $(id -un) dans les commandes), le compte du serveur est diwall-mcp, le compte du modèle est agent, le répertoire chiffré est gardé dans /var/lib/diwall-mcp, et le port de débogage est 9222.

use_pty. Debian 13 fournit Defaults use_pty dans /etc/sudoers : le fichier du paquet sudo 1.9.16 l’a à la ligne 15. Les deux postes l’avaient. Le poste Wayland a une règle de plus, pour un autre programme, et rien d’autre ne diffère du fichier du paquet :

$ dpkg-query -W '-f=${Version}\n' sudo
1.9.16p2-3+deb13u2
$ mkdir -p /var/tmp/test/apt-sudo
$ cd /var/tmp/test/apt-sudo
$ apt-get download sudo
0% [Working]
Get:1 http://deb.debian.org/debian trixie/main amd64 sudo amd64 1.9.16p2-3+deb13u2 [2086 kB]
0% [1 sudo 9772 B/2086 kB 0%]
100% [Working]
Fetched 2086 kB in 0s (8236 kB/s)
$ dpkg-deb --fsys-tarfile sudo_1.9.16p2-3+deb13u2_amd64.deb
$ tar -xO ./etc/sudoers
$ grep -n use_pty
15:Defaults	use_pty
$ diff /dev/fd/63 /dev/fd/62
6a7
> %alice ALL=(ALL) NOPASSWD:/usr/bin/veracrypt

Mettre un niveau en place, depuis le poste de départ

Les étapes ci-dessous suivent l’ordre dans lequel un niveau se construit : chacun s’ajoute au précédent, si bien que le compte agent ne peut exister avant diwall-mcp, ni diwall-mcp avant le poste de départ. Elles finissent sur le niveau intransigeant, celui qui répond au titre ; arrêtez-vous au niveau normal seulement si vous acceptez que le modèle garde votre écran, vos fichiers de démarrage et sudo.

Le poste de départ, inconscient (déconseillé)

Ce qu’il ferme : rien par le noyau. Ce qu’il laisse ouvert : toutes les voies sauf la voie 4, qui est sans objet parce que le modèle détient déjà tout. Coût : aucun.

C’est l’état d’un poste après l’installation et le premier lancement, sans autre étape. Un modèle qui a un shell sous votre compte liste le répertoire chiffré et lit les identifiants, le journal et les téléchargements ; lit les cookies de vos sessions ouvertes dans le profil du navigateur ; pilote le navigateur par son port de débogage sans passer par le serveur ; réécrit le profil de l’opérateur et l’unité qui lance le serveur ; efface sa trace dans le journal ; répond à la fenêtre de confirmation à votre place ; lit la mémoire du serveur et du navigateur ; capte un mot de passe que vous tapez en écrivant un alias dans les fichiers de démarrage de votre shell ; et devient administrateur si votre compte est du groupe sudo. Le rapport qui clôt ce niveau montre neuf voies ouvertes sur dix. Les contrôles du serveur tiennent encore pour ce qui passe par ses outils ; ils ne tiennent pas contre un shell.

Étape 1. Installez le paquet et les outils des niveaux suivants, depuis le dossier qui contient le fichier du paquet. La sous-commande isolation arrive avec la version 0.0.2 : prenez ce paquet ou un plus récent, quel que soit le numéro de version que montre la sortie ci-dessous.

sudo apt install ./mcp-server-diwall-browser_*_all.deb socat nftables x11-xserver-utils chromium
Note, selecting 'mcp-server-diwall-browser' instead of './mcp-server-diwall-browser_0.0.2-1_all.deb'
socat is already the newest version (1.8.0.3-1+deb13u1).
nftables is already the newest version (1.1.3-1).
x11-xserver-utils is already the newest version (7.7+11).
chromium is already the newest version (156.0.8078.73-1~deb13u1).
Installing:
  mcp-server-diwall-browser
Summary:
  Upgrading: 0, Installing: 1, Removing: 0, Not Upgrading: 0
  Download size: 0 B / 318 kB
  Space needed: 755 kB / 268 GB available
Continue? [Y/n] y
Setting up mcp-server-diwall-browser (0.0.2-1) ...
mcp-server-diwall-browser installed.

Étape 2. Créez le répertoire chiffré avec son profil d’opérateur, puis lancez le navigateur dédié. create demande un mot de passe deux fois, puis une troisième fois pour ouvrir le volume, et montre une clé maître une seule fois : notez-la sur papier. Chromium est utilisé parce que Google Chrome ouvre une fenêtre de conditions d’utilisation à son premier lancement sur un profil neuf et garde le port de débogage fermé tant qu’elles ne sont pas acceptées.

mcp-server-diwall-browser-setup create --browser chromium
mcp-server-diwall-browser-setup browser
Volume chiffré gocryptfs : ~/Vaults/Main.enc, ouvert sur ~/Vaults/Main.
Choose a password for protecting your files.
Password:
Repeat:

Your master key is:

    <master key>

The gocryptfs filesystem has been created successfully.
Password:
Filesystem mounted and ready.
Volume ouvert. Profil écrit : ~/.config/mcp-server-diwall-browser/profile.json.
Navigateur lancé (processus 13553) sur le profil du volume, détaché de ce terminal. Ouvrez-y vous-même vos sessions sur les sites.

Étape 3. Appelez le serveur une fois, comme le ferait votre client, pour que son journal existe. La sonde envoie les trois messages du protocole, attend, et garde de la réponse de status le répertoire chiffré et le navigateur.

{ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"status","arguments":{}}}'; sleep 6; } | mcp-server-diwall-browser 2>/dev/null | tail -n 1 | grep -o 'encrypted_directory[^}]*}\|browser\\": {[^}]*}'
encrypted_directory\": {\"state\": \"mounted\"}
browser\": {\"attached\": true, \"product\": \"Chrome\", \"version\": \"156.0.8078.73\"}

Étape 4. Lisez le rapport. Neuf voies sont ouvertes et la voie 4 est sans objet : le niveau est inconscient.

mcp-server-diwall-browser-setup isolation
Essai des dix voies depuis le compte « alice » (uid 1002) ; le serveur tourne sous « alice » (uid 1002).
Rien n'est écrit, aucun mot de passe n'est demandé, aucun sudo n'est lancé, aucun contenu du serveur n'est lu.

 1. Lire le répertoire chiffré : ouverte
    le point de montage ~/Vaults/Main se liste depuis ce compte
 2. Lire le profil du navigateur : ouverte
    le dossier du profil du navigateur se voit depuis ce compte
 3. Piloter le navigateur par son port de débogage : ouverte
    la connexion à 127.0.0.1:9222 aboutit
 4. Se faire passer pour le navigateur : sans objet
    le serveur tourne sous ce compte : le modèle a déjà tout
 5. Changer ce que le serveur s'autorise : ouverte
    ce compte écrit dans ~/.config/mcp-server-diwall-browser
 6. Effacer ou réécrire sa trace dans le journal : ouverte
    ce compte écrit dans le dossier du journal
 7. Répondre à la fenêtre de confirmation : ouverte
    l'écran :0 accepte une connexion de ce compte
 8. Lire la mémoire du serveur, du navigateur ou de gocryptfs : ouverte
    ce compte ouvre la mémoire du processus 1753 de « alice »
 9. Capter un mot de passe que l'opérateur tape : ouverte
    ce compte écrit dans ~/.bashrc, que le shell de « alice » lit à chaque ouverture
10. Devenir administrateur : ouverte
    ce compte est du groupe « sudo » : il devient administrateur, avec un mot de passe que la voie 9 capte, ou sans lui si une règle l'en dispense

Niveau atteint : inconscient (déconseillé)

Niveau normal : le serveur sur un second compte

Ce qu’il ferme par le noyau : les voies 1, 2, 3, 5, 6 et 8. Par le serveur : la voie 4. Ce qu’il laisse ouvert : les voies 7, 9 et 10. Coût : le compte diwall-mcp, une unité systemd et une règle de pare-feu ; les téléchargements et les captures sont dans le répertoire chiffré de diwall-mcp, que vous lisez avec sudo -u diwall-mcp (le montage n’a pas allow_other, si bien que root est refusé aussi) ; pour sortir un fichier de ce répertoire, voyez le paragraphe qui suit l’étape 9.

Étape 1. Fermez le volume du poste de départ. close arrête d’abord le navigateur.

mcp-server-diwall-browser-setup close
Fermeture du navigateur avant le démontage.
Volume fermé : ~/Vaults/Main.

Étape 2. Créez le compte du serveur. Il n’a ni mot de passe, ni sudo, ni autre groupe que le sien ; adduser le met dans users, ce que la seconde commande retire.

sudo adduser --disabled-password --gecos "" diwall-mcp
sudo gpasswd -d diwall-mcp users
id diwall-mcp
usermod: no changes
Removing user diwall-mcp from group users
uid=1000(diwall-mcp) gid=1000(diwall-mcp) groups=1000(diwall-mcp)

Étape 3. Créez le répertoire chiffré sous ce compte, hors de son répertoire personnel, pour que le refus de la voie 1 vienne du montage et non du mode d’un dossier. L’assistant demande le mot de passe comme pour le poste de départ.

sudo install -d -m 755 -o diwall-mcp -g diwall-mcp /var/lib/diwall-mcp
sudo -iu diwall-mcp mcp-server-diwall-browser-setup create --crypt-dir /var/lib/diwall-mcp/Main.enc --mount-dir /var/lib/diwall-mcp/Main --browser chromium
Volume chiffré gocryptfs : /var/lib/diwall-mcp/Main.enc, ouvert sur /var/lib/diwall-mcp/Main.
Choose a password for protecting your files.
Password:
Repeat:

Your master key is:

    <master key>

The gocryptfs filesystem has been created successfully.
Password:
Filesystem mounted and ready.
Volume ouvert. Profil écrit : ~/.config/mcp-server-diwall-browser/profile.json.

Étape 4. Donnez à diwall-mcp l’accès à votre écran, qui dure jusqu’à la fermeture de votre session, puis lancez son navigateur. La fenêtre s’ouvre sur votre écran. DISPLAY=:0 est l’affichage de votre session : echo $DISPLAY le dit. Sous une session Wayland, xhost ouvre XWayland, le serveur X que GNOME lance pour les applications qui ne parlent pas Wayland, et DISPLAY est l’affichage de XWayland (:1 sur le poste mesuré : :0 était le serveur de l’écran de connexion) ; la section sur une session Wayland, plus bas, donne les sorties. Cet accès est le seul endroit où la frontière est ouverte exprès : le compte du serveur peut saisir sur votre écran, et pas seulement ouvrir les fenêtres de confirmation. C’est acceptable parce que chaque niveau fait confiance au code du serveur et traite le modèle en attaquant.

xhost +SI:localuser:diwall-mcp
sudo -iu diwall-mcp env DISPLAY=:0 mcp-server-diwall-browser-setup browser
localuser:diwall-mcp being added to access control list
Navigateur lancé (processus 14123) sur le profil du volume, détaché de ce terminal. Ouvrez-y vous-même vos sessions sur les sites.

Étape 5. Écrivez le pont : un socket systemd que votre compte peut ouvrir, et un service qui lance un serveur par connexion sous diwall-mcp. SocketGroup est le groupe du compte qui lance le client.

sudo tee /etc/systemd/system/mcp-server-diwall-browser.socket >/dev/null <<EOF
[Unit]
Description=Bridge to the browser MCP server: socket of the model account

[Socket]
ListenStream=/run/mcp-server-diwall-browser/mcp.sock
Accept=yes
SocketUser=root
SocketGroup=$(id -un)
SocketMode=0660
DirectoryMode=0755

[Install]
WantedBy=sockets.target
EOF
sudo tee /etc/systemd/system/mcp-server-diwall-browser@.service >/dev/null <<'EOF'
[Unit]
Description=Browser MCP server, one connection (%i)

[Service]
User=diwall-mcp
WorkingDirectory=~
Environment=DISPLAY=:0
ExecStart=/usr/bin/mcp-server-diwall-browser
StandardInput=socket
StandardOutput=socket
StandardError=journal
NoNewPrivileges=yes
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now mcp-server-diwall-browser.socket
stat -c '%A %U %G %n' /run/mcp-server-diwall-browser/mcp.sock
Created symlink '/etc/systemd/system/sockets.target.wants/mcp-server-diwall-browser.socket' -> '/etc/systemd/system/mcp-server-diwall-browser.socket'.
srw-rw---- root alice /run/mcp-server-diwall-browser/mcp.sock

Étape 6. Demandez son status au serveur par le pont, avec la sonde du poste de départ.

{ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"status","arguments":{}}}'; sleep 6; } | socat - UNIX-CONNECT:/run/mcp-server-diwall-browser/mcp.sock | tail -n 1 | grep -o 'encrypted_directory[^}]*}\|browser\\": {[^}]*}'
encrypted_directory\": {\"state\": \"mounted\"}
browser\": {\"attached\": true, \"product\": \"Chrome\", \"version\": \"156.0.8078.73\"}

Étape 7. Fermez le port de débogage à tout compte autre que diwall-mcp. Le refus admin-prohibited fait échouer une connexion aussitôt avec No route to host, une erreur qu’un port fermé ne donne jamais : la règle se distingue ainsi d’un navigateur qui ne tourne pas. Le port est le browser.debug_port du profil. nftables.service charge la règle à chaque démarrage.

sudo install -d /etc/nftables.d
sudo tee /etc/nftables.d/diwall-mcp.nft >/dev/null <<'EOF'
table inet diwall_mcp {
  chain output {
    type filter hook output priority 0; policy accept;
    oifname "lo" tcp dport 9222 meta skuid != "diwall-mcp" reject with icmpx type admin-prohibited
  }
}
EOF
echo 'include "/etc/nftables.d/diwall-mcp.nft"' | sudo tee -a /etc/nftables.conf
sudo nft -c -f /etc/nftables.conf
sudo systemctl reload nftables
systemctl is-enabled nftables
include "/etc/nftables.d/diwall-mcp.nft"
enabled

Sur un poste où nftables.service est désactivé (systemctl is-enabled nftables répond disabled), reload échoue avec nftables.service is not active, cannot reload et la règle n’est pas chargée. Ne démarrez pas le service avant d’avoir lu /etc/nftables.conf : ses propres règles s’appliquent dès qu’il démarre, et le fichier du poste mesuré refuse par défaut les connexions entrantes et n’a pas de règle pour le port SSH. Chargez plutôt la table seule ; elle dure jusqu’au prochain démarrage :

$ systemctl is-active nftables
inactive
$ sudo nft -f /etc/nftables.d/diwall-mcp.nft
$ sudo nft list ruleset
table inet diwall_mcp {
	chain output {
		type filter hook output priority filter; policy accept;
		oifname "lo" tcp dport 9222 meta skuid != 1001 reject with icmpx admin-prohibited
	}
}
$ systemctl is-active nftables
inactive

Étape 8. Essayez trois voies à la main depuis votre compte (le compte du modèle à ce niveau). La voie 3 est refusée par la règle et diwall-mcp se connecte toujours ; la voie 1 est refusée par le montage ; la voie 10 dépend de vos règles sudo.

bash -c 'exec 3<>/dev/tcp/127.0.0.1/9222'
sudo -u diwall-mcp bash -c 'exec 3<>/dev/tcp/127.0.0.1/9222' && echo "diwall-mcp connects"
ls /var/lib/diwall-mcp/Main
sudo -n true && echo "sudo without a password: yes"
bash: connect: No route to host
bash: line 1: /dev/tcp/127.0.0.1/9222: No route to host
diwall-mcp connects
ls: cannot access '/var/lib/diwall-mcp/Main': Permission denied
sudo without a password: yes

La ligne sudo without a password: yes est ce que la sonde a trouvé sur le compte mesuré, non un réglage à obtenir. Là où cette ligne n’apparaît pas, la voie 10 n’est pas fermée pour autant : un sudo qui demande un mot de passe reste ouvert tant que la voie 9 peut capter le mot de passe que vous tapez. La ligne est écrite par le echo de la commande quand sudo -n true réussit, ce qui demande une règle NOPASSWD : sur un compte qui n’en a pas, sudo répond sudo: a password is required, la ligne n’est pas écrite et le code de sortie est 1 (sorties dans la section sur une session Wayland).

Étape 9. Lisez le rapport depuis votre compte.

mcp-server-diwall-browser-setup isolation
Essai des dix voies depuis le compte « alice » (uid 1002) ; le serveur tourne sous « diwall-mcp » (uid 1000).
Rien n'est écrit, aucun mot de passe n'est demandé, aucun sudo n'est lancé, aucun contenu du serveur n'est lu.

 1. Lire le répertoire chiffré : fermée par le noyau
    le noyau refuse ce compte au point de montage (/var/lib/diwall-mcp/Main) : le montage n'a pas d'accès pour un autre compte
 2. Lire le profil du navigateur : fermée par le noyau
    le noyau refuse ce compte au dossier du profil, qui est dans le volume
 3. Piloter le navigateur par son port de débogage : fermée par le noyau
    le noyau écarte la connexion au port 9222 (règle de pare-feu par compte)
 4. Se faire passer pour le navigateur : fermée par le serveur
    le serveur installé refuse un port tenu par un autre compte (debug_endpoint_other_account)
 5. Changer ce que le serveur s'autorise : fermée par le noyau
    le profil de l'opérateur, l'unité du service et le code du serveur ne s'écrivent pas depuis ce compte
 6. Effacer ou réécrire sa trace dans le journal : fermée par le noyau
    le noyau refuse ce compte au dossier du journal, qui est dans le volume
 7. Répondre à la fenêtre de confirmation : ouverte
    l'écran :0 accepte une connexion de ce compte
 8. Lire la mémoire du serveur, du navigateur ou de gocryptfs : fermée par le noyau
    le noyau refuse ce compte à la mémoire des 13 processus essayés de « diwall-mcp »
 9. Capter un mot de passe que l'opérateur tape : ouverte
    ce compte écrit dans ~/.bashrc, que le shell de « alice » lit à chaque ouverture
10. Devenir administrateur : ouverte
    ce compte est du groupe « sudo » : il devient administrateur, avec un mot de passe que la voie 9 capte, ou sans lui si une règle l'en dispense

Niveau atteint : normal

Sortir un fichier du répertoire chiffré. cp lancé avec sudo -u diwall-mcp est refusé quand le dossier de destination est le vôtre, parce que diwall-mcp n’y écrit pas. Soit votre propre compte écrit le fichier, en le lisant avec diwall-mcp et en redirigeant la sortie, soit la copie va dans un dossier où tout le monde peut écrire, et elle appartient alors à diwall-mcp :

$ mkdir -p /var/tmp/test/recu
$ sudo -n -u diwall-mcp sh -c 'mkdir -p /var/lib/diwall-mcp/Main/downloads && echo "contenu d essai" > /var/lib/diwall-mcp/Main/downloads/essai.txt'
$ sudo -u diwall-mcp cp /var/lib/diwall-mcp/Main/downloads/essai.txt /var/tmp/test/recu/
cp: cannot create regular file '/var/tmp/test/recu/essai.txt': Permission denied
$ sudo -u diwall-mcp cat /var/lib/diwall-mcp/Main/downloads/essai.txt
$ ls -l /var/tmp/test/recu/
total 4
-rw-rw-r-- 1 alice alice 16 Nov  1 00:49 essai.txt
$ cat /var/tmp/test/recu/essai.txt
contenu d essai
$ rm /var/tmp/test/recu/essai.txt
$ chmod 1777 /var/tmp/test/recu
$ sudo -u diwall-mcp cp /var/lib/diwall-mcp/Main/downloads/essai.txt /var/tmp/test/recu/
$ ls -l /var/tmp/test/recu/
total 4
-rw-rw-r-- 1 diwall-mcp diwall-mcp 16 Nov  1 00:49 essai.txt

Sous une session Wayland (GNOME)

Les commandes ci-dessus ont été jouées sous X11, puis rejouées dans une session GNOME sous Wayland (Debian 13, GNOME Shell 48.7), où les mêmes dix voies ont été essayées. Les sorties de cette section viennent de ce second poste.

La session. Demandez à votre session laquelle c’est et où se trouve XWayland :

$ systemctl --user show-environment
$ grep -E '^(DISPLAY|WAYLAND_DISPLAY|XDG_SESSION_TYPE)='
DISPLAY=:1
WAYLAND_DISPLAY=wayland-0
XDG_SESSION_TYPE=wayland
$ ls -ld /run/user/1000 /run/user/1000/wayland-0
drwx------ 15 alice alice 580 Nov  1 00:15 /run/user/1000
srwxrwxr-x  1 alice alice   0 Nov  1 00:14 /run/user/1000/wayland-0
$ ls -l /tmp/.X11-unix
total 0
srwxrwxrwx 1 root root 0 Nov  1 00:08 X0
srwxrwxr-x 1 alice  alice  0 Nov  1 00:14 X1
srwxrwxr-x 1 alice  alice  0 Nov  1 00:14 X2
$ gnome-shell --version
GNOME Shell 48.7

Le répertoire de votre session est fermé aux autres comptes (drwx------) et le socket Wayland est dedans : une application d’un autre compte ne l’atteint pas. Elle n’atteint votre écran que par XWayland, que xhost ouvre.

Ce que change xhost. Les commandes sont les mêmes ; l’accès passe par XWayland. Une fenêtre de diwall-mcp ouverte avec le DISPLAY de XWayland existe pour votre écran ; avec le seul WAYLAND_DISPLAY, elle ne s’ouvre pas du tout :

$ sleep 3
$ sudo -n -u diwall-mcp env DISPLAY=:1 zenity --info --title essai-b-x11 --text 'essai b' --timeout 8
MESA: error: Failed to attach to x11 shm
MESA: error: Failed to attach to x11 shm
$ xdotool search --name essai-b-x11
33554436
$ ps -o user=,comm= -p 15904
diwall-+ zenity
$ wait
$ sudo -n -u diwall-mcp ls /run/user/1000/wayland-0
ls: cannot access '/run/user/1000/wayland-0': Permission denied
$ sudo -n -u diwall-mcp env -u DISPLAY -u XAUTHORITY WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000 GDK_BACKEND=wayland zenity --info --text 'essai b' --timeout 8
(zenity:16088): Gtk-WARNING **: 00:49:10.708: Failed to open display

Deux détails sont sortis de ces essais. Le rendu par défaut de zenity (GTK 4) sous un autre compte crée la fenêtre mais la laisse invisible (MESA: error: Failed to attach to x11 shm) ; depuis la version 0.0.2, le serveur lance zenity avec GSK_RENDERER=cairo, et il préfère kdialog quand il est installé. Et sudo garde le DISPLAY et le XAUTHORITY de votre compte sauf instruction contraire : retirez-les pour essayer Wayland seul, comme ci-dessus.

Voie 7. Au niveau normal, votre compte répond à la fenêtre de confirmation de diwall-mcp : la fenêtre est ouverte sous diwall-mcp comme le serveur l’ouvre, puis mise au premier plan et validée avec xdotool depuis votre compte, et le programme reçoit grant.

$ sleep 3
$ sudo -n -u diwall-mcp env DISPLAY=:1 kdialog --title essai-reponse --radiolist 'Autoriser ?' refuse Refuser on grant Autoriser off
Detected locale "C" with character encoding "ANSI_X3.4-1968", which is not UTF-8.
Qt depends on a UTF-8 locale, and has switched to "C.UTF-8" instead.
If this causes problems, reconfigure your locale. See the locale(1) manual
for more information.
$ xdotool search --name essai-reponse
16777252
33554439
$ xdotool windowfocus 33554439
$ xdotool key Down
$ xdotool key Return
$ wait
$ cat /var/tmp/test/reponse.txt
grant
$ ls -l /dev/uinput
crw------- 1 root root 10, 223 Nov  1 00:47 /dev/uinput

Au niveau intransigeant, agent n’atteint ni le socket Wayland ni le fichier-socket de XWayland (Permission denied), et ne peut employer ni xdotool ni /dev/uinput. Le socket abstrait de XWayland accepte la connexion : c’est donc l’autorisation de XWayland qui refuse agent ; sans cookie lisible et sans règle xhost, rien ne passe. Une règle ouverte à agent ouvre l’écran, et le rapport le dit :

$ sudo -iu agent xdotool getmouselocation
Authorization required, but no authorization protocol specified
Error: Can't open display: (null)
Failed creating new xdo instance
$ sudo -iu agent test -w /dev/uinput
$ sudo -iu agent head -c 1 /dev/input/event0
head: cannot open '/dev/input/event0' for reading: Permission denied
$ sudo -iu agent test -w ~/.bashrc
$ xhost +SI:localuser:agent
localuser:agent being added to access control list
$ sudo -iu agent mcp-server-diwall-browser-setup isolation
Essai des dix voies depuis le compte « agent » (uid 1002) ; le serveur tourne sous « diwall-mcp » (uid 1001).
Rien n'est écrit, aucun mot de passe n'est demandé, aucun sudo n'est lancé, aucun contenu du serveur n'est lu.
 1. Lire le répertoire chiffré : fermée par le noyau
    le noyau refuse ce compte au point de montage (/var/lib/diwall-mcp/Main) : le montage n'a pas d'accès pour un autre compte
 2. Lire le profil du navigateur : fermée par le noyau
    le noyau refuse ce compte au dossier du profil, qui est dans le volume
 3. Piloter le navigateur par son port de débogage : fermée par le noyau
    le noyau écarte la connexion au port 9222 (règle de pare-feu par compte)
 4. Se faire passer pour le navigateur : fermée par le serveur
    le serveur installé refuse un port tenu par un autre compte (debug_endpoint_other_account)
 5. Changer ce que le serveur s'autorise : fermée par le noyau
    le profil de l'opérateur, l'unité du service et le code du serveur ne s'écrivent pas depuis ce compte
 6. Effacer ou réécrire sa trace dans le journal : fermée par le noyau
    le noyau refuse ce compte au dossier du journal, qui est dans le volume
 7. Répondre à la fenêtre de confirmation : ouverte
    l'écran :1 accepte une connexion de ce compte par son socket abstrait
 8. Lire la mémoire du serveur, du navigateur ou de gocryptfs : fermée par le noyau
    le noyau refuse ce compte à la mémoire des 27 processus essayés de « diwall-mcp »
 9. Capter un mot de passe que l'opérateur tape : fermée par le noyau
    les fichiers de démarrage de « alice » et les événements du clavier sont fermés à ce compte
10. Devenir administrateur : fermée par le noyau
    aucun groupe d'administration, aucune règle lisible à son nom, injection dans le terminal refusée ; une règle que seul root lit n'est pas visible d'ici
Niveau atteint : normal
$ sudo -iu agent xdotool getmouselocation
x:1010 y:368 screen:0 window:4194313
$ xhost -SI:localuser:agent
localuser:agent being removed from access control list

Voies 9 et 10. Les mêmes essais qu’en X11 donnent les mêmes résultats. Votre compte écrit dans ses fichiers de démarrage et ne lit pas les événements du clavier ; use_pty est présent, legacy_tiocsti vaut 0, et votre règle sudo fait écrire sa ligne à la sonde :

$ ls -l /dev/input/event0
crw-rw---- 1 root input 13, 64 Nov  1 00:47 /dev/input/event0
$ head -c 1 /dev/input/event0
head: cannot open '/dev/input/event0' for reading: Permission denied
$ test -w ~/.bashrc
$ echo writable
writable
$ sudo -n grep -rn use_pty /etc/sudoers /etc/sudoers.d/
/etc/sudoers:15:Defaults	use_pty
/etc/sudoers.d/kdesu-sudoers:4:Defaults!/usr/lib/*/libexec/kf5/kdesu_stub !use_pty
$ cat /proc/sys/dev/tty/legacy_tiocsti
0
$ sudo -n true
$ echo 'sudo without a password: yes'
sudo without a password: yes
$ sudo -n -u diwall-mcp sudo -n true
sudo: a password is required

Depuis agent, le terminal est un autre que le vôtre, ce que donne use_pty, et sudo demande un mot de passe :

$ sudo -iu agent tty
/dev/pts/1
$ tty
/dev/pts/0
$ sudo -iu agent sudo -n true
sudo: a password is required

Niveau intransigeant : le modèle sur un troisième compte

Ce qu’il ferme : chacune des dix voies, par le noyau sauf la voie 4, que ferme le serveur. Ce qu’il laisse ouvert : aucune. Coût : le compte agent ; le client se lance avec sudo -iu agent, depuis votre terminal, et use_pty de sudo empêche le client d’injecter des frappes dans ce terminal. Debian 13 fournit Defaults use_pty dans /etc/sudoers, et les deux postes mesurés l’avaient ; sur un autre système, vérifiez que cette ligne est présente, sinon l’injection n’est pas refusée.

Étape 1. Créez le compte du modèle, de la même façon que diwall-mcp.

sudo adduser --disabled-password --gecos "" agent
sudo gpasswd -d agent users
id agent
usermod: no changes
Removing user agent from group users
uid=1001(agent) gid=1001(agent) groups=1001(agent)

Étape 2. Remettez le socket au groupe d’agent. Un fichier complémentaire ajoute le changement, de sorte que le défaire retire un fichier et laisse l’unité du niveau normal comme elle était.

sudo install -d /etc/systemd/system/mcp-server-diwall-browser.socket.d
printf '[Socket]\nSocketGroup=agent\n' | sudo tee /etc/systemd/system/mcp-server-diwall-browser.socket.d/uncompromising.conf
sudo systemctl daemon-reload
sudo systemctl restart mcp-server-diwall-browser.socket
stat -c '%A %U %G %n' /run/mcp-server-diwall-browser/mcp.sock
[Socket]
SocketGroup=agent
srw-rw---- root agent /run/mcp-server-diwall-browser/mcp.sock

Étape 3. Votre propre compte n’ouvre plus le pont ; agent l’ouvre.

socat - UNIX-CONNECT:/run/mcp-server-diwall-browser/mcp.sock < /dev/null 2>&1 | sed 's/^.*socat\[[0-9]*\] //'
{ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"status","arguments":{}}}'; sleep 6; } | sudo -u agent socat - UNIX-CONNECT:/run/mcp-server-diwall-browser/mcp.sock | tail -n 1 | grep -o 'encrypted_directory[^}]*}\|browser\\": {[^}]*}'
E connect(, AF=1 "/run/mcp-server-diwall-browser/mcp.sock", 41): Permission denied
encrypted_directory\": {\"state\": \"mounted\"}
browser\": {\"attached\": true, \"product\": \"Chrome\", \"version\": \"156.0.8078.73\"}

Étape 4. Déclarez le pont comme commande de votre client MCP, qui tourne sous agent. Dans la déclaration du guide de l’opérateur, section 2.1, la command est la ligne ci-dessous et il n’y a pas de bloc environment : le profil, l’écran et le répertoire courant sont fixés par l’unité.

{
  "command": ["socat", "STDIO", "UNIX-CONNECT:/run/mcp-server-diwall-browser/mcp.sock"]
}

Étape 5. Essayez les mêmes trois voies depuis agent, puis lisez le rapport depuis agent.

sudo -iu agent bash -c 'exec 3<>/dev/tcp/127.0.0.1/9222'
sudo -iu agent ls /var/lib/diwall-mcp/Main
sudo -iu agent sudo -n true
sudo -iu agent mcp-server-diwall-browser-setup isolation
bash: connect: No route to host
bash: line 1: /dev/tcp/127.0.0.1/9222: No route to host
ls: cannot access '/var/lib/diwall-mcp/Main': Permission denied
sudo: a password is required
Essai des dix voies depuis le compte « agent » (uid 1001) ; le serveur tourne sous « diwall-mcp » (uid 1000).
Rien n'est écrit, aucun mot de passe n'est demandé, aucun sudo n'est lancé, aucun contenu du serveur n'est lu.

 1. Lire le répertoire chiffré : fermée par le noyau
    le noyau refuse ce compte au point de montage (/var/lib/diwall-mcp/Main) : le montage n'a pas d'accès pour un autre compte
 2. Lire le profil du navigateur : fermée par le noyau
    le noyau refuse ce compte au dossier du profil, qui est dans le volume
 3. Piloter le navigateur par son port de débogage : fermée par le noyau
    le noyau écarte la connexion au port 9222 (règle de pare-feu par compte)
 4. Se faire passer pour le navigateur : fermée par le serveur
    le serveur installé refuse un port tenu par un autre compte (debug_endpoint_other_account)
 5. Changer ce que le serveur s'autorise : fermée par le noyau
    le profil de l'opérateur, l'unité du service et le code du serveur ne s'écrivent pas depuis ce compte
 6. Effacer ou réécrire sa trace dans le journal : fermée par le noyau
    le noyau refuse ce compte au dossier du journal, qui est dans le volume
 7. Répondre à la fenêtre de confirmation : fermée par le noyau
    l'écran refuse ce compte (sans cookie lisible), et /dev/uinput lui est fermé (1 écran(s) essayé(s))
 8. Lire la mémoire du serveur, du navigateur ou de gocryptfs : fermée par le noyau
    le noyau refuse ce compte à la mémoire des 13 processus essayés de « diwall-mcp »
 9. Capter un mot de passe que l'opérateur tape : fermée par le noyau
    les fichiers de démarrage de « alice » et les événements du clavier sont fermés à ce compte
10. Devenir administrateur : fermée par le noyau
    aucun groupe d'administration, aucune règle lisible à son nom, injection dans le terminal refusée ; une règle que seul root lit n'est pas visible d'ici

Niveau atteint : intransigeant

Revenir vers un niveau moins strict

Défaites dans l’ordre inverse. Les commandes retirent les comptes et les unités ; le répertoire chiffré de diwall-mcp garde vos sessions de navigateur, et la dernière commande de la seconde étape le supprime avec elles : copiez d’abord ce qu’il vous faut.

D’intransigeant à normal. Retirez le fichier complémentaire, puis le compte du modèle avec son répertoire personnel. Votre propre compte rouvre le pont.

sudo rm /etc/systemd/system/mcp-server-diwall-browser.socket.d/uncompromising.conf
sudo rmdir /etc/systemd/system/mcp-server-diwall-browser.socket.d
sudo systemctl daemon-reload
sudo systemctl restart mcp-server-diwall-browser.socket
sudo deluser --remove-home agent

De normal à inconscient. Retirez l’accès à votre écran, fermez le volume et le navigateur de diwall-mcp, retirez le pont, la règle de pare-feu et le compte, puis le répertoire chiffré.

xhost -SI:localuser:diwall-mcp
sudo -iu diwall-mcp mcp-server-diwall-browser-setup close
sudo systemctl disable --now mcp-server-diwall-browser.socket
sudo rm /etc/systemd/system/mcp-server-diwall-browser.socket /etc/systemd/system/mcp-server-diwall-browser@.service
sudo systemctl daemon-reload
sudo rm /etc/nftables.d/diwall-mcp.nft
sudo rmdir /etc/nftables.d
sudo sed -i '\|^include "/etc/nftables.d/diwall-mcp.nft"$|d' /etc/nftables.conf
sudo systemctl reload nftables
sudo deluser --remove-home diwall-mcp
sudo rm -r /var/lib/diwall-mcp
localuser:diwall-mcp being removed from access control list
Fermeture du navigateur avant le démontage.
Volume fermé : /var/lib/diwall-mcp/Main.
Removed '/etc/systemd/system/sockets.target.wants/mcp-server-diwall-browser.socket'.

Quand une fenêtre de confirmation a tourné sous diwall-mcp, la dernière commande peut échouer avec userdel: user diwall-mcp is currently used by process … : la fenêtre a laissé un bus de session derrière elle. Arrêtez les processus du compte, attendez quelques secondes, supprimez de nouveau le compte, puis retirez ce qui reste : le fichier-socket dans /run (l’unité n’est plus là, le fichier n’est pas retiré avec elle) et, sur un poste où nftables.service est désactivé, la table chargée à la main, que reload ne décharge pas :

$ ps -u diwall-mcp -o pid,comm
    PID COMMAND
  15910 dbus-launch
  15911 dbus-daemon
  15949 xdg-desktop-por
  15955 xdg-permission-
  15961 xdg-document-po
  15972 xdg-desktop-por
  15977 gvfsd
  15983 gvfsd-fuse
  15999 at-spi-bus-laun
  16005 dbus-daemon
  16011 at-spi2-registr
$ sudo pkill -u diwall-mcp
$ sleep 5
$ ps -u diwall-mcp -o pid,comm
    PID COMMAND
$ sudo deluser --remove-home diwall-mcp
$ ls -la /run/mcp-server-diwall-browser
total 0
drwxr-xr-x  2 root root  60 Nov  1 00:49 .
drwxr-xr-x 40 root root 960 Nov  1 00:49 ..
srw-rw----  1 root alice    0 Nov  1 00:49 mcp.sock
$ sudo nft list ruleset
table inet diwall_mcp {
	chain output {
		type filter hook output priority filter; policy accept;
		oifname "lo" tcp dport 9222 meta skuid != 1001 reject with icmpx admin-prohibited
	}
}
$ sudo nft delete table inet diwall_mcp
$ sudo rm -r /run/mcp-server-diwall-browser

D’inconscient à rien. Fermez votre volume et retirez le paquet. Votre répertoire chiffré reste là où create l’a mis ; supprimez-le vous-même si vous n’en avez pas besoin.

mcp-server-diwall-browser-setup close
sudo apt purge mcp-server-diwall-browser
Le volume est déjà fermé.
REMOVING:
  mcp-server-diwall-browser*
Summary:
  Upgrading: 0, Installing: 0, Removing: 1, Not Upgrading: 0
  Freed space: 755 kB
Continue? [Y/n] y
Removing mcp-server-diwall-browser (0.0.2-1) ...
mcp-server-diwall-browser removed. Your operator profile and your encrypted directory were not touched.
Purging configuration files for mcp-server-diwall-browser (0.0.2-1) ...
mcp-server-diwall-browser removed. Your operator profile and your encrypted directory were not touched.

Ce qu’aucun niveau ne promet

  • Une faille du noyau ou du serveur.
  • Un secret que vous collez dans une conversation.
  • L’usage d’une session ouverte dans le navigateur dédié : le modèle peut y agir, dans les limites de la liste rouge, à tous les niveaux.
  • Ce que le fournisseur du modèle reçoit des pages que le modèle lit. Un modèle local supprime cette exposition ; il ne supprime pas une injection.

Un niveau se mesure à un moment donné : un groupe ajouté à un compte, une règle sudoers ou un changement d’xhost le fait baisser. Relancez mcp-server-diwall-browser-setup isolation après tout changement du poste.