Politique d'organisation¶
Une organisation peut imposer à tous ses postes un socle que l'utilisateur ne peut pas assouplir : profil minimal, fournisseurs autorisés, règles de permissions et de masquage, niveau d'audit, serveurs MCP autorisés. La politique est un fichier déployé par l'administrateur (MDM, Ansible), signé avec minisign et vérifié à chaque démarrage. Elle est tracée dans le journal d'audit et dans l'export de conformité.
Sans politique, tenon se comporte comme décrit dans utilisation.md. Une politique ne fait que durcir : elle n'élargit jamais ce que la configuration permet.
Emplacement¶
Le dossier système contient trois fichiers :
| Plateforme | Dossier |
|---|---|
| Linux | /etc/tenon/ |
| macOS | /Library/Application Support/tenon/ |
| Fichier | Contenu |
|---|---|
policy.toml |
la politique |
policy.toml.minisig |
signature minisign de policy.toml |
policy.pub |
la clé publique de l'organisation (format .pub de minisign) |
Le dossier et les trois fichiers doivent appartenir à root (uid 0) et ne pas être modifiables par le groupe ni par les autres (par exemple chmod 755 sur le dossier et chmod 644 sur les fichiers). Sinon tenon refuse de démarrer.
TENON_POLICY_DIR désigne un autre dossier. Elle n'est lue que si le dossier système n'a pas de policy.toml : elle peut ajouter une politique (politique personnelle ou de test), jamais en retirer ou en remplacer une système. Les permissions de ce dossier ne sont pas contrôlées ; la signature l'est toujours.
Pas de policy.toml dans l'un ni l'autre : pas de politique.
Signer une politique¶
minisign -G -p policy.pub -s org.key # une fois : paire de clés
minisign -Sm policy.toml # écrit policy.toml.minisig
# Linux
sudo install -d -o root -g root -m 755 /etc/tenon
sudo install -o root -m 644 policy.toml policy.toml.minisig policy.pub /etc/tenon/
# macOS
sudo install -d -o root -g wheel -m 755 "/Library/Application Support/tenon"
sudo install -o root -m 644 policy.toml policy.toml.minisig policy.pub "/Library/Application Support/tenon/"
Les deux formats de signature minisign sont acceptés : préhaché (ED) et historique (Ed).
L'identifiant de clé de la signature doit être celui de policy.pub, et la signature du fichier comme la signature globale (commentaire de confiance) doivent être valides.
Toute modification de policy.toml après la signature fait refuser le démarrage.
Contenu¶
id = "acme-dev" # obligatoire, [a-z0-9_-]{1,64}
version = 3 # obligatoire, entier >= 1
profile = "eu-only" # optionnel : plancher
[providers] # optionnel : liste blanche
allow_hosts = ["api.mistral.ai", "*.scaleway.ai"]
allow_local = true # hôtes locaux (localhost, littéraux IP de boucle et privés) autorisés
[[providers.define]] # optionnel : fournisseurs fournis par l'organisation
id = "interne"
name = "vLLM interne"
base_url = "https://llm.acme.eu/v1"
api_key_env = "ACME_LLM_KEY"
model = "qwen3"
country = "FR"
jurisdiction = "eu"
context_window = 128000
qualifications = ["secnumcloud"] # optionnel : déclarée, jamais vérifiée
[[permissions.rules]] # deny / ask seulement
tool = "shell"
match = "curl *"
decision = "deny"
[[redaction.rules]]
name = "acme-token"
pattern = "ACME-[A-Z0-9]{32}"
[audit]
level = "full" # optionnel : full | metadata, imposé
[audit.timestamp] # optionnel : horodatage RFC 3161
url = "https://tsa.acme.eu/tsr"
ca = """-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----""" # racines de confiance optionnelles
[sandbox] # optionnel : socle d'isolation système
mode = "required" # auto | required ("off" refusé)
deny_read = ["/srv/secrets"] # ajouté à la liste intégrée
[classification] # optionnel : s'ajoute aux listes utilisateur et projet
paths = ["rh/**"]
markers = ["ACME CONFIDENTIEL"]
[routing] # optionnel : impose le fournisseur local
local = "interne-local" # un préréglage local ou un fournisseur de providers.define
[budget] # optionnel : impose un plafond de tokens par session
session_tokens = 1000000
[memory] # optionnel : borne la mémoire
enabled = false # false : pas de mémoire
project = false # false : .tenon/memory ignoré
max_retention_days = 30 # plafonne retention_days de [memory] (1 à 3650)
[mcp]
mode = "allowlist" # any (défaut) | allowlist | off
allow_hosts = ["mcp.acme.eu"]
allow_commands = ["/usr/local/bin/mcp-git"]
Seuls id et version sont obligatoires.
Valeurs refusées au chargement : id ou version absents ou invalides ; profil inconnu ; règle allow ; décision ou outil invalides (mêmes règles que la configuration) ; motif de masquage qui ne compile pas ; entrée de allow_hosts vide, avec schéma, chemin, port ou joker ailleurs qu'en tête (*. suivi d'au moins deux labels) ; entrée de allow_commands non absolue ; mcp.mode inconnu ; sandbox.mode = "off" ou autre mode inconnu ; entrée sandbox.deny_read relative ; clé network sur une règle (une politique ne peut pas ouvrir le réseau) ; audit.level inconnu ; entrée providers.define invalide (mêmes règles que [[providers]], jurisdiction obligatoire ; qualifications n'accepte que secnumcloud) ; audit.timestamp.url qui n'est pas en https (http n'est permis que pour un hôte local) ; audit.timestamp.url dont l'hôte n'est pas local sous profile = "strict-local" ; audit.timestamp.ca sans certificat PEM valide ; memory.max_retention_days hors de 1 à 3650 ; clé inconnue.
Mémoire¶
[memory] borne la mémoire gouvernée :
enabled = false: ni index, ni outils de mémoire, ni/remember.tenon memory list|show|forgetcontinuent de fonctionner : une demande d'effacement reste toujours possible.project = false:.tenon/memoryest ignoré en lecture et en écriture.tenon memory forgetettenon memory list --scope projectle voient encore : une entrée de projet reste toujours effaçable.max_retention_days: borne haute deretention_dayspour les entréesuseretglobal.
La valeur effective est la plus stricte entre la politique, la configuration globale et la configuration du projet. Un refus s'écrit memory: disabled by policy.
Application¶
Ordre de fusion : défauts, configuration globale, configuration projet, politique.
- Profil : le profil effectif est le plus strict de ceux de la configuration globale, du projet et de la politique. Si la configuration en demande un plus faible, tenon affiche une notice et le relève :
profil « <c> » de la configuration plus souple que le profil « <p> » de la politique <id> : relevé. - Qualifications : sous une politique, les
qualificationsdéclarées dans la configuration utilisateur (fournisseurs et serveurs MCP) sont retirées, avec un avis :politique <id> : qualifications déclarées dans la configuration ignorées (<ids>). Seules lesqualificationsdeproviders.definecomptent : un utilisateur ne peut pas s'auto-qualifier pour satisfaire un profilsecnumcloud. tenon ne vérifie jamais une qualification.default_providerpeut désigner un fournisseur défini par la politique. - Fournisseurs définis : fusionnés après la configuration de l'utilisateur. Un fournisseur de l'utilisateur de même
idest ignoré, avec un avertissement :politique <id> : fournisseur « <pid> » de la configuration ignoré (défini par la politique). - Liste blanche de fournisseurs (seulement si
[providers]est présent) : l'hôte debase_urldu fournisseur (minuscules, sans port) doit être égal à une entrée deallow_hosts, ou couvert par une entrée*.x.eu(tout sous-domaine dex.eu, jamaisx.eului-même), ou être local avecallow_local = true. [providers]présent sansallow_hostset avecallow_local = falserefuse tous les fournisseurs.- Les fournisseurs définis par la politique (
define) sont soumis àallow_hostscomme les autres : listez leurs hôtes. - Un fournisseur choisi non autorisé arrête tenon au démarrage, avant tout accès réseau, avec le code 1 :
fournisseur « <pid> » (hôte « <h> ») non autorisé par la politique <id>.--allow-non-eune lève pas ce refus. - Le même contrôle s'exécute avant chaque envoi, après celui du profil : rien n'est envoyé et le tour est bloqué (événement
Error,code: "policy"). - Règles de permissions : placées en tête.
denyest absolue ;askprime les règlesallowde l'utilisateur. - Isolation : le mode effectif est le plus strict de la configuration globale, du projet, de la politique et du profil (
off<auto<required) ;deny_readde la politique s'ajoute aux autres.writen'existe pas dans une politique. Voir utilisation.md. - Masquage : les règles de la politique s'ajoutent aux autres.
- Classification :
[classification] pathsetmarkerss'ajoutent aux listes de l'utilisateur et du projet ; personne ne peut en retirer. Voir utilisation.md. - Routage :
[routing] localest imposé et remplace lerouting.localde l'utilisateur, avec une notice :politique <id> : routing.local imposé (<id>). Il doit désigner un fournisseur de juridictionlocal: un préréglage local, un fournisseur deproviders.defineou une entrée[[providers]]de l'utilisateur. Ce contrôle a lieu quand la politique est appliquée à la configuration, pas au chargement du fichier de politique ; sinon le démarrage échoue avecpolicy violation: routing.local must name a local provider. Les sessions de la politique sont donc routées vers ce fournisseur quand elles deviennent sensibles ; l'export de conformité signale toute session qui a atteint un fournisseur non local après le classement. - Budget :
[budget] session_tokens(entier positif) est imposé ; le plafond effectif est le plus bas entre global, projet et politique : un utilisateur peut le baisser, jamais le relever.warn_atreste celui de l'utilisateur. Au plafond, le tour suivant se termine avecbudget_exceeded(code de sortie 4 avec--strict). Voir utilisation.md. - Niveau d'audit :
audit.levelde la politique remplace celui de la configuration, dans les deux sens : une organisation peut imposermetadata(minimisation des données) oufull(traçabilité). S'ils diffèrent, une notice est affichée :politique <id> : niveau d'audit « <p> » imposé (configuration : « <c> »). - Horodatage de l'audit (
[audit.timestamp]) :urlremplace l'urlde l'utilisateur ;ca(racines de confiance PEM, couvertes par la signature), s'il est présent, remplace leca_filede l'utilisateur. Sansca, leca_filede l'utilisateur reste appliqué. S'ils diffèrent, une notice est affichée :politique <id> : horodatage d'audit imposé (<url>).cade la politique passe avantca_filedans le choix des racines ;--tsa-capasse avant tout. tenon n'intègre aucun préréglage de TSA ; l'organisation désigne son autorité. Sous macOS et Windows, fournissez unca: les racines système ne permettent pas de vérifier les certificats de TSA. Sousstrict-local, un hôte non local est refusé. Voir utilisation.md. - MCP :
offretire tous les serveurs ;allowlistgarde un serveur seulement si son hôte (http) figure dansallow_hosts(même correspondance que les fournisseurs, sansallow_local) ou sicommand[0](stdio) figure tel quel dansallow_commands. Un serveur retiré n'est pas démarré, avec une notice (politique <id> : serveur MCP « <sid> » ignoré (non autorisé)) et un événementMcpServerDown. Le contrôle MCP du profil s'applique aussi aux serveurs gardés.
Échecs au démarrage¶
tenon refuse de démarrer (code 1) quand une politique est présente mais pas digne de confiance. Le message est :
tenon : politique : <fichier> : <raison>
En anglais : tenon: policy: <file>: <reason>.
Causes :
policy.toml.minisigoupolicy.pubmanque ;- l'identifiant de clé de la signature diffère de celui de
policy.pub; - la signature du fichier ou la signature globale est invalide ;
- dossier système seulement : le dossier ou un des trois fichiers est modifiable par le groupe ou les autres, ou n'appartient pas à root ;
- le TOML est invalide, contient une clé inconnue, ou une valeur est refusée (voir plus haut).
La politique est vérifiée une fois, au démarrage, avant le chargement de la configuration. tenon config check échoue de la même façon. Un fournisseur non autorisé est une erreur de démarrage distincte (voir plus haut).
Vérifier une politique : tenon policy check¶
tenon policy check [dossier]
Vérifie la signature et le contenu de la politique du dossier (défaut : le dossier système) et affiche un résumé. Les propriétaire et permissions ne sont pas contrôlés : vous pouvez valider une politique avant de la déployer. La configuration de l'utilisateur et du projet n'est pas lue.
politique : acme-dev v3 (clé 0123456789ABCDEF)
profil : eu-only
fournisseurs : 2 hôtes autorisés, local oui, 1 définis
règles : 1
masquage : 1
audit : full
mcp : allowlist
Sans [providers] la ligne est fournisseurs : pas de liste blanche ; sans profil profil : aucun ; sans niveau d'audit audit : non imposé.
Code 0 si la politique est valide ; 1 si elle est invalide ou absente (tenon : aucune politique dans <dossier>).
Traçabilité¶
- L'écran d'accueil et
tenon config checkaffichentpolitique : acme-dev v3 (clé <KeyID>)quand une politique est active. L'identifiant de clé fait 16 caractères hexadécimaux majuscules, comme dans minisign. SessionStartedenregistrepolicy(id,version,key_id). Les sessions sans politique n'ont pas cette clé.- L'export de conformité liste les politiques des sessions dans la plage (clé
policies), avec le nombre de sessions de chacune. Au niveau metadata, la politique est inconnue : le rapport indique « non disponible au niveau metadata ».
Limites¶
Une politique borne ce que fait tenon. Les règles de permissions s'appliquent aux commandes shell, et [sandbox] mode = "required" les confine, mais la liste blanche d'hôtes des fournisseurs ne restreint pas les accès réseau d'une commande qui a reçu le réseau ni ceux des programmes MCP stdio, et un administrateur de la machine peut toujours la retirer. Les hooks imposés par l'organisation, le rafraîchissement par URL, l'expiration ou la révocation, plusieurs clés de confiance et Windows ne sont pas pris en charge.