All checks were successful
PR Check — Rust / rust (pull_request) Successful in 9m23s
The removal trigger #312 was written for had already fired — I filed the issue without checking whether a newer plist existed. plist 1.10.0 ships quick-xml 0.41.0, which carries the fix, within tauri's existing bound: cargo update -p plist -> plist 1.8.0 -> 1.10.0 quick-xml 0.38.4 -> 0.41.0 So RUSTSEC-2026-0194 and -0195 are resolved rather than accepted, and leave .cargo/audit.toml the day they entered it. rsa is now the only entry, and the guard loops on that crate alone; its rationale comment is re-pointed accordingly, since it was written entirely around quick-xml/plist. Also bumps the two yanked crates (#313). tauri-plugin-deep-link 2.4.8 -> 2.4.9: upstream's 2.4.9 is a single commit, "Fix broken iOS custom URL schemes", so the defect behind the yank is iOS-only and never reached this desktop app — v0.14.0 shipping 2.4.8 was not a user-facing problem, which is why neither Security nor Fixed applies to it in the changelog. spin 0.9.8 -> 0.9.9; every 0.9.x up to 0.9.8 is yanked, which reads as a bulk yank rather than a defect. The #310 changelog bullet is amended rather than contradicted: it sits in the same unreleased section and would otherwise ship two opposing claims in the same release notes. Two of its statements were wrong. It said three advisories remained (now one), and it said tar sits on "real code paths in the shipped app" — tar is compiled, but its vulnerable extraction path is only reached by the AppImage and macOS installers this project does not bundle. The rustls-webpki half stands: TLS runs on every update check. ADR 0018's decision is untouched; an amendment header marks the passages that are now historical, including the "override is impossible" alternative, which plist 1.10.0 made false the same day. cargo audit from the repo root: 0 vulnerabilities, warnings 23 -> 21 (the two yanked ones). From src-tauri/ it reports 1 — that is the cwd sensitivity of .cargo/audit.toml, not a regression. Guard: 2 checks + canary, exit 0. cargo check + cargo test green (106 tests). Lock diff: 4 packages, 666 before and after. Resolves #312 Resolves #313 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
11 KiB
Markdown
94 lines
11 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`)
|
||
|
||
> **Amendement du 2026-07-27 (#312/#313)** — la décision et ses trois règles sont inchangées ; seules les entrées ont bougé. Les deux advisories `quick-xml` ont **quitté la liste le jour même** : `plist` 1.10.0 tire `quick-xml` 0.41.0, qui porte le correctif, dans la borne existante de `tauri`. Elles sont donc **résolues, pas acceptées**, et le mécanisme a fonctionné comme prévu — la vérification par cible a montré qu'un correctif était devenu atteignable, et l'entrée est partie. Il ne reste que `RUSTSEC-2023-0071` (`rsa`), et le garde-fou ne boucle plus que sur ce crate.
|
||
>
|
||
> Trois passages ci-dessous sont **datés du 2026-07-27 avant ce bump** et à lire comme historiques : `quick-xml` comme exemple de l'insuffisance de `--target all` (règle 1), l'entrée `quick-xml` du tableau de contexte, et l'alternative « override / `[patch.crates-io]` » — cette dernière **n'est plus vraie**, `plist` 1.10.0 déclarant désormais `quick-xml ^0.41.0`. L'argument de fond qu'elle illustre (`[patch.crates-io]` ne franchit pas une frontière semver-incompatible) reste correct, seul le cas d'espèce a disparu.
|
||
|
||
## 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 l'a joué pour `quick-xml` (et a servi : le déclencheur avait sauté sans que personne le remarque), #319 le fait pour `rsa`. **Toute entrée de la liste doit en avoir une**, sinon le garde-fou ne couvre qu'une moitié du risque.
|
||
|
||
## 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. *(Périmé le jour même — `plist` 1.10.0 déclare `quick-xml ^0.41.0`, et un simple `cargo update -p plist` a suffi ; voir l'amendement en tête. Le principe reste, le cas d'espèce a disparu.)*
|
||
- **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`, **fait le 2026-07-27**), #313 (`tauri-plugin-deep-link` yanked, fait), #314 (cron jamais déclenché), #315 (smoke-test du cycle de mise à jour), #319 (déclencheur de retrait `rsa`, la dernière entrée restante)
|