Status: Phase 0 (pre-launch). Not yet shipped to users. This repo is the open-source local-side companion to the Lucairn cloud gateway.
What is Sensitive Mode?
A menubar/tray app + local TLS-terminating daemon (lucairnd) that lets EU knowledge workers route supported AI tools on their laptop through the Lucairn sanitization gateway. In the current dogfood build, Claude Code, Cursor API mode, OpenAI-compatible API clients, and extension-backed ChatGPT.com web are the active protected surfaces. The ChatGPT.com path is intentionally non-MITM: the menubar app opens Chrome with Lucairn's bundled extension in a dedicated protected profile, and the VM release matrix smoke-tests that path through Chrome DevTools. Claude Desktop, claude.ai, ChatGPT Desktop, and Gemini stay visible as Coming Soon / diagnostic rows until their repeatable smoke gates are clean.
For the full architecture, threat model, trust posture, and roadmap, see the v1 design spec. Topic-specific documentation lives alongside it in docs/:
- Architecture — the five interception paths, daemon internals, cloud endpoints
- Threat model — per-install root CA primitive, trust scenarios, defenses
- Phase 0 + v1 roadmap — build sequence, decision gates, scope estimates
- Glossary — domain terminology + acronyms
- FAQ — buyer-facing questions
This repo is Phase 0 / pre-launch. It is private during the build and will flip public at v1 launch with full reproducible-build CI + Sigstore release signing. Right now, the repo holds:
- Documentation under
docs/ - An MIT LICENSE file
- A placeholder CI workflow under
.github/workflows/ci.yml(disabled by default until Declade Actions billing is restored) - PR + issue templates under
.github/ - A CONTRIBUTING.md describing the early-access contributor flow
Component scaffolds (lucairnd/, ui/, extension/, adapters/, installer/, tests/) land in subsequent PRs.
Open source (this repo, MIT): lucairnd daemon, Tauri-based menubar app, browser extension, per-vendor chat-protocol adapters, onboarding wizard, root-CA generation logic.
Closed source (Lucairn cloud gateway at gateway.lucairn.eu): sanitization models + L3 PII shield, ID Bridge mapping store, Lucairn Witness signing, cert chain assembly, account/billing/control plane.
Rationale: every line that runs on a user's machine — i.e., everything that touches plaintext or system trust — is auditable. The closed-source backend is where Lucairn's IP moat lives. Standard precedent: Tailscale, Mullvad, Signal, ProtonVPN, Cloudflare WARP all run open-source clients + closed-source backends.
lucairn-sensitive-mode-client/
├── lucairnd/ # Go daemon: TLS termination, vendor adapter dispatch
├── ui/ # Tauri 2 (Rust + WebView): cross-platform menubar/tray
├── extension/ # MV3 browser extension (Chrome + Safari + Firefox + Edge)
├── adapters/ # Per-vendor chat-protocol adapters (TS, shared lucairnd ↔ extension)
├── installer/ # Onboarding wizard + root-CA generation logic
├── docs/ # Design spec, threat model, architecture, FAQ
├── tests/ # Integration tests + reproducible-build verification
└── .github/workflows/ # Public CI: deterministic builds + Sigstore signing
(Build instructions land per-component when each scaffold PR ships.)
On macOS, the lucairnd daemon must run as root because it binds the
privileged port 127.0.0.1:443 (so AI tools' default HTTPS traffic can be
intercepted locally). For day-to-day use we don't want the user opening a
terminal and typing sudo every laptop boot — a launchd LaunchDaemon
handles auto-start.
The preferred dogfood routing mode is transparent PF routing: supported API
clients keep resolving the real vendor IPs for supported Anthropic,
OpenAI API, and Claude consumer-chat hosts, while macOS routes those IPs
to local lucairnd through a managed PF anchor. ChatGPT Desktop and
OpenAI consumer hosts stay unrouted by default because the official
desktop client rejects local TLS inspection before chat can run; protected
ChatGPT work is handled through the browser-extension path instead of the
PF/TLS layer. OpenAI API routing is for API clients on api.openai.com,
not for the account-backed ChatGPT Desktop app. The older
/etc/hosts loopback block remains as
fallback/cleanup compatibility, but new wizard and Settings flows use the
transparent PF path first.
The menubar's Open protected ChatGPT.com action launches Chrome or
Chrome for Testing with Lucairn's bundled unpacked extension and a dedicated
Lucairn Protected ChatGPT browser profile. This keeps the supported
ChatGPT consumer path explicit instead of opening the user's normal browser
and implying that any chatgpt.com tab is protected.
For release smoke, run the signed-in protected ChatGPT.com profile with Chrome remote debugging enabled, then run:
script/chatgpt_web_smoke.mjs \
--cdp-url http://127.0.0.1:9222 \
--report artifacts/chatgpt-web-smoke/latest.jsonThe smoke test submits a real prompt, asserts that the actual
/backend-api/f/conversation POST contains a Lucairn placeholder and not the
local sensitive value, checks that the visible response contains the local
value with no placeholders, reloads the conversation, and repeats the visible
placeholder check. Native ChatGPT Desktop remains outside v1 support because
the app rejects local TLS inspection before chat can run.
The VM app matrix includes the same harness. It launches Chrome or Chrome for
Testing with the dedicated Lucairn Protected ChatGPT profile, Lucairn's
extension, and DevTools on LUCAIRN_CHATGPT_WEB_CDP_URL when no debug browser
is already reachable:
script/vm_app_matrix.sh --chatgpt-web-smokeThe profile still has to be signed into ChatGPT for a real conversation smoke.
Use --chatgpt-web-profile-dir DIR when you intentionally want a different
signed-in test profile, or --skip-chatgpt-web-launch when a browser is
already running with DevTools exposed.
script/claude_web_smoke.mjs is the matching opt-in harness for the
extension-backed claude.ai path. It drives a signed-in protected Chrome
profile through DevTools, submits a real Claude prompt, and asserts that the
outgoing /api/organizations/.../chat_conversations/.../completion POST is
placeholder-redacted while the visible answer is locally relinked.
The VM matrix keeps this smoke off by default while claude_browser remains a
diagnostic / Coming Soon row. To require it after signing into claude.ai in
the protected Chrome profile, run:
script/vm_app_matrix.sh --claude-web-smokeThe setup wizard's last screen (Step 5 — Routing) carries an "Auto-start at login" checkbox. With it ticked (default ON), clicking Continue:
- Creates
~/Library/Logs/LucairnSensitiveMode/AS THE USER (no admin prompt; the user already owns~/Library/Logs/). A symlink-safety check refuses to install if the path already exists as a symbolic link — closes a same-UID pivot to a system directory. - Fires an osascript admin prompt that ONLY:
a. Writes the LaunchDaemon plist to
/Library/LaunchDaemons/eu.lucairn.sensitivemode.plist(root-owned 0644). b.chmod 644the plist. c.launchctl bootstrap system /Library/LaunchDaemons/eu.lucairn.sensitivemode.plist.
Re-running the install path on an already-loaded daemon is idempotent —
the plist is overwritten and launchctl bootstrap exits 37 ("Service is
already loaded"), which we map to ok rather than a red error banner.
The plist:
Label:eu.lucairn.sensitivemodeUserName:root(required for:443bind)RunAtLoad: true (auto-start at boot)KeepAlive: true (restart-on-crash)EnvironmentVariables.LUCAIRN_LISTEN_ADDR:127.0.0.1:443EnvironmentVariables.SUDO_UID/SUDO_GID: the invoking user's UID/GID, so the H-2 file-ownership preservation logic chowns rotated state files (control-token, conversations.db, provenance.db) back to the user even though launchd (NOT sudo) spawned the daemon.
launchctl list | grep lucairn
# Expect: <pid> 0 eu.lucairn.sensitivemode
launchctl print system/eu.lucairn.sensitivemode | head
# Expect: state = runningThe menubar's Sensitive Mode settings panel carries an "Uninstall auto-start" button. Clicking it fires an osascript admin prompt that:
launchctl bootout system /Library/LaunchDaemons/eu.lucairn.sensitivemode.plist(silenced; returns 36 when the daemon isn't loaded — benign for uninstall).rm -f /Library/LaunchDaemons/eu.lucairn.sensitivemode.plist.
Both steps are idempotent: re-running uninstall after a successful uninstall is a no-op (returns 0).
LaunchAgent in ~/Library/LaunchAgents/ runs as the user and can't bind
:443. LaunchDaemon in /Library/LaunchDaemons/ (root-owned) runs
system-wide and can specify UserName=root. Auto-start of :443 thus
requires LaunchDaemon. The H-2 chown-back logic ensures user-created
state files don't inherit root ownership.
In v1, auto-start is per-installer. The user who runs the wizard's
"Auto-start at login" checkbox is the user whose ~/Library/Logs/
LucairnSensitiveMode directory the daemon writes to, and whose UID/GID
the daemon chowns back to via the H-2 file-ownership preservation
plumbing. On a shared workstation with multiple macOS user accounts, only
ONE user can have the LaunchDaemon's SUDO_UID / SUDO_GID set to their
account at a time — the daemon will run system-wide but only owns its
state files for the installer. Multi-user shared workstations are not
supported in v1; teams that need per-user isolation should run the
daemon on each user's laptop separately. A multi-user-aware path
(per-user LaunchAgent on a non-privileged port + a single shared root
LaunchDaemon for :443 that fans out via UID-aware routing) is a
follow-up workstream.
Auto-start on Linux (systemd user units / polkit-fronted helper) and
Windows (Service Control Manager) are queued as follow-up workstreams
(LSM-launchd-linux / -windows). On those platforms the wizard
currently surfaces a platform_unsupported envelope; users run lucairnd
manually until the per-platform paths land.
See CONTRIBUTING.md. The repo is private during Phase 0 / v1 build; broader contribution opens at v1 launch.
MIT — see LICENSE.