From bef2dbe9886baba2e6b27fa361995c563e47ee0a Mon Sep 17 00:00:00 2001 From: flemming-it Date: Tue, 21 Jul 2026 13:23:58 +0200 Subject: [PATCH] fix(store,doctor): doc-verifier findings on the trust surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The doc-verifier pass over the new trust/exposure surfaces came back PASS with five improvements, all applied: - the store policy notice gains a 'Learn more' into the security doc (the notice named security.require_signatures but not where it lives) - 'blocked' disables the trust gate's install button — an active button contradicted the 'install would be refused' statement right above it (guard test added) - the unknown-exposure tooltip now says what the operator can do (check where the name resolves) - dead l10n key verifPillUnverified removed (unverified is the page-level notice, never a card pill) - stale header comment in install_verification.dart corrected Signed-off-by: flemming-it --- lib/data/install_verification.dart | 5 +++-- lib/l10n/app_de.arb | 3 +-- lib/l10n/app_en.arb | 3 +-- lib/l10n/app_localizations.dart | 8 +------- lib/l10n/app_localizations_de.dart | 5 +---- lib/l10n/app_localizations_en.dart | 5 +---- lib/pages/store.dart | 7 +++++++ lib/widgets/chain_install_confirm.dart | 8 +++++++- test/install_verification_test.dart | 12 ++++++++++++ 9 files changed, 34 insertions(+), 22 deletions(-) diff --git a/lib/data/install_verification.dart b/lib/data/install_verification.dart index e3bc4b9..6e4a934 100644 --- a/lib/data/install_verification.dart +++ b/lib/data/install_verification.dart @@ -2,8 +2,9 @@ // per-entry statement of how an install would be verified under // the CURRENT policy (computed hub-side with the same resolvers // the install gate enforces). One mapping, used by the trust-gate -// dialog, the module detail sheet and the store card, so every -// surface tells the same story. +// dialog and the store card's blocked pill, so every surface +// tells the same story. (The module detail sheet routes installs +// through the dialog and needs no copy of its own.) import '../l10n/app_localizations.dart'; diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 89dcfb9..8543a2f 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1194,7 +1194,7 @@ "svcExposurePublic": "öffentlich erreichbar", "svcExposureUnknown": "Reichweite unbekannt", "svcExposurePublicHint": "Dieser Dienst-Endpunkt liegt auf einer öffentlichen Adresse — prüfen Sie, ob er abgesichert ist (TLS, Auth, Firewall).", - "svcExposureUnknownHint": "Hostname statt IP-Adresse — der Hub löst zur Einstufung bewusst keine Namen auf.", + "svcExposureUnknownHint": "Hostname statt IP-Adresse — der Hub löst zur Einstufung bewusst keine Namen auf. Prüfen Sie selbst, wohin der Name zeigt.", "doctorLinkStore": "Store öffnen", "doctorLinkApprovals": "Freigaben öffnen", "doctorLinkAudit": "Protokoll öffnen", @@ -1629,7 +1629,6 @@ "installConfirmTrustTitle": "Vertrauen & Sicherheit", "installConfirmTrustBody": "Das Modul läuft in einer Sandbox: Es darf nur auf die Netzwerk-Endpunkte, Dateien und Umgebungsvariablen zugreifen, die es selbst deklariert — der Hub setzt diese Liste durch. Die vollständige Berechtigungsliste sehen Sie nach der Installation in den Modul-Details.", "storePolicyUnverifiedNotice": "Die Signaturpflicht ist in der Hub-Richtlinie ausgeschaltet — Installationen werden nicht kryptografisch geprüft. Der Installations-Dialog zeigt den Status je Modul; für geprüfte Installationen security.require_signatures aktivieren.", - "verifPillUnverified": "unsigniert", "verifPillBlocked": "blockiert", "verifPinnedKey": "Signatur wird geprüft — hinterlegter Store-Schlüssel", "verifPinnedKeyBody": "Der Hub verifiziert das Paket beim Installieren gegen den fest hinterlegten Schlüssel dieser Quelle. Ein manipuliertes Paket wird abgelehnt.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 98787fc..d0f72b2 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1218,7 +1218,7 @@ "svcExposurePublic": "publicly reachable", "svcExposureUnknown": "reach unknown", "svcExposurePublicHint": "This service endpoint sits on a public address — make sure it is protected (TLS, auth, firewall).", - "svcExposureUnknownHint": "Hostname instead of an IP address — the hub deliberately does not resolve names to classify.", + "svcExposureUnknownHint": "Hostname instead of an IP address — the hub deliberately does not resolve names to classify. Check yourself where the name resolves.", "doctorLinkStore": "Open the store", "doctorLinkApprovals": "Open approvals", "doctorLinkAudit": "Open the audit log", @@ -1653,7 +1653,6 @@ "installConfirmTrustTitle": "Trust & security", "installConfirmTrustBody": "The module runs in a sandbox: it may only touch the network endpoints, files, and environment variables it declares itself — the hub enforces that list. The full permission list is visible in the module details after installation.", "storePolicyUnverifiedNotice": "Signature enforcement is switched off in the hub policy — installs are not cryptographically verified. The install dialog shows the per-module status; enable security.require_signatures for verified installs.", - "verifPillUnverified": "unsigned", "verifPillBlocked": "blocked", "verifPinnedKey": "Signature checked — pinned store key", "verifPinnedKeyBody": "The hub verifies the bundle at install time against this source's pinned key. A tampered bundle is refused.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index bb0796a..0e1e9e5 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3725,7 +3725,7 @@ abstract class AppLocalizations { /// No description provided for @svcExposureUnknownHint. /// /// In en, this message translates to: - /// **'Hostname instead of an IP address — the hub deliberately does not resolve names to classify.'** + /// **'Hostname instead of an IP address — the hub deliberately does not resolve names to classify. Check yourself where the name resolves.'** String get svcExposureUnknownHint; /// No description provided for @doctorLinkStore. @@ -4940,12 +4940,6 @@ abstract class AppLocalizations { /// **'Signature enforcement is switched off in the hub policy — installs are not cryptographically verified. The install dialog shows the per-module status; enable security.require_signatures for verified installs.'** String get storePolicyUnverifiedNotice; - /// No description provided for @verifPillUnverified. - /// - /// In en, this message translates to: - /// **'unsigned'** - String get verifPillUnverified; - /// No description provided for @verifPillBlocked. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 6670717..e76d4c2 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2167,7 +2167,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get svcExposureUnknownHint => - 'Hostname statt IP-Adresse — der Hub löst zur Einstufung bewusst keine Namen auf.'; + 'Hostname statt IP-Adresse — der Hub löst zur Einstufung bewusst keine Namen auf. Prüfen Sie selbst, wohin der Name zeigt.'; @override String get doctorLinkStore => 'Store öffnen'; @@ -2910,9 +2910,6 @@ class AppLocalizationsDe extends AppLocalizations { String get storePolicyUnverifiedNotice => 'Die Signaturpflicht ist in der Hub-Richtlinie ausgeschaltet — Installationen werden nicht kryptografisch geprüft. Der Installations-Dialog zeigt den Status je Modul; für geprüfte Installationen security.require_signatures aktivieren.'; - @override - String get verifPillUnverified => 'unsigniert'; - @override String get verifPillBlocked => 'blockiert'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0da91f4..b9c4777 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2171,7 +2171,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get svcExposureUnknownHint => - 'Hostname instead of an IP address — the hub deliberately does not resolve names to classify.'; + 'Hostname instead of an IP address — the hub deliberately does not resolve names to classify. Check yourself where the name resolves.'; @override String get doctorLinkStore => 'Open the store'; @@ -2906,9 +2906,6 @@ class AppLocalizationsEn extends AppLocalizations { String get storePolicyUnverifiedNotice => 'Signature enforcement is switched off in the hub policy — installs are not cryptographically verified. The install dialog shows the per-module status; enable security.require_signatures for verified installs.'; - @override - String get verifPillUnverified => 'unsigned'; - @override String get verifPillBlocked => 'blocked'; diff --git a/lib/pages/store.dart b/lib/pages/store.dart index 0f81bed..14523ee 100644 --- a/lib/pages/store.dart +++ b/lib/pages/store.dart @@ -15,6 +15,7 @@ import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; import '../data/error_presentation.dart'; import '../data/hub.dart'; import '../data/install_verification.dart'; +import 'welcome.dart' show showFaiDoc; import '../data/system_actions.dart'; import '../data/today_story_loader.dart'; import '../l10n/app_localizations.dart'; @@ -399,6 +400,12 @@ class _StorePageState extends State { child: ChainInlineHelp( icon: Icons.gpp_maybe_outlined, text: l.storePolicyUnverifiedNotice, + // One click to the security doc that + // explains WHERE the key lives + // (doc-verifier finding). + onLearnMore: () => + showFaiDoc(context, 'security'), + learnMoreLabel: l.buttonLearnMore, ), ), if (_aiThinking || diff --git a/lib/widgets/chain_install_confirm.dart b/lib/widgets/chain_install_confirm.dart index 00bad65..12c0c15 100644 --- a/lib/widgets/chain_install_confirm.dart +++ b/lib/widgets/chain_install_confirm.dart @@ -258,7 +258,13 @@ class ChainInstallConfirmDialog extends StatelessWidget { child: Text(l.buttonCancel), ), FilledButton( - onPressed: () => Navigator.pop(context, true), + // "blocked" means the hub WOULD refuse this install (policy + // demands signatures, no key material for this source) — + // offering an active Install button would contradict the + // statement right above it (doc-verifier finding). + onPressed: item.installVerification == 'blocked' + ? null + : () => Navigator.pop(context, true), child: Text(l.buttonInstall), ), ], diff --git a/test/install_verification_test.dart b/test/install_verification_test.dart index 9ad48bd..709f733 100644 --- a/test/install_verification_test.dart +++ b/test/install_verification_test.dart @@ -105,6 +105,18 @@ void main() { ); }); + testWidgets('blocked disables the install button', (tester) async { + await tester.pumpWidget( + _host(ChainInstallConfirmDialog(item: _item(verification: 'blocked'))), + ); + await tester.pumpAndSettle(); + // The statement says the hub would refuse — the button must + // not contradict it. + expect(find.text('Installation würde abgelehnt'), findsOneWidget); + final button = tester.widget(find.byType(FilledButton)); + expect(button.onPressed, isNull); + }); + testWidgets('trust gate keeps the generic note for old hubs', (tester) async { await tester.pumpWidget(_host(ChainInstallConfirmDialog(item: _item()))); await tester.pumpAndSettle();