feat: secrets masqués — couverture universelle clés API/mots de passe + clic pour copier (#188)
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# Fiche — #188 · Secrets masqués : couverture universelle + clic pour copier
|
||||
|
||||
- **Statut** : ✅ livré le 2026-10-08 (ouvert le 2026-10-08)
|
||||
- **Effort** : ~1 jour · **Impact** : 🟡
|
||||
- **Zones** : `backend/secret_redactor.py`, `backend/render.py`,
|
||||
`backend/routers/files_read.py`, `frontend/js/viewer.js`,
|
||||
`frontend/style.css`, `frontend/locales/{fr,en}.json`
|
||||
|
||||
## Problème
|
||||
|
||||
Le redacteur (`secret_redactor.py`) ne masquait que les JWT, les clés
|
||||
`sk-`/`pk-`/`rk-`, `AKIA…`, `ghp_…`, les connection strings et les
|
||||
affectations `api_key=`/`token=`/`secret=` de **20 caractères ou plus**.
|
||||
Résultat : la majorité des clés API réelles (Google, Slack, Stripe, GitLab,
|
||||
Hugging Face, npm…) et **tous les mots de passe courts** passaient en clair
|
||||
dans l'aperçu markdown, et un masque affiché (`[CLÉ API MASQUÉE]`) n'était
|
||||
pas récupérable : il fallait ouvrir la source pour relire la valeur.
|
||||
|
||||
## Solution
|
||||
|
||||
1. **Détection universelle** — une alternation unique de formats
|
||||
fournisseur (OpenAI/Anthropic `sk-`, Stripe `sk_live_`/`whsec_`, GitLab
|
||||
`glpat-`, GitHub `ghp_`/`github_pat_`, Google `AIza…`/`ya29.`, AWS
|
||||
`AKIA`/`ASIA`, Slack `xoxb-`, SendGrid `SG.`, Hugging Face `hf_`, npm,
|
||||
Docker, Resend, Square, Atlassian, Discord, Telegram, `Bearer …`) en
|
||||
plus des motifs déjà présents ; le plancher des affectations génériques
|
||||
passe de 20 à 8 caractères.
|
||||
2. **Mots de passe** — motif dédié (`[MOT DE PASSE MASQUÉ]`) valable toute
|
||||
longueur et toute quote (`password=`, `"passwd": "…"`, `db_password=`,
|
||||
`passphrase=`, `mot de passe=`), qualificateur inclus. `PWD=` (dossier de
|
||||
travail shell) reste épargné : `pwd` n'est reconnu que qualifié.
|
||||
3. **Clic → copie** — le pipeline de rendu passe par des
|
||||
**placeholders** (`\uE000n\uE001`) qui survivent à la conversion markdown
|
||||
(un `<span>` littéral dans un bloc de code serait affiché tel quel), puis
|
||||
`restore_masks()` les réécrit après le rendu :
|
||||
- **aperçu authentifié** (`GET /api/file/…`, `click_to_copy=True`) :
|
||||
`<span class="secret-mask" data-secret="valeur">[CLÉ API MASQUÉE]</span>` —
|
||||
la valeur n'est jamais affichée, un clic la copie (listener délégué
|
||||
unique sur la zone de contenu, toast i18n FR/EN, infobulle) ;
|
||||
- **partages publics, exports PDF, contexte IA / MCP** : libellé seul,
|
||||
la valeur ne quitte pas le serveur.
|
||||
Les blocs `<pre><code>` portant un masque ne sont pas re-colorés
|
||||
(highlight.js reconstruirait le markup et perdrait le badge).
|
||||
|
||||
## Hors périmètre (volontaire)
|
||||
|
||||
- La vue « source » (fichier brut) et les aperçus de fichiers
|
||||
**non-markdown** ne sont pas masqués : c'est le fichier lui-même qui est
|
||||
affiché, le redacteur ne s'y appliquait jamais.
|
||||
- Le serveur MCP / l'assistant IA continuent de recevoir les libellés seuls
|
||||
(inchangé).
|
||||
|
||||
## Tests
|
||||
|
||||
`tests/test_api_main.py::TestSecretRedactor` : formats fournisseurs
|
||||
(paramétrés), mots de passe (paramétrés), `PWD=` non masqué, round-trip
|
||||
placeholder → span cliquable, rendu markdown cliquable vs libellé seul.
|
||||
Reference in New Issue
Block a user