Privacy by design signifie intégrer la protection des personnes dès la conception: minimiser les données, protéger les flux et contrôler les usages. Avec RAG (retrieval-augmented generation), l’enjeu est double: maîtriser ce qui entre (index, requêtes) et ce qui sort (réponses, citations, journaux). Objectif: utilité maximale, exposition minimale.
objectifs et périmètre
- cadrer la finalité: à quoi sert l’assistant et à quoi il ne doit pas servir.
- limiter l’entrée: n’indexer et n’envoyer que l’utile.
- filtrer la sortie: supprimer les PII résiduelles et forcer les citations.
- prouver la conformité: journaux, rétention courte, droit à l’oubli.
minimiser
- n’envoyer que les champs utiles à la tâche (ex: id, sujet, catégorie).
- masquer les PII bien avant l’inférence (email, téléphone, adresse).
- définir une rétention courte et automatique des journaux.
-- champs autorisés vers le pipeline RAG (exemple)
SELECT id, subject, category
FROM tickets
WHERE pii_masked = true
AND updated_at >= NOW() - INTERVAL '90 day';
ingest_policy:
purpose: "support"
allow_fields: ["id","subject","category","created_at"]
deny_fields: ["email","phone","address","iban"]
retention_days: 30
protéger
- chiffrer au repos et en transit (TLS et clés managées).
- appliquer des contrôles d’accès par rôles (RBAC) et RLS/CLS côté entrepôt.
- centraliser les journaux d’accès, sans PII, avec rétention courte.
-- vue masquée pour CLS simple
CREATE OR REPLACE VIEW support.tickets_view AS
SELECT
id,
subject,
category,
CASE WHEN has_privilege('see_email') THEN email ELSE 'masked' END AS email
FROM support.tickets_src;
-- RLS: lecture par périmètre d'équipe
ALTER TABLE support.tickets_src ENABLE ROW LEVEL SECURITY;
CREATE POLICY rls_support ON support.tickets_src
FOR SELECT TO role_support
USING (team = current_setting('app.team', true));
contrôler
- restreindre les sources: seul l’index autorisé est interrogeable.
- imposer les citations: chaque réponse doit référencer les documents utilisés.
- refuser poliment hors périmètre: mieux vaut ne pas répondre que halluciner.
rag_policy:
source_allowlist: ["support_faq_v2","kb_internal_v1"]
max_chunks: 6
require_citations: true
outguard:
pii_redaction: true
banned_patterns: ["num_secu","iban","carte_bancaire"]
refusals:
out_of_scope_message: "Désolé, cette demande dépasse le périmètre défini."
pipeline RAG avec contrôles (vue verticale)
boucle de correction
- canal de réclamation simple (lien “corriger ceci” dans l’interface).
- droit à l’oubli: suppression dans l’index et propagation vers les caches.
- post-mortem après incident: causes, correctifs, prévention.
-- droit à l'oubli: marquer et purger de l'index
UPDATE support.kb_internal_v1
SET forget_flag = true, forget_ts = NOW()
WHERE doc_id = $1;
-- job de purge périodique
DELETE FROM support.kb_internal_v1
WHERE forget_flag = true
AND NOW() - forget_ts >= INTERVAL '1 day';
métriques et SLO de confidentialité
- taux de réponses avec citation valide.
- fuites PII détectées par le filtre de sortie (objectif: 0).
- délai de traitement droit à l’oubli (SLO, ex: 24 h).
- couverture de l’allowlist (toutes les requêtes proviennent de sources autorisées).
privacy_slo:
citations_rate_min: 0.98
pii_leak_rate_max: 0.0
forget_request_mttr_hours: 24
allowlist_coverage: 1.0
-- suivi du taux de citations (extrait)
SELECT DATE_TRUNC('day', ts) AS d,
AVG(CASE WHEN citations >= 1 THEN 1 ELSE 0 END) AS citations_rate
FROM rag.responses_metrics
WHERE ts >= NOW() - INTERVAL '30 day'
GROUP BY 1 ORDER BY 1;
exemples complets
cas 1: masquage d’email et de téléphone en entrée
import re
EMAIL = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
PHONE = re.compile(r"\+?\d[\d\s\-]{7,}\d")
def mask_input(text: str) -> str:
text = EMAIL.sub("[email_masqué]", text)
text = PHONE.sub("[téléphone_masqué]", text)
return text
cas 2: vérification des citations en sortie
def has_valid_citations(answer, citations):
if not citations:
return False
# Exige au moins un identifiant de source référencé dans la réponse
return any(cid in answer for cid in citations)
cas 3: configuration d’un index limité
index_config:
name: "support_faq_v2"
include_paths:
- "faq/howto/"
- "faq/policies/"
exclude_paths:
- "drafts/"
- "personnel/"
tokenizer: "fr_core_news_md"
max_chunk_tokens: 512
bonnes pratiques
- écrire des politiques simples et testables; valider en CI la présence des champs critiques.
- tracer les versions de l’index et des filtres; consigner les changements.
- privilégier des messages de refus clairs plutôt qu’une réponse incertaine.
- vérifier régulièrement la dérive des journaux: volumes, champs, durée de rétention.
- aligner les SLO privacy avec la sécurité et le juridique (mêmes seuils, même calendrier).
pièges
- copier “tout” vers l’index -> fuite -> minimiser strictement et masquer en amont.
- logs verbeux -> PII en clair -> filtrer avant stockage, rétention courte.
- sources non revues -> RAG à risque -> revue éditoriale et étiquettes “autorisé”.
- citations optionnelles -> opacité -> rendre les citations obligatoires et contrôlées.
- droit à l’oubli lent -> non-conformité -> mécaniser marquage et purge, SLO de 24 h.
faq
-
Comment limiter l’entrée sans perdre en qualité ? Définissez une allowlist de champs et de répertoires; masquez les PII; gardez des extraits courts mais pertinents.
-
Faut-il toujours imposer des citations ? Oui pour les usages décisionnels ou sensibles: elles apportent traçabilité et facilitent l’audit.
-
Que faire si une requête sort du périmètre ? Répondre par un refus explicite et proposer ce que l’assistant peut faire dans son périmètre.
-
Comment prouver la conformité au quotidien ? Exposez des métriques privacy (citations, fuites PII, MTTR oubli) et conservez des journaux sans PII avec TTL court.
-
Puis-je anonymiser au lieu de pseudonymiser pour l’index ? Préférez le masquage sélectif et la minimisation; l’anonymisation complète est souvent difficile à garantir en libre texte.