diff --git a/CHANGELOG.md b/CHANGELOG.md index 0635adb..4a3c316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Documentation checked against 0.1.1. The security assessment now gives each mitigation the status + the code actually supports, including four that are only partial; the roadmap, the site's + quickstart, the dependency inventory, and the verification guide describe the released packages + instead of a release still to come. + ## [0.1.1] - 2026-09-29 ### Fixed diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 7fb1c5c..0abf618 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -12,13 +12,12 @@ policies governing how dependencies are selected, pinned, monitored, and upgrade ## 1. Dependency Directory -> Every runtime crate except `notify-rust`, `ksni`, and `gettext-rs` has been added. The tables +> Every runtime crate below is in use; `notify-rust` was considered and not added. The tables > record the *choice* of dependency and the reason for it; exact versions are pinned by the > committed `Cargo.lock` — major versions are noted where they matter (`gtk4` 0.10 and -> `libadwaita` 0.8 must move together). The **License** column states the -> license the crate is expected to carry; it is confirmed against the crate's own metadata by -> `cargo deny check licenses` in CI at the moment the crate is added, and corrected here if it -> differs. +> `libadwaita` 0.8 must move together). The **License** column was checked against each crate's +> own metadata (`cargo metadata`) for 0.1.1. No license check runs in CI yet; `cargo deny` is the +> intended tool. ### 1.1 Runtime Dependencies @@ -35,7 +34,7 @@ policies governing how dependencies are selected, pinned, monitored, and upgrade | `tracing` | 0.1 | MIT | Structured logging and the spans that measure the latency targets. | Span-based rather than line-based, which is what makes the identifier-redaction policy enforceable per target instead of per call site. | | `tracing-subscriber` | 0.3, feature `env-filter` | MIT | `EnvFilter`, driven by `USBGUARD_GUI_LOG`. | Same. | | `thiserror` | 2 | MIT OR Apache-2.0 | Derives the error taxonomy that crosses layers. | Compile-time only in effect; adds no runtime behaviour to get wrong. | -| `gettext-rs` | Resolved at add time | MIT | Translation catalogue lookup. | The platform's own i18n mechanism, so translations integrate with the distribution's tooling rather than a bespoke format. | +| `gettext-rs` | 0.8, default features off, feature `gettext-system`; optional, behind `gui` | MIT | Translation catalogue lookup. | The platform's own i18n mechanism, so translations integrate with the distribution's tooling rather than a bespoke format. | ### 1.2 Development Dependencies @@ -45,8 +44,8 @@ policies governing how dependencies are selected, pinned, monitored, and upgrade | `cargo-fuzz` / `libfuzzer-sys` | Toolchain, not a manifest entry | MIT OR Apache-2.0 | Fuzzing the parser; target of zero panics over 10⁶ inputs. | | `cargo-llvm-cov` | Toolchain | MIT OR Apache-2.0 | Coverage measurement against the >90% parser target. | | `cargo-audit` | Toolchain | MIT OR Apache-2.0 | `make audit` — RustSec advisory scanning. | -| `cargo-deny` | Toolchain | MIT OR Apache-2.0 | License and duplicate-dependency checks; the authority for the License columns here. | -| Clippy, rustfmt | Toolchain component | MIT OR Apache-2.0 | `make lint`; Clippy carries the `disallowed-methods` lint that enforces the no-panic and runtime-boundary rules. | +| `cargo-deny` | Toolchain | MIT OR Apache-2.0 | License and duplicate-dependency checks. Not yet run in CI. | +| Clippy, rustfmt | Toolchain component | MIT OR Apache-2.0 | `make lint`; Clippy enforces the no-panic rules (lint levels in `Cargo.toml`, denied outright on `src/rules/`) and, through `disallowed-methods`, the runtime-boundary rule. | | `cargo-deb`, `cargo-generate-rpm` | Toolchain | MIT | Native package building. | ### 1.3 System-Level Dependencies @@ -58,6 +57,7 @@ policies governing how dependencies are selected, pinned, monitored, and upgrade | GTK | 4.14 | Distribution | Runtime and build-time (`gtk4-devel`). | | libadwaita | 1.5 | Distribution | Runtime and build-time (`libadwaita-devel`). | | GLib | Matching GTK | Distribution | Main loop, GSettings, GResource (`glib2-devel`). | +| GNU gettext | any | Distribution | Build time only: `msgfmt` compiles the translations. Without it the build succeeds in English only, with a warning. | | `pkg-config` | any | Distribution | Build-time discovery of the above. | | A C toolchain | any | Distribution | Required by the `-sys` crates underlying the GTK bindings. | | D-Bus (system bus) | any | Distribution | The only transport to the daemon. | diff --git a/MAINTAINERS.md b/MAINTAINERS.md index c887ccd..df1c59e 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,6 +1,6 @@ # Maintainers -This document lists the maintainers of USBGuard GUI (USBGuardGUI) and defines the processes +This document lists the maintainers of USBGuardGUI and defines the processes governing maintainership responsibilities, access, and lifecycle. --- @@ -45,9 +45,9 @@ the case for a solo-maintained project. | Resource | Access Holder | Notes | | :------------------------- | :------------ | :------------------------------------------------------- | | GitHub Repository (Admin) | `onyks` | Full admin access | -| Package Registry | `onyks` | Owner of the published package | -| GitHub Actions Secrets | `onyks` | Manages CI/CD credentials | -| Release Signing | `onyks` | Signs release artifacts (Sigstore keyless / GPG) | +| Package Registry | `onyks` | Owner of `usbguard-gui` on crates.io, published by the release workflow through Trusted Publishing | +| GitHub Actions Secrets | `onyks` | Manages CI/CD credentials; the `crates-io` environment holds none since Trusted Publishing | +| Release Signing | `onyks` | Sigstore keyless signing by the release workflow; no signing key exists ([docs/releasing.md](docs/releasing.md)) | | Security Reporting Inbox | `onyks` | `github.tgwp7@aleeas.com` — see [SECURITY.md](SECURITY.md) | --- diff --git a/README.md b/README.md index 168cfb4..a4292cf 100644 --- a/README.md +++ b/README.md @@ -129,9 +129,9 @@ For verifying release assets, see the [Release Verification Guide](docs/verifica ```bash # Build dependencies -sudo dnf install gtk4-devel libadwaita-devel gcc # Fedora -sudo apt install libgtk-4-dev libadwaita-1-dev build-essential # Debian / Ubuntu -sudo pacman -S gtk4 libadwaita base-devel # Arch +sudo dnf install gtk4-devel libadwaita-devel gcc gettext # Fedora +sudo apt install libgtk-4-dev libadwaita-1-dev build-essential gettext # Debian / Ubuntu +sudo pacman -S gtk4 libadwaita base-devel gettext # Arch git clone https://github.com/onyks-os/USBGuardGUI.git cd USBGuardGUI @@ -287,7 +287,7 @@ Contributions are welcome. The areas where help matters most: 1. **USBGuard on other distributions** — behaviour of the D-Bus bridge and its Polkit defaults on systems other than the ones already checked. 2. **GTK 4 / libadwaita** — accessibility, keyboard navigation, and adaptive layouts. -3. **Packaging** — Flathub submission, and distribution packages. +3. **Packaging** — packages in the distributions' own repositories. 4. **Translations** — the interface is in English and Italian; [docs/translating.md](docs/translating.md) explains how to add a language, no Rust needed. diff --git a/ROADMAP.md b/ROADMAP.md index f586787..b5f1d81 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # USBGuardGUI Development Roadmap -This document outlines the **realistic, near-term** development plan for USBGuard GUI. Items +This document outlines the **realistic, near-term** development plan for USBGuardGUI. Items beyond this scope are tracked as ideas in [GitHub Issues](https://github.com/onyks-os/USBGuardGUI/issues) rather than as committed release dates. @@ -36,13 +36,14 @@ Delivered: - **v0.2.0**, code complete: device actions with the runtime/permanent choice, the policy view with safe removal and the disambiguation dialog, the new-rule dialog (guided and text modes), cancellable operations with the 20 s / 180 s notices, runtime parameters, preferences. -- **Later, done**: notifications with a quick action (§9.5), background mode and the tray icon - (§9.6), start at login. Still to do: localization (§9.7) and Flathub submission. -- **Packaging**: `.deb` (built on Ubuntu 24.04, installs on Debian and Ubuntu), `.rpm` (installs - on Fedora, pulling in `usbguard-dbus`), an Arch `PKGBUILD`, and a Flatpak manifest. +- **Beyond the plan**: notifications with a quick action (§9.5), background mode and the tray + icon (§9.6), start at login, and localization (§9.7) with an Italian translation. +- **Packaging and release**: `.deb` (built on Ubuntu 24.04, installs on Debian and Ubuntu), `.rpm` + (installs on Fedora, pulling in `usbguard-dbus`), an Arch package, and a Flatpak bundle, built + and signed by one release workflow; the crate on crates.io. -No release exists yet. Before tagging: the open Phase 0 items below, a manual pass over the -v0.2 actions on a real system, and screenshots for the AppStream metadata. +Released: **0.1.0** (2026-09-27), with everything above, and **0.1.1** (2026-09-29), which fixes the +status icon in the Flatpak. The v0.2.0 scope below shipped in 0.1.0. --- @@ -153,7 +154,7 @@ non-UTF-8 bytes) — and idles under the memory target of §13.2. --- -## Next — v0.2.0: acting on policy +## v0.2.0 scope: acting on policy (shipped in 0.1.0) **Goal:** the window can change what it displays, with the runtime/persistent distinction made explicit at every step. @@ -169,15 +170,28 @@ explicit at every step. --- -## Later +## Next -Not committed, and in no particular order: localization (§9.7) and Flathub submission (§14.1). -Notifications, the tray, background mode, the Flatpak manifest, and native packages are done. +Not committed, and in no particular order. Most come from checking +[`docs/security-assessment.md`](docs/security-assessment.md) against the 0.1.1 code: they are the +mitigations it marks `Partial` or untested. + +| Item | Description | +| :--- | :---------- | +| **Automate T2 and T13** | A daemon restart during an operation (reported as inconclusive rather than failed), and a Polkit prompt left open for ten minutes, as mock tests. | +| **Identifier-free logs, tested** | A test asserting that a default-level log contains no device identifier, and a notice in the `debug` output itself that it contains them (today only `--help` says so). | +| **Measure filesystem access** | The §13.2 `strace` measurement, with the program's own accesses told apart from those of the libraries it links. | +| **Translations** | Open issues invite French, Spanish, Russian, Chinese, German, Brazilian Portuguese, and Japanese; [docs/translating.md](docs/translating.md) explains how. | +| **Arch User Repository** | A `usbguard-gui` package, when AUR registrations reopen. | +| **Reproducible builds** | See [docs/verification.md](docs/verification.md) §5. | One upstream contribution is worth more than any of them: proposing `removeRuleIfMatches(id, expected_text)` to USBGuard would eliminate the rule-removal race at its root rather than narrowing it client-side. +Flathub is not planned for now. The Flatpak bundle attached to each release installs on any +distribution. + --- ## Explicitly Out of Scope diff --git a/docs/interfaces.md b/docs/interfaces.md index b5100d0..2f616b5 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -156,12 +156,12 @@ That is the complete list. In particular: | `/usr/share/metainfo/io.github.onyks_os.UsbguardGui.metainfo.xml` | AppStream metadata. | Package lifetime | | `/usr/share/glib-2.0/schemas/io.github.onyks_os.UsbguardGui.gschema.xml` | GSettings schema. | Package lifetime | | `/usr/share/icons/hicolor/…/io.github.onyks_os.UsbguardGui.svg` | Application icon. | Package lifetime | -| `/usr/share/locale/…/usbguard-gui.mo` | Translation catalogues. | Package lifetime | | `/usr/share/locale//LC_MESSAGES/usbguard-gui.mo` | Translation catalogues. | Package lifetime | | `/usr/share/doc/usbguard-gui/70-usbguard-gui.rules.example` | Example Polkit rule — **shipped inert, never installed into `/etc/polkit-1/rules.d/`.** | Package lifetime | -The post-install script modifies no system configuration. Its only action is the GSettings schema -recompilation that packaging conventions require. +The packages have no post-install script and modify no system configuration. The GSettings schema +is compiled by the distribution's own GLib package, through the trigger it registers for +`/usr/share/glib-2.0/schemas/`. ### D-Bus interfaces consumed diff --git a/docs/security-assessment.md b/docs/security-assessment.md index e46f1ed..222eb6f 100644 --- a/docs/security-assessment.md +++ b/docs/security-assessment.md @@ -22,10 +22,10 @@ procedures live in [`SECURITY.md`](../SECURITY.md). ## 1. Security Objectives -> **Status of this document.** The program is specified but not yet implemented. Every mitigation -> below is marked **Specified** rather than **Implemented** until the corresponding code exists and -> the test that proves it passes. This document is written first on purpose: the threat model is an -> input to the design, not a report on it. +> **Status of this document.** Written before the code, on purpose: the threat model was an input +> to the design, not a report on it. It was checked against the code of 0.1.1 (2026-09-30), and +> each mitigation now carries the status that check found — including where the code does less +> than, or something different from, what was specified. USBGuardGUI is an unprivileged client that asks a root daemon to change USB device authorization. It holds no privilege of its own and enforces no policy. Its security value is therefore narrow and @@ -88,19 +88,22 @@ boundaries it crosses as a supplicant. For each component, enumerate threats under **S**poofing, **T**ampering, **R**epudiation, **I**nformation disclosure, **D**enial of service, and **E**levation of privilege. -Severity is the impact on the objectives in §1, not a CVSS score. **Status** is `Specified` while -the mitigation exists only in `architecture.md`, and becomes `Implemented` when the code and the -test that proves it both exist. +Severity is the impact on the objectives in §1, not a CVSS score. **Status** is one of: + +- `Implemented` — the code exists, and a named test proves it; +- `Implemented, untested` — the code exists, but no automated test covers it yet; +- `Partial` — part of the mitigation exists; the row says which part does not; +- `Specified` — the mitigation exists only in `architecture.md`. ### 3.1 D-Bus client and proxy layer (`src/dbus/`) | STRIDE | Threat | Severity | Mitigation | Status | | :----- | :----- | :------- | :--------- | :----- | | S | A process other than the real bridge owns `org.usbguard1` and feeds the program a fabricated device list. | Medium | The system bus arbitrates well-known name ownership, and the bus policy governs who may own `org.usbguard1`. A local process that can take that name has already won at a lower layer. The program does not attempt to re-authenticate the daemon. | Accepted (see §4) | -| T | Device attributes or rule text crafted to break the display or the rule generator. | High | Every field is parsed into a domain type; the canonical renderer quotes and escapes everything; round-trip tests and a fuzz target cover the parser. | Specified | +| T | Device attributes or rule text crafted to break the display or the rule generator. | High | Every field is parsed into a domain type; the canonical renderer quotes and escapes everything; round-trip tests and a fuzz target cover the parser. | Implemented — `render_then_parse_is_identity` (proptest), fuzz targets `parse_rule` and `round_trip`, T6 | | R | A change made through the GUI cannot be distinguished afterwards from one made through the CLI. | Low | The daemon is the audit point and logs its own changes; this program adds no audit surface and claims none. | Accepted | -| I | Error strings returned by the daemon are surfaced verbatim and may contain device identifiers. | Medium | Diagnostic text shown to the user is the program's own; daemon detail is attached only in the diagnostic panel, and never written to a default-level log. | Specified | -| D | An insertion burst from a hub with 40+ ports floods the event stream. | Medium | The event worker coalesces within a bounded latency window and resynchronizes rather than replaying per-event; T3 of the resilience matrix is the test. | Specified | +| I | Error strings returned by the daemon are surfaced verbatim and may contain device identifiers. | Medium | Diagnostic text shown to the user is the program's own; daemon detail is attached only in the diagnostic panel, and never written to a default-level log. | Partial — never logged at the default level; but when USBGuard rejects a request, its reply text is shown in that operation's toast (“USBGuard refused the request: …”), not only in the diagnostic panel | +| D | An insertion burst from a hub with 40+ ports floods the event stream. | Medium | The event worker coalesces within a bounded latency window and resynchronizes rather than replaying per-event; T3 of the resilience matrix is the test. | Implemented — T3 | | E | — | — | The layer holds no privilege to elevate. Every privileged effect is the daemon's, behind three checkpoints this program does not control. | N/A | ### 3.2 Rule language parser and generator (`src/rules/`) @@ -110,20 +113,20 @@ the program, and the component with the smallest blast radius, because it touche | STRIDE | Threat | Severity | Mitigation | Status | | :----- | :----- | :------- | :--------- | :----- | -| T | Rule injection: a device name containing a quote, a backslash, or a newline is interpolated into a generated rule and changes its meaning. | **Critical** | Rules are never produced by string concatenation. A rule is built as a typed structure and rendered by the canonical quoter; a round-trip check on the rendered output is a required test. | Specified | -| D | A pathological input causes a panic, an infinite loop, or unbounded allocation. | High | No `unwrap` and no panic in the parser, enforced by a Clippy `disallowed-methods` lint that fails CI; `cargo fuzz` over the parser with a target of zero panics in 10⁶ inputs; input length is bounded. | Specified | -| T | Non-UTF-8 bytes in a device name corrupt the displayed text or the generated rule. | Medium | Lossy decoding at the boundary, with the row shown with partial fields rather than dropped or panicking; T6 of the resilience matrix. | Specified | +| T | Rule injection: a device name containing a quote, a backslash, or a newline is interpolated into a generated rule and changes its meaning. | **Critical** | Rules are never produced by string concatenation. A rule is built as a typed structure and rendered by the canonical quoter; a round-trip check on the rendered output is a required test. | Implemented — `hostile_device_name_cannot_inject`, `render_then_parse_is_identity`, fuzz target `round_trip` | +| D | A pathological input causes a panic, an infinite loop, or unbounded allocation. | High | No `unwrap` and no panic in the parser, enforced by `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic)]` on `src/rules/`, which fails CI; `cargo fuzz` over the parser with a target of zero panics in 10⁶ inputs; input length is bounded (`MAX_INPUT_LEN`). | Implemented — 10⁶ inputs per fuzz target without a finding; `TooLong` test; weekly fuzzing workflow | +| T | Non-UTF-8 bytes in a device name corrupt the displayed text or the generated rule. | Medium | Lossy decoding at the boundary, with the row shown with partial fields rather than dropped or panicking; T6 of the resilience matrix. | Implemented — T6 | | I | — | — | The parser holds no secret. | N/A | ### 3.3 Action path — device actions and rule modification (`src/ui/`, `src/dbus/commands.rs`) | STRIDE | Threat | Severity | Mitigation | Status | | :----- | :----- | :------- | :--------- | :----- | -| E | A Polkit authorization obtained for one action is used to perform a different one. | **Critical** | One authorization per method call, checked by the bridge, not by this program. The program issues exactly the call the user confirmed, with no batching and no deferred replay of a pending operation. | Specified | -| T | Rule-ID volatility: the ruleset shifts between reading and removing, and the wrong rule is removed. | **Critical** | Rules are held by canonical text, not by ID; the ruleset is re-read immediately before removal and the text re-matched; a mismatch yields `AlreadyGone` and aborts. Two textually identical rules produce a disambiguation dialog listing evaluation positions, never a guess. T4 and T5 are the tests. | Specified | -| T | An optimistic UI update makes the user believe a change took effect that the daemon rejected. | High | No optimistic updates. The view changes only on a daemon signal or a confirmed reply; an operation interrupted by a daemon restart is reported inconclusive and the state is re-read (T2). | Specified | -| S | A crafted notification reply causes an action on a device other than the one the notification was about. | High | Notification actions carry an opaque token resolved against the program's own live device map; a token that no longer resolves opens the window instead of acting. | Specified | -| D | A Polkit prompt the user leaves open for ten minutes causes the operation to time out and the UI to hang. | Medium | Operations are cancellable and have no client-side timeout shorter than the Polkit interaction; the target is zero timeouts on waits up to ten minutes (T13). | Specified | +| E | A Polkit authorization obtained for one action is used to perform a different one. | **Critical** | One authorization per method call, checked by the bridge, not by this program. The program issues exactly the call the user confirmed, with no batching and no deferred replay of a pending operation. | Implemented, untested — by construction: each user action is one method call | +| T | Rule-ID volatility: the ruleset shifts between reading and removing, and the wrong rule is removed. | **Critical** | Rules are held by canonical text, not by ID; the ruleset is re-read immediately before removal and the text re-matched; a mismatch yields `AlreadyGone` and aborts. Two textually identical rules produce a disambiguation dialog listing evaluation positions, never a guess. T4 and T5 are the tests. | Implemented — T4, T5 | +| T | An optimistic UI update makes the user believe a change took effect that the daemon rejected. | High | No optimistic updates. The view changes only on a daemon signal or a confirmed reply; an operation interrupted by a daemon restart is reported inconclusive and the state is re-read (T2). | Partial — no optimistic updates: a reply only clears the row's pending state, and the new state arrives with the daemon's signal. An operation interrupted by a daemon restart is reported as failed, not inconclusive, and the state is re-read on reconnection. T2 is not automated | +| S | A crafted notification reply causes an action on a device other than the one the notification was about. | High | Notification actions carry an opaque token resolved against the program's own live device map; a token that no longer resolves opens the window instead of acting. | Implemented, untested — the token is the device id, checked against the device's descriptor hash (or rule text) recorded when the notification was sent; a mismatch opens the window and says nothing was changed | +| D | A Polkit prompt the user leaves open for ten minutes causes the operation to time out and the UI to hang. | Medium | Operations are cancellable and have no client-side timeout shorter than the Polkit interaction; the target is zero timeouts on waits up to ten minutes (T13). | Implemented, untested — no client-side method timeout is set, and each pending operation has a Cancel button; T13 is not automated | | R | — | Low | The daemon records the change. | Accepted | ### 3.4 Session-bus integrations — notifications and tray (`src/notify.rs`, `src/tray.rs`) @@ -131,16 +134,16 @@ the program, and the component with the smallest blast radius, because it touche | STRIDE | Threat | Severity | Mitigation | Status | | :----- | :----- | :------- | :--------- | :----- | | S | A hostile process registers as the `StatusNotifierWatcher` or the notification server and impersonates the program's UI. | Medium | Any process in the user's session can already do this to any application; it is a session-bus property, not a flaw here. The consequence is bounded: neither integration can cause an action, because the action path re-resolves every token against live state and every privileged effect still passes Polkit. | Accepted | -| I | A notification body displays a device name on a lock screen or a shared display. | Medium | Notification bodies carry the device's presentation name only, never its serial number or hash, and the notification can be disabled entirely with `notify-inserted`. | Specified | -| D | The tray's D-Bus name registration fails under Flatpak because it depends on the in-sandbox pid. | Low | Failure degrades to background mode, which remains fully functional, with a one-time visible notice rather than silence. | Specified | +| I | A notification body displays a device name on a lock screen or a shared display. | Medium | Notification bodies carry the device's presentation name only, never its serial number or hash, and the notification can be disabled entirely with `notify-inserted`. | Implemented, untested — a device without a name is shown as “USB device ” | +| D | The tray's D-Bus name registration fails under Flatpak, where the sandbox may not own `org.kde.StatusNotifierItem--`. | Low | Inside a sandbox the tray registers with its unique bus name instead (0.1.1). Where no tray can be shown, background mode remains fully functional, with a one-time visible notice rather than silence. | Implemented — checked manually in the Flatpak against a StatusNotifierWatcher; not automated | ### 3.5 Configuration, logging, and diagnostics (`src/config.rs`, `src/dbus/diagnostics.rs`) | STRIDE | Threat | Severity | Mitigation | Status | | :----- | :----- | :------- | :--------- | :----- | -| I | A log or a `--diagnose` output pasted into a public bug report reveals the user's hardware. | High | Device names, serial numbers, and hashes are never logged above `debug`, and the `debug` level says so in its own output. `--diagnose` reports access state and remedies, never a device list. | Specified | -| T | A tampered GSettings value changes what the program sends to the daemon. | Low | GSettings holds presentation state only. No key affects which request is sent or whether it is confirmed — a claim that is checkable by reading §2 of `interfaces.md` against the request path. | Specified | -| E | The diagnostic probe triggers a Polkit prompt during startup that the user cannot attribute to anything. | Medium | The probe sequence runs **after** the window is presented, never during startup, so any prompt it causes belongs to a window the user can see. The write-access probe is deliberately empty: the only honest test of write access is a write, and a test write would change the system's USB policy to find out whether it may. | Specified | +| I | A log or a `--diagnose` output pasted into a public bug report reveals the user's hardware. | High | Device names, serial numbers, and hashes are never logged above `debug`, and the `debug` level says so in its own output. `--diagnose` reports access state and remedies, never a device list. | Partial — the default level is `warn`, and no `warn` or `info` message carries a device identifier; `--diagnose` lists no device. `--help` states that the `debug` level records device names and serial numbers. Missing: the `debug` output itself does not say so | +| T | A tampered GSettings value changes what the program sends to the daemon. | Low | GSettings holds presentation state only. No key affects which request is sent or whether it is confirmed — a claim that is checkable by reading §2 of `interfaces.md` against the request path. | Implemented, untested — `default-persistence` only preselects the choice in the dialog; the user still confirms it | +| E | The diagnostic probe triggers a Polkit prompt during startup that the user cannot attribute to anything. | Medium | The probe sequence runs **after** the window is presented, never during startup, so any prompt it causes belongs to a window the user can see. The write-access probe is deliberately empty: the only honest test of write access is a write, and a test write would change the system's USB policy to find out whether it may. | Implemented, untested — the probe starts on the window's first map; `--background` runs no probe | --- @@ -158,7 +161,7 @@ property that matters: the program fails in a way it can explain. | **Rule text as identity assumes stable canonicalization.** If a future daemon normalizes rule text differently, held handles stop matching. | Medium | Text is the only stable identity the API exposes; IDs are positional. | Handles are re-read immediately before use, so the failure mode is `AlreadyGone` on a rule that still exists — confusing, but never destructive. | | **Any process in the user's session can impersonate the session-bus services** the program uses. | Medium | This is a property of the session bus, shared by every desktop application. Defending it is out of this program's reach. | The action path never trusts a session-bus reply as sufficient authority: tokens are re-resolved against live state, and every privileged effect still passes Polkit. | | **The program cannot verify that its D-Bus peer is the genuine bridge.** | Medium | Well-known name ownership is arbitrated by the bus, under the bus policy. A local process able to take `org.usbguard1` has already defeated a lower layer. | Bus policy (checkpoint A) is treated as a distinct diagnostic state rather than folded into "permission denied", so a hardened or altered policy is visible rather than silent. | -| **The tray's D-Bus name under Flatpak depends on the in-sandbox pid.** | Low | The workaround is empirical, not guaranteed. | Failure degrades to background mode with a visible notice. | +| **The tray under Flatpak registers without the well-known name the StatusNotifierItem specification asks for.** | Low | Watchers in current use accept the unique name, but the specification does not promise it. | Failure degrades to background mode with a visible notice. | | **The bridge is optional packaging on every reference distribution.** | Low (security) / High (usability) | Not this project's decision to make. | `BridgeNotInstalled` is a first-class diagnostic state with a per-distribution install command — the single message most likely to determine whether a user ever sees the program work. | --- @@ -183,7 +186,8 @@ property that matters: the program fails in a way it can explain. - All GitHub Actions are pinned to full commit SHAs. - `GITHUB_TOKEN` permissions are declared read-only at workflow level and elevated per job only when required. -- Branch protection requires review and passing status checks before merge. +- A repository ruleset on `main` requires passing status checks and a linear history, and forbids + force pushes and deletion. Review is not required: the project has one maintainer. --- @@ -191,11 +195,11 @@ property that matters: the program fails in a way it can explain. | Control | Type | Where implemented | Verified by | | :------ | :--- | :---------------- | :---------- | -| Input validation | Preventive | `src/rules/` — parser, lexer, canonical renderer; `src/dbus/client.rs` — every field parsed into a domain type at the boundary | Round-trip property tests, `cargo fuzz`, resilience tests T5 and T6 | -| Least privilege | Preventive | The whole design: no setuid, no capabilities, no helper daemon, no access to `/etc`, `/var`, or `/sys` | `strace` filtered on those three prefixes must report zero accesses (§13.2) | -| Fail-closed defaults | Preventive | `default-persistence` defaults to runtime-only; no optimistic UI updates; `AlreadyGone` aborts rather than guessing; `DeniedUnattributed` shows both remedies rather than the likelier one | Resilience tests T2, T4, T5 | -| No-panic discipline | Preventive | `clippy.toml` `disallowed-methods` lint over the parser and the runtime boundary | `cargo clippy --all-targets -- -D warnings` in CI | -| Identifier redaction | Preventive | `src/` logging policy: device names, serials, and hashes never above `debug` | Review, plus a test asserting a default-level log contains no identifier | +| Input validation | Preventive | `src/rules/` — parser, lexer, canonical renderer; `src/dbus/client.rs` — every field parsed into a domain type at the boundary | Round-trip property tests, `cargo fuzz`, resilience tests T3, T5, and T6 | +| Least privilege | Preventive | The whole design: no setuid, no capabilities, no helper daemon; the program's own code reads nothing under `/etc`, `/var`, or `/sys` (it reads `/usr/lib/os-release` rather than `/etc/os-release`) and writes only its autostart entry in the user's home | Review. The libraries it links, such as fontconfig, read system configuration as in any GTK application, so a `strace` of the process is not expected to show zero; the §13.2 `strace` measurement has not been taken | +| Fail-closed defaults | Preventive | `default-persistence` defaults to runtime-only; no optimistic UI updates; `AlreadyGone` aborts rather than guessing; `DeniedUnattributed` shows both remedies rather than the likelier one | Resilience tests T4 and T5 (T2 is not automated) | +| No-panic discipline | Preventive | `#![deny(...)]` panic lints on `src/rules/`, the same lints as warnings crate-wide, and `clippy.toml` `disallowed-methods` against a bare `tokio::spawn` | `cargo clippy --all-targets -- -D warnings` in CI | +| Identifier redaction | Preventive | `src/` logging policy: device names, serials, and hashes never above `debug` | Review only: the planned test asserting that a default-level log contains no identifier does not exist yet | | Static analysis | Detective | CodeQL, linter | CI | | Dependency scanning | Detective | Dependabot, `make audit` | CI | | Fuzzing | Detective | `make fuzz` | CI (weekly) | diff --git a/docs/verification.md b/docs/verification.md index 2353770..592c812 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -3,6 +3,16 @@ Every USBGuardGUI release is published with checksums, a Sigstore signature, and a CycloneDX SBOM. This guide shows how to verify an artifact before installing it. +Each release carries the `.deb`, the `.rpm`, the Arch package (`.pkg.tar.zst`), the `.flatpak` +bundle, the bare binary, the `.crate`, and `sbom.json`; next to each one, its Sigstore bundle +(`.sigstore.json`), and a `SHA256SUMS` file covering them all. The examples below use +version 0.1.1: + +```bash +VERSION=0.1.1 +gh release download "v$VERSION" --repo onyks-os/USBGuardGUI # or download from the web page +``` + ## 1. Verify the Checksums Download the artifact and `SHA256SUMS` from the @@ -23,11 +33,13 @@ key to steal. Install [`sigstore`](https://pypi.org/project/sigstore/) and verif python -m pip install sigstore sigstore verify identity \ - --cert-identity "https://github.com/onyks-os/USBGuardGUI/.github/workflows/release.yml@refs/tags/v0.1.0" \ + --cert-identity "https://github.com/onyks-os/USBGuardGUI/.github/workflows/release.yml@refs/tags/v$VERSION" \ --cert-oidc-issuer "https://token.actions.githubusercontent.com" \ ``` +`sigstore` finds the bundle `.sigstore.json` next to the artifact by itself. + ## 3. Verifying Signer Identity The `--cert-identity` value must exactly match the release workflow path and the tag being verified. @@ -46,7 +58,7 @@ the SBOM but absent from that file should be reported as an issue. ## 5. Reproducing the Build ```bash -git clone --branch v0.1.0 https://github.com/onyks-os/USBGuardGUI.git +git clone --branch "v$VERSION" https://github.com/onyks-os/USBGuardGUI.git cd USBGuardGUI make build sha256sum dist/* diff --git a/docs/web/how-to/background.md b/docs/web/how-to/background.md index ae4aade..bde688c 100644 --- a/docs/web/how-to/background.md +++ b/docs/web/how-to/background.md @@ -9,7 +9,7 @@ To keep announcing after the window is closed: **Main menu → Preferences**, an - **Keep running in the background** — closing the window hides it instead of quitting; - **Start at login** — start hidden when you log in, so the first device of the session is - announced too. + announced too. Not offered in the Flatpak. ## With and without a status icon @@ -19,5 +19,5 @@ to quit. **Stock GNOME has no status icon area** — it needs the *AppIndicator and KStatusNotifierItem Support* extension. Without it, the program still runs and still announces devices; the first time -you close the window it tells you so. To reopen the window, launch USBGuard again from the +you close the window it tells you so. To reopen the window, launch USBGuardGUI again from the application menu; to quit, use **Main menu → Quit** (or Ctrl+Q). diff --git a/docs/web/index.md b/docs/web/index.md index 1b69ef5..030aeeb 100644 --- a/docs/web/index.md +++ b/docs/web/index.md @@ -91,8 +91,9 @@ This site follows the [Diátaxis](https://diataxis.fr/) framework: ## Project Status -The program is implemented and tested, and **no version has been released yet**; the first will be -`0.1.0`. Until then, build it [from source](tutorials/quickstart.md#1-install). See the +The current version is **0.1.1**. Packages for Debian and Ubuntu, Fedora, and Arch, and a Flatpak +bundle, are on the [releases page](https://github.com/onyks-os/USBGuardGUI/releases/latest); the +[quickstart](tutorials/quickstart.md#1-install) shows how to install them. See the [changelog](release-notes/changelog.md) and the [roadmap](https://github.com/onyks-os/USBGuardGUI/blob/main/ROADMAP.md). diff --git a/docs/web/reference/dependencies.md b/docs/web/reference/dependencies.md index 7ee7f7a..2767b45 100644 --- a/docs/web/reference/dependencies.md +++ b/docs/web/reference/dependencies.md @@ -22,10 +22,10 @@ versions are pinned by the committed `Cargo.lock`. | `futures-util` | 0.3 | MIT OR Apache-2.0 | Stream combinators for event coalescing. | | `tracing`, `tracing-subscriber` | 0.1, 0.3 | MIT | Structured logging, driven by `USBGUARD_GUI_LOG`. | | `thiserror` | 2 | MIT OR Apache-2.0 | The error taxonomy that crosses layers. | +| `gettext-rs` | 0.8, feature `gettext-system` | MIT | Translation catalogue lookup. Optional: `gui`. | Desktop notifications use GIO's own `GNotification`, which already speaks both the session-bus -protocol and the Flatpak notification portal, so no notification crate is needed. Translations -(`gettext-rs`) are planned. +protocol and the Flatpak notification portal, so no notification crate is needed. `cargo build --no-default-features` builds the headless commands (`--diagnose`, `--list-devices`, `--list-rules`) without GTK. @@ -50,6 +50,7 @@ protocol and the Flatpak notification portal, so no notification crate is needed | GTK | 4.14 | Widgets. Build: `gtk4-devel` / `libgtk-4-dev` / `gtk4`. | | libadwaita | 1.5 | Adwaita widgets. Build: `libadwaita-devel` / `libadwaita-1-dev` / `libadwaita`. | | GLib | Matching GTK | Main loop, GSettings. `glib-compile-schemas` compiles the settings schema at build time. | +| GNU gettext | any | Build time only: `msgfmt` compiles the translations. Without it the build is English only. | | A C toolchain, `pkg-config` | any | Required by the `-sys` crates under the GTK bindings. | | D-Bus (system bus) | any | The only transport to the daemon. | diff --git a/docs/web/release-notes/changelog.md b/docs/web/release-notes/changelog.md index 3350bb3..a54e1ef 100644 --- a/docs/web/release-notes/changelog.md +++ b/docs/web/release-notes/changelog.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Documentation checked against 0.1.1. The security assessment now gives each mitigation the status + the code actually supports, including four that are only partial; the roadmap, the site's + quickstart, the dependency inventory, and the verification guide describe the released packages + instead of a release still to come. + ## [0.1.1] - 2026-09-29 ### Fixed diff --git a/docs/web/tutorials/quickstart.md b/docs/web/tutorials/quickstart.md index 0e3ea7b..1b3ea8c 100644 --- a/docs/web/tutorials/quickstart.md +++ b/docs/web/tutorials/quickstart.md @@ -10,36 +10,55 @@ prompts), a USB stick you can plug and unplug freely, and about 15 minutes. ## 1. Install -No release has been published yet, so build from source. +Download the package for your distribution from the +[latest release](https://github.com/onyks-os/USBGuardGUI/releases/latest) and install it. The +package pulls in USBGuard and its D-Bus bridge. === "Fedora / RHEL" ```bash - sudo dnf install usbguard usbguard-dbus gtk4-devel libadwaita-devel gcc + sudo dnf install ./usbguard-gui-0.1.1-1.x86_64.rpm ``` === "Debian / Ubuntu" ```bash - sudo apt install usbguard libgtk-4-dev libadwaita-1-dev build-essential + sudo apt install ./usbguard-gui_0.1.1-1_amd64.deb ``` === "Arch" ```bash - sudo pacman -S usbguard gtk4 libadwaita base-devel + sudo pacman -U ./usbguard-gui-0.1.1-1-x86_64.pkg.tar.zst ``` -Then, with a [Rust toolchain](https://rustup.rs/) (1.85 or newer): +=== "Flatpak" -```bash -git clone https://github.com/onyks-os/USBGuardGUI.git -cd USBGuardGUI -cargo build --release -``` + ```bash + flatpak install --user ./usbguard-gui-0.1.1.flatpak + ``` -The program is `target/release/usbguard-gui`. To install it as a package instead, run -`make package-deb` or `make package-rpm` and install the file from `dist/`. + The Flatpak does not contain USBGuard: install `usbguard` (and, on Fedora, `usbguard-dbus`) + from your distribution as well. + +To check a download before installing it, see the +[verification guide](https://github.com/onyks-os/USBGuardGUI/blob/main/docs/verification.md). + +??? note "Building from source instead" + + With a [Rust toolchain](https://rustup.rs/) (1.85 or newer) and the build dependencies: + + ```bash + sudo dnf install usbguard usbguard-dbus gtk4-devel libadwaita-devel gcc gettext # Fedora + sudo apt install usbguard libgtk-4-dev libadwaita-1-dev build-essential gettext # Debian / Ubuntu + sudo pacman -S usbguard gtk4 libadwaita base-devel gettext # Arch + + git clone https://github.com/onyks-os/USBGuardGUI.git + cd USBGuardGUI + cargo build --release + ``` + + The program is `target/release/usbguard-gui`. `cargo install usbguard-gui` works too. ## 2. Start USBGuard safely @@ -60,9 +79,12 @@ The `umask 077` matters: the daemon refuses a rule file that other users can rea ## 3. Check that you can reach the daemon ```bash -target/release/usbguard-gui --diagnose +usbguard-gui --diagnose ``` +(From a source build: `target/release/usbguard-gui --diagnose`. From the Flatpak: +`flatpak run io.github.onyks_os.UsbguardGui --diagnose`.) + **What just happened:** the program checked, in order, that there is a system bus, that USBGuard's D-Bus bridge is installed and running, and that you are allowed to read from the daemon. The last line should be: @@ -76,9 +98,7 @@ If it is anything else, the lines below it name the cause and the command that f ## 4. Open the window -```bash -target/release/usbguard-gui -``` +Start **USBGuardGUI** from your application menu, or run `usbguard-gui`. You should see your USB devices, each with its state — *Allowed* or *Blocked* — shown by an icon **and** a word, never by colour alone.