A lispy web app runner with simple security (main-branch-only mirror of https://forge.ejuarezg.com/ejuarezg/tailcat-wormhole)
web-app-runner HANDOFF.md
10.0 kB
Markdown
at main

Handoff #

Project state #

A Common Lisp GTK4/WebKitGTK web-app runner is implemented in this repository.

  • Run from checkout: ./bin/web-app-runner <URL>
  • Build standalone executable: ./bin/build-web-app-runner → ./web-app-runner
  • Test: asdf:test-system :clgtk4-web-app-runner/tests (command in README.md)
  • ASDF system: clgtk4-web-app-runner
  • Main source: src/web-app-runner.lisp
  • Main branch now contains the merged pass credential feature and the follow-up privacy/credentials UX changes.
  • The uncommitted Tomb-backed profile work remains stashed on the feature/tomb-backed-profiles branch as WIP Tomb-backed profiles before main merge.
  • The previous main tip was ff33b18; the pass branch was merged with a merge commit before the current uncommitted changes.

The generated executable contains SBCL and the Lisp application; it does not need Quicklisp or the checkout at runtime, but still dynamically needs GTK4, WebKitGTK, and their typelibs.

Terminology #

  • Runner: one web-app-runner process and its PIN-gated browsing session.
  • Runner window: one GTK browser window owned by that process.
  • Runner profile: target-specific WebKit data/cache directory, not a profile shared with other browsers or WebKit applications.

Implemented behavior #

PIN and data permissions #

  • First launch sets a six-digit PIN; later launches require it before a WebKit session or WebView is created.
  • The verifier is a PBKDF2-SHA256 verifier (600,000 iterations) at $XDG_DATA_HOME/web-app-runner/pin-verifier (fallback: ~/.local/share/web-app-runner/pin-verifier). The PIN itself is never stored. If it is missing while runner profiles exist, startup deletes all runner profile data/cache before offering replacement-PIN setup, preventing a new PIN from silently unlocking retained authenticated sessions.
  • Five unsuccessful PIN entries impose a 30-second cooldown.
  • Runner data/cache/profile directories are explicitly chmodded to 0700. The PIN verifier is chmodded to 0600; existing verifier permissions are corrected when it is read.
  • This is ordinary same-account filesystem protection, not encryption or a defense against a process/user that can already use the account.
  • PBKDF2 verification measured about 1.33 seconds and allocated heavily in an earlier measurement. Do not weaken it without deciding the local-PIN threat model.

Isolated profiles and cleanup #

  • A persistent WebKit network session is created only after PIN success and after a target URL is known.
  • Profiles live under $XDG_DATA_HOME/web-app-runner/profiles/<target>/ and cache under $XDG_CACHE_HOME/web-app-runner/profiles/<target>/.
  • The profile includes cookies, local storage, IndexedDB, cache, service-worker data, etc., and is separate from other WebKitGTK apps and other target keys.
  • Clear runner data on close is synchronized across a runner's windows. It changes no live data when toggled. After the final window closes, WebKitGTK clears the target profile asynchronously and the runner waits up to 15s.
  • Cleanup errors or timeouts retain the profile and print an error rather than deleting directories while WebKit may still write.
  • Caveat: separate runner processes for the same hostname share the same profile. Multiple URLs in one invocation currently use a combined profile key. Decide whether either sharing behavior is acceptable.

Privacy lock and navigation #

  • The title bar has a Lock button. It applies an opaque, input-blocking PIN overlay to every runner window while retaining live WebKit views. A correct PIN resumes exactly the same page state without a reload. This is a convenience privacy lock, not a security boundary against the unlocked OS account or process inspection.
  • While privacy-locked, back, forward, reload/stop, and the address field are disabled. Their pre-lock navigation state is recomputed on unlock.
  • Shortcuts: Ctrl+Shift+L locks all runner windows; Ctrl+L is captured at GTK's capture phase before WebKit and focuses/selects the active address bar. The previous application-accelerator-only implementation did not work when WebKit had focus; 06ab347 changed this. The final Ctrl+L change still needs a manual desktop confirmation.
  • Browser windows use GTK HeaderBar. The runner watches GNOME's org.gnome.desktop.interface color-scheme and sets GTK's dark preference; this fixed a light header on a GNOME prefer-dark desktop.
  • Address, back, and forward state are refreshed from load-changed and notify::uri, notify::can-go-back, and notify::can-go-forward to support History API / SPA navigation that does not emit a full page load.
  • The runner automatically privacy-locks after five minutes without pointer or keyboard activity. Ctrl+Shift+L remains the manual lock shortcut.
  • Credentials use Ctrl+Shift+P for the active browser window. The Credentials icon and shortcut are omitted when pass --version is unavailable. The Credentials window has a Show password toggle and is destroyed when the runner privacy-locks.

Important implementation details #

Construct-only network-session #

cl-gtk4's generated webkit:make-web-view does not accept the construct-only network-session property. make-web-view-with-network-session uses CFFI and g_object_new_with_properties() with a manually populated GValue. Every browser WebView must use this helper; reverting to webkit:make-web-view uses the shared default WebKit session.

Website-data clearing also uses CFFI because the generated binding did not marshal the async callback correctly. The callback calls webkit_website_data_manager_clear_finish() before deleting profile files.

WebKitGTK foreign libraries are loaded lazily. Loading WebKit while saving an SBCL image started foreign threads and made program-op crash; do not restore top-level cffi:use-foreign-library for WebKitGTK.

Validation performed #

  • asdf:test-system :clgtk4-web-app-runner/tests passes. Tests cover target key derivation, host parsing (ports, userinfo, IPv6, case), profile path scoping, and removal of temporary data/cache profile roots when the verifier is missing.
  • Standalone executable builds successfully and runs --help after being copied outside the checkout, without Quicklisp.
  • PIN directory/file permission check against a temporary XDG data directory produced 0700 and 0600, respectively.
  • The executable was launched with a temporary profile after theme and shortcut initialization; it remained running without startup errors.
  • Runtime dependencies are documented in README.md: GTK4, WebKitGTK 6.0, GLib/GIO, GObject Introspection typelibs, and a Wayland/X11 display. Checkout development additionally needs SBCL, Quicklisp, and the listed Lisp systems; Credentials additionally needs pass/GnuPG, with Tomb optional. The Credentials control is hidden if pass is unavailable.
  • A user manually confirmed the privacy-lock overlay works. The new five-minute inactivity timer, credentials shortcut, password visibility toggle, and pass-availability hiding still need real-display manual confirmation. Full cleanup and CFFI WebView desktop integration remain only partially smoke-tested.

Documentation #

  • README.md: terminology, usage, PIN/profile locations, privacy-lock limits, shortcuts, build, tests, desktop-entry installation, AI-assistance disclosure, and AGPL-3.0-or-later licensing.
  • docs/design-decisions.md: critical security/isolation decisions and noncritical presentation/distribution/password-store decisions.
  • docs/lock-plan.md: threat model and distinction between privacy overlay and a future genuine security lock.
  • docs/profile-isolation-plan.md: isolation status and outstanding work.
  • docs/pass-tomb-plan.md: password-store/Tomb boundary, implemented UI, age/GnuPG boundary, and staged plan.
  • data/com.ejuarezg.web-app-runner.desktop: Freedesktop launcher for an installed executable on PATH.
  1. Manual desktop regression test

    • Verify automatic locking after five minutes and activity reset on motion, clicks, and keyboard input.
    • Verify Ctrl+Shift+P, the Show password toggle, and that the Credentials icon/shortcut disappear when pass is unavailable.
    • Confirm Ctrl+L focuses/selects the address bar while WebKit has focus.
    • Confirm locked windows visibly gray navigation controls and cannot navigate; unlock and verify their correct prior enabled state.
    • Navigate an SPA / History API site and verify URL and back/forward update immediately.
    • Test normal retention and clear-on-close isolation using Duck and Photos.
  2. Profile semantics

    • Decide same-host concurrent-process sharing/locking behavior.
    • Decide whether multiple command-line URLs should deliberately share their current combined profile or instead get a session/profile per target.
  3. Robustness/tests

    • Add real-display integration coverage for custom WebView creation, cleanup completion, lock controls, and keyboard shortcuts.
    • Add automated cleanup-path behavior tests, not only derivation/scoping.
    • Consider visibly reporting cleanup failure before the final window exits.
  4. Password-store hardening

    • The pass credential feature is merged into main. Credential lookup/save is synchronous and can briefly block the GTK UI during GPG. Multiple-account selection and form filling remain future work.
    • Clipboard clearing is best-effort because clipboard managers may retain history. The runner does not unlock password-store Tombs or handle keys.
  5. Deferred features

    • Private/no-storage mode via an ephemeral network session.
    • Confirmed “forget data now” action.
    • Strong lock that destroys views, idle/focus/suspend locks, keyring/PAM, and optional host allowlist.