cargo update -p rustls-webpki -p tar moves rustls-webpki 0.103.9 -> 0.103.13 and tar 0.4.44 -> 0.4.46, both within the existing Cargo.toml bounds. They sit under tauri-plugin-updater, which downloads and unpacks application updates, so all six of their advisories were reachable in the shipped binary. The remaining three can neither be fixed nor reached. quick-xml (2x 7.5 high) is pulled by plist, which tauri only needs for Apple bundling: its per-target trees are empty for both shipped targets and it appears solely under x86_64-apple-darwin. Its fix is >= 0.41.0 while plist requires ^0.38, a semver-incompatible boundary [patch.crates-io] cannot cross. rsa has no published fix at all and is never compiled — its only parent is sqlx-mysql, an artifact of sqlx's multi-backend graph on a SQLite project. Leaving those three to red the daily gate forever would reproduce the signal loss that #232 removed the `|| true` to fix, so they move into a versioned .cargo/audit.toml. Entries are keyed by advisory ID, never by crate, so a new advisory against the same crate still reds the gate; each carries its reachability proof and its removal condition. A blocking step in check-rust.yml re-proves that justification on every PR touching src-tauri/ or .cargo/, and fails if a suppressed crate enters a shipped target's graph — the scenario that would rot the list is itself a src-tauri change. It separates cargo tree's exit status from its output (an absent crate and a failed invocation both print nothing) and asserts a canary crate is still found, so its silence proves something. cargo audit: 9 vulnerabilities -> 0, warnings unchanged at 23 (cargo-audit 0.22.2, advisory-db 0bfde9d6 of 2026-07-27). cargo check + cargo test green (106 tests); npm build + 871 vitest green. Resolves #310 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
90 lines
9 KiB
Markdown
90 lines
9 KiB
Markdown
# ADR 0018 — Suppression d'advisories RustSec non atteignables : liste par ID, preuve par cible livrée, garde-fou anti-péremption
|
||
|
||
- Status: **Accepted**
|
||
- Date: 2026-07-27
|
||
- Issues: #310 (les 9 advisories découvertes, cette décision), #312 (déclencheur de retrait des entrées `quick-xml`), #314 (le cron de `audit.yml` n'a jamais démarré)
|
||
- S'appuie sur #232 (split de `check.yml`, création de `audit.yml`, retrait du `|| true` qui masquait la sortie de `cargo audit`)
|
||
|
||
## Contexte
|
||
|
||
#232 a retiré le `|| true` qui avalait la sortie de `cargo audit` et créé `audit.yml`, un job quotidien (06:00 UTC) **volontairement bloquant** : il *est* le canal de notification pour les advisories publiées entre deux PR Rust, `check-rust.yml` ne tournant que sur ~1 PR sur 40.
|
||
|
||
Le retrait du masque a découvert 9 advisories RustSec préexistantes dans `src-tauri/Cargo.lock`. Six sont atteignables et corrigeables par un simple `cargo update` (`rustls-webpki` ×4, `tar` ×2, tous sur le chemin de `tauri-plugin-updater`). Les trois restantes ne le sont pas :
|
||
|
||
| Crate | Advisories | Situation |
|
||
|---|---|---|
|
||
| `quick-xml` 0.38.4 | RUSTSEC-2026-0194, RUSTSEC-2026-0195 (7.5 high) | Tiré par `plist`, dont `tauri` ne dépend que pour le bundling Apple. Absent des arbres `x86_64-unknown-linux-gnu` et `x86_64-pc-windows-msvc` ; présent seulement sur `x86_64-apple-darwin`, cible que le projet ne livre pas. Corrigé en `>= 0.41.0` alors que `plist` exige `^0.38` — frontière semver-incompatible que `[patch.crates-io]` ne peut pas franchir. |
|
||
| `rsa` 0.9.10 | RUSTSEC-2023-0071 (Marvin, 5.9 medium) | Aucun correctif publié (liste `patched` vide). Seul parent dans le lock : `sqlx-mysql`, artefact du graphe multi-backend de sqlx ; le projet parle à SQLite via `tauri-plugin-sql`. `cargo tree -i rsa --target all` ne retourne rien. |
|
||
|
||
Le problème n'est donc pas de corriger ces trois advisories — c'est impossible — mais de décider ce que devient le gate quotidien. Le laisser rouge en permanence reproduit exactement le défaut que #232 venait de supprimer : une alarme qui sonne tous les jours sans qu'on puisse rien y faire finit ignorée, et la prochaine advisory réelle se noie dedans. C'est la même perte de signal que le `|| true`, obtenue par un autre chemin.
|
||
|
||
## Décision
|
||
|
||
**Une liste de suppressions par ID d'advisory, dans un `.cargo/audit.toml` versionné à la racine du dépôt**, encadrée par trois règles.
|
||
|
||
### Règle 1 — critère d'admission
|
||
|
||
Une advisory ne peut être listée que si son crate est absent du graphe de dépendances de **toutes** les cibles livrées (Windows et Linux), **ou** si aucun correctif n'a été publié. Une advisory atteignable dont le correctif existe se corrige, elle ne se supprime jamais.
|
||
|
||
La preuve est mécanique et reproductible, et doit être refaite contre le lock courant :
|
||
|
||
```
|
||
cargo tree --manifest-path src-tauri/Cargo.toml -i <crate> --target x86_64-unknown-linux-gnu
|
||
cargo tree --manifest-path src-tauri/Cargo.toml -i <crate> --target x86_64-pc-windows-msvc
|
||
```
|
||
|
||
Sortie vide sur les deux cibles = entrée justifiée. `--target all` ne suffit pas : il répond « présent » pour des crates conditionnels Apple qui ne sont compilés nulle part chez nous, ce qui est précisément le cas de `quick-xml`.
|
||
|
||
### Règle 2 — clé par ID, jamais par crate
|
||
|
||
Les entrées portent un ID d'advisory (`RUSTSEC-YYYY-NNNN`), jamais un nom de crate. Une nouvelle advisory déposée contre un crate déjà listé **repasse le gate au rouge**, volontairement : elle est examinée pour elle-même. C'est ce qui distingue une suppression justifiée d'un silence permanent, et un élargissement au crate entier suffirait à annuler le bénéfice du gate.
|
||
|
||
### Règle 3 — garde-fou anti-péremption
|
||
|
||
L'atteignabilité est une propriété du graphe **résolu aujourd'hui**, pas une propriété permanente. Un bump `tauri`/`plist` rendant `quick-xml` inconditionnel laisserait la suppression cacher une advisory devenue vivante, et l'audit resterait vert : l'inverse exact du rouge permanent que cette décision cherche à éviter.
|
||
|
||
`check-rust.yml` porte donc une étape bloquante qui rejoue la règle 1 pour chaque crate supprimé, sur les deux cibles livrées, et échoue si l'un d'eux entre dans un arbre. Elle est placée après `cargo check` (index de registre déjà chaud) et passe `--locked` pour que `cargo tree` ne réécrive pas le lock contre lequel l'audit a été pris. Le scénario qui périmerait la justification est lui-même une modification de `src-tauri/`, soit exactement ce qui déclenche ce workflow.
|
||
|
||
Trois détails la rendent fiable plutôt que décorative :
|
||
|
||
- le code de sortie de `cargo tree` est testé séparément de sa sortie standard — un crate absent sort en 0 avec un stdout vide, tandis qu'un échec de `cargo tree` sort en non-zéro avec un stdout vide lui aussi ; sans cette distinction, une panne de l'outil se lirait comme une preuve d'absence ;
|
||
- un **canari** (`tar`, dépendance réellement présente) doit être trouvé à chaque exécution, faute de quoi le silence de la boucle ne prouve rien ;
|
||
- `.cargo/**` est ajouté aux `paths` de `check-rust.yml` pour qu'une PR ne touchant que la liste déclenche bien la vérification.
|
||
|
||
Le pendant — une entrée devenue *inutile*, que rien ne signale — est couvert par une issue de suivi portant la condition de retrait (#312 pour `quick-xml`).
|
||
|
||
## Alternatives considérées
|
||
|
||
- **Laisser `audit.yml` rouge en permanence.** Rejeté : c'est la perte de signal que #232 venait de corriger, obtenue autrement. Une alarme toujours rouge n'est plus une alarme.
|
||
- **Remettre `continue-on-error` ou `|| true` sur le job quotidien.** Rejeté pour la même raison, en pire : cela supprime le signal pour *toutes* les advisories, pas seulement pour les trois inévitables.
|
||
- **Dupliquer des `--ignore` dans `audit.yml` et `check-rust.yml`.** Rejeté : deux listes à maintenir en phase, sans endroit naturel où écrire la justification. Le fichier `.cargo/audit.toml` est lu par les deux workflows sans qu'aucun n'ait à le savoir, et il porte les preuves à côté des entrées.
|
||
- **Ignorer au niveau du crate plutôt que de l'advisory.** Rejeté — voir la règle 2.
|
||
- **Un override / `[patch.crates-io]` pour `quick-xml`.** Impossible : le correctif est en `0.41.0` et `plist` exige `^0.38`. `[patch.crates-io]` ne franchit pas une frontière semver-incompatible.
|
||
- **Retirer `sqlx-mysql` du graphe pour éliminer `rsa`.** Écarté : `rsa` n'étant compilé sur aucune cible, l'opération serait un contorsionnement du manifeste pour un gain nul.
|
||
|
||
## Conséquences
|
||
|
||
### Positives
|
||
|
||
- `audit.yml` redevient un signal exploitable : rouge veut dire « quelque chose de nouveau et d'actionnable », et non « les trois mêmes advisories qu'hier ».
|
||
- Les six advisories réellement atteignables sont corrigées, sans toucher `Cargo.toml` (`rustls-webpki` 0.103.9 → 0.103.13, `tar` 0.4.44 → 0.4.46, dans les bornes existantes).
|
||
- La justification de chaque suppression vit à côté de l'entrée, avec les commandes pour la rejouer — un lecteur futur n'a pas à re-dériver l'analyse ni à re-litiguer la branche « override ».
|
||
|
||
### Négatives
|
||
|
||
- **Un audit vert ne signifie plus « zéro advisory »**, mais « zéro advisory hors de la liste ». Le contrat du gate a changé et doit être lu comme tel — d'où la mention dans `audit.yml`, dans `docs/architecture.md` et dans `CLAUDE.md`.
|
||
- La liste et la boucle du garde-fou sont **couplées à la main** : ajouter une entrée dans `.cargo/audit.toml` sans ajouter son crate dans `check-rust.yml` laisse la nouvelle entrée sans couverture. Les deux fichiers portent le rappel réciproque, mais rien ne l'impose mécaniquement.
|
||
- Le garde-fou ne tourne que sur les PR touchant `src-tauri/` ou `.cargo/`. C'est le bon déclencheur pour le scénario visé, mais ce n'est pas une vérification continue.
|
||
|
||
### Neutre
|
||
|
||
- Les 23 `warning` (bindings gtk-rs GTK3 non maintenus, `fxhash`, crates *yanked*) ne sont pas concernés : ils ne comptent pas dans le code de sortie de `cargo audit` et ne sont donc pas ce qui rend le gate vert ou rouge. Le crate *yanked* `tauri-plugin-deep-link 2.4.8`, dépendance directe, est traité séparément en #313.
|
||
- Un `workflow_dispatch` vert prouve que la commande sort en 0, **pas** que l'alarme quotidienne existe : au moment de cette décision, `audit.yml` n'a encore jamais été déclenché par son `schedule` (#314).
|
||
|
||
## Liens
|
||
|
||
- [`.cargo/audit.toml`](../../.cargo/audit.toml) — la liste, ses preuves et ses critères de retrait
|
||
- [`.forgejo/workflows/check-rust.yml`](../../.forgejo/workflows/check-rust.yml) — étape « Verify suppressed advisories are still unreachable »
|
||
- [`.forgejo/workflows/audit.yml`](../../.forgejo/workflows/audit.yml) — le gate quotidien bloquant
|
||
- [ADR 0009](0009-proxy-price-fetching-via-maximus-api.md) — récupération de cours via `reqwest`, l'un des deux chemins qui rendent `rustls-webpki` atteignable
|
||
- Issues #310 (cette décision), #312 (retrait `quick-xml`), #313 (`tauri-plugin-deep-link` yanked), #314 (cron jamais déclenché), #315 (smoke-test du cycle de mise à jour)
|