# Security policy `ltk` is a UI toolkit for Wayland clients. It runs inside the trust boundary of the application that links it: the toolkit decodes images, parses theme JSON, lays out widget trees and drives a GLES / software renderer. A bug in any of those paths can crash the host application or, in worst-case, expose information that a co-tenant Wayland client should not have. This document describes how to report a vulnerability and what we consider in or out of scope. ## Supported versions `ltk` is in active development. Security fixes land on the latest released minor version on crates.io and on the `master` branch in this repository. Older minor versions are not patched. ## Reporting a vulnerability Email **info@liberux.net** with: - a description of the issue, - the smallest reproducer you can share (Rust source preferred), - the `ltk` version and Rust toolchain you used, - whether the issue is exploitable today or requires a follow-up condition. Please do **not** open a public GitHub issue, pull request, or discussion for unpatched vulnerabilities. We aim to acknowledge reports within 5 working days and ship a fix within 30 days for high-severity issues. If you do not hear back, escalate by sending a follow-up email. You may use PGP if you prefer — request the current public key in your first message and we will reply with it before sharing any sensitive details. ## In scope - memory-safety bugs reachable from any public API on the `ltk` crate root or its `core::UiSurface` runtime-free surface, - panics or undefined behaviour triggered by a theme JSON document, a font file, or an image buffer that an application would reasonably accept from user content (e.g. a wallpaper picker, a notification icon), - denial-of-service through unbounded memory growth in caches or layout recursion that the toolkit performs on behalf of the application, - credential leaks in widgets that expose a `secure` mode (currently `TextEdit`) — for example, plaintext password residue in heap dumps after the widget is dropped. ## Out of scope - bugs that require a malicious Wayland compositor: ltk treats the compositor as part of the trusted computing base and does not defend against compositor-level spoofing, layer-shell privilege misuse, or protocol-level attacks, - bugs that require an attacker to set environment variables on the user's process (`LTK_THEMES_DIR`, `LTK_FORCE_SOFTWARE`, `XDG_DATA_HOME`): ltk is not designed to run inside setuid binaries or anywhere the env is not under the user's control, - bugs in third-party dependencies (`tiny-skia`, `fontdue`, `image`, `smithay-client-toolkit`, …): report those upstream. We will track the fix and bump the dependency in a release note. ## Hardening features - `TextEdit::secure( true )` zeroizes the underlying string buffer on drop via `secure_mem::secure_zero` (volatile writes + a `compiler_fence` so the optimiser cannot elide the wipe). The guarantee covers two copies the runtime owns: the `TextEdit` itself and the per-frame `WidgetHandlers::TextEdit` snapshot the layout pass produces for input dispatch — both implement `Drop` and run the wipe before the allocation returns to the allocator. The next `view()` rebuild replaces the previous frame's snapshot, so the typical lifecycle is "one frame on the heap, then overwritten on drop". Single-line and multiline are mutually exclusive with secure (passwords have no line breaks); the text-input-v3 IME path is also skipped in secure mode so preedit / commit strings never reach the compositor's IME stack. ### What `TextEdit::secure` does **not** cover The wipe only reaches buffers ltk allocated. Callers retain responsibility for everything outside the widget tree: - **Application-owned `String`s.** `text_edit( "Pwd", &self.password )` only borrows `self.password`; the credential lives on the consumer's struct. Wipe it explicitly (via `secure_mem::secure_zero` on `password.as_bytes_mut()`) once the auth handshake completes, or put the credential in a wrapper type with a `Drop` impl that does the wipe. - **`on_change` callback copies.** Each keystroke fires a closure with a fresh `String` clone of the current value. If the closure stores or forwards that value (e.g. to a worker thread for PAM), each stored copy is the consumer's responsibility — ltk no longer owns it past the closure call. - **OS-level disclosure surfaces.** Swap, hibernation images, and core dumps are outside any user-space wipe's reach. For threat models that require resistance to these, link an `mlock`-aware allocator, restrict the process's `PR_SET_DUMPABLE` to `0`, and consider running on a no-swap mount. ltk itself does not call `mlock` because the buffers it owns are short-lived (single frame) and `mlock`-ing them per-frame would dominate the cost without materially improving the threat model. - **Compositor-side state.** The compositor sees pointer / keyboard events for the surface but never the rendered glyphs (since `secure` paints bullets). Custom IMEs that the user has installed can still observe keystrokes — that is an OS-level concern outside ltk's trust boundary. Login / lock-screen consumers should pair `TextEdit::secure` with an explicit wipe of their own state on the success path. The [`docs/cookbook.md`](./docs/cookbook.md) "Password field with PAM submit" recipe shows the canonical shape: clear `self.password` after spawning the worker thread so the in-flight clone in the worker is the only remaining copy, and let the worker's drop-on-completion run its own wipe. - `draw_image_data` (both backends) refuses to upload a buffer whose length does not match `width × height × 4`. The mismatch path logs once and draws nothing. - Glyph caches and gradient stops have soft caps to bound memory growth under unusual content. Exceeding the cap drains older entries (glyphs) or truncates the input (gradient stops) with a stderr warning. ## Supply chain ltk targets the Rust toolchain shipped with Debian stable (currently 1.85). Direct dependencies are pinned at the minor-version level except `image`, which is pinned to an exact version because the crate's MSRV and feature surface change between patch releases. We monitor RustSec advisories manually; running `cargo audit` in your own CI is recommended.