Repository navigation
Conversation
Today any process running as the user can connect to the agent socket and obtain credentials for any logged-in role, and a squatter on the socket can receive OAuth codes from clients. This records the design for closing both, so the Linux implementation and a separate macOS implementation can follow one document: - mutual client/agent verification anchored in a setgid marker group (Linux/BSD) or code signatures (macOS), with `mairu harden` and re-exec for non-packaged installs - platform authorization (polkit, LocalAuthentication, or FIDO touch through the forwarded ssh-agent) with per-session grants and per-exec leases - integrity-protected authorization policy whose values stay in $HOME, recorded in a group-only directory and Secret Service - a mairu.platform protobuf contract, served in-process or by a mutually verified helper, plus notifications and opening URLs - headless operation, with tmux over SSH as the reference workflow Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Notifications and URL opening carry no security weight, so they don't need to be the agent's decision. Clients now emit every event and pick how to deliver it: their own backend, the agent (and through it the platform helper), a user-supplied helper command, or nothing. The choice lives in unprotected config.json, environment and flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pipe-pane -O and capture-pane only see pane output, and password prompts disable echo, so they never see the password. The real paths are input-side: reading the user-owned pty, tmux key bindings set through the server socket, fake prompts, and ptrace when Yama allows it. The same applies to sudo in the same pane, which uses the same login password, so sudo escalation is now stated as out of scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A homegrown rule format would be one more thing to learn. Cedar keeps IAM's evaluation model (default deny, forbid overrides permit), comes with schema validation and tooling, and is a Rust crate. A human check becomes context.presence, much like MFA conditions in IAM: the agent asks only when some fresh presence would make the policy permit the request, and remembered grants and leases are just other candidate presences. Unapproved files contribute only their forbid policies, so they can only tighten. Settings that aren't policies move to authorization.json. The schema and every policy in this spec were validated with cedar-policy 4.14.0 in strict mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every anchor on a hardened host is root-owned, so the design is only as strong as the user's path to root: sudo and polkit passwords, NOPASSWD, root-equivalent groups, and on WSL2 `wsl.exe -u root` via interop. Spelling that out exposed a gap: approval fell back to whatever methods were in effect, so a captured password could approve a policy registering someone else's key. Approval now uses only the anchor stored with the last approved integrity record. WSL2 gets integrated (interop on, Windows Hello via a KeyCredential helper, a guardrail only) and isolated (interop off, ssh-agent over `ssh -A`, native-Linux strength) profiles, with open questions that need a WSL2 host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The combined spec mixed three concerns with different reviewers and lifecycles: - hardening.md: threat model, trust chain, marker group, `mairu harden`, client/agent mutual verification, WSL2 profiles, macOS signing, and the stack order for all layers - policy.md: when the agent asks, grants and leases, presence methods, the Cedar policy, integrity states and the approval anchor - platform-helper.md: the Platform trait, mairu.platform protocol, helper mutual verification, polkit, integrity stores, notifications/URLs, headless and macOS backends Content moved as is, with section references rewritten to point to the right file. This also repairs the digest definition, which the previous commit had mangled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each numbered section of the three specs now opens with a short show-me style visual: pseudocode, call trees, file trees, Mermaid sequence, flow and state diagrams, or a diff. They summarize the section for a first read; the existing text is untouched and stays normative. Every Mermaid block parses with mermaid 11.4.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeping platform-service code out of the process that holds session tokens shrinks what a bug in D-Bus, Secret Service, portal or LocalAuthentication handling can reach, and having one IPC path on every OS removes in-process/IPC branching. - the Platform trait splits into ProcessPlatform (peer credentials, process facts; in-process everywhere) and PlatformServices (the PlatformHelper contract; only ever HelperClient in the agent) - the helper shares the agent's lifetime over an inherited socketpair: no filesystem rendezvous, parent checked by the helper, child trusted by construction, EOF ends it - clients make one connection and reach services only through agent RPCs; convenience modes are auto|agent|command|none, with a SessionHint so the helper can reach the client's desktop session - macOS uses the same binary as its helper and plans no app bundle - the helper layer moves right after install in the stack Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Official builds will carry an Apple Developer ID, so macOS can rely on a team-based identity that is stable across versions instead of pinning cdhashes and root-owned copies. Builds without one still need to work, for packagers and for contributors outside the developer program. - hardening §9: official, third-party and ad-hoc tiers, decided at run time from the binary's own signature; a security floor in a signed embedded Info.plist against downgrades to old releases; the release pipeline; harden for ad-hoc builds; Homebrew left open pending research - hardening §15: dev profile isolating contributor builds from an installed mairu, Linux and macOS contributor workflows without the developer program, CI jobs, and maintainer checks before release - platform-helper §11: embedded Developer ID notifier for official builds, locally assembled ad-hoc notifier otherwise; keychain store per tier Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Homebrew only re-signs Mach-O files it rewrites, casks are never re-signed, and homebrew/cask now disables casks failing Gatekeeper, which a notarized build passes. A cask in the tap is therefore the proposed shape for official builds, with a from-source formula under another name; the choice stays open until the tap changes. The research also showed ways the notifier could break: Gatekeeper rejects modified notarized bundles, App Management can block another team from touching one, a quarantined parent can quarantine what it writes, and `open -a` resolves by name. Extraction now uses a fresh directory per version and flavour, strips quarantine, checks the designated requirement, launches by full path, and uses a bundle id per flavour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Homebrew gets a cask for the signed release binary, with the from-source formula renamed to mairu-src. aqua, mise and ubi keep installing the release tarball unchanged: the binary carries the notifier and extracts it itself, so installers need no bundle awareness. Those installers run versions side by side, so removing notifiers of other versions could break a version still in use; cleanup now removes notifiers that went unused for 30 days instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Official-only properties are checked by a maintainer as a releng checklist step before publishing, and pull requests touching the signing pipeline get a manually triggered dry run on their branch, since forks never receive the release secrets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Floor" did not say what is being bounded. The value is the oldest mairu a peer will talk to, so name it minimum-client-version (Cargo.toml metadata) and MairuMinimumClientVersion (signed Info.plist). It is raised by releng only in releases containing a security fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A flaw confined to the agent's own handling is fixed by upgrading the running agent and needs no lockout of older clients; only client-side and protocol-level flaws justify refusing older peers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The built-in policy capped every presence at 12 h, so a long-running `mairu exec` would hit a prompt on its next credential refresh and fail when unattended. Leases now last the process lifetime under policy control, but lapse after the process goes 1 h without credentials, counted from the expiry of the last ones it obtained on demand. The sidecar's auto-refresh pre-fetches regardless of use, so it marks those requests; they never move the idle clock and never raise a prompt, returning PRESENCE_REQUIRED instead. IDE callers keep the 5-minute idle by default, with a documented policy to relax it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Splitting the helper by session-bus exposure was considered and declined in favour of the simpler process model. The residual risk (session-bus input sharing a process with authorization answers) is recorded in the security table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
What an SSH agent requires before signing (a touch, a confirmation, Touch ID, or nothing) is invisible to mairu, so insisting on FIDO keys judged something mairu cannot know. Any registered key type is now accepted: registering a key states that its use needs the user's approval, and choosing such a key is the user's responsibility. sk- keys still have their user-present flag verified, since that is the one lock visible in a signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Measured on a Windows 11 25H2 host: - `wsl.exe -u root` gives root without a prompt from inside a distro with interop on, and from any Windows process or interop-enabled distro regardless of the target's setting, so the isolated profile only protects against code inside that distro - a Hyper-V socket channel needs an admin-registered service GUID and carries no peer credentials, so it is not the default - WSLg has no Linux browser, xdg-open or notification daemon, so `open_url` uses rundll32 url.dll,FileProtocolHandler (explorer.exe fallback, whose exit status lies) and `auto` honors $BROWSER Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Listing other distributions needs interop, which an isolated distribution lacks, so doctor states the limit instead of checking it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Measured on macOS 27.2: - LOCAL_PEERTOKEN and LOCAL_PEERPID follow the last process to do I/O, unlike Linux SO_PEERCRED; lending a connected fd to a genuine signed mairu makes its token stand in for the attacker, so one check per connection is unsound. Q-M10 opens the transport question: re-check per read, or XPC with per-message audit tokens. - The legacy keychain gives no integrity: same-user code deleted an item and planted a forged one trusting only mairu, read without a prompt. The keychain store is now weak, and strong integrity needs the protected directory through harden; the data-protection keychain needs an entitlement an unbundled binary lacks. - Socketpairs report the creator until the child does I/O (Q-M1), and notifier results so far are interim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
LOCAL_PEERTOKEN reports whichever process last did I/O on the socket, so a genuine signed mairu lent an attacker's connected fd passes the check. Re-checking after every read only narrows that to a race. XPC stamps every message with its sender's audit token, so macOS clients talk to the agent through a per-user LaunchAgent Mach service instead, verified on every message. Running the agent in gui/<uid> also puts LocalAuthentication prompts in the GUI session (Q-M2). How SSH-only logins reach the service is left open as Q-M11. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
The legacy keychain gives no integrity: same-user code can delete the record and plant a forged one that mairu accepts. Counting it as approval would let that code relax a policy which signed macOS builds otherwise enforce. Every tier can now get the protected directory through `sudo mairu harden`, as on Linux. Developer ID builds additionally run the platform helper from a signed bundle carrying keychain-access-groups, so the record lives in the data-protection keychain, where only the team's entitled code can touch it, without root. With two strong stores, records gain a generation so a store that missed an approval is brought up to date rather than read as tampered. Weak stores approve only on unhardened agents. Recovery on macOS goes through an administrator authorization, since root cannot reach the data-protection keychain item. Also record the last WSL2 findings: Windows Hello signatures are RSA PKCS #1 v1.5 with SHA-256 (with a test vector), and Windows OpenSSH's FIDO support does not offer Windows Hello as an authenticator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Windows Hello is offered to ssh-keygen once the setting to save passkeys to this device is on, and the platform credential is created. OpenSSH_for_Windows 9.5p2 then rejects its self-attestation, the same failure as a third-party passkey provider, so the isolated WSL2 profile still has no Windows Hello. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
From the unlocked re-run on macOS 27.2: - A Background job in user/<uid> reaches a gui/<uid> Mach service while a console session exists, so clients bootstrap the agent into gui/<uid> and fall back to user/<uid> only without a GUI login. - LocalAuthentication shows its sheet on the console from either domain, but the administrator dialog needs gui/<uid>. - Ad-hoc notifiers start out denied with no prompt and must be turned on in System Settings; the permission is keyed by bundle id, so it survives rebuilds. Name the flavours apart and ship an icon. - Secure Enclave key blobs can be used by any binary, so they would prove only that someone was at the Mac; they are not used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Same-user code can start any genuine binary, including an old official agent with an agent-side flaw, and current clients would accept it as long as it met the minimum client version. Raising that value for agent-side flaws would also lock out older clients that were never at fault, so clients get their own minimum for the agent instead. Also document SSH agents that ask for Windows Hello before signing as an option for isolated WSL2, and have the Homebrew cask refresh an existing hardened copy on upgrade, without failing the upgrade when sudo does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
With root and a real SSH session on macOS 27.2: - A setgid exec by a user outside the group makes a restricted process (P_SUGID, DYLD_* stripped, task_for_pid refused), so the marker group carries over (Q-M9). Ad-hoc re-signing is deterministic, so the harden copy adds no identity of its own. - From SSH, clients reach and bootstrap the gui/<uid> LaunchAgent while a console login exists (Q-M11); the administrator dialog fails with -60007, and the notifier shows on the console. - LocalAuthentication shows its sheet on the console for requests from SSH as well. Such requests keep the platform sheet by default, with the origin in the reason text. The agent derives it from the peer's audit session and exposes it as context.console_session, so a policy can forbid it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
No unmanaged Mac and no Developer ID are available yet. Q-M11 without a console login, Q-M3's MDM question, and the official-build checks (Q-M12, the Developer ID notifier, harden on a notarized quarantined binary) are deferred. The design does not hinge on them: the client falls back to user/<uid> whenever gui/<uid> cannot be bootstrapped, and a helper bundle that does not work falls back to harden alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
The notifier was kept apart from the helper bundle on the assumption that notifications need a LaunchServices launch, which cannot pass the helper's socketpair. That was never tested: what is confirmed is only that the poster must be a signed bundle's main executable. If a directly exec'd bundle can post notifications, the notifier is merged into the helper bundle, and ad-hoc builds assemble an unentitled helper bundle so the helper is always a bundle on macOS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
A directly exec'd bundle executable can use UNUserNotificationCenter once the bundle is registered with `lsregister -f`; the separate notifier existed only because the earlier tests launched it through `open`, which registers as a side effect. The helper bundle now posts notifications itself, so macOS has one bundle per flavour. Builds without an embedded bundle assemble an unentitled one from their own binary, recording the agent's cdhash in the signed Info.plist so the helper can verify its parent, and the agent checks the running child against the cdhash it expects. A notification click starts the bundle without --fd; that instance exits at once. Also pin down the extraction rules: per-version directories (plus the agent's cdhash for assembled bundles), a separate base directory for the dev profile, a tar archive embedded in the signed binary, the agent writing the bundle at startup and `harden` writing its own root-owned copy, and how concurrent writers settle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
The marker was looked up by name and compared with the effective gid. Taking it from what this exec's setgid bit actually granted, confirmed by a reverse lookup, keeps another NSS source's `mairu` group from shadowing it, and AT_SECURE keeps an inherited effective gid from counting. Gids 0 and 65534 are never a marker. A password on the group would let anyone who knows it `newgrp mairu` and hold the marker as real and effective gid, which both passes the peer check and opens the protected directory. `harden` removes such a password, and the agent, clients and platform helper reject peers whose real gid is the marker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
A one-time suggestion is easy to miss and never comes back. Show it again at most once per terminal session, every 7 days by default (`harden_suggestion.interval_days`, 0 for every session), unless turned off through config.json or MAIRU_NO_HARDEN_SUGGESTION. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
A week between reminders is long enough to forget the suggestion entirely. Default to one hour (`harden_suggestion.interval_secs`), counted across all terminals; 0 keeps the once-per-terminal-session mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
The detector already matched process ancestry and environment names; only its output was hard-wired to "AI agent". Requests now carry labels in context.tools, configured per label in tool_detection, with `ai-agent` as the built-in one that the built-in policies use. Labels are a guardrail: they can be evaded and forged, and policies may use them to restrict or to grant knowing that. Humans can also mark a terminal session with `mairu lock --tool <label>`; the mark follows the ancestry into tools' tty-less sessions, and removing it early always asks the human, since the marked tool could otherwise do so itself. The hardening/ai-agents layer becomes hardening/tool-detection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Before fetching, the agent knows only the server and the role name, and a custom server's role names need not say which account they open. Authorization now runs twice: gate 1 (AssumeRole) as before, so a refused request fetches nothing, and gate 2 (UseCredentials) once the credentials are in hand, with sts:GetCallerIdentity cached alongside them and the role's Account parent set to the real account. Gate 2 runs the full decision procedure, so a forbid that a fresh check would satisfy makes the agent ask then, showing the real identity. Always-on built-ins keep gate 2 open by default and refuse credentials whose account differs from the one the role name declares. The STS endpoint comes only from approved settings, never from the environment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Policies name credential servers by id, but servers.d was not protected: same-user code could point an approved `dev` at the production credential server and wait for the next routine login, after which dev's relaxed rules would apply to production. Each server's identity fields are now digested into the approved record. A request uses the stricter of the policy's state and its server's: a server not yet approved gets only built-ins and forbids, and an approved server whose config changed is locked down, without affecting the others. The config checked is the one captured in the session at login, since that is what fetches credentials. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Implementers read and execute the specs in order, one per gh-stack layer, so the three topic specs become 19 numbered ones following the spec template. 001 is the shared threat model and roadmap, 002 to 016 are the Linux layers, and 017 to 019 are for the macOS implementer. - 002-build-layout is new: LTO and stripped release builds, a default `cli` feature gating client and agent code so the platform helper binary stays small, CI size reports, and per-architecture macOS releases instead of the universal binary - gate 2, policy approval and Windows Hello become their own layers, and notify moves before wsl2 - diagrams are Mermaid Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Diagrams translated literally from the ASCII summaries mostly listed items instead of showing a flow. Each one is now redrawn to fit GitHub's page width, replaced with a table where it was a mapping, or dropped where the text right after it already says the same. - the hardened copy lives under a configurable harden home (`--home`, `MAIRU_HARDEN_HOME`, `harden_home`): `/usr/local` on Linux, and `/opt/mairu` on macOS, where Homebrew makes `/usr/local` writable by the user on Intel Macs - Linux release binaries embed the Windows Hello helper for the matching architecture and write it out on first use Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
mise, aqua, ubi with --extract-all, Homebrew casks and distribution packages all keep files next to the binary, so release archives carry the macOS helper bundle and the Windows Hello helper beside mairu instead of embedding them. Builds from source ship none and keep the fallbacks they needed anyway: an assembled bundle on macOS, a configured path on WSL2. This drops the helper from every binary and the build-order dependency in the release workflow. - 002 defines the lookup: next to the canonical executable, then ../lib/mairu/ for packages; harden copies the helpers it finds - the Windows Hello helper is pinned by SHA-256 (recorded by release builds, or approved in authorization.json), since a replaced helper could have the human's gesture sign another statement - Q-M14 and Q-W6 open: registering bundles from installers' directories, and running the Windows helper from the Linux filesystem - 018: standalone ubi overwrites in place rather than keeping a directory per version Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
A security audit of the numbered specs found ways for same-uid code to defeat the design that the specs did not cover: - the agent built AWS clients from ~/.aws/config and AWS_* variables, honoured HTTPS_PROXY, and rustls-native-certs reads SSL_CERT_FILE even under AT_SECURE: a hardened agent now clears its environment to an allowlist, builds AWS clients explicitly, uses no proxy and the system roots; approved settings may add a proxy and roots (004, 009, 011, 017) - one approval obtained under false pretences could install an attacker's approval key: approval needs a terminal and a typed confirmation code, and an anchor change needs a second check and waits 24 hours, cancellable (012, 013, 019) - upgrades told users to run user-writable binaries with sudo: the installed hardened copy now verifies a sigstore attestation of the new binary and copies it without running it (002, 005, 018) - fs.suid_dumpable=1 left hardened processes traceable (003) - one platform prompt was shared by different requests with the same grant key; coalescing is now per peer and request (007) - older hardened copies counted as genuine on Linux: minimum client and agent versions, and an inventory of hardened copies (004, 005) - a repointed server was locked down rather than refused (012) Also recorded: helper-held keys do not close the integrated WSL2 gap, since root reached through interop is the same Windows user (015, 001). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
- 017 still claimed old hardened copies are unreachable on Linux, which the audit disproved; it now points at the Linux minimum versions of 004 - the rule that agent connections are O_CLOEXEC and closed before the executor runs the user's command was only a threat-table row; it is now part of the executor's rules in 003 and the 004 checklist Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
A code typed back from the terminal binds nothing that same-user code cannot reproduce in its own pseudo-terminal. Q-A1 records the alternatives under exploration, number matching through the platform prompt among them, until a design is settled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
Approval now works like `mairu exec --confirm-trust`: the client shows both files, the summary and the approval digest, and asks before any presence prompt appears (or takes --confirm <digest> for scripts). A reflexive touch has nothing to approve, and with one approval at a time, approval and credential checks are never pending together. The typed confirmation code, the second check and the 24-hour delay on approval-key changes are dropped: the code was reproducible by same-user code, and the delay made every key change wait a day with little left to protect against. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Today any process running as the user can connect to the agent socket and obtain credentials for any logged-in role, and a squatter on the socket can receive OAuth codes from clients. This records the design for closing both as 19 numbered specs under
docs/specs/, one per layer of this stack, so implementers can read and execute them in order:clifeature so the platform helper binary stays small, CI size reports, and per-architecture macOS releases instead of the universal binarymairu hardenand re-exec for non-packaged installsmairu.platformprotobuf contract, served by a mutually verified helper process that runs every platform servicepkttyagentfor headless use) with per-session grants and per-exec leases, decided by Cedar policy where a human check is a context condition like MFA in IAMsts:GetCallerIdentityai-agent, as a guardrail$HOMEthat apply only theirforbidpolicies until approved, and approval anchored in integrity records, with per-server digestshardenand the release pipeline, and a helper bundle with LocalAuthentication and keychain integrity storesEvery guarantee is bounded by the path to root, and policy approval is anchored in the protected record so a captured password cannot approve key changes. Open macOS and WSL2 questions are marked Q-M and Q-W.
This is the bottom of a stack; the implementation layers follow after these specs are reviewed.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Dg17LE9aWqsb1VBUCVcKLM