Compare commits

..
Author SHA1 Message Date
bruno 6713510ce9 Merge pull request 'fix(ci): tag OpenAPI 'Coffres Secrets' declare + compteur de themes 15 -> 16' (#34) from fix/ci-tests-openapi-themes into main
CI / security (push) Failing after 0s
CI / lint (push) Successful in 2m57s
CI / test (push) Successful in 5m37s
CI / build (push) Successful in 3m15s
CI / e2e (push) In progress
2026-10-28 08:27:31 -04:00
bruno bf5b32de16 Merge pull request 'feat(ui): icone coffre-fort SVG pour le noeud Secrets' (#33) from fix/icone-secrets-coffre-fort into main
CI / security (push) Successful in 1m58s
CI / lint (push) Successful in 2m55s
CI / test (push) Failing after 1s
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 08:25:45 -04:00
bruno 2a1780b43d docs: commentaire 15 -> 16 themes integres (meme compteur obsolete que test_themes.py)
CI / security (pull_request) Failing after 0s
CI / lint (pull_request) Successful in 3m11s
CI / test (pull_request) Failing after 5m8s
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
2026-10-28 08:25:38 -04:00
bruno be05ec99b5 fix(tests): test_theme_count 15 -> 16 (theme spark ajoute en 2.80.2) 2026-10-28 08:25:35 -04:00
bruno 3a42bc034d fix(tests): declare le tag OpenAPI 'Coffres Secrets' (test_used_tags_are_declared) 2026-10-28 08:25:32 -04:00
bruno 27e7664ef0 feat(ui): dimensionnement de l'icone coffre-fort du noeud Secrets
CI / lint (pull_request) Failing after 4s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
CI / security (pull_request) Successful in 1m56s
2026-10-28 08:24:23 -04:00
bruno ccbf1d3269 feat(ui): icone coffre-fort SVG pour le noeud Secrets (remplace le cadenas emoji) 2026-10-28 08:24:16 -04:00
bruno 0ca14739f9 Merge pull request 'fix(lint): tri des imports dans backend/routers/coffres.py (I001)' (#32) from fix/ruff-coffres-imports into main
CI / security (push) Successful in 1m56s
CI / lint (push) Successful in 3m14s
CI / test (push) Failing after 5m5s
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 08:16:47 -04:00
bruno d9192ef355 fix(lint): tri des imports dans backend/routers/coffres.py (I001)
CI / lint (pull_request) Failing after 0s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
CI / security (pull_request) Successful in 1m59s
2026-10-28 08:13:35 -04:00
bruno 4c9bfd576c Merge pull request 'chore(release): version 2.81.0 (Coffre Secrets Phases 2-5)' (#31) from fix/version-2.81.0 into main
CI / security (push) Failing after 0s
CI / lint (push) Failing after 1m50s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 00:16:49 -04:00
bruno 20898cade7 chore(release): bump version 2.81.0 (Coffre Secrets Phases 2-5)
CI / lint (pull_request) Failing after 0s
CI / security (pull_request) Failing after 0s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
2026-10-28 00:16:25 -04:00
bruno 2c12639499 chore(release): bump version 2.81.0 (Coffre Secrets Phases 2-5) 2026-10-28 00:16:21 -04:00
bruno 7d2b2067ef chore(release): bump version 2.81.0 (Coffre Secrets Phases 2-5) 2026-10-28 00:16:19 -04:00
bruno 0b52e6ee86 chore(release): bump version 2.81.0 (Coffre Secrets Phases 2-5) 2026-10-28 00:16:16 -04:00
bruno cafac69008 chore(release): bump version 2.81.0 (Coffre Secrets Phases 2-5) 2026-10-28 00:16:12 -04:00
bruno 871875b8e5 chore(release): bump version 2.81.0 (Coffre Secrets Phases 2-5) 2026-10-28 00:16:10 -04:00
bruno e5ae3e48d6 chore(release): bump version 2.81.0 (Coffre Secrets Phases 2-5) 2026-10-28 00:16:08 -04:00
bruno 76de3dd75e chore(release): bump version 2.81.0 (Coffre Secrets Phases 2-5) 2026-10-28 00:16:05 -04:00
bruno 687427381d chore(release): bump version 2.81.0 (Coffre Secrets Phases 2-5) 2026-10-28 00:16:03 -04:00
bruno e418d87802 Merge pull request '[Coffre Secrets] Phase 5 — Documentation de deploiement et mise en service' (#30) from feat/coffre-phase5-deploiement into main
CI / lint (push) Failing after 0s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 00:15:09 -04:00
bruno a1f3690085 Merge pull request '[Coffre Secrets] Phase 2 — Migration api_keys.json, sauvegarde/restauration KEK, contrat distribution, consommateurs + correctif UI modale' (#29) from feat/coffre-phase2 into main
CI / lint (push) Failing after 1s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 00:15:06 -04:00
bruno 0a7bed10bf Phase 5 (issue #23) : correction numerotation etape lecteurs internes (4, pas 5)
CI / lint (pull_request) Failing after 1s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
CI / security (pull_request) Failing after 0s
2026-10-28 00:11:20 -04:00
bruno 32996ce9aa Phase 5 (issue #23) : deploiement et mise en service du coffre Secrets 2026-10-28 00:10:51 -04:00
16 changed files with 417 additions and 21 deletions
+5 -1
View File
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **En cours de développement** : les changements à venir sont listés dans la section > **En cours de développement** : les changements à venir sont listés dans la section
> [Unreleased](#unreleased). La dernière version livrée est **2.80.2**. > [Unreleased](#unreleased). La dernière version livrée est **2.81.0**.
--- ---
@@ -14,6 +14,10 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
--- ---
## [2.81.0] — 2026-10-28
---
## [2.80.2] — 2026-10-27 ## [2.80.2] — 2026-10-27
### Corrigé ### Corrigé
+3 -3
View File
@@ -4,7 +4,7 @@
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive. **Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
[![Version](https://img.shields.io/badge/Version-2.80.2-blue.svg)]() [![Version](https://img.shields.io/badge/Version-2.81.0-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -977,8 +977,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
## 📝 Changelog ## 📝 Changelog
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.80.2). Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.81.0).
--- ---
*Projet : ObsiGate | Version : 2.80.2 | Dernière mise à jour : Septembre 2026* *Projet : ObsiGate | Version : 2.81.0 | Dernière mise à jour : Septembre 2026*
+3 -3
View File
@@ -2,7 +2,7 @@
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. **Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
[![Version](https://img.shields.io/badge/Version-2.80.2-blue.svg)]() [![Version](https://img.shields.io/badge/Version-2.81.0-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -1152,8 +1152,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
## 📝 Changelog ## 📝 Changelog
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.80.2). See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.81.0).
--- ---
*Project: ObsiGate | Version: 2.80.2 | Last updated: September 2026* *Project: ObsiGate | Version: 2.81.0 | Last updated: September 2026*
+1 -1
View File
@@ -1 +1 @@
2.80.2 2.81.0
+1
View File
@@ -41,6 +41,7 @@ TAGS_METADATA: list[dict[str, str]] = [
{"name": "Webhooks", "description": "HTTP callbacks signed with HMAC-SHA256 for file events."}, {"name": "Webhooks", "description": "HTTP callbacks signed with HMAC-SHA256 for file events."},
{"name": "Conflicts", "description": "Detect and resolve Syncthing sync-conflict files."}, {"name": "Conflicts", "description": "Detect and resolve Syncthing sync-conflict files."},
{"name": "Admin", "description": "Admin-only system monitoring: stats, audit log, backup stats and live stream."}, {"name": "Admin", "description": "Admin-only system monitoring: stats, audit log, backup stats and live stream."},
{"name": "Coffres Secrets", "description": "Secret vaults: encrypted coffres, secrets, consumers, grants and distribution — metadata only, values are never exposed."},
{"name": "Plugins", "description": "Install, enable and manage user plugins."}, {"name": "Plugins", "description": "Install, enable and manage user plugins."},
{"name": "Push", "description": "Web Push (VAPID) subscription management and test notifications."}, {"name": "Push", "description": "Web Push (VAPID) subscription management and test notifications."},
{"name": "Duplicates", "description": "Duplicate-note detection and confirmed merge (#166)."}, {"name": "Duplicates", "description": "Duplicate-note detection and confirmed merge (#166)."},
+1 -1
View File
@@ -34,10 +34,10 @@ from __future__ import annotations
import logging import logging
import os import os
from backend.auth.middleware import require_admin, require_auth
from fastapi import APIRouter, Body, Depends, HTTPException, Request from fastapi import APIRouter, Body, Depends, HTTPException, Request
from backend.audit import get_recent_entries, log_coffre_event from backend.audit import get_recent_entries, log_coffre_event
from backend.auth.middleware import require_admin, require_auth
from backend.coffre_crypto import ( from backend.coffre_crypto import (
CryptoIndisponibleError, CryptoIndisponibleError,
KEKAbsenteError, KEKAbsenteError,
+1 -1
View File
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]] [[package]]
name = "obsigate-desktop" name = "obsigate-desktop"
version = "2.80.2" version = "2.81.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"env_logger", "env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "obsigate-desktop" name = "obsigate-desktop"
version = "2.80.2" version = "2.81.0"
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
authors = ["Bruno Charest"] authors = ["Bruno Charest"]
edition = "2021" edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
"productName": "ObsiGate", "productName": "ObsiGate",
"version": "2.80.2", "version": "2.81.0",
"identifier": "com.obsigate.desktop", "identifier": "com.obsigate.desktop",
"build": { "build": {
"frontendDist": "../frontend", "frontendDist": "../frontend",
+390
View File
@@ -0,0 +1,390 @@
# Coffre Secrets ObsiGate — Déploiement et mise en service (Phase 5, issue #23)
Document de déploiement du coffre Secrets, issu des Phases 1a/1b
(issues #17, #18), 2 (issue #20), 3 (issue #21, côté agent-commun)
et 4 (issue #22, côté TikTak, Flowdeck, Kanbante).
**Règles de gouvernance (non négociables) :** travail en branche +
pull request, jamais sur `main`, jamais de merge, de release, de tag,
de fermeture d'issue ni de changement de label sans le go explicite
de Bruno. Ce document ne décrit aucune valeur réelle : les exemples
utilisent des valeurs fictives (`TEST-VALUE-XXXX`) et aucune KEK
n'est jamais écrite ici, ni dans un journal, ni dans une note, ni
dans le dépôt.
Documents compagnons (même branche de référence, Phase 2) :
- `docs/COFFRE-contrat-distribution.md` — contrat
`POST /api/coffres/distribuer` pour Spark, TikTak, Flowdeck,
Kanbante (authentification par consommateur, contextes, codes
d'erreur stables).
- `docs/COFFRE-sauvegarde-restauration.md` — sauvegarde
(`data/coffres.json` + `data/coffres_valeurs.json`, 0600) et
restauration sur instance vierge, perte de KEK.
## 1. Prérequis et variables d'environnement
### 1.1 Dépendance
- `cryptography>=44.0` dans `backend/requirements.txt` (dépendance
Phase 1a). Sans elle, les opérations de chiffrement répondent
503 `crypto_indisponible` : c'est un mode dégradé explicite, pas
un repli en clair. Il n'y a jamais de stockage ni de distribution
de valeur en clair.
- Chiffrement par enveloppe AES-256-GCM : KEK (256 bits) → DEK par
coffre → valeur du secret. Un tag GCM invalide refuse le
déchiffrement (testé : `tests/test_coffre_secrets.py`,
`test_tag_invalide_refuse`).
### 1.2 Variables
| Variable | Rôle | Défaut |
| --- | --- | --- |
| `OBSIGATE_SECRETS_ENABLED` | Activation du coffre. Tant qu'elle n'est pas à `true`, **toutes** les routes coffre répondent 503 `secrets_desactives`. | désactivé |
| `OBSIGATE_KEK` | KEK, base64 de 32 octets (ou hex 64 caractères). **Prioritaire** sur le fichier si les deux sont définis. | absente |
| `OBSIGATE_KEK_FILE` | Chemin d'un fichier contenant la KEK, **en 0600, hors dépôt et hors arborescence des vaults**. Un fichier trop permissif (droits groupe/autre) est refusé explicitement (`KEKInvalideError`). | absent |
| `OBSIGATE_DATA_DIR` | Répertoire des données (`data/` par défaut) : `coffres.json`, `coffres_valeurs.json` et l'historique `api_keys.json`. | `data` |
La KEK est générée une seule fois (admin, coffres activés) via
`POST /api/coffres/admin/generer-kek` ; ObsiGate **ne la stocke
jamais**. Au moment de sa génération : la copier immédiatement
dans un gestionnaire de mots de passe ou sur un support hors ligne,
hors du serveur ; vérifier la copie en la relisant depuis le
gestionnaire, jamais depuis l'écran qui ne l'affichera plus.
Ne jamais la coller dans le dépôt, dans une note ObsiGate, dans le
même dossier que `data/`, ni dans un journal.
**Recommandation de déploiement : préférer `OBSIGATE_KEK_FILE`**
(fichier 0600) à `OBSIGATE_KEK` en variable d'environnement, pour
éviter que la KEK apparaisse dans un historique de processus ou un
dump d'environnement. Si les deux sont définis, `OBSIGATE_KEK`
l'emporte — vérifier qu'aucune ancienne valeur ne traîne dans
l'environnement au moment de basculer sur le fichier.
### 1.3 Ce que la sauvegarde contient (rappel)
Deux fichiers seulement portent le coffre, tous deux en 0600 :
- `data/coffres.json` : métadonnées des coffres, DEK chiffrées,
consommateurs (hashes SHA-256 des jetons seulement, jamais le
jeton en clair), octrois, métadonnées des secrets ;
- `data/coffres_valeurs.json` : versions chiffrées des secrets.
Copiés seuls, ils ne donnent accès à aucune valeur. La KEK ne fait
pas partie de la sauvegarde : elle est copiée à part, hors serveur.
Sauvegarder les deux fichiers et la KEK au même endroit annulerait
la protection du chiffrement.
## 2. Décisions ouvertes de la spec — tranchées par défaut
Les décisions ouvertes de la spec (§15, §16), rappelées dans
l'issue #23, sont confirmées comme tranchées selon les défauts
recommandés, en attendant une décision contraire explicite de
Bruno. Aucune d'elles ne nécessite de changement de code pour la
mise en service décrite ci-dessous :
| Décision | Choix confirmé par défaut |
| --- | --- |
| Glyphe du nœud Secrets | Cadenas U+1F510 (🔐), distinct de la maison du Home et du diamant du Partage. Le nœud est un nœud virtuel sous le Home de l'utilisateur courant seulement (voir §4). |
| Stockage de la KEK | Par fichier 0600, hors dépôt (`OBSIGATE_KEK_FILE`) ; `OBSIGATE_KEK` reste supportée comme solution de secours. |
| Expiration des octrois | 90 jours par défaut à la création, renouvelable (création d'un nouvel octroi). Un octroi expiré refuse la distribution (`octroi_expire`, 403) ; un octroi révoqué refuse dès l'appel suivant (`octroi_revoque`, 403). |
| Copie manuelle | Presse-papiers seul, sans affichage de la valeur. Patron supporté : consommateur temporaire créé par un admin puis révoqué immédiatement, auditée comme distribution de contexte `copie_manuelle`. **Aucun endpoint dédié de copie par session n'est ajouté** : il contredirait la règle « distribution réservée aux consommateurs » et exigerait une ré-authentification qui n'existe pas encore — décision volontaire, à trancher avec Bruno avant toute implémentation. |
| Coffres partagés | Phase 2 ultérieure. En mise en service : coffres personnels d'abord, plus le coffre d'instance « ObsiGate interne » (slug `obsigate-interne`, admin seulement) issu de l'import. |
## 3. Mise en service pas à pas
**Ordre impératif.** Chaque étape est validée avant de passer à la
suivante. En particulier : **les lecteurs internes sont branchés
sur le coffre AVANT toute purge du clair** (étape 4 avant étape 7),
et la purge n'a lieu qu'après validation complète (étape 6).
### Étape 0 — Préparer, coffre encore désactivé
1. Déployer le code coffre (Phases 1–2) sur l'instance, **sans
activer le flag** : `OBSIGATE_SECRETS_ENABLED` reste absent ou à
`false`. Vérifier que toutes les routes `/api/coffres/*`
répondent 503.
2. Sauvegarder l'état actuel : copie de `data/` (dont
`api_keys.json`) et de la configuration, hors serveur.
3. Générer la KEK (voir §1.2) et la copier hors serveur. Ne pas
encore la référencer dans la configuration de l'instance si
l'étape 1 n'est pas terminée — une KEK présente sans flag actif
ne distribue rien, mais sa présence doit rester traçable.
### Étape 1 — Créer les consommateurs et les octrois
Avant d'activer le flag, préparer les consommateurs nommés, un par
application cliente (convention de nommage, non imposée par le
code — l'autorisation porte sur le jeton, pas sur le nom) :
| Consommateur | Usage | Contexte de distribution |
| --- | --- | --- |
| `spark` | Agent Spark (agent-commun, Phase 3) | `contexte_type = "mission"`, `contexte_id` = id de mission (forme imbriquée `contexte: {type, id}` acceptée) |
| `tiktak` | Synchronisation TikTak (Phase 4) | `contexte_type = "tiktak_sync"`, `contexte_id` = id utilisateur |
| `flowdeck` | Connecteurs Flowdeck (Phase 4) | Contexte workspace, vérifié côté Flowdeck (voir §5) |
| `kanbante` | Webhooks Kanbante (Phase 4) | Contexte webhook, distribution à chaque appel puis oubli |
Création en une seule opération avec les octrois :
```
POST /api/coffres/consommateurs/avec-octrois
{"nom": "tiktak", "octrois": [{"coffre_slug": "...", "secret_nom": "..."}]}
```
- `secret_nom` absent = tout le coffre (octroi coffre-wide).
- Le jeton (`csk_…`) n'est rendu **qu'une seule fois**, à la
création. Le noter immédiatement dans le coffre de
configuration de l'application cliente concernée, jamais dans
une note ObsiGate ni dans le dépôt. Il n'est jamais relisté :
`GET /api/coffres/consommateurs` ne renvoie que `id`, `nom`,
`jeton_prefixe`, `statut`, `cree_par`, `cree_le`.
- La révocation est immédiate : `DELETE
/api/coffres/consommateurs/{id}` révoque toutes les
distributions de ce consommateur, sans toucher aux autres.
Une mission Spark déjà démarrée conserve son environnement
jusqu'à sa fin ; la mission suivante repasse en `acces_requis`.
À ce stade, les consommateurs et octrois peuvent être créés alors
que les secrets n'existent pas encore : les octrois pointent vers
des coffres/secrets à créer aux étapes suivantes. Vérifier la
liste des consommateurs et des octrois avant d'activer le flag.
### Étape 2 — Activer le flag
1. Définir `OBSIGATE_KEK_FILE` (fichier 0600, hors dépôt) puis
`OBSIGATE_SECRETS_ENABLED=true`.
2. Redémarrer l'instance.
3. Vérifier : la vue Secrets s'ouvre ; `GET /api/coffres` répond
200 (liste vide ou coffres existants) ; sans KEK valide, les
routes répondent 503 `kek_absente` — dans ce cas, corriger la
KEK avant toute création de secret.
### Étape 3 — Importer l'existant (admin seulement)
Bouton « Importer l'existant » de la vue Secrets (admin
seulement), en deux temps séparés :
1. **Aperçu** : `GET /api/coffres/migration/api-keys` liste les
candidats de `data/api_keys.json` (clés IA et clés d'outils).
2. **Import** : `POST /api/coffres/migration/api-keys/import`
copie les entrées avec valeur vers le coffre d'instance
« ObsiGate interne » (slug `obsigate-interne`), chiffrées.
Un secret déjà présent n'est **jamais écrasé**.
`api_keys.json` n'est **pas modifié** par l'import.
Vérifier après import : les secrets importés apparaissent dans la
vue Secrets avec `has_value = true`, et aucune valeur n'est
visible (métadonnées seules : nom, type, version, dates, octrois
actifs — jamais de valeur, jamais de texte chiffré, jamais de
nonce).
### Étape 4 — Brancher les lecteurs internes AVANT toute purge
**Point de vigilance principal de la mise en service.** Les
lecteurs actuels d'ObsiGate (`backend/ai.py`,
`backend/tools/secrets.py`) lisent encore `data/api_keys.json`
directement ; ils ne sont pas encore branchés sur le coffre
(reste à faire explicite des Phases 2–5). Tant que ce branchement
n'est pas livré et validé, **purger le clair priverait l'IA et les
outils de leurs clés**.
Ordre à respecter :
1. Ne pas purger.
2. Livrer (en branche + PR, jamais mergé sans go) le branchement
des lecteurs internes sur une distribution depuis le coffre
d'instance, puis le valider sur l'instance : l'IA et les outils
fonctionnent avec `api_keys.json` rendu temporairement
inaccessible en lecture (test de coupure contrôlée), ou à
défaut avec un secret de test distribué depuis le coffre.
3. Seulement après cette validation, passer à l'étape 7.
Si Bruno accepte une interruption temporaire de l'IA/outils, le
documenter explicitement avant la purge ; sinon, la purge attend
le branchement.
### Étape 5 — Brancher les applications clientes
Pour chaque application (branches dédiées, jamais mergées sans
go), appliquer le patron commun **distribuer → utiliser →
oublier** : le serveur distribue à chaque opération, utilise la
valeur en mémoire, puis l'oublie ; aucune valeur en cache, aucune
persistance. Si ObsiGate est indisponible, l'opération échoue
proprement, sans repli sur une ancienne valeur.
- **Spark (agent-commun, Phase 3)** : identités référencées
`coffre:<slug>/<nom>` (slug 3–64, nom 3–128, minuscules /
chiffres / `-` / `_`), sans colonne valeur ; le LLM ne voit que
la référence et un accès oui/non. Sans octroi, la mission passe
en `acces_requis` en nommant l'accès manquant ; avec octroi et
mission approuvée (enveloppe budgétaire), distribution puis
injection en variable d'environnement de l'exécuteur, jamais
dans le prompt, jamais dans les étapes d'activité. Fin de
mission = environnement détruit.
- **TikTak (Phase 4)** : référence seule stockée ; distribution
serveur à chaque synchro ; masquage côté Android (ni référence
complète ni valeur dans les réponses vues par une clé `tck_`).
Les configurations existantes sont marquées `a_resaisir = 1`
(migration 019 : colonne en clair supprimée, référence vide) :
il n'y a **pas de conversion automatique** — chaque
configuration doit être re-saisie par référence.
- **Flowdeck (Phase 4)** : `workspace_id` + référence sur les
connecteurs ; distribution en mémoire ; isolation A vs B
testée (un workspace ne distribue pas avec l'octroi d'un autre).
Les connecteurs natifs Gitea/GitHub et les clés LLM ne sont pas
basculés ; pas de sélecteur UI (API seulement).
- **Kanbante (Phase 4)** : secret webhook migré en référence,
distribuée à chaque webhook puis oubliée ; client générique
réutilisable. Les jetons forge chiffrés Fernet ne sont **pas**
migrés (distribution par appel requise, incompatible avec le
cache `ForgeRegistry`) ; les secrets webhook historiques en
clair restent acceptés par compatibilité, sans migration
automatique.
### Étape 6 — Valider (avant purge)
Exécuter la checklist de validation de l'instance réelle (§4).
Tant qu'un point échoue, ne pas purger et ne pas considérer la
mise en service comme terminée.
### Étape 7 — Purger, seulement après validation
`POST /api/coffres/migration/api-keys/purger`, **après
confirmation explicite** (`confirmer = true`), et uniquement pour
les noms migrés. Sans confirmation, rien n'est supprimé — jamais
automatiquement.
Après purge, vérifier :
- `api_keys.json` ne contient plus les noms purgés ;
- l'IA et les outils fonctionnent toujours (lecteurs branchés,
étape 4) ;
- une distribution de test via un consommateur existant aboutit
(succès seul vérifié, valeur ni affichée ni consignée).
### Étape 8 — Sauvegarder et tester la restauration
1. Sauvegarder `data/coffres.json` + `data/coffres_valeurs.json`
(0600) par la sauvegarde habituelle du serveur, et la KEK à
part, hors serveur (§1.3).
2. Rejouer la restauration sur une instance vierge (procédure
`docs/COFFRE-sauvegarde-restauration.md` §3 : arrêt de la
cible, copie des deux fichiers en 0600, même KEK, redémarrage,
vérification `has_value = true` + distribution de test +
`distribution_ok` dans le journal). La suite automatisée
(`tests/test_coffre_secrets.py`, classe
`TestRestaurationVierge`) simule cette restauration ; elle ne
remplace pas un essai sur instance réelle.
3. En cas de perte de KEK : perte d'accès assumée, par
construction — aucun moyen de récupérer les valeurs. Recréer
les coffres avec une nouvelle KEK, régénérer les valeurs côté
fournisseur, révoquer les anciennes côté fournisseur, recréer
consommateurs et octrois. Ne jamais tenter de « réparer » les
anciens fichiers avec une nouvelle KEK (le déchiffrement
échouera sur tag GCM invalide, ce qui est attendu).
## 4. Checklist de validation par Bruno sur instance réelle
Validation exigée par l'issue #23 avant tout usage réel. Chaque
point est validé sur capture, comme pour les autres livraisons :
- [ ] **Sidebar** : le nœud « Secrets » (cadenas U+1F510)
apparaît sous le Home de l'utilisateur courant seulement, et
pas sous les autres vaults ; cliquer dessus ouvre la vue
Secrets sans naviguer dans les fichiers.
- [ ] **Secret de test** : création d'un secret de test
(valeur fictive, ex. `TEST-VALUE-XXXX`) dans un coffre
personnel, puis lecture : métadonnées seules, aucune valeur
affichée, aucun texte chiffré, aucun nonce.
- [ ] **Journal d'audit** : après une distribution de test, le
journal contient `distribution_ok` pour la référence, et un
`grep` du journal sur la valeur de test ne renvoie **rien**
(ni valeur, ni texte chiffré, ni nonce, ni jeton).
- [ ] **Autorisations** : un admin ne voit pas le coffre
personnel d'autrui ; une ACL `*` sur un vault ne donne aucun
droit sur un coffre ; une session utilisateur, même admin, ne
distribue pas (401 `consommateur_invalide`).
- [ ] **Distribution** : contexte manquant refusé (400
`contexte_manquant`) ; octroi révoqué refusé dès l'appel
suivant (403 `octroi_revoque`) ; octroi expiré refusé (403
`octroi_expire`) ; secret expiré refusé (410 `secret_expire`).
- [ ] **Import / purge** : l'import ne modifie pas
`api_keys.json` et n'écrase pas un secret existant ; la purge
sans confirmation explicite ne supprime rien.
- [ ] **Restauration** : sauvegarde + KEK externe restaurées sur
une instance vierge, distributions fonctionnelles.
- [ ] **Intégrations** : mission Spark en `acces_requis` sans
octroi, puis injection exécuteur sans trace dans les étapes ;
synchro TikTak sans valeur côté Android ; isolation workspace
Flowdeck (A ne distribue pas avec l'octroi de B).
- [ ] **Modale** : la modale de création de secret n'est plus
coupée en haut (`.obsigate-modal` bornée au viewport, corps
seul défilant, en-tête et pied toujours visibles).
## 5. Limites connues à la mise en service
À lire avant de déclarer la mise en service terminée :
- **Lecteurs internes non branchés** (`ai.py`,
`tools/secrets.py` lisent encore `api_keys.json` en clair) :
purger avant de les brancher priverait l'IA/outils de leurs
clés (§3, étape 4).
- **Endpoint dédié de copie manuelle par session non ajouté**
(décision volontaire, §2) : la copie manuelle passe par un
consommateur temporaire admin, presse-papiers seul.
- **Isolation workspace Flowdeck non contrôlée côté ObsiGate** :
`/distribuer` n'autorise aujourd'hui que les octrois de type
`consommateur` ; l'isolation workspace repose sur le serveur
Flowdeck lui-même (il ne demande que les références de son
workspace, avec son propre jeton). Un contrôle serveur strict
reste à spécifier si Bruno l'exige.
- **Identités agent-commun** : détenteur libre hors coffre
conservé (historique) ; révocation Identités non propagée vers
ObsiGate (pas d'endpoint) ; exécuteurs CLI synchrones
uniquement.
- **TikTak** : « sélecteur » = saisie validée, pas de liste
déroulante (aucune API de listage côté ObsiGate) ;
configurations existantes à re-saisir, pas de conversion
automatique.
- **Flowdeck** : natifs Gitea/GitHub et clés LLM non basculés ;
pas de sélecteur UI (API seulement) ; `workspace_id` pas encore
branché depuis AgentEngine.
- **Kanbante** : jetons forge chiffrés Fernet non migrés ;
pas de migration automatique des secrets webhook historiques.
- **Recherche / graphe / index** : par construction, un coffre
n'est pas un vault de notes — il ne passe ni par l'indexeur, ni
par la recherche, ni par le graphe, et aucun outil MCP de secret
n'existe. Aucune métadonnée de secret ne doit apparaître dans
ces index (vérification par revue de code, pas de suite
automatisée dédiée — voir le bilan Phase 5).
## 6. Repli (rollback)
- Désactiver le flag (`OBSIGATE_SECRETS_ENABLED` absent ou
`false`) : toutes les routes coffre répondent 503, les fichiers
`coffres.json` / `coffres_valeurs.json` restent intacts et
re-distribueront dès réactivation avec la même KEK.
- Révoquer un consommateur (`DELETE
/api/coffres/consommateurs/{id}`) coupe immédiatement ses
distributions, sans toucher aux autres consommateurs ni aux
secrets.
- Ne jamais « réparer » des fichiers chiffrés avec une nouvelle
KEK, ne jamais restaurer `coffres.json` sans
`coffres_valeurs.json` (et inversement), ne jamais copier la
KEK au même endroit que la sauvegarde.
## 7. Références
- Spec « Coffre de secrets ObsiGate » §6 (autorisations), §7/§10/§11
(API), §13/§14 (sauvegarde/restauration), §15/§16 (tests,
déploiement).
- Issues ObsiGate : #17 (Phase 1a), #18 (Phase 1b), #19 (Phase 1c
UI), #20 (Phase 2), #21 (Phase 3, Spark), #22 (Phase 4, TikTak /
Flowdeck / Kanbante), #23 (Phase 5, ce document).
- Suites de tests : `tests/test_coffre_secrets.py` (ObsiGate, 35
tests), `tests/test_identites_coffre_phase3.py` (agent-commun,
16 tests), `tests/test_coffre_reference.py` (TikTak, 17 fonctions
/ 26 cas), `tests/test_coffre_reference_flowdeck.py` (Flowdeck,
16 tests), `tests/test_coffre_client.py` (Kanbante, 11 fonctions
/ 25 cas). Voir le bilan Phase 5 pour la matrice de couverture
complète et les manques.
+1 -1
View File
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap # ObsiGate — Roadmap
> **Version :** 2.80.2 | **Dernière mise à jour :** 2026-10-27 > **Version :** 2.81.0 | **Dernière mise à jour :** 2026-10-28
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact > **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
> vers les fonctionnalités livrées. > vers les fonctionnalités livrées.
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)** > - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
+2 -2
View File
@@ -59,11 +59,11 @@ function _sharedIcon(size = 16) {
// renommer/déplacer/supprimer). Le menu ⋮ est un menu propre ci-dessous. // renommer/déplacer/supprimer). Le menu ⋮ est un menu propre ci-dessous.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** Icône cadenas (U+1F510) du nœud Secrets, distincte de la maison et du diamant. */ /** Icône coffre-fort du nœud Secrets (SVG Safe Vault Streamline, fourni par Bruno), distincte de la maison et du diamant. Le tracé utilise currentColor pour rester lisible en thème clair comme sombre (le noir d'origine serait invisible en sombre). */
function _secretsIcon() { function _secretsIcon() {
const s = document.createElement("span"); const s = document.createElement("span");
s.className = "coffres-icon-lock"; s.className = "coffres-icon-lock";
s.textContent = "\u{1F510}"; s.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14" aria-hidden="true"><g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"><path d="M12.5 0.5h-11c-0.552285 0 -1 0.447715 -1 1V11c0 0.5523 0.447715 1 1 1h11c0.5523 0 1 -0.4477 1 -1V1.5c0 -0.552285 -0.4477 -1 -1 -1Z"/><path d="M8.5 8c0.9665 0 1.75 -0.7835 1.75 -1.75S9.4665 4.5 8.5 4.5s-1.75 0.7835 -1.75 1.75S7.5335 8 8.5 8Z"/><path d="M8.5 3.25V4.5"/><path d="M8.5 8v1.25"/><path d="M11.5 6.25h-1.25"/><path d="M6.75 6.25H5.5"/><path d="m10.62 4.12988 -0.88001 0.88"/><path d="m7.26 7.48999 -0.88 0.88"/><path d="m10.62 8.36999 -0.88001 -0.88"/><path d="m7.26 5.00988 -0.88 -0.88"/><path d="M3 4.5V8"/><path d="M2 12v1.5"/><path d="M11.5 12v1.5"/></g></svg>';
return s; return s;
} }
+1 -1
View File
@@ -653,7 +653,7 @@ function applyTheme(themeKey, mode) {
root.style.setProperty(key, vars[key]); root.style.setProperty(key, vars[key]);
}); });
// #180 — the spreadsheet grid follows the active theme: derive its tokens // #180 — the spreadsheet grid follows the active theme: derive its tokens
// from the generic surfaces so every theme (15 built-in × 4 modes, custom // from the generic surfaces so every theme (16 built-in × 4 modes, custom
// imports) gets a matching grid instead of the hardcoded dark/light pair. // imports) gets a matching grid instead of the hardcoded dark/light pair.
// color-mix keeps the zebra stripe subtle on both dark and light grounds. // color-mix keeps the zebra stripe subtle on both dark and light grounds.
// #185 — `--grid-border` no longer reuses `--border` (which sits 1-2% off the // #185 — `--grid-border` no longer reuses `--border` (which sits 1-2% off the
+2 -1
View File
@@ -14372,7 +14372,8 @@ body.has-draco-bar .app-container { height: calc(100vh - 60px); }
.coffres-alerts { margin: 12px 0; } .coffres-alerts { margin: 12px 0; }
.coffres-alert { color: var(--warning, #f59e0b); padding: 2px 0; } .coffres-alert { color: var(--warning, #f59e0b); padding: 2px 0; }
.coffres-form { display: grid; gap: 8px; max-width: 560px; margin: 8px 0; } .coffres-form { display: grid; gap: 8px; max-width: 560px; margin: 8px 0; }
.coffres-icon-lock { font-size: 16px; line-height: 1; } .coffres-icon-lock { display: inline-flex; align-items: center; line-height: 1; }
.coffres-icon-lock svg { width: 16px; height: 16px; flex: none; }
.coffres-alert-dot { color: var(--warning, #f59e0b); font-weight: 700; margin-left: 4px; } .coffres-alert-dot { color: var(--warning, #f59e0b); font-weight: 700; margin-left: 4px; }
.coffres-context-menu { position: fixed; z-index: 1000; min-width: 220px; background: var(--bg-secondary, #161b22); border: 1px solid var(--border, #21262d); border-radius: 8px; padding: 4px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); } .coffres-context-menu { position: fixed; z-index: 1000; min-width: 220px; background: var(--bg-secondary, #161b22); border: 1px solid var(--border, #21262d); border-radius: 8px; padding: 4px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); }
.coffres-context-item { display: block; width: 100%; text-align: left; padding: 8px 12px; background: transparent; border: none; color: var(--text-primary, #e6edf3); cursor: pointer; border-radius: 6px; } .coffres-context-item { display: block; width: 100%; text-align: left; padding: 8px 12px; background: transparent; border: none; color: var(--text-primary, #e6edf3); cursor: pointer; border-radius: 6px; }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "obsigate", "name": "obsigate",
"version": "2.80.2", "version": "2.81.0",
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.", "description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
"main": "patch.js", "main": "patch.js",
"directories": { "directories": {
+3 -3
View File
@@ -2,7 +2,7 @@
Validates: Validates:
- Theme JSON structure validity - Theme JSON structure validity
- All 15 built-in themes have required keys and valid CSS colors - All 16 built-in themes have required keys and valid CSS colors
- High-contrast and sepia mode generators produce valid CSS vars - High-contrast and sepia mode generators produce valid CSS vars
- Import/export round-trip - Import/export round-trip
- AVAILABLE_MODES includes all 4 modes - AVAILABLE_MODES includes all 4 modes
@@ -80,9 +80,9 @@ class TestThemeStructure:
assert '_genSepia' in self.content assert '_genSepia' in self.content
def test_theme_count(self): def test_theme_count(self):
"""Should have 15 built-in themes.""" """Should have 16 built-in themes."""
names = _extract_theme_names(self.content) names = _extract_theme_names(self.content)
assert len(names) == 15, f"Expected 15 themes, found {len(names)}: {names}" assert len(names) == 16, f"Expected 16 themes, found {len(names)}: {names}"
def test_required_export_functions(self): def test_required_export_functions(self):
"""All required functions should be exported.""" """All required functions should be exported."""