DOCUMENTATION
Tout ce qu'il faut pour
mettre Veilforge en route.
Cette page explique le fonctionnement de la passerelle, ses endpoints, son format de jetons et son modèle de sécurité. Elle complète la page d'accueil, qui contient une démonstration interactive, et le Swagger, qui décrit chaque route au format OpenAPI.
01
Démarrage rapide
Veilforge est une application Python (FastAPI) qui s'exécute en local. Aucune base externe n'est nécessaire : les correspondances sont stockées dans un fichier SQLite chiffré à côté du projet.
Python 3.11 ou plus récent est requis.
Copiez .env.example vers .env et renseignez les secrets pour tout usage au-delà du développement.
Ouvrez ensuite http://localhost:8787 : la page d'accueil contient la démonstration.
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env
uvicorn veilforge.main:app --reload --port 8787
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
uvicorn veilforge.main:app --reload --port 8787
http://localhost:8787— page de présentation avec la démonstration masque/restaure ;http://localhost:8787/docs— cette documentation ;http://localhost:8787/swagger— interface Swagger / OpenAPI ;http://localhost:8787/v1— point d'entrée compatible OpenAI.
02
Comment marche l'exemple
La démonstration de la page d'accueil exécute exactement deux appels à votre instance locale. Aucun modèle n'est sollicité : c'est le même mécanisme que la passerelle, isolé pour que vous puissiez le voir.
- Mask —
POST /api/v1/maskreçoit votre texte, détecte les valeurs sensibles, remplace chacune par un jeton et renvoie l'identifiant de session. - Transport — vous envoyeriez ce texte protégé au modèle. Dans la démonstration, le texte reste chez vous.
- Unmask —
POST /api/v1/unmaskreçoit la réponse contenant les jetons, la même session, et rétablit les valeurs d'origine.
# 1) Masquage
curl -s http://localhost:8787/api/v1/mask \
-H "Authorization: Bearer local-dev-key" \
-H "Content-Type: application/json" \
-d '{"text":"Écrire à ana@example.com au +33 6 12 34 56 78"}'
# → {"masked_text":"Écrire à [[EMAIL_001]] au [[PHONE_001]]",
# "session_id":"vf_...", "entities":[...]}
# 2) Restauration (même session)
curl -s http://localhost:8787/api/v1/unmask \
-H "Authorization: Bearer local-dev-key" \
-H "Content-Type: application/json" \
-d '{"text":"Réponse : [[EMAIL_001]] sera prévenu.","session_id":"vf_..."}'
# → {"text":"Réponse : ana@example.com sera prévenu.","unresolved":[]}
03
Passerelle compatible OpenAI
Veilforge implémente le contrat de /v1/chat/completions. Vous ne changez rien dans votre code : vous modifiez seulement l'URL de base et la clé.
Avec un fournisseur
Définissez VEILFORGE_UPSTREAM_URL (URL de base, sans /chat/completions) et VEILFORGE_UPSTREAM_API_KEY. La passerelle masque la requête, la transmet, puis restaure la réponse.
Sans fournisseur
La passerelle renvoie un écho de développement marqué "mock": true. Ce contenu n'est jamais présenté comme une réponse de modèle : il sert uniquement à tester la chaîne.
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8787/v1",
api_key="local-dev-key", # VEILFORGE_API_KEY
)
reponse = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user",
"content": "Écris un mail à ana@example.com"}],
)
print(reponse.choices[0].message.content)
curl -s http://localhost:8787/v1/chat/completions \
-H "Authorization: Bearer local-dev-key" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini",
"messages":[{"role":"user","content":"Contact ana@example.com"}]}'
- Streaming — si
"stream": true, les événements SSE sont transmis et démasqués à la volée ; l'en-têteX-Veilforge-Sessionrenvoie la session utilisée. - Session réutilisable — envoyez
X-Veilforge-Session: vf_...pour conserver les mêmes jetons d'une requête à l'autre. - Échec amont — en mode strict, une erreur du fournisseur renvoie
502sans jamais restituer le texte protégé : la demande est refusée plutôt que risquée. - Champ protégés — le masquage s'applique récursivement aux messages, aux schémas d'outils, aux arguments et aux résultats d'outils (chaînes uniquement), en ignorant
model,role,stream,temperatureetmax_tokens.
04
Référence API
Toutes les routes /api/v1/* exigent l'en-tête Authorization: Bearer <VEILFORGE_API_KEY>. Le budget est de 120 requêtes par minute et par adresse IP. La page /api/v1/health et la racine sont exemptées.
| Méthode | Chemin | Rôle |
|---|---|---|
| POST | /api/v1/detect | Liste les entités détectées, sans créer de session ni de jeton. |
| POST | /api/v1/mask | Remplace les valeurs par des jetons, crée (ou réutilise) la session et chiffre les correspondances. |
| POST | /api/v1/unmask | Rétablit les valeurs d'origine. session_id obligatoire. |
| POST | /api/v1/analyze | Combine masquage et évaluation : renvoie l'aperçu protégé et un risque high / none. |
| POST | /api/v1/sessions | Crée une session vide et renvoie son identifiant. |
| GET | /api/v1/sessions | Liste les 100 sessions les plus récentes (identifiant, création, échéance). |
| GET | /api/v1/analytics | Compteurs locaux : masquages, sessions, rétention. |
| GET | /api/v1/health | État du service : mode, fournisseur configuré. Sans authentification. |
| POST | /v1/chat/completions | Passerelle compatible OpenAI, avec restauration de la réponse. |
Corps de requête commun
{
"text": "Texte à analyser",
"session_id": "vf_...", // optionnel : réutilise une session
"custom_dictionary": { // optionnel : valeurs à protéger
"CLIENT_INTERNE": ["nom de code Projet Sirius"]
}
}
Réponse de POST /api/v1/mask
{
"masked_text": "Écrire à [[EMAIL_001]] avant le [[DATE_OF_BIRTH_001]]",
"session_id": "vf_Kd8s...",
"entities": [
{
"type": "EMAIL",
"placeholder": "[[EMAIL_001]]",
"value": "ana@example.com",
"start": 9,
"end": 24,
"confidence": 0.99,
"source": "rules",
"fingerprint": "1d420a5c0cf5948a"
},
{
"type": "DATE_OF_BIRTH",
"placeholder": "[[DATE_OF_BIRTH_001]]",
"value": "12/07/1988",
"start": 34,
"end": 44,
"confidence": 0.88,
"source": "rules",
"fingerprint": "7c19be03f8a41d55"
}
]
}
start/end servent à surligner chaque champ détecté dans l'aperçu local. Le champ value n'est jamais écrit dans le journal d'audit, qui ne conserve que des catégories, des comptages et des empreintes SHA-256 tronquées.05
Jetons & sessions
Un jeton a la forme [[TYPE_001]] : un type en majuscules, puis un numéro séquentiel sur trois chiffres, propre à chaque session.
| Règle | Explication |
|---|---|
| Stable dans la session | La même valeur reçoit toujours le même jeton : le contexte du dialogue est préservé. |
| Isolé entre sessions | Deux sessions indépendantes ne partagent aucune correspondance. |
| Numérotation continue | Le compteur reprend au valeur la plus élevée déjà utilisée pour ce type. |
| Inconnu = conservé | Un jeton absent du coffre (session expirée, clé changée) reste tel quel et apparaît dans unresolved. |
| Rétention | Les sessions expirent après VEILFORGE_RETENTION_HOURS (24 h par défaut). |
VEILFORGE_ENCRYPTION_KEY n'est pas définie, une clé éphémère est générée au démarrage : après un redémarrage, les anciennes correspondances deviennent illisibles et les jetons sont conservés comme « inconnus ». Définissez toujours une clé stable en production.06
Détection
Le détecteur par défaut repose sur des règles déterministes, vérifiables et rapides. Chaque entité est associée à un score de confiance ; les chevauchements sont résolus en gardant le span le plus fiable.
| Type | Exemple | Confiance | Remarque |
|---|---|---|---|
EMAIL | ana@example.com | 0,99 | — |
CREDIT_CARD | 4242 4242 4242 4242 | 0,99 | Contrôle de Luhn obligatoire |
SECRET | sk_live_…, ghp_…, JWT | 0,99 | Préfixes d'API, tokens JWT, en-têtes de clé privée |
PASSWORD | mot de passe : …, pwd= … | 0,99 | Mots de passe en clé/valeur, français et anglais |
BANK_ACCOUNT | compte n° 123456789012 | 0,93 | Libellé bancaire suivi d'un numéro |
IBAN | FR76 3000 6000 0112 … | 0,96 | Format bancaire conservateur |
IP_ADDRESS | 192.168.1.24 | 0,94 | — |
NATIONAL_ID | passeport AB1234567 | 0,91 | Mot-clé contextuel requis |
DATE_OF_BIRTH | né le 12/07/1988 | 0,88 | Indice multilingue requis |
PHONE | +33 6 12 34 56 78 | 0,83 | Formes internationales |
PERSON | client Jean Dupont | 0,69 | Uniquement avec un indice humain à proximité |
ADDRESS | 24 rue des Oliviers, 69001 Lyon | 0,90 | Numéro + voie, code postal et ville optionnels |
| Valeur personnalisée | Projet Sirius | 1,00 | Via custom_dictionary |
- Multilingue — indices en anglais, français, malgache, allemand et espagnol.
- Cas d'usage du dictionnaire — noms internes, codenames, clients récurrents : envoyez-les dans
custom_dictionarypour un masquage certain. - Étendre — branchez un NER local (spaCy, Presidio) en implémentant le protocole
Detector: le reste de la chaîne ne change pas. - Limite honnête — une détection par règles n'est pas une garantie de conformité. Faites toujours relire les sorties sensibles.
{
"text": "Le client Projet Sirius demande un devis.",
"custom_dictionary": {
"CLIENT": ["Projet Sirius", "Compte Azurelion"]
}
}
07
Configuration
Tout se règle par variables d'environnement, lues au démarrage du service.
| Variable | Défaut | Rôle |
|---|---|---|
VEILFORGE_API_KEY | local-dev-key | Clé portée par les clients (Authorization: Bearer …). À changer impérativement en production. |
VEILFORGE_ENCRYPTION_KEY | éphémère | Clé Fernet du coffre. Sans elle, les correspondances ne survivent pas à un redémarrage. |
VEILFORGE_UPSTREAM_URL | vide | URL de base du fournisseur compatible OpenAI. Vide = écho de développement. |
VEILFORGE_UPSTREAM_API_KEY | vide | Clé transmise au fournisseur en amont. |
VEILFORGE_FAIL_CLOSED | true | true : refuse toute forme non prise en charge. false : mode permissif, à éviter. |
VEILFORGE_RETENTION_HOURS | 24 | Durée de vie des sessions et de leurs correspondances. |
VEILFORGE_DB_PATH | veilforge.db | Chemin du fichier SQLite (chiffré au repos). |
VEILFORGE_API_KEY=change-me-in-production
VEILFORGE_ENCRYPTION_KEY=<python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())">
VEILFORGE_UPSTREAM_URL=https://api.openai.com/v1
VEILFORGE_UPSTREAM_API_KEY=sk-...
VEILFORGE_FAIL_CLOSED=true
VEILFORGE_RETENTION_HOURS=24
08
Sécurité
Le principe directeur : seul du contenu tokenisé franchit la frontière. Tout le reste reste sur la machine qui exécute le service.
Ce qui est chiffré
Les correspondances jeton → valeur sont chiffrées avec Fernet et isolées par session. Le fichier SQLite ne contient donc aucune valeur sensible en clair.
Ce qui est journalisé
Uniquement des catégories, des comptages et des empreintes SHA-256 tronquées. Aucune valeur détectée n'est écrite dans le journal d'audit.
Mode strict
Les formes de contenu non prises en charge et les pannes amont sont refusées. Le diagnostic est audité, la divulgation n'a pas lieu.
Contrôle d'accès
Clé API obligatoire sur les routes métier, limitation à 120 requêtes par minute et par IP, sessions à durée de vie limitée.
Checklist de mise en production
- Remplacer
local-dev-keypar une longue valeur aléatoire. - Définir
VEILFORGE_ENCRYPTION_KEYet la conserver stable (gestionnaire de secrets). - Toujours
VEILFORGE_FAIL_CLOSED=true. - Servir le service en HTTPS derrière un reverse proxy, exposé seulement à vos applications.
- Choisir un
VEILFORGE_DB_PATHsur un volume chiffré et sauvegardé. - Surveiller le journal d'audit (événements
mask,unmask,upstream_error). - Relire les limites de la section Détection avant de déclarer une conformité.
09
Architecture
Le service est volontairement linéaire : un détecteur, un coffre de correspondances chiffré, une passerelle HTTP. Aucune donnée n'est envoyée à un tiers autre que le fournisseur que vous configurez.
client ─▶ Veilforge (détection + coffre chiffré) ─▶ fournisseur
client ◀─────────── restauration ◀──────────────── fournisseur
POST /v1/chat/completions
1. création/réutilisation de la session
2. masquage récursif du payload (messages, tools, results)
3. transmission au fournisseur, ou écho marqué mock:true
4. restauration de la réponse (y compris le flux SSE)
5. audit : types, comptages, empreintes
Le code est organisé en trois modules : veilforge/detection.py (règles et protocole Detector), veilforge/vault.py (sessions, chiffrement, journal) et veilforge/main.py (routes FastAPI et passerelle).
10
Tests & Docker
python -m pytest -q
# 3 passed — détection, aller-retour masque/restauration, jetons inconnus
docker compose up --build
# service sur le port 8787, données dans le volume veilforge-data (/data)
Le conteneur expose le port 8787, place la base dans /data et redémarre automatiquement. Pensez à fournir un .env (le compose le lit) contenant les secrets décrits dans Configuration.
11
Questions fréquentes
Mes données partent-elles vers un modèle ?
Seul le texte masqué est transmis, et uniquement si vous avez configuré VEILFORGE_UPSTREAM_URL. Sans fournisseur, la passerelle renvoie un écho de développement. La démonstration de la page d'accueil n'appelle aucun modèle.
Puis-je utiliser Veilforge sans interface ?
Oui. Le projet est API-first : la page d'accueil et la démonstration sont des clients de plus des routes /api/v1/*, documentées dans le Swagger.
Que se passe-t-il si je change la clé de chiffrement ?
Les correspondances anciennes deviennent illisibles : les jetons concernés sont conservés tels quels et remontés dans unresolved, jamais dévoilés par erreur. Créez une nouvelle session pour repartir proprement.
Comment ajouter un détecteur plus précis ?
Implémentez le protocole Detector (méthode detect) avec un modèle NER local, puis remplacez l'instance dans vault.detector. Le coffre, la passerelle et les jetons restent inchangés.
Où sont stockées les données ?
Dans un fichier SQLite local (VEILFORGE_DB_PATH), avec les correspondances chiffrées. Le journal d'audit ne contient que des catégories, des comptages et des empreintes.
Comment montrer un modèle à un client sans révéler ses données ?
Lancez la démonstration de la page d'accueil : le texte devient [[EMAIL_001]], puis revient à l'identique. C'est la même mécanique que la passerelle, en local.
Prêt à essayer ?
Lancez la démonstration interactive sur la page d'accueil, ou explorez les routes dans le Swagger.