From 7dfe26c83de6408d57290f99227c3bbfb67e81a5 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Sun, 27 Sep 2026 09:37:21 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20CI=20security=20=E2=80=94=20echo=20pip-a?= =?UTF-8?q?udit=20sans=20di=C3=A8se=20(runner=20Act)=20BUG-083?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/ci.yml | 5 ++- CHANGELOG.md | 17 ++++++++- README.fr.md | 6 +-- README.md | 6 +-- VERSION | 2 +- desktop/Cargo.lock | 2 +- desktop/Cargo.toml | 2 +- desktop/tauri.conf.json | 2 +- docs/ISSUES_TODOLIST.md | 2 + docs/ROADMAP.md | 2 +- package.json | 2 +- tests/test_ci_workflow.py | 78 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 tests/test_ci_workflow.py diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 89a2200..fed36b6 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -142,7 +142,10 @@ jobs: # Reste non bloquant tant que les montées de version requises # (starlette via fastapi, weasyprint) ne sont pas qualifiées : # upgrade FastAPI = chantier de régression dédié, hors périmètre. - run: pip-audit || echo "pip-audit found vulnerabilities (non-blocking, see #87)" + # NOTE runner Gitea Act (BUG-083) : aucun `#` dans le `run:` + # ci-dessous (tronqué au premier `#`, même entre guillemets, ce qui + # cassait la citation de l'echo) — la réf #87 ne vit qu'ici. + run: pip-audit || echo "pip-audit found vulnerabilities (non-blocking)" # ── Docker build ────────────────────────────────────────────────── build: diff --git a/CHANGELOG.md b/CHANGELOG.md index fa5227a..8682c15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). > **En cours de développement** : les changements à venir sont listés dans la section -> [Unreleased](#unreleased). La dernière version livrée est **2.28.9**. +> [Unreleased](#unreleased). La dernière version livrée est **2.28.10**. --- @@ -14,6 +14,21 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [2.28.10] — 2026-09-27 + +### Corrigé + +- **BUG-083 — job CI `security` rouge : le runner tronquait le `#` du `run:` pip-audit.** + Le runner Gitea Act coupe naïvement au premier `#` (même entre + guillemets) : `echo "... see #87)"` devenait une citation non fermée + (`unexpected EOF while looking for matching '"'"`). Seul `run:` du + workflow avec un `#` ; l'echo n'a plus de `#` (réf `#87` en commentaire + YAML, jamais vu par le shell). Garde-fou : `tests/test_ci_workflow.py` + (aucun `#` dans le code des `run:`, `upload.test.mjs` verrouillé dans + l'étape JSDOM — BUG-082). + +--- + ## [2.28.9] — 2026-09-27 ### Corrigé diff --git a/README.fr.md b/README.fr.md index c2af4b5..6c81b22 100644 --- a/README.fr.md +++ b/README.fr.md @@ -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. -[![Version](https://img.shields.io/badge/Version-2.28.9-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.28.10-blue.svg)]() [![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/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -976,8 +976,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l ## 📝 Changelog -Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.28.9). +Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.28.10). --- -*Projet : ObsiGate | Version : 2.28.9 | Dernière mise à jour : Septembre 2026* +*Projet : ObsiGate | Version : 2.28.10 | Dernière mise à jour : Septembre 2026* diff --git a/README.md b/README.md index b0098e8..b4bf35a 100644 --- a/README.md +++ b/README.md @@ -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. -[![Version](https://img.shields.io/badge/Version-2.28.9-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.28.10-blue.svg)]() [![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/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -1151,8 +1151,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ## 📝 Changelog -See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.28.9). +See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.28.10). --- -*Project: ObsiGate | Version: 2.28.9 | Last updated: September 2026* +*Project: ObsiGate | Version: 2.28.10 | Last updated: September 2026* diff --git a/VERSION b/VERSION index 187eac9..92b28c1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.28.9 +2.28.10 diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index a839c55..937a76b 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "obsigate-desktop" -version = "2.28.9" +version = "2.28.10" dependencies = [ "chrono", "env_logger", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 4d8dc7b..e15bacb 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsigate-desktop" -version = "2.28.9" +version = "2.28.10" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" authors = ["Bruno Charest"] edition = "2021" diff --git a/desktop/tauri.conf.json b/desktop/tauri.conf.json index fe915a9..6e04341 100644 --- a/desktop/tauri.conf.json +++ b/desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "productName": "ObsiGate", - "version": "2.28.9", + "version": "2.28.10", "identifier": "com.obsigate.desktop", "build": { "frontendDist": "../frontend", diff --git a/docs/ISSUES_TODOLIST.md b/docs/ISSUES_TODOLIST.md index fda285a..35ffc80 100644 --- a/docs/ISSUES_TODOLIST.md +++ b/docs/ISSUES_TODOLIST.md @@ -188,6 +188,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | *BUG-076* | [🟡 IMPORTANT] Assistant IA : après une action de l'agent, l'arborescence et le document ouvert ne sont pas rafraîchis dynamiquement | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/bookslm.js` | Mode agent : créer/supprimer un fichier ou dossier, modifier le document ouvert → l'UI ne bouge pas | `frontend/js/bookslm.js` : `MUTATING_TOOLS`/`FILE_WRITE_TOOLS`, refresh d'arborescence débouncé sur event `tool`, `_notifyFileWritten` étendu (xlsx/docx/csv/pdf). Tests : `tests/frontend/ai.test.mjs`, `tests/frontend/editor-inline.test.mjs` | Aucun refresh explicite sur les événements `tool` mutateurs (repose uniquement sur le watcher SSE) ; `_notifyFileWritten` ignore les créations de documents (xlsx/docx/csv/pdf) | | *BUG-077* | [🟡 IMPORTANT] Assistant IA : aucun bouton « Stop » pour arrêter l'exécution de l'agent à tout moment | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/bookslm.js` | Mode agent : lancer une longue tâche → le bouton Envoyer est désactivé, impossible d'arrêter (seule la fermeture du panneau abort) | `frontend/js/bookslm.js` + `frontend/style.css` : bouton d'envoi → Stop (`_syncSendButton`/`_stopGeneration`/`_markStopped`), i18n `ai.stop`/`ai.stopped`. Tests : `tests/frontend/ai.test.mjs` (+2) | `_abortCtrl` n'est déclenché que par `close()` ; aucun signal d'arrêt côté client pendant le stream | | *BUG-078* | [🟡 IMPORTANT] Fichiers de code : la coloration syntaxique (highlight.js) disparaît — les feuilles de thème sont basculées à partir de la **clé** de thème au lieu du **mode** | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/themes.js`, `frontend/js/ui.js`, `tests/frontend/unit.test.mjs` | Ouvrir un fichier `.py`/`.sh`/`.ps1`/`.yml` : le code s'affiche en texte brut, sans couleurs | `frontend/js/themes.js` : `applyTheme` bascule `hljs-theme-dark`/`hljs-theme-light` selon le **mode** (`isDark`). `frontend/js/ui.js` : `initTheme`/`applyTheme` résolvent le mode persisté (`obsigate-theme-mode`) au lieu de traiter la clé (`defaut-obsigate`) comme un mode. Test : `unit.test.mjs` (+1). | Les deux feuilles étaient désactivées car `defaut-obsigate !== "dark"` et `!== "light"` ; résultat **non déterministe** selon l'ordre `UI.initTheme()` (clé) / `Sync.init()` → `themes.initThemes()` (mode). Vérifié Playwright : 5/5 chargements colorés (`.py`), sépia/contraste élevé sur la palette claire | +| *BUG-083* | Job CI `security` rouge : le runner Gitea Act tronque le script `pip-audit` au premier `#` (citation de l'echo non fermée → `unexpected EOF while looking for matching '"'`) | 🟢 corrigé | P0 | 📦 build | IA | `.gitea/workflows/ci.yml`, `tests/test_ci_workflow.py` (nouveau) | CI job `security`, étape `Pip-audit` | `run:` assaini (echo sans `#`, réf `#87` en commentaire YAML) ; `tests/test_ci_workflow.py` (2 tests : aucun `#` dans le code des `run:`, `upload.test.mjs` verrouillé en étape JSDOM) ; vérifié : 56 passed (ci_workflow + e2e_harness + version), contre-preuve OK sur l'ancien `ci.yml` | Seul `run:` du workflow contenant un `#` (`see #87` dans l'echo). Les `#` des noms d'étapes (Bandit, Npm audit) sont inoffensifs (ces étapes passent). Correctif : echo sans `#`, réf `#87` en commentaire YAML | | *BUG-082* | CI `lint` rouge : `tests/frontend/upload.test.mjs` (import statique `jsdom`) exécuté dans l'étape racine où `jsdom` n'est jamais installé | 🟢 corrigé | P0 | 🧩 tests | IA | `.gitea/workflows/ci.yml`, `tests/frontend/upload.test.mjs` | CI job `lint` → `ERR_MODULE_NOT_FOUND: jsdom` (introduit par `7bee4a2`, jamais vert depuis) | `upload.test.mjs` déplacé dans l'étape JSDOM (les deux branches) ; vérifié : étape racine verte + `upload` vert depuis `tests/frontend/` | Seul fichier de l'étape racine avec import statique jsdom ; `jsdom` ne vit que dans `tests/frontend/node_modules` (installé par l'étape JSDOM). Correctif : déplacer `upload.test.mjs` dans l'étape JSDOM | | *BUG-081* | `GET /api/auth/mfa/status` → 500 quand l'auth est désactivée (`user` None, `AttributeError` sur `user.get`) | 🔴 ouvert | P1 | ⚙️ backend | IA | `backend/auth/router.py::mfa_status` (l.821-831) | Auth désactivée : `curl http://127.0.0.1:2029/api/auth/mfa/status` → 500 (reproduit live 2026-09-27, `e2e-server.err.log` l.116-183) | — | `require_auth` laisse passer le pseudo-user anonymous, `get_user(username)` → None non gardé. Trouvé via les logs E2E pendant BUG-080 | | *BUG-080* | [🔴 BLOQUANT] E2E locaux bloqués toute la nuit : `npm run test:e2e:ps` ne termine jamais (serveurs orphelins sur le port 2029, `npx playwright install` sans `--yes` ni garde-fou, suite ~130 tests sans timeout global) | 🟢 corrigé | P0 | 🧩 tests | IA | `scripts/run-e2e-local.ps1`, `scripts/run-e2e-local.sh`, `scripts/e2e-server.ps1`, `playwright.config.ts`, `tests/test_e2e_harness.py` (nouveau) | `npm run test:e2e:ps` | `run-e2e-local` : `npx --yes`, skip install Chromium si présent (`E2E_INSTALL_BROWSERS=1`), timeouts `E2E_TIMEOUT_SEC` (900)/`E2E_BROWSER_INSTALL_TIMEOUT_SEC` (600, exit 124) ; `playwright.config.ts` : `globalTimeout` 15 min local / 30 min CI (`E2E_GLOBAL_TIMEOUT_MS`) ; `e2e-server.ps1` : pidfile = vrai owner du port, `stop` tue l'arbre complet. Tests : `tests/test_e2e_harness.py` (8/8), cycle start/stop live (pidfile cohérent, port libéré) | Constat 2026-09-27 : `e2e-server.ps1 start` OK (READY 12 s) mais run suivant pendu toute la nuit ; 2 python orphelins (PID 81180 parent + 81936 sur le port, pidfile périmé). Double processus systématique (parent `.venv` parqué + enfant qui sert — aussi sur flowdeck/3.13 : environnemental, sans impact après correctif). Trouvé au passage : BUG-081 (`/api/auth/mfa/status` → 500 auth désactivée) | @@ -278,6 +279,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | 2026-09-24 | BUG-079 | Correction | `backend/main.py`, `tests/test_api_main.py`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-079** : `GET /api/diagnostics` renvoyait 500 « dictionary changed size during iteration ». Le handler itérait `inv.word_index.values()` et `index.items()` en direct alors que l'indexeur les modifiait depuis un autre thread (rebuild initial dans `_search_executor`, hooks incrémentaux `add_document`/`remove_document`) → `RuntimeError` dans le générateur. Correctif : **snapshot avant itération** (`list(index.items())`, `inv.word_index.copy()`) — copie C atomique sous le GIL, pas de verrou ajouté. Test de non-régression déterministe (`RaceDict` fait grossir le dict pendant l'itération ; échoue sans le correctif, passe avec). Vérifié : pytest 1305 passed / 6 skipped, ruff 0, mypy 0 (80 fichiers), validate-imports 40 modules, unit 12/12. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-27 | BUG-080, BUG-081 | Correction + enregistrement | `scripts/run-e2e-local.ps1`, `scripts/run-e2e-local.sh`, `scripts/e2e-server.ps1`, `playwright.config.ts`, `tests/test_e2e_harness.py` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-080** : run E2E local pendu toute la nuit → harnais anti-blocage : `npx --yes` (plus de prompt interactif), install Chromium sautée si présent (`E2E_INSTALL_BROWSERS=1`), timeouts `E2E_TIMEOUT_SEC` (900)/`E2E_BROWSER_INSTALL_TIMEOUT_SEC` (600, exit 124), `globalTimeout` Playwright (15 min local / 30 min CI, `E2E_GLOBAL_TIMEOUT_MS`), pidfile resynchronisé sur le vrai owner du port + `stop` qui tue l'arbre complet (orphelins 81180/81936 nettoyés, port 2029 libéré). Diagnostic : double processus systématique (parent `.venv` parqué + enfant qui sert — environnemental, aussi sur flowdeck/3.13). **BUG-081** (ouvert, non traité) : `GET /api/auth/mfa/status` → 500 auth désactivée (`user` None, `router.py:827`, reproduit live). Vérifié : `test_e2e_harness.py` 8/8, cycle start/stop live (pidfile cohérent, port libéré). | 🟢 corrigé (en attente vérif utilisateur) ; BUG-081 🔴 ouvert | | 2026-09-27 | BUG-082 | Correction CI | `.gitea/workflows/ci.yml`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-082** : `lint` rouge (`ERR_MODULE_NOT_FOUND: jsdom` sur `upload.test.mjs`, rouge depuis `7bee4a2`) — seul fichier de l'étape frontend racine avec import statique `jsdom`, alors que `jsdom` n'est installé que dans `tests/frontend/node_modules` (étape JSDOM). `upload.test.mjs` déplacé dans l'étape JSDOM (les deux branches). Vérifié : étape racine verte (validate-imports, unit, pretty, media-viewer, mfa-settings, config-ai-keys) + `upload` vert depuis `tests/frontend/`. | 🟢 corrigé (en attente vérif utilisateur) | +| 2026-09-27 | BUG-083 | Correction CI | `.gitea/workflows/ci.yml`, `tests/test_ci_workflow.py` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-083** : job `security` rouge — le runner Gitea Act tronque naïvement au premier `#` (même entre guillemets) : `echo "... see #87)"` devenait une citation non fermée (`unexpected EOF while looking for matching '"'"`, `/var/run/act/workflow/4` ligne 2). Seul `run:` du workflow avec un `#` (les `#` des noms d'étapes Bandit/Npm audit sont inoffensifs, ces étapes passent). Correctif : echo sans `#` (réf `#87` en commentaire YAML). Garde-fou `test_ci_workflow.py` (aucun `#` dans le code des `run:`, `upload.test.mjs` verrouillé en étape JSDOM — BUG-082) + contre-preuve sur l'ancien `ci.yml`. Vérifié : 56 passed. | 🟢 corrigé (en attente vérif utilisateur) | --- diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 8b21034..ac62e9a 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # ObsiGate — Roadmap -> **Version :** 2.28.9 | **Dernière mise à jour :** 2026-09-27 +> **Version :** 2.28.10 | **Dernière mise à jour :** 2026-09-27 > **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact > vers les fonctionnalités livrées. > - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)** diff --git a/package.json b/package.json index 83087ca..9db127e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsigate", - "version": "2.28.9", + "version": "2.28.10", "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", "directories": { diff --git a/tests/test_ci_workflow.py b/tests/test_ci_workflow.py new file mode 100644 index 0000000..c6622f2 --- /dev/null +++ b/tests/test_ci_workflow.py @@ -0,0 +1,78 @@ +"""Garde-fous du workflow CI Gitea (BUG-082, BUG-083). + +Sans dépendance (pas de PyYAML) : analyse ligne à ligne de +`.gitea/workflows/ci.yml`, suffisante pour les conventions de ce fichier. +""" +from __future__ import annotations + +import re +from pathlib import Path + +CI_YML = Path(__file__).resolve().parent.parent / ".gitea" / "workflows" / "ci.yml" + + +def _run_bodies() -> list[tuple[int, str]]: + """Toutes les lignes shell de chaque bloc `run:` → [(n° ligne, code)].""" + lines = CI_YML.read_text(encoding="utf-8").splitlines() + bodies: list[tuple[int, str]] = [] + i = 0 + while i < len(lines): + m = re.match(r"^(\s*)run:(?:\s*\|\s*)?$", lines[i]) + inline = re.match(r"^(\s*)run:\s+(\S.*)$", lines[i]) + if m: + base = len(m.group(1)) + i += 1 + while i < len(lines): + cur = lines[i] + if not cur.strip(): + i += 1 + continue + if len(cur) - len(cur.lstrip()) <= base: + break + bodies.append((i + 1, cur.strip())) + i += 1 + elif inline: + bodies.append((i + 1, inline.group(2).strip())) + i += 1 + else: + i += 1 + return bodies + + +class TestRunnerProofScripts: + def test_no_hash_inside_run_bodies(self): + """BUG-083 : aucun `#` dans le code shell des `run:`. + + Le runner Gitea Act tronque naïvement au premier `#` (même entre + guillemets) : `echo "... see #87)"` devenait une citation non + fermée → `unexpected EOF while looking for matching '"'` (job + `security` rouge). Les lignes-commentaires shell (`# ...`) restent + autorisées : leur troncature est sémantiquement neutre. + """ + offenders = [ + f"L{n}: {code}" + for n, code in _run_bodies() + if not code.startswith("#") and "#" in code + ] + assert not offenders, ( + "BUG-083 : `#` interdit dans le code des `run:` " + f"(tronqué par le runner) :\n" + "\n".join(offenders) + ) + + +class TestUploadInJsdomStep: + def test_upload_runs_with_jsdom_available(self): + """BUG-082 : `upload.test.mjs` (import statique `jsdom`) ne tourne + que dans l'étape JSDOM, où `tests/frontend/node_modules` existe.""" + text = CI_YML.read_text(encoding="utf-8") + assert "Frontend JSDOM tests" in text + root_part, jsdom_part = text.split("Frontend JSDOM tests", 1) + root_steps = root_part.split("Frontend unit tests", 1)[1] + assert "upload.test" not in root_steps, ( + "BUG-082 : `upload.test.mjs` ne doit pas tourner dans l'étape " + "racine (jsdom indisponible)" + ) + assert jsdom_part.count("node upload.test.mjs") >= 2, ( + "BUG-082 : `node upload.test.mjs` attendu dans les deux branches " + "de l'étape JSDOM" + )