Repository navigation
docs(fork): host tools design: agents on a person's own computer (limited account, fast own seeing/acting layer, runner-side activity) - #26
Conversation
…ugh a limited OS account Design only, no code. Builds on upstream linked environments, MCP tool forwarding, the background service and server settings; the fork adds a host toolkit, a session helper in the desktop app, per-OS guard tasks and one availability setting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r-side activity, tray and wind-down, link renewal
Owner feedback 2026-11-13:
- no activity gate on host tools; activity-aware scale-down moved to runners (§11)
- tray icon with pause, wind down ("safe to shut down") and kill switch (§12)
- link expiry checked upstream (30-day MCP-client session, no refresh grant); fork option: refresh while in use, or a longer-lifetime setting (§10)
- seeing and acting: own agent-native engine, most direct route first and pixels last; batches with on-computer waits, diffed text snapshots, recipes, latency budget and benchmark, why not classic computer use (§5-§8)
- safety: per-app see/act policy, built-in refusals, indicator excluded from captures, instant take-over, held-input journal, idempotent batches (§6.5, §7.2)
- invisible by default as a tested requirement (§6.6); speculative parallel batches for side-effect-free alternatives (§7.4)
- failure table rows for wind-down, runner scale-down, take-over, mid-drag crash, half-done UI action, speculative batches
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
lens: security-design · reviewer: Opus 5.5 (t3lens-26-sec) · read at: 2f6c78c finding: BLOCKER B1. Files the agent account writes become code run as the person (§3 allowed folders, §4.1, §6.3 finding: BLOCKER B2. SYSTEM and the person's processes follow links the agent planted (§3 "inherited" grants, §4.1 daily re-apply, §4.2 deny entries, §4.3 VSS and rsync restore, §7.4 sandbox copy and class-3 restore, host_undo). Attack: in a rw folder, the agent account creates a junction or symlink to C:\Windows\System32, /etc or the person's profile, or a hard link to a file of the person's. The daily guard task (SYSTEM) re-applies the inherited Modify grant through the junction, which gives the agent account write access to system files and so admin. Or host_undo or the rsync restore (root) writes the snapshot content through a path the agent swapped for a symlink. Or the helper (as the person) applies ACLs or copies through the link. Required: every operation by SYSTEM, root or the person on a path inside an agent-writable folder opens without following reparse points or symlinks (FILE_FLAG_OPEN_REPARSE_POINT; O_NOFOLLOW; openat2 RESOLVE_BENEATH|RESOLVE_NO_SYMLINKS). It refuses files with a link count above 1. It checks that the final resolved path is under the allowed root before it writes or sets an ACL. Recursive ACL application stops at reparse points. Add a per-OS test: a planted junction, symlink and hard link do not widen any grant or redirect any restore. finding: BLOCKER B3. The agent can widen its own policy through act apps (§6.5 refusals, §4.5 PAUSED file, §1 "a file only you and admins can change", §14 "Clicking its own permissions wider"). The refusal list covers the policy screens only. The policy JSON and the PAUSED file can be changed by the person's account, and so by any finding: MAJOR M1. The T3 server shares an account with the code it runs (§3 "How commands run", §6.1, §4 intro). host_run, delegated agents and speculative runs all run as the same account that owns the computer's T3 data (auth DB, issued sessions, link ceilings), its provider logins and possibly its install files. A hostile build script can raise a read-only link's ceiling, mint MCP sessions, read any outbound link credential the computer's T3 holds, steal model API logins, patch the T3 install so it keeps running at boot, and call the engine channel directly with fake caller details. Required: commands run under a second, unprivileged identity (a second account, or AppContainer on Windows, a sandbox profile on macOS, a separate uid or user namespace on Linux) that cannot read or write the T3 server's data, secrets or install. The install is owned by an admin and read-only to both. The engine pipe or socket admits only the server's identity, not the command identity. The computer's T3 holds no outbound link to the agent machine. If that is not taken, §14 must state that one hostile command owns the computer-side T3 and its link state. finding: MAJOR M2. The OS does not enforce speculative sandboxes for files and IPC (§7.4). "run steps marked read-only" is the agent's own claim. Nothing says how a finding: MAJOR M3. The network block misses loopback, the tailnet and metadata addresses (§3 Network, §10). "Your local network blocked except the linked machine" says nothing of 127.0.0.1 and ::1, so the agent account reaches every local service of the person and of other accounts: a desktop T3, dev servers, browser debug ports, a container API, sync GUIs, a fleet dispatcher. A per-account firewall rule for "the LAN" usually leaves out 100.64.0.0/10 and fd7a:115c:a1e0::/48 (other tailnet nodes, including hypervisor hosts), link-local, 169.254.169.254 and the tailnet DNS. Required: the default-deny list for the account includes loopback (except the T3 server's own port), RFC1918, CGNAT/tailnet v4 and v6, link-local, ULA and multicast, with explicit allow entries. The first-install verify probes each class. The computer's T3 gives loopback callers no trust or bootstrap (other local accounts are remote clients). finding: MAJOR M4. The clipboard and the scope of redaction (§6.2 "clipboard if asked", §6.3 finding: MAJOR M5. Synthetic input has a time-of-check to time-of-use gap (§6.3 "before each input step the engine checks …", §8.1 "1,000 characters ≤ 150 ms"). The check happens once per step, then a burst of SendInput or CGEvent follows. A notification, an app that steals focus or a window the person opens takes the foreground mid-burst, and the typed text lands there: in a terminal it runs as the person, in a finding: MAJOR M6. Any thread on the agent machine can call host tools (§9 "a non-plan caller", §4.6). The agent machine runs many agents, including ones that read untrusted input (public PR text, issues, web pages, CI logs). Attack: an injected instruction in a PR body read by a review thread leads it to call tool_search, then host_do on the person's desktop. The availability setting is per computer, not per caller. Required: host tools are granted per project or thread on the agent machine (default: none, the person grants them), and a thread whose turn includes untrusted outside content gets read tools at most unless granted. The computer's logs record the calling thread or project id that the agent machine forwards, and the tray shows it while acting. finding: MAJOR M7. Refresh tokens have no theft controls (§10 option 1). Under the doc's rules, a stolen refresh token lives forever as long as the thief keeps using it. "Used in the last 7 days" is judged by the client. Reuse of a rotated token outside the 60 s grace is not handled. There is no absolute lifetime. Required: the computer (the server side) tracks last use. A rotated token presented after the grace revokes the whole token family and raises a tray alert (RFC 9700 refresh-token reuse detection). The family has a maximum lifetime (a setting, for example 90 days) after which the link must be paired again. Refresh is bound to the link's client identity. A revoke issued while a refresh is in flight wins. Each renewal is logged and shown in the tray. finding: MAJOR M8. Some §15 rows fail open, and some rows are missing (the rerun / half-way / old-state question). (a) "snapshot store full: writes and runs go on": a hostile agent first fills the disk or VSS area so old shadows are evicted, then deletes files with no undo. VSS also keeps 64 copies, while the stated cadence makes about 96 a day. Required: when a snapshot cannot be taken, or keeping it would evict one younger than the retention, writes and runs are refused. The agent account gets a disk quota. (b) Secret deny entries rely on a live watcher. Files created while the helper is down, before the first pass, or by an overflowing watcher are readable until the daily re-apply. Required: on helper start (and after a watcher overflow) a full deny pass runs before file tools are enabled, and the server refuses file reads while the pass is not current. (c) Install half-applied (firewall rule missing, deny pass not done, account in a wrong group after a later change) is not a row. Required: host tools stay off until the install verify passes, and host_status re-runs it. (d) The helper dies while the engine lives: §6.5 says the helper draws the indicator, and §15 "engine or helper dies" says the indicator goes with the engine. Required: the engine stops acting within a set time when the helper's heartbeat stops, so it never acts without an indicator. (e) Missing rows: turning host tools off or uninstalling (folder grants, deny ACEs, firewall rules, tasks, refresh tokens, the account), and a folder removed from the list while an agent process holds an open handle (the kill or restart that removal needs). finding: MAJOR M9. host_undo can wipe the person's own work (§4.3, §9). The watcher records every writer, and undo "since 14:05" restores every changed file, so an agent's undo (injected or confused) reverts the person's edits made in that window. Required: an agent-called host_undo restores only files whose changes since the time came from the agent account (writer recorded per change), and compares each file against the hash it had at listing before writing. Restoring files the person changed is offered only in the tray. finding: MAJOR M10. The agents' browser runs as the person and drives it over raw CDP (§6.3 Browser, §6.4). The engine runs as the person, so this browser can open file:///<the person's profile>, download anywhere, and run page script. Any web page it visits is an injection source with the person's file rights behind it. Required: the agents' browser runs as the agent account (or under the M1 command identity) when headless. When visible, file:// and other local schemes are blocked by browser policy, downloads are pinned to a rw folder, and there are no extensions and no sync sign-in. CDP is exposed only as typed verbs, never passed through. State which of these applies to bridge-driven browsers. finding: minor m1. Pause does not stop everything (§4.5): "running host_run jobs are stopped" through a job object or process group, but a daemonised child (setsid, or a scheduled run in its own session) survives pause and resume. Required: pause ends every agent-account process except the T3 server, or the doc says only the kill switch is complete. |
…put ladder Owner scope A (whole computer except sensitive things, one batched ask), separate host and agent accounts with a command broker, link-safe file operations, network deny classes, guard-owned policy store, writer-aware undo, a four-rung input ladder, private-copy speculative runs sized from free hardware, long link lifetime with one-click extension and device binding, runner budgets from measured free hardware, wind down without a timer, power-loss recovery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
lens: security-design r2 · reviewer: Opus 5.5 (t3lens-26-sec-r2) · read at: 57f3917 r1 status:
finding: BLOCKER N1. Any app's file dialog, launcher or link runs agent-written files as you. This gets around the file-type rule and the full-user-rights list (§4.3 rule 4, §4.2 full-user-rights row, §6.3, §14 first row, §14.1 B1a). The file-type rule covers only the engine's own finding: MAJOR N2. Forwarded upstream tools other than delegate_task still start work as the host account (§3.2, §2 table "delegate_task … off in v1", §14 M1 row). §3.2 switches off delegated agents because a provider would run as the host account and could reach T3's data. But any upstream tool that takes finding: MAJOR N3. The auto-run denies (§4.3 rule 3) can be bypassed by renaming or newly creating the folder, and IDE auto-sync makes "nothing the agent wrote runs as you" untrue (In short, §4.3 rule 3, §14.1 B1e). (1) The deny entries sit on file objects, not on paths. The inherited Modify grant gives DELETE, and so rename, on the finding: MAJOR N4. The guard trusts the helper's word that consent happened, and a full-rights grant leaves running code behind (§4.2 "Confirmed where the agent cannot click", §4.3 rule 5, §14.1 B3c). The store is written by the guard "when the helper asks". The OS consent prompt is shown by the helper, which runs as you. During a full-rights grant (or through N1), code running as you can inject into the helper or the engine, or call the guard's channel directly, and ask for a widening with no prompt. The guard cannot tell. The grant is scoped "this task", but code that ran as you can add a Run key, a Startup entry, a scheduled task, a LaunchAgent, a finding: MAJOR N5. The computer's own non-loopback addresses and global IPv6 on the LAN are not in the deny classes (§3.5). The loopback rule says the agent account reaches only its own listeners. But your services usually listen on all addresses. The computer's own global IPv6 address, and other LAN devices with global IPv6 addresses (SLAAC on the same /64), fall in none of the listed classes, so they count as "the public internet". Attack: a hostile build script run as the agent account connects to [own global IPv6]:<a dev server, a browser debug port, a container API or a sync UI's port>. It does the same to a NAS or router on the LAN by its global address. Required: the deny list includes every address of every local interface (both families, tracked when addresses change) and every on-link IPv6 prefix. The install verify probes your own global address and an on-link neighbour, and refuses to enable host tools when either answers. finding: MAJOR N6. Speculative apply and macOS slot users get around the denies (§7.4). (1) §7.4 does not say who applies a winner's layer to the real folders. It names the §3.4 link-safe operations, which are for privileged writers. If the guard, the helper or the engine applies the layer, the agent account's OS deny entries do not hold. A layer that contains finding: MAJOR N7. New input-ladder back ends open new surfaces (§6.4 rungs 3-4, §3.1). (1) Rung 3 on Windows offers "a loopback RDP session of the agent account". That conflicts with §3.1, which denies log on through RDP and keeps the account out of Remote Desktop Users, and with "no password anyone types". To make it work, the RDP listener would be opened to the LAN and tailnet with a login for the agent account. (2) Rung 4's Linux fallback, "uinput through a root-owned guard device", gives input at kernel level. If your account can reach that device, every process running as you can inject input past Wayland's input isolation. That input reaches the lock screen, polkit agents and VT switching, and the engine's target checks cannot hold it back. Required: drop loopback RDP, or require the listener bound to loopback only, NLA, a guard-held random credential and its own install-verify check. Drop the uinput fallback, or make the device reachable only by a guard-owned process that applies the §6.7 refusals itself. finding: MAJOR N8. On power loss, restore and fallback can turn narrowing back into widening (§12.3, §15 "protected store edit" and "power loss" rows). (1) "Falls back to the last good version" when the signature fails can bring back an older store with grants you revoked since, an auto-run unlock you withdrew, or no pause. (2) If you press Revoke, Deny or Pause and the tray confirms before that write is durable, a power cut brings the grant back on the next start. Required: a narrowing write (revoke, deny, pause, kill switch, narrowing the reach map) is fsynced before the tray confirms it. On fallback, the guard replays every narrowing record in its own log since that version. If it cannot, it starts with no grants beyond the built-in defaults and with pause on, and the tray says so. Add both to §15 and to the power-cut test. finding: MAJOR N9. The undo guarantee is overstated under heavy agent writes (§4.3 rule 6, §14 "Damage to your folders"). The quota bounds bytes stored, not blocks changed. VSS and copy-on-write snapshots grow with every overwritten block. Attack: the agent deletes or damages files at T. It then rewrites one large file inside its quota over and over. The snapshot store hits its tight cap, and after the 1-hour floor the snapshot from before T is evicted. So "any file can go back … within 24 hours" holds only for about the floor time, and §14 does not say so. Required: state the real guarantee: 24 h normally, at least the floor (1 h) under heavy writes. The tray warns when a snapshot is evicted before 24 h. Optionally, refuse agent writes when eviction would drop the newest snapshot that comes before an agent delete or overwrite that is not yet undone. finding: MAJOR N10. Background runners on a personal PC have no stated isolation (§11, §2 "Next to the fork's fleet runners"). §11 brings CI jobs and other projects' runners onto the person's PC. Only "separate accounts" is said. A CI job runs repository code, maybe from a PR. Nothing gives runner accounts the §3.5 network deny classes (loopback, LAN, tailnet), a disk quota, no access to your session, or the §3.3 reach check. A hostile PR job could reach your dev servers, other tailnet nodes and every world-readable folder. Required: runner slot accounts get the same deny classes, quota, session isolation and reach check as the agent account, checked by the install verify. Or §14 states that runners on a personal PC are a separate trust zone with its own rules, and names those rules. finding: minor n1. Linux |
… internet Folds in the owner decisions of 2026-11-13 on the second security review (blocker N1, majors N2-N10, minors n1-n8): acting in the UI acts as you, with OS-enforced protected areas, OK per action and an activity log; a forwarded-tool allowlist; auto-run quarantine and rename denies; frozen copies for approved runs; no direct internet for the agent group; durable narrowing; take-over pause with no idle timer; 365-day bound links; runner isolation. Failure table, tests and open points updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
lens: correctness · reviewer: Opus 5.5 (t3lens-26-cor) · read at: 9fde35b finding: major · §10 rule 1-2, L1064-1072 · Extending a link does not reach the linking side. The linking side stores finding: major · §4.4 "Who enforces it", L528-529, and §3.3 reach check L217-219 · The doc says protected areas are OS-enforced because writing to system areas needs elevation. That is false for some of the listed areas. On Windows, finding: major · §6.4 rung 3, L672, and §6.5 L714-717 · The design cannot drive rung 3 in the agent account's own window station (Windows) or display (Linux Xvfb as the agent account). The engine runs only as you, in your session (§6.1). UIA, finding: major · §4.3 rule 6, L435-436 · On Linux without btrfs/ZFS/LVM-thin, the fallback is "a copy of only the files the agent changed". That cannot be made before the change for finding: major · §4.3 rule 2, L374-376, and §9 L1007 · The fail-closed deny pass gates only the file tools ("before file tools are enabled", "the server refuses file reads"). finding: major · §3.5 L247-248 · "Name lookups go only to the tailnet's resolver, so DNS cannot carry data out" is wrong. MagicDNS (100.100.100.100) forwards names outside the tailnet to the global resolvers, so finding: major · §3.5 L247, §14 row M3 · "Only your own linked machines" opens every port on them, including the agent machine itself: its T3, SSH, databases and dev services listening on the tailnet. A hostile build script on the computer then gets a lateral route to the server. The floor protects only the computer's own services. Fix: allow per linked machine only the services named in config (for example the git server and package cache ports), default none. Name the residual in §14. finding: major · §4.2 L325-326, §12.3 L1217, §15 (missing row) · A per-goal network opening and the one-address exception are firewall state that outlives its grant. §12.3 ends "this task" grants on start, and pause ends them too, but nothing says the allow rules are removed. After a crash or power cut the agent account keeps the opened network (fail open). Fix: open the rules with a ledger line written first. Remove them on start, on pause and on the kill switch, before the agent account's network comes up. Or use WFP dynamic-session filters that vanish with the guard's session. Add a §15 row and a §16 power-cut test. finding: major · §4.4 L530 layer (3) and L532-537, §4.3 rule 8 L474, §15 (missing rows) · The agent-caused process set and the per-grant persistence baseline are runtime state with no §15 row. A guard restart or power cut in the middle of a grant loses (a) the marks, so the kill switch can no longer end "every agent-caused process running as you", and (b) the start baseline, so the end-of-grant diff never runs. Fix: journal the marks (pid plus start time, so reused pids are not hit) and the baselines in the guard's ledger. On start, re-attach to the marked processes that are still alive. For an unfinished grant, diff against its baseline (or, if the baseline is missing, show every persistence entry newer than the grant start). Add §15 rows and §16 tests. finding: major · §4.3 rule 6 L439 together with rule 3 L395 and §4.4 L530 · Quarantined auto-run paths and held protected-area changes go "into the undo store". That store keeps items for 24 h and has a size cap that refuses what does not fit. Nothing says (a) whether a held or quarantined item that is still waiting for Keep or Remove expires after 24 h, which silently loses your choice or the file, or (b) what happens when a quarantine move or revert does not fit the cap. Refusing is not possible for a reaction, and leaving the file in place fails open. Fix: keep held and quarantined items outside the 24 h and cap rules, with their own reserved space. On overflow, block the agent's writes and runs (fail closed) and alert in the tray. Add §15 rows ("held item older than 24 h", "undo store full during quarantine"). finding: minor · §4.3 rule 3 L390-391 (Linux) · Bind mounts made in a mount namespace that the agent's command owns as root of its own user namespace can be unmounted by that command. Fix: say that the namespace is set up so the command runs as a non-root mapped uid with no capabilities (the mounts are then locked), or that a privileged helper sets it up. Same note for the network namespace and pasta in §3.5 L266. finding: minor · §4.4 L530 layer (1) · The lock on files and folders that sit in folders you own does not hold. Your account (and code running as you) can replace finding: minor · §3.5 L269 · Windows Defender Firewall block rules always beat allow rules, so "block all for the agent group except the linked machines, plus a one-address exception" cannot be written as ordinary firewall rules. Fix: say it is done with raw WFP filters in the fork's own sublayer with weights. finding: minor · §10 L1081-1085 · A link bound to a tailnet node identity that is later used over the LAN route (routes list tailnet and LAN, L1040) fails the node check. It is then refused and marked suspect, a false alarm. Fix: always bind with the key pair (DPoP) and use the tailnet identity only as an extra check when the call arrives over the tailnet. Also note that asking local tailscaled finding: minor · §7.4 L887 · overlayfs gives undefined behaviour when the lower (real) folders change while the overlay is mounted, and you keep editing them. CAS at apply time catches the final conflict, but the run may already have read torn data. Fix: name it in §14, or snapshot-mount the lower layer where the filesystem allows. finding: minor · §7.4 L908-910 · Windows: if all alternatives share one AppContainer SID (the read ACEs on the real folders are persistent, per the uninstall list), each alternative can write the other alternatives' private layers. Fix: one fixed package or capability SID for the read ACEs on the real folders, plus a per-alternative AppContainer SID that only its own layer grants. finding: minor · §7.2 L857 / §15 L1314 · The journal keeps entries 24 h. A retry with the same key after that runs the batch again. A same-key retry with a different step list returns the old result without any error. Fix: keep key tombstones (key plus step-list hash) longer than any retry window, refuse a reused key after that, and refuse a key whose step list does not match. Add both to the §15 row. finding: minor · §7.4 L929 with §12.3 L1215 · finding: minor · §4.2 L312 vs L332-333 · Not clear whether a 2-minute timeout counts as a Deny. If it does, one missed dialog blocks the item for the whole task and for 10 minutes. Fix: say that a timeout is not a Deny for the wear-down rules. finding: minor · §6.7 L763-767 vs §4.2 L339-340 and §17 open points 2-3 · Pressing Continue after a take-over is a widening, but it comes from the helper. Code running as you could forge it unless it is classed as an item that needs OS consent while an agent-caused process runs. §6.7 also labels as owner decisions the points that §17 still lists as open (2: does pause stop commands; 3: does the agent's ask resume). Fix: class Continue under the §4.2 consent rule, and mark §6.7's scope as provisional until the owner answers. Also state the lifetime of a take-over pause tied to a task that has ended or is gone after a restart (§15 L1317 says only "kept"). finding: minor · §4.3 rule 8 L475-476 · "the kill switch relies only on the OS" contradicts the same rule: the guard ends the processes, and agent-caused marking is the guard's own tracking. Fix: "relies only on the guard and the OS, not on T3 or the helper". finding: minor · §12.2 L1193-1194 vs §12.3 L1216 · Wind-down ends "at the next sign-in" in one place and is "cleared" at the guard's start in the other (§15 L1325 says sign-in). Fix: pick one. finding: minor · §4.3 rule 6 L432, §12.2 L1189, §7.4 L941 · The 30-minute reuse rule (and the 48-per-day count under the VSS limit of 64) conflicts with the wind-down snapshot of "folders changed since the last one" and with "snapshot first" before a winner is applied. Fix: say whether these force a fresh snapshot (then count them in the limit, which also counts System Restore copies) or reuse the last one. On macOS the OS deletes local snapshots under disk pressure, so neither the size cap nor the 1-hour floor can be guaranteed there; say so. finding: minor · In short L41 · "undo the agent's file changes of the last 24 hours" over-promises against §4.3 rule 6 (a 1-hour floor under heavy writes, and no undo of command changes on some Linux filesystems). Fix: "of up to the last 24 hours (§4.3 rule 6)". finding: minor · §9 L1017 · "Nine tools (plus the file group)": the table has eight besides the two file groups (status, run, undo, observe, do, recipe, ask, named). Fix: "Eight". finding: minor · §2 L108-109 · "no change to upstream files except one registration line per toolkit and setting, plus the small link changes of §10" leaves out the forwarded-tool allowlist on the computer (§3.2), turning off T3's terminal and provider features, finding: minor · §3.3 L203-204 · On Linux the install verify checks only finding: minor · §17 L1388-1402 · Costs leave out Apple's Endpoint Security entitlement (needs Apple's approval and Full Disk Access) and macOS finding: minor · §16 · New rules with no test: clearing the sudo timestamps (§4.4), the 10 s agent-caused window and descendant marking (§4.4), unlocking verdict: changes needed. 0 blockers, 10 majors, 19 minors. All of them are doc fixes; none of them changes an owner decision. |
|
lens: security-design r3 · reviewer: Opus 5.5 (t3lens-26-sec-r3) · read at: 9fde35b r2 status:
finding: BLOCKER P1. Protected areas are not OS-enforced where the OS leaves them writable to standard users (§4.4 list line 513, "Who enforces it" lines 528-529, In short lines 30-31). The doc gives two reasons. Commands are stopped "by the OS: standard accounts ...". Acting as you is stopped because "writing there needs elevation". Both are false for common spots. C:\ProgramData grants BUILTIN\Users create-file and create-folder rights, and CREATOR OWNER gets full control of what is made. Many installers leave Program Files subfolders writable by Users: game launchers and updaters do this often, and some have a SYSTEM service that runs from there. Some HKLM keys grant Users write. On macOS the admin group can write /Applications with no prompt, and the user owns Homebrew's prefix. On Linux, /opt and /usr/local subtrees are often owned by the user. The agent account is in BUILTIN\Users, so this needs no UI at all. The §3.3 reach check walks the drives, but it lists only what can be reached, lets you "accept" it, and does not cover your own account. Attack: (1) An injected goal runs host_run as the agent account and finds a Users-writable launcher folder under Program Files (x86) or a writable ProgramData app folder. (2) It writes a DLL that the app or its SYSTEM service loads, or it replaces the updater exe. (3) The next start runs it as you, or the service runs it as SYSTEM. No ask, no OS consent, no hold: layer 2 of §4.4 watches only persistence points, not system areas. The same works through the UI as you, and on macOS by replacing a bundle in /Applications. Required: (a) The reach check walks every protected area as the agent account and as your standard (filtered) token, and lists every writable file, folder and registry key. (b) For the agent group, the install adds Deny write, delete and create-child entries (link-safe, §3.4) on every writable spot it finds, with no "accept". The entries are re-checked daily, and the verify fails if one is missing. (c) For your account, those spots join the protected-area watch (§4.4 layer 2), and the lock while acting covers them where they take ACLs, so a change is held. (d) The §4.4 table says "the OS, except spots it leaves writable to users, which (a)-(c) cover". (e) §16 adds tests: an agent-account write to a Users-writable ProgramData folder and to a writable Program Files folder is denied, and a write as you there in an acting batch is held. finding: MAJOR P2. Frozen copies trust a git object store the agent can write (§4.3 rule 4 lines 412-415, §9.1 lines 1027-1036). The helper "makes a git worktree at a commit", and "the same hash later needs no new ask". But .git/objects (with objects/info/alternates and packs) and .git/worktrees stay writable to the agent: rule 3 denies only config, hooks and info. Git does not re-hash objects on normal reads, so a commit id does not prove what is checked out. Attack: (1) You approve a named deploy command at commit C, and the diff looks fine. (2) The agent overwrites the loose object for a blob in C, or adds an alternates entry or a pack, so the same name now holds different content. (3) The next run makes a fresh worktree at C, needs no ask, and runs agent code as you with your deploy key. For an admin command, the same swap between the ask and the checkout runs agent code elevated, which is beyond what decision A accepted. Required: (a) The frozen copy is built only from verified objects. The helper clones into the protected folder with object checking (for example finding: MAJOR P3. The network floor can be bypassed through system services and VMs (§3.5 lines 247-248 and 265-270, §14 line 1245). (1) DNS: "Name lookups go only to the tailnet's resolver, so DNS cannot carry data out" is not true. Windows resolves names through the DNS Client service, not under the agent's SID, so WFP rules by SID do not see it. macOS resolves through mDNSResponder. On Linux the namespace blocks 127.0.0.53, but resolved's varlink socket and nscd's socket are filesystem sockets, so the namespace can still reach them. Tailscale's 100.100.100.100 also forwards names outside the tailnet to public resolvers. A lookup of .attacker.example carries data out. (2) By default pasta maps the namespace's gateway address to the host's loopback, so "its own loopback" holds only when that mapping is off. (3) WSL2: a standard user can finding: MAJOR P4. Held persistence is reverted after it has already run, and what it started is not ended (§4.4 line 530 layer 2, lines 532-537, §15 line 1304). A standard user can register a scheduled task for itself without admin rights. The Task Scheduler service starts the task's process, so that process is not marked agent-caused, and the registration event names the user, not the process. Attack: (1) In an acting batch, a file run as you calls schtasks with a registration trigger, or registers a task and then runs finding: MAJOR P5. Code running as you, or an agent, can press tray and T3 actions that widen (§4.2 lines 335-357, §6.7 lines 763-767, §12.1 lines 1174-1180, §4.3 rule 3 lines 392-402). The guard checks OS consent only for full rights, elevation and protected areas, and for other widening only "while an agent-caused process runs as you", which is best effort (§4.4 line 535-537). Some actions are not classed at all: Resume after Pause, Continue after a take-over, Keep on a quarantined auto-run path, "make it mine", accepting a reach-check find, and turning host tools back on after a failed verify. The tray runs as you at the same integrity, so any process running as you can invoke its menu through UI Automation or send it input. "Continue in T3" can be sent by anything that can drive T3's UI, such as a browser tab on this computer that the engine does not recognise as a host-tools screen. Attack: (1) A process that escaped the mark (P4) waits. (2) You take over, and acting stays paused. (3) The process invokes the tray's Continue, or Keep on a quarantined tasks.json. (4) The agent goes on acting on your screen, or the IDE task runs as you. Required: (a) The doc classes every tray and T3 action as narrowing or widening. (b) Widening actions are written only through the guard. They need OS consent that the guard checks whenever an acting batch, a full-rights grant or a lock window ran in the last N minutes (not only while a marked process is alive), and always for Keep of a quarantined auto-run path and for "make it mine". (c) The computer accepts "Continue in T3" only from a person's T3 UI session, never from an MCP or agent session. (d) The engine refuses any window that shows a T3 page with host-tools controls, including Continue banners. finding: MAJOR P6. The new-repository rule exempts more than .git (§4.3 rule 3 lines 398-402). The exemption is worded for the whole repository: "New repositories (git init, git clone by the agent) are agent work and are not quarantined". But git's safe.directory check protects only .git. A clone of a hostile repository brings .vscode/tasks.json with runOn folderOpen, .idea run configurations, .envrc and plug-in files, all written by the agent account. Attack: (1) An injected goal asks the agent to clone a repo into your code folder and look at it. (2) You open the folder in VS Code (a parent code folder is often trusted) or in IntelliJ. (3) The task runs as you with no click. Required: (a) Only .git is exempt (it is covered by safe.directory). (b) Every other auto-run path inside a new clone is quarantined like any other new auto-run path, and "make it mine" shows them in the same ask. (c) §16 adds a test: a clone with a folderOpen task. finding: MAJOR P7 (state question). The undo store also holds quarantined and held items, but its cap and its 24-hour expiry fail open there (§4.3 rule 6 lines 437-441, rule 3 lines 392-397, §4.4 line 530, §15 lines 1304 and 1306). Pre-images are refused when the store is full. A quarantine or a held revert, though, follows something that has already happened. The doc does not say what happens when the undo store is full, or when a held item is older than 24 hours. Attack: (1) The agent fills the undo store close to its cap with pre-images of large writes, which is allowed within its quota. (2) It creates a new .vscode/tasks.json or a Run key. (3) The guard cannot move it into the store, so it either leaves the file in place (fail open) or deletes it with no record. Or: a held change waits over a weekend, the 24-hour rule drops it, and Keep or Remove then silently has nothing to act on. Required: (a) Quarantine and held reverts never fail open. They use a reserved area outside the pre-image cap, or the guard removes the path and logs its content hash and first bytes, and agent writes are refused until there is room again. (b) The 24-hour rule never expires a held or quarantined item that is still unanswered. (c) Add both cases to §15: "undo store full during a quarantine or hold" and "held item older than 24 h". finding: minor p1. Honest residuals (§14.1 N1 line 1272, §14 lines 1239 and 1255). "Until the kill switch" is not true for a process that escaped the agent-caused mark (§4.4 lines 535-537). Such a process survives the kill switch, and survives a restart if it set up persistence. The Elevation row should also say that at the default UAC level with an admin account, auto-elevation defeats the guard-checked consent, the protected store and every protected area, not only system writes. |
…s 1-7 - User-writable system spots get deny entries for the agent group and the guard's watch for the owner (owner answer 7, lens P1). - Engine worker and the agent's own loopback RDP session for rung 3; never depends on the owner's sign-in (spike 2026-11-13). - Network: only named services on linked machines, guard resolver, openings ledger; take-over pause covers the whole computer, Continue needs consent. - Undo store sized to not fill (owner answer 6); "Run" defined (answer 1); UAC Always notify by default (answer 4); driver measure-first (answer 5). - Held entries' processes ended, service-started processes marked, marks and baselines journalled, lock leases, key tombstones, layer_gone, link expiry sync on the linking side, DPoP always; §15 rows and §16 tests for each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
lens: correctness · reviewer: Opus 5.5 (t3lens-26-cor) finding: [major] A. Link extension, "lower the lifetime" and the |
|
lens: security-design r4 · reviewer: Opus 5.5 (t3lens-26-sec-r4) r3 status:
finding: BLOCKER · §3 L145-148, §3.1 L171, §6.4 rung 3 L823 · B1. Remote Desktop redirection hands the agent's session the client account's files, clipboard and keys. The doc says the guard "opens" the agent's RDP session on the computer itself. It does not say which account runs the RDP client, and it says nothing about RDP device redirection. RDP carries virtual channels from the client into the session: clipboard, drives (\tsclient\C), printers, smart cards, WebAuthn (security keys and Windows Hello), Plug and Play and USB devices, audio and camera. Redirected drives and the clipboard are served by the client process with the client's token. mstsc turns on clipboard, printer, smart card and WebAuthn redirection by default; drive redirection is one setting away. Attack: (1) The guard (SYSTEM) or a helper running as you starts the client. (2) The agent's code inside its own session reads \tsclient\C... with SYSTEM's or your rights, or reads your clipboard through rdpclip, or asks your security key for a WebAuthn assertion. (3) The reach map, the secret deny pass, the sensitive set and the clipboard markers are all bypassed, because the read is done by the client account, not the agent group. Fix: (a) turn off every redirection on the server side, by the machine policies "Do not allow clipboard / drive / COM / LPT / printer / smart card / Plug and Play / audio and video capture / WebAuthn redirection" (fDisableClip, fDisableCdm, fDisableCcm, fDisableLPT, fDisableCpm, fEnableSmartCard=0, fDisablePNPRedir, fDisableAudioCapture, fDisableCameraRedir, fDisableWebAuthn; names to verify per build), and check each in the install verify; (b) run the RDP client as a dedicated low-rights account (or as the agent account), never as SYSTEM or as you; (c) say in §17 that on Windows client editions these policies are machine-wide, so they also apply when the owner uses Remote Desktop into this PC (an owner choice); (d) §16 test: from inside the agent's session, \tsclient, the clipboard and a smart card reader are not reachable. finding: major · §3.1 L171, §6.4 L823, §17 L1627-1630 and L1661-1664 · M1. Windows client editions allow one interactive session at a time (to verify in the planned spike, but this is the documented licence and behaviour of Windows 10/11 Pro). When another user signs in by RDP while you are signed in, Windows tells the new user that you will be disconnected, and shows you a prompt that disconnects your session after about 30 seconds if you do not press Cancel. So the agent's session would lock you out of your own desktop, or be refused. While your session is disconnected, every on-screen rung is refused (§6.7 L903-905). Tools that patch termsrv.dll to allow more sessions break the licence and are not an option. Fix: the spike must test with you signed in and active, and also with you away. If it is confirmed, rung 3 on Windows Pro needs another base, for example a desktop in a non-interactive window station of the agent account in session 0 (no GPU; to verify for UI Automation and app compatibility), and §1, §3.1, §6.4 and owner question 2 change to match. Multi-session RDP stays for Server editions only. finding: major · §3.1 L170 and L176, §3.3 L208-209 and L250-252 · M2. An interactive session changes what the agent account is. Before r5 the agent account never signed in interactively, so the doc denies only its Startup folders and Run keys and lets the guard delete its scheduled tasks after the fact. With its own RDP session: (1) its token gains INTERACTIVE, REMOTE INTERACTIVE LOGON and LOCAL, so ACLs that grant these SIDs open to it, and the reach check (run with a batch or service token) does not see them; (2) its own per-user logon autostarts now run at each session start, outside the broker, its job object and its gates (deny pass current, pause): HKCU Winlogon Shell, Environment\UserInitMprLogonScript, Policies\Explorer\Run, per-user COM registrations under HKCU\Software\Classes that its Explorer loads, Active Setup, tasks "at logon" and "run only when logged on"; (3) the agent account owns its own profile and HKCU, and an owner always has WRITE_DAC, so it can remove the Deny entries on its own Startup folder and Run keys (the same is true for its LaunchAgents folder on macOS). Fix: (a) run the reach check also with the token of the open RDP session (WTSQueryUserToken by the guard); (b) apply the whole §4.4 Windows persistence list to the agent accounts' own profiles, with the guard as owner of those keys and folders (or an OWNER RIGHTS ACE), so the agent cannot rewrite the DACL; (c) deny the agent group write on the Task Scheduler root folder (task folders have their own security descriptor; to verify) instead of deleting tasks after they ran; (d) open the session only while the broker gate is open, and log it off (WTSLogoffSession) on pause, on the kill switch and when the deny pass is stale. finding: major (state) · §3 L145-148, §3.1 L170-171, §15 L1489-1528 · M3. The agent's own session adds stored state that no §15 row or uninstall step covers. Asked word for word: what happens when this runs again, stops half-way, or meets existing, old or empty state? (1) Order: if RDP is turned on before the drop rule exists and the install stops there, the RDP listener is open on the LAN and the tailnet for your own admin account. The rule must exist first, for TCP and UDP 3389, and the verify must probe both. (2) The RDP sign-in right is granted while the session is on and taken back otherwise; a crash in between leaves it granted. (3) Daily password rotation: the broker's "run whether signed in or not" task stores the password. Changing the password without updating the task (or a crash between the two) stops the broker at the next boot. Either use an S4U task (no stored password) or write the new password to a ledger first and update the task and the account in one recoverable step. (4) A guard crash leaves an orphaned agent session; disabling an account (kill switch) does not end its open sessions. (5) The uninstall row L1528 does not list: turning RDP off again, the RDP firewall rules, the user right, the redirection policies (B1), the WSL/VM/BITS denies, the App Control rules and the UAC level setup changed. Fix: one §15 row "agent session" (enable order, right, password, orphan, logoff on kill) and the missing uninstall items, each with a power-cut test. finding: major · §1 L62, §6.1 L736-740, §6.5 L865-869 · M4. The engine worker is fully under agent control, and the doc trusts it. (1) "A channel only the engine and the broker can open": an ACL can name only accounts, and the broker, the worker and every finding: major · §4.4 "Run" L648-656, §14 L1450, §16 L1582-1583 · M5. The "Run" gate does not stop what it names, and the OS can do better without a driver. (1) Windows "ends it at its process-start event": ETW real-time events arrive in buffers (the flush timer is 1 second by default), so the process has run its code before the guard sees it. (2) A DLL the agent placed and that a running SYSTEM service or one of your apps loads causes no process start at all, so it is never ended. Scripts run through an interpreter (powershell -File, python x.py, bash x.sh) are a start of the interpreter, not of the placed file. (3) macOS AUTH_EXEC and Linux FAN_OPEN_EXEC_PERM also miss library loads and interpreter-run scripts. (4) fanotify allows pending permission events when the listener's fd closes, so blocking stops while the guard is down. Fix: (a) on every agent-placed file in a protected area (approved or kept), the guard puts a Deny FILE_EXECUTE entry for Everyone (SYSTEM included, link-safe, guard-owned) until the run is approved. Windows checks FILE_EXECUTE when it maps an image, so exe and DLL starts and loads are refused before any code runs, with no driver; (b) for scripts and on macOS and Linux, deny the open itself (Endpoint Security AUTH_OPEN, fanotify FAN_OPEN_PERM on those files) except for the guard; (c) host tools and acting are off while the guard is not answering permission events; (d) say what Keep means for a kept autostart entry (does every later logon run need an OK?); (e) update §14 L1450 and the §16 tests (a DLL load into a test service, a script through an interpreter). finding: major (state) · §4.3 rule 6 L563-568, §4.4 L665, §15 L1496 · M6. A held change to an existing file or key has nothing to revert to. "Reverted at once into the reserved area" works for a new entry: remove it. For a change to an existing watched object (your shell profile, an existing Run value, a file in a user-writable Program Files folder), the old content is needed. On Windows and on Linux without blocking, the guard learns of the write after it happened, so it has no pre-image. The newest snapshot can be up to 30 minutes old or missing, and taking it back loses your own edits since then. The fallback at L566-567 ("the guard still removes the path") would delete your file. Asked: what happens when this meets existing state? Fix: (a) the guard keeps its own copy of every watched persistence object and every small watched system spot (registry values, profile files, plists, unit files), taken when the watch starts and refreshed after each change you make; the revert writes from that copy; (b) for large watched folders, the revert comes from the newest snapshot, and the tray names its time and the files it would roll back, with nothing applied without you; (c) never delete an existing file as a fallback; (d) add a §15 row "held change to an existing object" and a test. finding: major · §4.4 L667-673 and L683, §14 L1429, §14.1 L1464 · M7. Escaping the agent-caused mark is easy at any time, not only after the lock window. (1) Parent spoofing: a marked process running as you opens Explorer and starts a child with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS. ETW's ParentProcessID then names Explorer. Only the event header names the real creator. (2) COM into the running Explorer (ShellWindows / IShellDispatch2.ShellExecute): Explorer starts the child, and Explorer was not started in the window. (3) WMI Win32_Process.Create after the window. Each gives an unmarked process at once. The kill switch then does not end it, and the 30-minute consent rule stops counting once the last marked process ends. Fix: (a) count descendants by the real creator (the ETW event header's process id; on macOS the Endpoint Security parent audit token, not the "original ppid"); (b) mark starts by Task Scheduler, WMI, COM/DCOM activation and the Explorer shell-execute path while any marked process is alive, not only during the window; use the WMI-Activity event's ClientProcessId where it exists; (c) state in §4.4 and §14 that Explorer COM is a known way around the mark. finding: major · §4.2 L404-445, §6.7 L915-923 · M8. The OS consent is not tied to the item it approves. On Windows, the UAC prompt names only the binary ("T3 confirm, verified publisher ..."). A process running as you (marked or escaped) can start the same signed confirm binary itself, through ShellExecute "runas", with the item of its choice. It can do this when you expect a prompt, for example right after you press Continue, or while an ask dialog is open. You approve the prompt, and the guard writes "Keep the quarantined tasks.json" or "make it mine". On macOS the caller of AuthorizationCopyRights chooses the prompt text (kAuthorizationEnvironmentPrompt), and one right serves every item. On Linux the polkit message is the same for every item. Fix: (a) the guard holds exactly one pending consent item, created from the person's click; a second request while one is pending cancels both and alerts; (b) Windows: the elevated confirm binary asks the guard for the pending item and shows its exact text in its own elevated window (UIPI keeps medium-integrity input out), and the person confirms there; the item never comes from the command line; (c) macOS: the guard defines a right per pending item with its own description and verifies that exact right; (d) Linux: pass the item text in the polkit details and check what the agent shows (to verify); (e) a §16 test: a confirm binary started by another process with a forged item is refused. finding: major · §3.5 L294-301 and L332-341, §3.3 L221-222, §14 L1435 · M9. On Windows, SMB, WebDAV and other system services still carry the agent's network traffic, and the DNS residual is larger than stated. (1) A UNC path (\host\share) is opened by the SMB redirector in the System process. \host@SSL\x is opened by the WebClient service (LocalService), which any user can trigger-start. Neither connection runs as the agent account's SID, so the per-SID WFP floor does not see it (to verify, but well known: SMB traffic is attributed to System, PID 4). So the agent reaches a LAN NAS (inside the floor) and the internet over HTTPS (WebDAV PUT is a full upload channel). The print spooler (adding an IPP printer by URL) and the WinHTTP auto-proxy service (a PAC URL chosen by the caller) are similar (to verify). (2) DNS through the DNS Client service or mDNSResponder is not "a little data" (L301): DNS tunnelling is a working two-way channel, so on Windows and macOS "no direct internet" does not hold for a determined agent. Mechanisms to verify before accepting the residual: a WFP RPC-layer filter by caller token on the DNS Client's RPC interface (FWPM_LAYER_RPC_UM with a user-token condition); on macOS, a sandbox profile for agent commands that denies mach-lookup to the mDNSResponder services. (3) Linux: the system D-Bus socket is still visible in the agent's mount namespace; hide it too, or check that no service there fetches on request (PackageKit refresh, fwupd). Fix: deny the agent group UNC and WebDAV (to verify how: an AppContainer for agent commands, WebClient off, or an RPC filter); add probes for a UNC path to an on-link neighbour, a WebDAV URL and a PAC fetch to the verify (L221-222, L338-341); rewrite the §14 L1435 residual as "a two-way channel through the system resolver" unless (2) closes it. finding: major · §3.3 L218-220, §3.5 L332-335 · M10. The WSL, VM and BITS denies use mechanisms that do not hold. (1) Deny execute on wsl.exe: the agent can copy wsl.exe (read is enough) and run the copy, or call WslLaunch in wslapi.dll, or call the WSL COM service directly. The same goes for hvc.exe and Windows Sandbox. (2) "An App Control rule": App Control for Business policies are machine-wide, not per group. AppLocker rules are per group, but they control files, not COM calls into a running service (BITS, WSL), and AppLocker enforcement on Pro is to verify. Fix: deny the agent and runner groups in the DCOM launch and access permissions of the service AppIDs (the WSL service, BITS, Windows Sandbox; to verify per build). Keep the file denies only as a second layer. Make the verify probe call the APIs (WslLaunch, the BITS COM interface), not only wsl.exe. finding: major · §10 L1261-1267, §10 L1213-1215 · M11. DPoP does not protect the request body, and upstream allows plain HTTP LAN routes for paired tokens (docs/internals/remote.md L49-53). A DPoP proof signs the method, the URL, the token hash, a time and an id. It does not sign the body. Attack: (1) An attacker on the same LAN (ARP spoofing on a café or hotel network when the computer is a laptop, or a hostile device at home) sits on a plain HTTP route. (2) It drops a live host-tools request and sends its own body with the same token and the same fresh, unused proof. (3) The computer runs finding: minor · §14 L1443 · n1. The N7 row still says "no RDP session for the agent account", which r5 changed. Restate N7 for the agent's session: input there reaches only that session's own secure desktop, and its password is held by the guard. |
…ctness fixes No Remote Desktop session for the agent (password set once, batch logon only, the guard starts the broker and the engine worker); the T3 confirm replaces the OS prompt for Continue and a renewed ask (real input only, bound to the item, phone or other computer counts); later screen options in one section; named services only on linked machines with a worked example. Closes the security r4 blocker and majors (guard proxy for named hosts, DCOM denies, service-path probes, deny execute on agent-placed files, guard copies for held changes, DPoP body signing, persisted replay cache) and the correctness majors (row expiry for link extension, no forced snapshots, unattended restart). §15 recovery rows and §16 tests for every changed rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
lens: security r5 · reviewer: Opus 5.5 (t3lens-26-sec-r5) summary: not ready yet. 0 blockers, 7 majors, 13 minors. Round 4 is mostly fixed (the RDP blocker is gone, and most majors are closed). The new majors are ways around the guard: the guard's own proxy skips the network floor, agent code can start outside the broker's job, stale or lost events fail open, and the confirm panel and passkey confirm can be driven or replayed. Doc at ddf5bdd. round-4 findings:
finding: [major] P1. The guard's proxy skips the per-agent network floor, §3.5 (L313-318, L342-350). The CONNECT/SNI proxy opens its outbound connection as the guard, not as the agent SID, so the floor (no loopback, LAN, link-local, metadata, tailnet) does not apply to it. An allowed host name that resolves to 127.0.0.1, 10.x/192.168.x, 169.254.169.254 or a 100.x tailnet address (DNS rebinding, or a name the agent controls) gives the agent a TLS path to local and LAN services. Fix: the proxy checks every resolved address against the same floor before it connects (and after each re-resolve), or opens its sockets under an account the floor covers. Add a test with a name that resolves to loopback and to a tailnet address. finding: [major] T1. Agent code can start outside the broker's job (L182, L188, L223, L572-574, L814-815). Routes: a scheduled task the agent registers for itself (S4U or a registration trigger; it runs because the agent keeps the batch logon right), WMI Win32_Process.Create, and out-of-process COM servers started for the agent. Such processes are not in the broker's job, so the job limits, the network floor tied to the job and kill-on-close do not apply. The guard removes them only after the fact, and nothing removes them while the guard is down. Fix: deny task registration, WMI process creation and DCOM launch/activation for the agent group up front (Task Scheduler and WMI namespace security, DCOM machine launch limits), and add a probe for each route that must fail. finding: [major] T2. A stale deny pass stops only new starts (L571-574). Processes already running keep reading. An agent can flood the file watcher on purpose until it overflows, then a long-running agent process reads secrets created during the stale window. Fix: while the pass is stale, suspend (or end) every process of the agent group until the full pass is done, and test it with a forced watcher overflow. finding: [major] T3. An unknown writer counts as "yours" (L682, L688-689, L1697). For holds and for refreshing the guard's copies this fails open. Lost ETW events (the agent can cause them by flooding) or a guard-down gap let agent persistence (Run keys, startup files, profile scripts) be accepted as your change and copied into the guard's good copy. Fix: an unknown writer counts as the agent while any agent-marked process is alive, or after any lost-events report, or after a guard-down gap. Never refresh a guard copy from an unknown writer; ask you instead. finding: [major] T4. A full reserved area keeps the agent's change (L676-678). "Locks it as it is" keeps an agent edit of an existing Run value. The agent can fill the 1 GB reserved area on purpose with changes that get quarantined, and then its next change stays. Fix: when the area is full, restore from the guard copy and keep only the hash and size of the dropped content in the log. Give quarantine a per-writer quota so one writer cannot fill it. finding: [major] T5. The confirm panel must act only on its own raw input (§4.2, L467-490). Windows: a process with a UIAccess token (an agent can get one by duplicating a UIAccess process token, Forshaw's technique) can send UIA Invoke or BM_CLICK past UIPI, and that makes no low-level input hook event. macOS: the panel runs as you as a LaunchAgent, so AXPress from any app you gave Accessibility (for example Terminal running agent code) presses the button. Fix: the panel decides only from its own raw keyboard/mouse input, exposes no accessibility actions on the confirm control, and ignores window messages that press it. List each injection API (SendInput, UIA, WM_ messages, AX, XTest) with a test that it fails. finding: [major] T6. The passkey and other-computer confirms (L487-490, L530, L549-550): (a) The challenge is only the item hash. The same text (for example "Continue") gives the same hash, so a captured assertion can be replayed. Fix: add a one-time nonce and an expiry to the challenge and mark it used. (b) The doc does not say which origin and RP ID serve the passkey page and the item text. If the agent machine serves either, it can show false text. Fix: name the origin; it must be served by the guard, never the agent machine. (c) Passkey registration needs a trusted channel (the panel), not a page the agent can reach. (d) "That guard's device key §10" is the T3 host account's DPoP link key, not a key only the guard holds. Fix: a separate guard confirm key, enrolled through the panel; the agent machine never holds one. finding: [minor] m1. Linux X11 panel text can be forged (L471, L480-481). On X11 any of your clients can draw over or change the panel window, so the 3-letter code proves you are present, not which text you read. Fix: on X11 confirm on the phone only (or on a separate VT), or state this as a residual in the risk table. finding: [minor] m2. The body digest (L1448-1450) must be a claim inside the signed DPoP JWT (for example finding: [minor] m3. The script read-deny goes by file extension (L781-782). A renamed copy or a script without an extension is readable. Fix: deny by folder or by owner, not by name; and say what protects these files on Linux/macOS while the guard is down. finding: [minor] m4. The panel runs as SYSTEM in your session and draws agent-supplied text. Fix: run the panel with the fewest rights it needs; escape bidi and control characters; show host names in punycode; cap the length. finding: [minor] m5. A second request cancels both (L475-476). The agent can use this to block every confirm (denial of service) and to train you to click quickly. Fix: rate-limit asks per agent, and show "too many requests" instead of cancelling forever. finding: [minor] m6. The pop-up can catch a click meant for something else. Fix: Deny is the default; the panel does not steal focus and opens away from the pointer; buttons are active only after a short delay; renewed asks are rate-limited even after timeouts. finding: [minor] m7. Cursor-jacking: the agent cannot call SetSystemCursor in your session, but code running as you can. The panel should use its own cursor and ignore the system one. finding: [minor] m8. The broker pipe is created by the agent account and can be squatted (L185). Fix: the guard creates it, like the worker channel, or T3 checks the server PID and account of the pipe before it trusts it. finding: [minor] m9. The agent password is in "the guard's store" (L157, L631), but that store is readable by you and by the host account. Fix: keep it in a store only the guard can read, and set "password never expires" so the account does not lock T3 out. finding: [minor] m10. The agent's batch processes need their own window station and desktop, not the service one (L1109); otherwise they can reach other services' windows. finding: [minor] m11. DCOM AppID security changes (L230-235, L1733): record the original descriptor in the ledger so uninstall restores it, and re-check after Windows updates, which can reset it. finding: [minor] m12. "Over the tailnet" (L1451) must be decided by the interface the connection came in on, not by the source address. And L316-317 claims more than the DNS/fronting residual at L1630 allows; say it is limited the same way. finding: [minor] m13. The proxy and the resolver run as SYSTEM and parse agent input. Run them under a low-rights service account. Also L68 and L882 disagree: is the worker in the broker's job or in a guard job? Pick one. |
|
lens: correctness r2 · reviewer: Opus 5.5 (t3lens-26-cor-r2) summary: not ready yet. 0 blockers, 6 majors, 11 minors. Reviewed docs/fork/host-tools.md at ddf5bdd; upstream refs checked at 8cd8a3c. Most round-1 findings are closed well. What is left is a few new holes in the new parts (proxy, secret pass, confirm panel, passkey replay, host task) and three stale lines that now contradict the owner's consent table at L513-517. Upstream refs: all cited lines match at 8cd8a3c (PeerLinks.ts:308/379/536/593/645-648, EnvironmentAuth.ts:79/1347, McpOAuth.ts:97/510-511/546/564, PeerLinkRequests.ts:106-112). SessionStore.verify checks earlier findings:
finding: [major] P. The proxy bypasses the floor. Where: L313-318 with L342. Why: the guard's CONNECT proxy connects as the guard, so the agent-SID / agent-group network floor does not apply to its outbound side. A named host the goal opened whose DNS answer is 127.0.0.1, an RFC 1918 or link-local address (169.254.169.254), one of this computer's own addresses or a LAN host gets connected anyway. That breaks L342 "the floor no answer removes". State question: a DNS answer that changes between the ask and the connect (rebinding) gets the same hole. Fix: the proxy resolves the name itself and applies the floor to every resolved address on every connect (refuse, log), and pins the address for the connection. §16 test: an opened name that resolves to 127.0.0.1, to 169.254.169.254 and to a LAN address is refused. finding: [major] S. Running agent processes during a stale secret pass. Where: L571-574, L1693. Why: while the deny pass for a new secret has not finished, the broker refuses new T3 requests, but agent processes that are already running (and their children, which they spawn without the broker) can still open the new secret. State question: a new secret file appears while a long agent command runs, or the guard restarts in the middle of a pass. Fix: the guard freezes (job object / cgroup freeze / SIGSTOP of the agent process group) or ends running agent-group processes until the pass is done, and a restart treats the pass as stale until it reruns. §16 test with a process already running when the secret appears. finding: [major] K. The confirm panel can be clicked by software. Where: L467-485. Why: "real input only" is stated, but the doc does not say the answer comes only from a real input event the panel's own low-level hook saw over the button. On Windows a uiAccess process passes UIPI and can use UIA Invoke or send BM_CLICK; on macOS any of your processes with the Accessibility right can send AXPress; on Linux an AT-SPI DoAction does the same. Any of these activates the button without an injected-input flag. Fix: say the panel ignores control activation and counts only a physical event (Windows: LLMHF_INJECTED clear from its own hook; macOS: CGEvent source state HID, not synthetic; Linux: the compositor-level input path), and hides itself from accessibility (no Invoke / AXPress / DoAction pattern). §16 test: UIA Invoke, BM_CLICK, AXPress and AT-SPI DoAction on the panel do nothing. finding: [major] R. Passkey and guard-signature confirms can be replayed. Where: L472-476, L487-490. Why: the pending item's hash is made from its text only. "Continue on your screen" has the same text every time, so a captured passkey assertion or guard signature for one Continue is valid for the next one. The replay cache (L1452) covers peer-link proofs, not these. Fix: each pending item carries a fresh random nonce from the guard; the WebAuthn challenge and the guard signature cover nonce + text hash; the guard accepts each nonce once. Also name the WebAuthn RP id and origin, and say that the item text the phone shows is rendered from the guard's signed item, never by the agent machine's T3 UI. finding: [major] H2. The host account's task password. Where: L184, L158, L1880. Why: on Windows a Task Scheduler task "run whether the user is signed in or not" stores the account's password, unless it uses S4U ("do not store password", which also has no network credentials). The doc does not say which. If it stores a password, L1880 "no task password to keep in step" is false, and a store-loss reset of the host account's password (L158, same pattern as the agent account) makes host T3 fail at the next boot with no error the person sees. State question: what happens after a restore of an old system image or a password reset? Fix: say S4U for the host task (T3 needs no network credentials for its own work), or the guard starts host T3 with the same batch logon it uses for the broker. Add a §15 row for the host task. finding: [major] W. Three stale lines contradict the consent table (L513-517, owner answer 3 2026-11-14 and owner decision 2026-11-13). (1) L1551: tray Continue "needs OS consent", but L515 says Continue needs the T3 confirm. (2) L1627 (§14): "the T3 confirm ... for Continue and full rights", but L516 says full rights need the OS consent. (3) L1766-1767 (§16): "widening during a full-rights grant needs OS consent", but L517 says the T3 confirm. An implementer or test writer will build the wrong check. Fix: change the three lines to match L515-517. finding: [minor] 1. Broker pipe or socket owner. Where: L185. Why: if the broker creates its pipe or socket as the agent account, an agent command can create a second instance of the same pipe name first or change its DACL, and T3 talks to it. Fix: T3 checks GetNamedPipeServerProcessId (Windows) / SO_PEERCRED or LOCAL_PEERPID (Linux, macOS) equals the broker pid the guard started, or the guard creates the pipe or socket and hands it over. finding: [minor] 2. Window station for batch-logon processes. Where: §3.1, §6.1. Why: a process started by finding: [minor] 3. Agent password expiry. Where: L1764-1766. Why: "not rotated" plus a default password policy (42 days on many machines, domain policies) means the batch logon fails later. Fix: the guard sets "password never expires" on the agent account and checks it at start. finding: [minor] 4. Linux pasta and the proxy port. Where: L313-318 with the Linux network row. Why: in a pasta namespace loopback is the namespace's own; the guard's proxy and resolver on host loopback are only reachable when pasta forwards them ( finding: [minor] 5. git over SSH. Where: L313-318. Why: ssh does not read HTTPS_PROXY/ALL_PROXY, so a goal that opens github.com for git over SSH fails ("it fails, nothing opens" is safe but surprising). Fix: say only HTTPS git works, or the broker sets finding: [minor] 6. Replay cache location. Where: L1452, L1729. Why: says "T3's database"; upstream replay markers are files in secretsDir (replayMarkers.ts), not database rows. Fix: say which one the fork uses; if files, it survives a restart as written, if DB, say why it differs from upstream. finding: [minor] 7. Two pending items over-trigger. Where: L475-476. Why: "a second request while one is pending cancels both and raises an alert" fires on a normal case: the person takes over, the agent's ask item "continue on your screen" is pending, and the person presses Continue in the tray. Fix: one item per kind; the same kind replaces, different kinds queue; alert only on a repeat from a source that is not a person (state question: guard restart drops both, L1010, fine). finding: [minor] 8. Snapshot at the cap. Where: L642-644 vs L690-691. Why: "at the cap the oldest goes" and "cannot be taken: refuse" read differently when VSS hits its 64-shadow limit or the shadow storage is full and Windows deletes old shadows by itself. Fix: say which wins: a failed or silently dropped snapshot refuses the batch. finding: [minor] 9. Hidden desktop and single-instance apps. Where: L678, L731 (rung 3 on a hidden desktop in your session). Why: when an app that is already open on your desktop is started on the hidden desktop, many single-instance apps (Office, browsers, Explorer) hand the request to the running instance, so the window opens in front of you and acting happens on your screen. Fix: rung 3 checks that the window it gets is on the hidden desktop and refuses (falls to rung 4) otherwise; a §16 test with an app already open. finding: [minor] 10. The verify with nobody signed in. Where: L274 (watch of HKCU). Why: the guard reads and watches your HKCU and uses your token while you are signed out; that needs your hive loaded and an S4U token. Fix: say S4U plus LoadUserProfile, or that the check is deferred to your next sign-in. finding: [minor] 11. Descendant catch-up on Windows and L1281 wording. Where: L817-822, L1281. Why: (a) the gap walk after a missed process-create event can only use ParentProcessId, which a process can spoof (PROC_THREAD_ATTRIBUTE_PARENT_PROCESS) and which is reused after the parent exits; use the job object membership (all agent processes run in the guard's job, L185) as the source of truth instead. (b) L1281 still says "snapshot first" while L640-644 now orders the steps differently. Fix: say job membership; align L1281 with L640-644. |
…wner defaults - Proxy checks every resolved address against the network floor, pins it, runs as a low-rights account. - T3 confirm: one-time nonce per item, only the panel's own raw input (no accessibility actions or window messages), one item per kind with rate limits, phone passkey on the guard's own page, a separate guard confirm key, X11 confirms on the phone. - No agent process outside the guard's job: task, WMI and DCOM starts denied up front, probed. - Unknown writers and full reserved area fail closed; stale deny pass freezes running processes. - Host T3 task uses S4U; guard creates the broker pipe and the agent window station. - Three stale consent lines fixed; owner questions 1-3 of 2026-11-14 written as pending defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ided Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
lens: correctness r3 · reviewer: Opus 5.5 (t3lens-26-cor-r3) · read at: 347be19 earlier findings:
finding: [major] 1. The DCOM deny sits in the machine-wide limits, so no AppID can be allowed. Where: L193 (also L239-241, test L1913-1915). Why: the machine-wide launch and activation limits are an upper bound. A Deny ACE for the agent group there wins over any per-AppID grant, so "the few servers the engine worker needs are allowed by AppID" cannot work. "Local activation" also covers connecting to an already running server. So the agent group would lose all out-of-process COM, including plain WMI queries. Many build and test tools use these (PowerShell CIM cmdlets, MSBuild and Visual Studio tooling, installers, language runtimes that read system info through WMI). The §16 test at L1913-1915 only checks that activation fails, so it would pass while builds break. Fix (sentence for the doc): "The install denies launch and activation for the agent group in the DCOM permissions of each AppID it must block (Task Scheduler, WMI process creation, WSL, BITS, Hyper-V, Windows Sandbox and the shell-execute brokers), not in the machine-wide limits, because a machine-wide Deny cannot be loosened per AppID; §16 also checks that the build and test tools the agent account needs still run." finding: [major] 2. The t3-net account cannot reach a DNS server, and it is not listed as an account. Where: L333-334, L376 (also L155, L187, L225, L1797, L1972). Why: the proxy and the resolver run as t3-net, and "that account's outbound sockets sit under the same floor", which denies loopback and LAN. On most computers the upstream DNS server is the LAN router, or a loopback stub (systemd-resolved at 127.0.0.53, a local DNS cache, the tailnet's own resolver address). Under the floor the guard's resolver can then resolve nothing, so every opened goal fails. Also, t3-net is a third local account, but §3 says "Two local accounts per computer" (L155). The account table (L187), the verify (L225 "both accounts") and the install row (L1797 "create both accounts") do not cover it, and §17 (L1972) counts "two accounts". So rerun, half-install and "an account of that name is an admin" have no rule for it. Fix (sentence): "The t3-net account's sockets are under the floor except for one named exception: the DNS servers the system is configured with, on port 53 only, re-read on every address change; t3-net is a third local account (Windows: a virtual service account or a standard user with only 'log on as a service'), created, checked and removed like the other two, with the same §15 install row and verify check." finding: [major] 3. After every reboot, Windows' own servicing changes are held as the agent's. Where: L779-783 (also L900, §15 L1809, test L1916-1918). Why: "for every change found after a guard-down gap" the unknown writer counts as the agent's, and the change is held. A held change is reverted from the guard's copy and shown with Keep or Remove (L763-766, L1809). A reboot is a guard-down gap. During shutdown and boot, Windows Update and servicing (TrustedInstaller) change scheduled tasks, services and HKLM Run values, and an app updater may run at boot. All of these would be reverted, and their processes ended, after each Patch Tuesday. The owner then gets a pile of Keep or Remove items with no way to tell which ones are real. State question: a clean shutdown with no agent process alive, then a reboot with updates. Fix (sentence): "A gap after a clean guard stop, with no agent-marked process alive and no open lock window in the journal, counts as yours; after an unclean gap, an unknown-writer change is reverted only for objects an agent-group or agent-caused process could have written, and a change to an object that needs elevation is logged and shown for review without being reverted." finding: [major] 4. Uninstall writes back whole old descriptors, and a re-apply can overwrite the recorded original. Where: L239-241, L407, §15 L1803, L1847. Why: the install records "each AppID's and launch limit's original security descriptor" and uninstall "restores exactly that". Any change Windows or another installer made to that descriptor in the months between is then undone, which can break system COM after a feature update. L1803 says a Windows update that resets one is "re-applied", but it does not say whether the ledger keeps the first original or records the reset one. If it records the reset one, a later uninstall puts back the wrong state. The WebClient start type (L407) has the same rerun problem: a second install run would record "Disabled" as the old start type, and it has no §15 row of its own. Also, L239 names only the AppIDs and launch limits, while L1847 also puts back the Task Scheduler and WMI descriptors, so the ledger rule should name all of them. Fix (sentence): "The ledger records each descriptor and the WebClient start type once, at the first change, and never overwrites that record; uninstall removes only the entries the guard added (those for the agent, runner and t3-net SIDs) and leaves the rest of the current descriptor as it is; the WebClient start type is put back only if it still has the value the install set. §15 gets a WebClient row, and §16 a test: install, run install again, change a descriptor by hand, uninstall; the hand change stays and only the guard's entries are gone." finding: [minor] 1. A copy taken again "from current state" bakes in an agent change. Where: L776-777, §15 L1808. Why: a missing copy (store lost, first start after an upgrade) is retaken from whatever is there. If an agent-caused change is already there, it becomes the trusted copy and every later revert restores it. Fix (sentence): "A retaken copy is marked unverified; its object is shown once in the tray for review, and a change to it is never reverted to an unverified copy without your OK." finding: [minor] 2. The unknown-writer share has no rule for when it is full. Where: L761-766. Why: an unknown-writer change is restored from the copy "only after its content was kept in the unknown-writer share". The doc does not say what happens when that quarter is full (for example after a gap with many changes, see major 3). Fix (sentence): "When the unknown-writer share is full, an unknown-writer change is left in place and locked, logged with its hash, and new agent work is refused until there is room." finding: [minor] 3. The daily re-apply freezes all agent work, and a guard crash can leave processes frozen. Where: L644-652, §15 L1805. Why: "after the daily re-apply, a full deny pass" makes the pass stale, so every agent process is frozen for the whole pass. That drops network connections and burns command timeouts every day, for no new secret. Also: a Windows job ends with the guard, but a Linux cgroup freezer and macOS SIGSTOP stay in place after a guard crash. The doc does not say the new guard thaws or ends them. Fix (sentence): "The daily re-apply runs while the last pass stays current and freezes nothing; only guard start and a watcher overflow make the pass stale; at start the guard ends every agent-group process it finds frozen from before." finding: [minor] 4. A queued confirm item can expire before it is shown. Where: L508-509 with L517. Why: the 2-minute expiry starts when the item is made, but items of different kinds "queue and are shown one at a time". An item that waits behind another for 2 minutes is dead when it appears. Fix (sentence): "The 2-minute expiry starts when the panel shows the item, not when it is made." finding: [minor] 5. The typed 3-letter code reaches the focused app. Where: L535-537 with L556-559. Why: the panel "does not take focus by itself", and the hardened confirm asks you to type a code. The hook only reads the physical keys, so the same keys also go to the app that has focus, which may be a terminal or an editor. Fix (sentence): "While the panel is open, its hook swallows the physical keys it reads for the code (Windows and macOS; on Linux the guard grabs the keyboard device for that time), so they reach no other app." finding: [minor] 6. "The agent machine holds no confirm key" conflicts when that machine is also a host-tools computer. Where: L548-550. Why: a machine can run agents and also be a host-tools computer with its own guard confirm key. The rule does not say which wins. Fix (sentence): "A computer that sends tool calls to this computer can never be enrolled as one of its confirm computers, and enrolment refuses it." finding: [minor] 7. The single-instance check comes after the window already appeared. Where: L1077. Why: the engine checks GetThreadDesktop of the window it gets, but by then the running copy has already opened the window on your desktop, which breaks the no-pop-in rule of §6.8. Fix (sentence): "Before starting an app on the hidden desktop, the engine checks whether that app already runs in your session; if it does, it uses rung 4 with your OK instead and never starts it there." finding: [minor] 8. §9.1 still says the OS prompt follows every protected-area Allow. Where: L1480. Why: "on Allow the OS's own elevation prompt follows" is true only for an elevated run. Under the default of pending question 1, a protected-area action that is not elevated takes the hardened T3 confirm and has no OS prompt. Fix (sentence): "On Allow, the guard checks the hardened T3 confirm (§4.2), and an elevated run also shows the OS's own prompt." finding: [minor] 9. Wording left-overs from earlier rounds. Where: L887, L1853, L1902, L2024. Why: L887 says "the broker's job", but §6.1 now says the agent processes run in the guard's job (L919-920). L1853 tests "the rare OS-consent items", but under the default those are hardened T3 confirm items. L1902 says "for the shown hash only", and L2024 "bound to the one item by its hash", but the binding is the nonce plus the text hash (L508-512). Fix: "the guard's agent job" (L887); "the hardened T3 confirm items" (L1853); "for that item's nonce only" (L1902); "bound to the one item by its one-time nonce" (L2024). finding: [minor] 10. The upstream function name for the request check. Where: L1555. Why: verifyDpopProof is in the shared package; the server-side request check is verifyRequestDpopProof in apps/server/src/auth/dpop.ts, which is where the fork's body-digest claim check would sit. Fix (sentence): "This check is new fork code in the server's verifyRequestDpopProof, next to the shared verifyDpopProof, which checks no extra claims." finding: [minor] 11. With nobody signed in, it is unclear whether host tools wait for your-token walk. Where: L285-287 with L262. Why: L262 says host tools come back with nobody signed in once the pass and the verify pass. L285-287 says that if the S4U logon or LoadUserProfile fails, the walk as your token and the HKCU watch wait for your next sign-in. It does not say whether host tools are on in that time, without the watch of your persistence points. Fix (sentence): "While the walk as your token and the HKCU watch wait for your sign-in, commands and file tools may run, but acting as you and every full-rights grant stay off." finding: [minor] 12. How pasta is started is not said (to verify). Where: L378-381. Why: when pasta starts as root it drops to an unprivileged user by default, and joining a network namespace the guard (root) made then needs capabilities it no longer has. Without a stated start mode, the per-command namespace may get no route, or pasta may keep running as root. Fix (sentence): "The guard starts pasta with --runas set to a dedicated low-rights user and only the capabilities it needs to join the command's namespace (CAP_SYS_ADMIN and CAP_NET_ADMIN, kept as ambient capabilities), and §16 checks that a command's namespace reaches the proxy and the resolver and nothing else." |
|
lens: security r6 · reviewer: Opus 5.5 (t3lens-26-sec-r6) · read at: 347be19 round-5 findings:
state check ("What happens when this runs again, stops half-way, or meets existing/old/empty state?"): covered in §15 for the S4U task (L1802), the up-front denies (L1803), the stale pass (L1805), guard copies (L1809), the proxy (L1819), confirm items and passkeys (L1844-1845). Missing: a full unknown-writer share (S6), macOS processes left stopped after a guard crash (n1), the t3-net account (n4), WebClient set back or still running (n7), and an old install whose proxy ran as SYSTEM (n4). finding: [major] S1. Code running as you can steer a real click or the typed code into the confirm panel (§4.2 rules 4 and 6, L521-539; hardened form L557-561; L1036 excludes only the indicator from capture). Attack (Windows): (1) the agent gets code running as you (open by owner decision, §4.4); (2) the agent asks for a widening item through host_ask, for example "the public internet, always" or a full-rights grant; (3) that code screen-captures the panel (a medium process can read any window's pixels) and learns the 3-letter code; (4) it shows its own window "type ABC to finish the update"; the panel does not take focus, and its low-level keyboard hook sees keys typed into any window, so the code counts; (5) after the 1 s arming it calls ClipCursor on the Allow button (or SetCursorPos just before a click); the next physical click anywhere lands on Allow, is not injected and is inside the button, so the guard accepts. macOS: CGWarpMouseCursorPosition does the same. Linux: the guard reads the code from evdev wherever it is typed, and a 3-letter code can also appear by chance in normal typing during the 2 minutes. Rule 3 also lets tray requests count as "a person", but code running as you can drive the tray, and a same-kind replacement swaps the text under a user who is about to click. Fix (requirements): (a) the panel is excluded from screen capture (Windows WDA_EXCLUDEFROMCAPTURE; macOS sharingType none, to verify on current macOS; Wayland only on Linux); (b) a click counts only if no cursor clip is active (GetClipCursor equals the whole virtual screen) and the pointer reached the button by hardware move events and rested there about 300 ms; a cursor position that differs from the one tracked from hardware moves refuses the click; (c) the typed code counts only while the panel is the foreground window after a hardware click on it; any other key resets it; on Linux the code is a fresh sequence (start key, code, Enter, nothing in between) and longer for the hardened form; (d) a replaced item re-arms the 1 s delay and shows "this request changed"; requests from the tray count as "from your session", not as proof of a person; (e) §16 tests: ClipCursor, SetCursorPos, CGWarpMouseCursorPosition, code typed into another window, capture of the panel. finding: [major] S2. The phone confirm page and its TLS key live in the same account as the agent-facing proxy (L333-335, L541-543). The proxy and resolver parse agent input (CONNECT lines, TLS ClientHello, DNS), and the doc itself plans for "a bug" there. Attack: (1) an agent command exploits a proxy parser bug and runs code as t3-net; (2) it reads the TLS key for the tailnet name or changes the page it serves; (3) the agent asks for a hardened item; (4) the page shows "Continue" but passes the real challenge (nonce + hash of the hardened item); the phone's passkey sheet shows only the site name, so you approve; (5) the guard checks the signature over nonce and hash and accepts. Separately, WebAuthn binds only the host name, not the port: any process that gets a certificate for the tailnet name (on Linux, code running as you when you are the Tailscale operator can run finding: [major] S3. Passkey registration and guard-key enrollment can be won by code running as you (L545-552, L1845). Passkey: (1) you start a registration and the panel shows the QR code; (2) code running as you captures it (see S1a) and registers its own software passkey named "iPhone" on the guard's page first (your account reaches this computer's tailnet name; the floor binds only the agent group); (3) you confirm "the new passkey's name" with real input; (4) that code now holds a lasting confirm, which also turns host tools back on after a kill switch. Guard key: B learns "this is computer A" from the tailnet node only; code running as you on A can answer B's enrollment request (on Windows any user can bind a free port while A's guard is down), show a look-alike A panel with the same code, and you confirm on the real B panel; B then trusts the attacker's key as A. Fix: the registration code is single-use and valid for one credential; the panel lists every registration attempt for it and aborts if more than one; the phone and the panel show the same fingerprint of the new credential; registering a passkey and enrolling a guard key are hardened-confirm actions on the computer that will trust them; B enrolls a key only if A's guard proves it with a guard identity key created by the admin-run install and exchanged during §10 pairing, never from what A's screen shows. finding: [major] S4. The floor excludes linked machines, so the proxy and "the public internet" open every port on them (L366-367 against L319-323 and the worked example L358-363; L329-335; test L1919-1922). Attack: (1) a goal opens a host whose DNS the attacker controls (prompt injection asks for "cdn.example-tools.dev", scope this task), or you open the public internet; (2) its name resolves to the agent machine's tailnet address; (3) the proxy's floor check passes because the floor carves out linked machines, and t3-net's firewall floor is "the same floor"; (4) the agent gets a TCP tunnel to that machine's database, dev server or T3 port, which the worked example says stay closed. With the public internet opened, direct connections hit the same carve-out. The §3.3 probe for "a port on a linked machine that config does not name" (L420) runs with nothing opened, so it does not catch this. Fix: the floor contains every tailnet address with no carve-out; named services are separate allow rules by address and port for the agent group's direct connections only; the proxy refuses every tailnet address; add tests with an opened name that resolves to a linked machine and with the public internet opened, probing an unnamed port on a linked machine. finding: [major] S5. SMB from the System process reaches the internet, not only the LAN, and the planned fix covers only the floor classes (L401-416, probes L246-250, §14 L1738). Attack: (1) an agent command opens \203.0.113.5\s\out or \name.attacker.example\s (the name goes through the system resolver); (2) the SMB redirector connects from the System process over TCP 445, or over SMB-over-QUIC on UDP 443 and alternate SMB ports in current Windows 11 and Server 2025 (to verify per build); (3) the agent writes any file it can read to that share, with no goal opening anything. The per-SID floor does not see it, the probe tests only an on-link neighbour, the candidate fix says "SMB to the floor's classes", and §14 says "on your LAN". The owner decision "no direct internet" therefore does not hold and the residual is understated. The same applies to an IPP printer URL on an internet host. Fix: the per-account block must refuse agent-caller SMB and spooler connections to every address except named services, not only the floor classes; probes add a UNC path to a public test address over TCP 445, over QUIC and on an alternate port, and an internet IPP URL; until a block holds, §3.5 and §14 say "your LAN and the internet". finding: [major] S6. A full unknown-writer share leaves a persistence change active (L758-767, L1814). Attack: (1) the agent floods file activity until ETW reports lost events (the doc says it can); (2) in that window, code running as you writes large changes to watched objects (100 MB profile scripts, Startup files); each counts as the agent's but has no task, so the per-task quota does not bound it, and its content is kept in the reserved area; (3) once the unknown quarter is full, a small Run-value change by an unknown writer "is restored from the copy only after its content was kept in the unknown-writer share", which no longer fits, so it is not restored; L1814 "locked rather than deleted" keeps the value in place and it runs at your next sign-in. A new Startup entry by an unknown writer is not covered by "a new path the agent created is removed" either. Fix: a change counted as the agent's under the unknown-writer rule is charged to the agent's total share (all tasks together), never to the unknown quarter; a held change that does not fit is neutralised in place without losing content (move the file into a guard-owned folder on the same volume, a rename that needs no space; move a registry value under a guard-owned key; disable a task, service or launch item), so nothing held stays active; a held change above a size cap is always neutralised in place; add a §15 row and a test for "unknown share full". finding: [minor] n1. The freeze races (L648-651, L1805). Windows: suspending each process in the job races with new children made during the walk; use a job-wide freeze, or cap the job's active process count and then repeat until every member is suspended; state that the job has no breakaway (JOB_OBJECT_LIMIT_BREAKAWAY_OK and SILENT_BREAKAWAY_OK off). macOS: a process forked during the SIGSTOP walk can send SIGCONT to its stopped siblings (same uid), so repeat until a full check finds every agent process stopped, re-check before the pass reads, or end the processes instead. End every agent-group process outside the job before the pass starts. Add a §15 row for macOS processes left stopped after a guard crash. "Only freezes its own processes" (L651-652) is overstated: it freezes every agent task and the worker on that computer; say so. finding: [minor] n2. A new secret file can be opened in the second before its deny, and the handle stays (L643, §14 L1752). An agent process watching a reach-map folder opens a new finding: [minor] n3. The machine-wide DCOM deny cannot have per-AppID exceptions (L193, test L1913-1914). The machine-wide launch and access limits are a ceiling: an AppID's own permission cannot grant past a Deny entry in the limit. So "the few servers the engine worker needs are allowed by AppID" does not work as written. State zero exceptions (measure that the worker needs none), or run what needs COM outside the agent group; never fall back to per-AppID denies (a deny list). Also add Secondary Logon (CreateProcessWithLogonW, finding: [minor] n4. The t3-net account is not in the install verify or §15 (L333-335, L375-377; §3.3 L223-254; only the uninstall row L1847 names it). The verify should check that it is not an admin, holds no sign-in right, that its firewall floor exists, and that the proxy and resolver listeners are owned by it. Its floor contains loopback, LAN and tailnet, which is where upstream DNS servers often are (127.0.0.53, the router, 100.100.100.100); state the narrow exception it gets (port 53 to the configured resolvers only) or that it resolves through the system resolver. State that plain-HTTP proxy requests (absolute-form GET, no CONNECT or SNI) are refused. Old state: an install whose proxy ran as SYSTEM removes that service before t3-net's starts. finding: [minor] n5. The ask rate limit is per task (L517-519). An agent that starts many tasks or threads gets 3 asks per 10 minutes each and can wear you down. Add a per-computer limit across tasks. finding: [minor] n6. S4U host task details (L189, L1802). An S4U logon has no password-derived DPAPI key; state that the host T3 keeps no secret under DPAPI or Credential Manager (or the verify tests that). Set the task's execution time limit to none (the default of 72 hours stops T3) and restart on failure. Old state: a task found storing a password is re-registered as S4U by the install, not only failed. finding: [minor] n7. WebClient off (L406-408, pending default). Setting the start type to Disabled does not stop a running WebClient; stop it too. Write the ledger line before the change. The daily verify re-checks the start type (an update or another program can set it back) and the WebDAV probe runs then too. Add a §15 row: half-applied install, found re-enabled, found running, already Disabled before install (uninstall keeps it Disabled). |
- DCOM: per-AppID denies computed from the descriptors (machine-wide limits untouched, measured allow list), Secondary Logon denied too - Service identities t3-net and t3-confirm (virtual service accounts / DynamicUser); t3-net may reach only the system DNS servers on port 53 - Floor holds every tailnet address; named services are separate allow rules for direct connections only; proxy accepts CONNECT only - SMB and printers to the internet blocked computer-wide (pending owner) - Hardened confirm only on a phone or another computer (pending owner); panel capture-excluded, no clip/warp, Linux keyboard grab; phone page only from registered phones, exact origin check, single-use registration, guard identity key from pairing - Held changes neutralised in place (no quota, no unknown share); clean guard stops count as yours; retaken copies unverified - Ledger records originals once; uninstall removes only the guard's entries; WebClient stopped and checked daily - Freeze: daily re-apply freezes nothing, job cap with no breakaway, macOS ends, leftovers ended at start; secret opens race-free Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
lens: correctness r4 · reviewer: Opus 5.5 (t3lens-26-cor-r4) · read at: a4d62fb earlier:
state check: asked "What happens when this runs again, stops half-way, or meets existing/old/empty state?" for every rule that writes state, including the new r7/r8 ones (WebClient, SMB filters, ledger-once, t3-net and t3-confirm identities, passkeys and guard confirm keys, guard identity key, clean-stop gap, held-in-place changes, take-over pause, tombstones). §15 covers all of them except two gaps: the "first registration" state can be re-entered by a narrowing (major 1), and the confirm page's certificate and tailnet name have no row (minor 4). finding: [major] 1. Removing every remote confirmer needs no check and resets the "first registration" path; on X11 that path has no safe route. Where: §4.2 rule 7 L614-618 ("the very first one, while nothing is registered yet, is confirmed with the OS's own admin prompt"), L638-643 (fallback "polkit (Wayland only; on X11 a phone or second computer is required)"), L555-557 (on X11 every T3 confirm goes to the phone), L706 ("Removing a passkey or a computer from the tray (narrowing) stops it counting at once"), narrowing row of the class table L659 (guard checks nothing), §15 L1955, §14 L1843. Attack, X11: (1) agent-caused code running as you (the open residual of §4.4) asks the helper to remove each registered phone and confirm computer; that is narrowing, so the guard checks nothing and nothing is shown beyond the tray list. (2) "Nothing is registered" is now true again, so the next phone registration is a "first one" and takes the OS admin prompt. (3) The code starts a registration naming a tailnet node it controls; nothing in the doc refuses this computer's own node or the agent machine (the confirm port filter then admits that node, L608-610), and a software WebAuthn authenticator there satisfies "user verification required". (4) On X11 the OS prompt is polkit, where §4.2 L690-691 and §14 L1856 already say code running as you can read the password you type and can inject input into the agent; the panel's fingerprint step on X11 proves only presence (L555-557), and any X client can redraw its text. Result: code running as you owns a "phone" and answers every hardened confirm itself (full-rights grants, protected-area Allow and Keep, host tools back on after the kill switch, outside store edits). On Windows, macOS and Wayland the same steps (1)-(2) silently move every hardened confirm from the phone back to this computer's own OS prompt, which still holds (UAC secure desktop, SecurityAgent) but is not what the owner set up and is not shown. Separately, X11 has no stated way to register the first phone at all: the hardened confirm needs a phone, the first phone needs the OS prompt, and the OS prompt is excluded on X11. Fix (requirements the doc can state): (a) the guard records in its store that remote confirmers were set up; removing the last one stays allowed (lost-phone recovery) but raises a tray alert "all your phones and confirm computers were removed", and the next registration then needs the OS admin prompt on Windows, macOS and Wayland only; (b) on X11 the first phone, and any phone after the last one was removed, is registered only from the admin-run install (or a re-run of it), never from a polkit prompt in the X session; (c) a registration refuses this computer's own tailnet node, the node of any computer that sends tool calls here, and any node already enrolled as something else, and the item text shows the node's name and OS as Tailscale reports them; (d) §15 row "passkeys and guard confirm keys" gets "the last one removed: tray alert, next registration as in (a)/(b)", and §16 a test: remove every phone as code running as you, then try to register a node on this computer and on the agent machine; both refused, and on X11 no polkit route registers one. §14 L1843 should also name the OS-prompt fallback, since it now says "taken only on a phone or another computer". finding: [minor] 1. The SMB residual says "your LAN" but the computer-wide filter allows the tailnet class too. Where: §3.5 L452-461, §14 L1846, §17 L2176-2177. Attack: where no per-account block holds and you accepted the residual, an agent command opens \100.x.y.z\share on a tailnet node. That reaches every linked machine's SMB service (outside "only the services named in config", §3.5 sec S4) and any node shared into your tailnet from another account, which is not one of your machines. Fix: "Where no per-account block holds, the accepted residual is SMB paths and printers on your LAN and on your tailnet, including your linked machines' SMB services and nodes shared into your tailnet"; and say it the same way in §14 and in the owner's view. finding: [minor] 2. The guard-to-guard channel for remote confirms is not specified. Where: §4.2 rule 7 L619-620 ("its guard fetches the item from this guard over the tailnet"), §3.5 listeners, §3.3 verify, §15 L1955. Attack: the guard (SYSTEM) now has a tailnet listener that the doc does not limit; the agent machine, which may be compromised and is on the tailnet, can reach it, try enrolments, flood item fetches, or probe a SYSTEM parser. Fix: "The guard's confirm channel listens on the tailnet interface only, accepts connections only from the nodes of enrolled confirm computers (and, during an enrolment, the one node you named), authenticates both sides with the guard identity keys from pairing before it parses anything else, and serves only the pending hardened item; the verify checks its listener and filter, and §16 checks that the agent machine cannot reach it." finding: [minor] 3. A link added after enrolment does not remove a confirm computer. Where: §4.2 L627-629, §15 L1955. Attack: computer B is enrolled as A's confirm computer; later B is linked to send tool calls to A (B becomes an agent machine for A). The rule says enrolment refuses such a computer, not that a later link ends its enrolment, so B keeps confirming hardened items for the machine its agents now drive. Fix: "Adding a link through which a computer sends tool calls here removes that computer's confirm enrolment in the same step, and the guard re-checks the link list at each confirm." finding: [minor] 4. The confirm page's certificate and tailnet name have no state rule. Where: §3 L183-184, §4.2 L606-613, §15 (no row). Why: WebAuthn needs HTTPS on the tailnet name, and a passkey is bound to that name. The doc does not say who obtains the certificate (e.g. the guard through the local Tailscale for the tailnet name), who renews it before it expires, or what happens when renewal fails or the computer's tailnet name changes; in both cases every registered phone stops working, and since phones are still "set up", the OS-prompt fallback does not apply, so the rare actions (including host tools back on after the kill switch) have no route. Fix: "The guard obtains and renews the confirm page's certificate for the computer's tailnet name and installs its key readable only by t3-confirm; an expired certificate or a changed tailnet name marks the phone path unavailable in the tray, and the hardened confirm then uses a confirm computer or the OS admin prompt until you register again"; plus a §15 row (cert renewal fails, name changed, cert left over after uninstall) and a §16 test. finding: [minor] 5. "Only on a phone or another computer" in §14 and §16 drops the stated fallback. Where: §14 L1843, §16 L1991-1993 and L2030-2031 ("never offered on this computer's own screen"). Why: §4.2 L636-642 falls back to the OS admin prompt on this computer when no phone or second computer exists. The §16 test as worded would fail on a fresh install with no phone. Fix: "the hardened confirm is never the confirm panel on this computer's screen; with no phone or confirm computer it is the OS admin prompt (not on X11)", in §14 and both §16 lines. |
|
lens: security r7 · reviewer: Opus 5.5 (t3lens-26-sec-r7) · read at: a4d62fb earlier:
state check ("What happens when this runs again, stops half-way, or meets existing/old/empty state?"): §15 now covers these: ledger-once for the up-front denies, WebClient and SMB; neutralise and restore finished from the ledger; unverified retaken copies; the clean-gap and unclean-gap rules; deny-execute journalled; lock leases; the freeze; service identities and an old SYSTEM proxy; passkeys and confirm keys; the DCOM re-walk; power loss. Still missing: finding: [major] M1. Guard-privileged operations on your own profile and persistence points are outside the link-safe rules.
finding: [minor] m1. The guard identity key is trusted on the word of the host T3 (§10 L1598-1602, §4.2 L622-625). The host T3s exchange identity keys and pass them to their guards. The host T3 is a non-admin process that faces the network, which is the M1 threat in §14 L1844. finding: [minor] m2. The rule "never from a computer that sends tool calls to this one" is checked at enrolment (§4.2 L627-628). L1955 can be read as enrolment-only too. finding: [minor] m3. The guard-to-guard item fetch has the guard itself (SYSTEM or root) serve and parse tailnet input (§4.2 L619-620). That runs against the S2 reasoning that moved the phone page to t3-confirm. finding: [minor] m4. Proxy openings and "the public internet" apply to the whole agent group, not to the task that asked (§3.5 L355-376). The default scope is "this task", but the loopback proxy cannot tell which task connects, and direct-connection filters are per SID. finding: [minor] m5. Per-user COM registrations are not named in the DCOM walk (§3.1 L207). The agent account can write its own HKCU\Software\Classes CLSID and AppID keys, and a LocalServer32 it registers there is started by the COM service, outside the guard's job. finding: [minor] m6. Old state of the ledger itself (§3.3 L257-264; §15 L1911-1913, L1957). The ledger is the only record of what the install changed. If it is lost, or restored from an old image, a reinstall records the guard's own values as the originals (the DCOM deny ACEs, WebClient Disabled, the QUIC setting), so uninstall can no longer tell them apart. |
Defer the remote confirm (phone and second computer: rule 7, the t3-confirm page and identity, registration, guard confirm and identity keys) to its own design, listed in section 17. The hardened confirm is the OS's own admin prompt in v1; X11 gets no on-screen acting and no high-risk OK. Drop the lock while acting (leases, start-time lock restore); the 2-min acting window and the 30-min rule stay as plain time windows. Add rule 1 (the guard only takes away on places another account can write, by file identity) and rule 2 (nothing running as you changes who confirms). Label controls boundary or tripwire in section 14. Fold the one-line minors (per-user COM in the DCOM walk, lost or old ledger, S4U re-register, SMB residual covers the tailnet); list the rest. Pending questions now point to issues 28 and 29. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
lens: t3lens26-cut-corr (correctness + state, last round) · reviewer: Opus 5.5 · read at d1b9af0 (diff a4d62fb..d1b9af0, docs/fork/host-tools.md) summary: close, but not merge-ready by two short text fixes. 2 majors (both one sentence), 4 minors for the merge list. cor r4 major 1 (last-phone reset) is closed. sec r7 M1 is closed for your profile but still open for the user-writable system spots. The cut left no dangling references. (1) Old majors
(2) Dangling references: none. §4.2 rule 7, (3) New holes [major] A. Rule 1 lets a give run as SYSTEM or root at user-writable system spots.
[major] B. On Linux the hardened confirm's "protected surface" runs as you.
(4) Author's open points
Minors (merge list)
State question (runs again, stops half-way, meets old or empty state):
verdict: merge after fixes A and B (one sentence each); the minors can go to the build list. |
|
lens: t3lens26-cut-sec (security, round 9 scope cut, diff-only) · reviewer: Opus 5.5 · read at d1b9af0 (diff a4d62fb..d1b9af0, docs/fork/host-tools.md) summary: almost ready. 0 blockers, 1 major, 5 minors. The cut is clean. Correctness r4 major 1 (resetting the last phone) is closed. Security r7 M1 is closed for the attack it named (the lock layer and its start-time restore are gone, and rule 1 covers your profile and persistence points). One part of M1 is still open: the wording of rule 1 lets the guard "give" with admin rights on the user-writable system spots. The fix is one sentence. I found no dangling references to the phone, t3-confirm, the Confirm page, the locks or leases, or the identity keys, and the "rule 7" in §4.3/§11 is the activity-log rule as it should be. state check (runs again, stops half-way, meets old or empty state): no build exists ("design, no code"), so no install ever had the lock layer or a phone. Nothing needs migrating, and rule 2 already makes the later remote-confirm design go through the admin-run install. The lock-window §15 row was removed. The "clean gap" rule in §4.3 rule 6 L853-854 now depends on "no open acting window in the journal", which comes from the batch journal and the grant records (§15 L1898, L1908). That works, but it is implied rather than stated (minor m4). X11 after a kill switch has no way back except the install (minor m3). check 1, majors:
finding: [major] M1b. Rule 1 gives "with the rights of the account that owns the place", but on the user-writable system spots the owner is an admin. Where: §3.4 L342; §4.3 rule 6 L836-837 (the guard keeps copies of "every small watched system spot" and restores them); §4.4 table L975 (system spots use "the same two layers", including the restore from the copy); the §3.4 link rules 1-5 cover only "a path inside an agent-writable folder" (L324), and system spots are denied to the agent group, so they are not agent-writable. Attack (Windows): (1) code running as you (the accepted residual) writes a DLL into a ProgramData folder that an installer made, owned by Administrators with Users:Modify. You are a known writer, so the guard refreshes its copy with that content. (2) A marked process changes the file, so the guard holds it: it renames the file away (a take-away, fine) and then restores the content from its copy. (3) Before the restore, the unmarked code swaps a subfolder on that path for a junction to a system folder. (4) The restore runs "with the rights of the account that owns the place", which is Administrators or SYSTEM, so the DLL lands in the system folder. That is SYSTEM code with no prompt. Rule 1's own reason ("a give the place's owner could make anyway") only holds when the owner is no stronger than the person who can write the place. §14 L1810 counts this as a major, because the tripwire itself hands out new rights. Fix (one line, L342): "is done with the rights of the least-privileged account that can write the place (your account for your profile, your persistence points and the user-writable system spots), never more; if that account cannot do it, the give is refused." Also widen L324 to "a folder any account other than the guard can write". finding: [minor] m1. Keep, "make it mine" and Keep-moves-it-back (the §17 open point, L2155-2157) need a stated build rule. As written, rule 1 makes three things impossible: Keep of a root-owned mode-000 file (§4.4 L965), Keep moving a neutralised item back out of the guard-owned folder (L826; you cannot read that folder), and "make it mine" (L744; neither you nor the agent account can chown to you). Refusing is safe, so this is not a hole. Recommended rule: (a) the guard may reverse its own earlier take-away, on a ledger-matched handle whose identity also includes the generation (the NTFS sequence number, or the birth time on macOS and Linux, so a reused inode does not match), with link count 1, into a parent opened by handle and also ledger-matched. That gives back only what was there before. (b) "make it mine" copies finding: [minor] m2. On Linux, polkit is not a "protected surface". Where: §4.2 L617-618 ("Code running as you cannot draw on or click that surface"); §14 L1806-1808 labels "the OS admin prompt" a boundary. The polkit authentication agent is a process in your session that runs as you. Code running as you can end it and register its own (Wayland too), then show other item text for the real request or read the password. Before the cut this was only the fallback; now it is the only hardened path on Linux. It still needs the admin password, so it remains a boundary against the agent account, but the text you read is not trustworthy against code running as you. Fix: say this in §4.2 and in the §14 "Elevation without a prompt" residual (today it names X11 only). Option for the build: the guard also shows the item's text and code in its own panel, and polkit counts only as proof of the password. finding: [minor] m3. On X11 there is no way back after the kill switch or a failed verify. Turning host tools back on and confirming an outside store edit are hardened actions, and §4.2 L562-563 refuses those on X11. This fails closed and is safe, but no recovery route is written down. Fix: "on X11 these are done by re-running the admin install." finding: [minor] m4. The acting window's state is implied, not stated. The §15 lock row that held the window's state is gone. Fix: add to §15 "marks and baselines" (L1898): "the acting window is read from the batch journal and grant records (end + 2 minutes); a stop inside it counts as unclean." finding: [minor] m5. Wording left over. L470 still says "hardened T3 confirm" (now "hardened confirm"). §14 B2 row (L1817) lists only the link-safe operations; add "the guard only takes away on places others can write (§3.4 rule 1)". §3.1 reach row (L202) says Modify is "applied by the guard"; under rule 1 that is a give, so the row should say "as you" or name the exception. §4.3 rule 6 L826 "restored from the guard's own copy" could add "(as the place's writer, §3.4)" (author's open point; rule 1 already covers it, since it names "restored content"). check 3, new holes from the cut: none besides M1b and m2. Without the lock, Windows has about 1 s between a write to a persistence point and its revert during an acting window (ETW cannot block). The watch ends every process the entry started, and §14 labels this a tripwire, so it stays a tripwire gap and is not a major. The §14 boundary and tripwire labels are right except the OS admin prompt on Linux (m2). verdict: merge after the one-line M1b fix. Put m1-m5 on the merge list. |
… protected surface Final text fix (round cap) of the r9 scope cut, from the last correctness and security diff lenses: - Audit rule 1 (§3.4): a give (owner, permissions, restored content, Keep) runs as the least-privileged account that can write the place (you), never as an administrator, SYSTEM or root; refused if that account cannot. Only exception: the guard undoing its own take-away on the same object, identity incl. generation and link count 1 matched through the checked handle. "Make it mine" copies as you. The link rules now cover every folder another account can write. - §4.2/§14: the Linux polkit agent runs as you and can be replaced, so it counts as the hardened confirm only where the build proves it cannot; otherwise those items are refused, as on X11. Where the hardened confirm is refused, the way back is re-running the admin install. - One-line minors: restore and Keep "as you", waiting for sign-in without a token; acting window state in §15; wording at §3.1 reach row, §4.2, §14 links row, §17. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Design only, no code. Adds
docs/fork/host-tools.md. It lets agents on one of a person's machines work on another of their computers. You give the agent a goal; it picks the fastest route: files, commands, app automation, app bridges, accessibility elements, and input last. Commands run as a separate limited agent account, so the OS enforces the rules.Round 9 = scope cut per stuck audit
The review rounds kept adding mechanisms. Round 9 only removes or narrows; it adds no new mechanism. The doc goes from 2191 to 2170 lines.
What was cut
t3-confirmpage and service identity, phone registration, guard confirm keys and guard identity keys (§10), the §1 Confirm page row, and their §15 rows and §16 tests. They are one line each in the new "Deferred" list in §17.What was added (rules only, no mechanism)
Final text fix (round cap)
From the last correctness and security diff lenses at d1b9af0; wording and narrowing only, no new mechanism.
.gitas you, never a recursive chown as root or SYSTEM. The §3.4 link rules now cover any folder an account other than the guard can write (also corr m4).Minors left at merge
setfsuid) and for the guard undoing its own take-away.docs/fork/host-tools.mdwas already red at d1b9af0 and is still red; no formatter was run (doc-only round, no builds).Open for the owner (§17)
General vs project
All of it is general. Machine names, addresses, accounts, folders, apps, bridges, recipes and named commands are local policy and settings. The examples use placeholders only.
Upstream fit
See §2 and §10 of the doc. Read at upstream
main8cd8a3c on 2026-11-13, with file:line for every claim. Related upstream items: pingdotgg#33183, pingdotgg#9884, pingdotgg#31755, pingdotgg#26757, pingdotgg#25024, pingdotgg#34293, pingdotgg#36102, pingdotgg#32639, pingdotgg#35519, pingdotgg#32158, pingdotgg#35826. Computer-use work considered but not built on: pingdotgg#16527-pingdotgg#16532 and pingdotgg#28050.🤖 Generated with Claude Code