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 inREADME.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-profilesbranch asWIP 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-runnerprocess 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 to0600; 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 closeis 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+Llocks all runner windows;Ctrl+Lis 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;06ab347changed this. The final Ctrl+L change still needs a manual desktop confirmation. - Browser windows use GTK
HeaderBar. The runner watches GNOME'sorg.gnome.desktop.interface color-schemeand sets GTK's dark preference; this fixed a light header on a GNOMEprefer-darkdesktop. - Address, back, and forward state are refreshed from
load-changedandnotify::uri,notify::can-go-back, andnotify::can-go-forwardto 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+Lremains the manual lock shortcut. - Credentials use
Ctrl+Shift+Pfor the active browser window. The Credentials icon and shortcut are omitted whenpass --versionis 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/testspasses. 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
--helpafter being copied outside the checkout, without Quicklisp. - PIN directory/file permission check against a temporary XDG data directory
produced
0700and0600, 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 ifpassis 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 onPATH.
Recommended next steps #
-
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 whenpassis unavailable. - Confirm
Ctrl+Lfocuses/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.
-
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.
-
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.
-
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.
-
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.