Threat model
wloop runs agents as you, on your machine, with your GitHub and Slack identities. This page says where its checks stop. Report a vulnerability as the repository’s security policy describes.
The agent acts as you
Section titled “The agent acts as you”A round publishes on GitHub through gh, with your login. On Slack it proposes drafts that go out with your token once their gate approves them, or posts at once through a tool the loop serves to the session, which tells the agent to ask you first when it is unclear whether you want the message posted in your name. wloop does not try to keep an agent from an identity that is already yours, on GitHub or on Slack.
The gates, the approval policies and the refusals below are workflow controls. They hold against an agent’s mistakes, and they keep text from other people from approving a draft: no agent’s or thread’s text sets a draft’s approval (see What waits for you). A draft whose policy waits for you is held, whoever steered it. They do not hold against an agent, steered or not, that acts as you by another path: gh, a tool that posts, reacts or edits on Slack, the daemon’s socket or your Keychain. To keep an agent from your identities, run it where they are not: a virtual machine or another user account that does not mount your home directory or forward your SSH agent or credentials. wloop does not set this up for you.
The same-user boundary
Section titled “The same-user boundary”Every protection in wloop assumes that the processes running as your user are on your side. An agent session with a shell is such a process.
- The daemon’s Unix socket (
<state>/daemon.sock, mode 0600 in a 0700 directory) serves the CLI with no further authentication. Any process running as you can start rounds, answer questions and approve drafts through it, and write a reply that goes out at once as you (thereplyverb andPOST /api/draftswith send now). - The Slack tokens live in your login Keychain, in items created through
/usr/bin/security. Any process running as you can read them with the same command, without a prompt, and post to Slack as you, just as it can runghas you on GitHub. - A session you drive acts with your authority: the loop never typed into it, or you prompted it since the loop last did, and no turn the loop sent runs in it. Such a session runs task verbs, answers other agents’ questions and approves drafts and rounds as you would; a worker session the loop is driving does none of it, and no session resolves a gate whose draft or question its own turn produced. The daemon tells them apart by the session that holds the request’s handle, so this is a guard against an unattended agent’s mistakes, not a security boundary: the handle is what the caller says it is.
wloop outbox approve,outbox retryandoutbox new --sendfollow the same rule, refuse in an agent session’s environment that names no workspace manager’s handle, and ask at a terminal unless--yes.slack sendhas no such refusal.- An agent that follows injected instructions has everything you have: your
ghtoken, your SSH keys, your Keychain and your files. - Every session the loop starts is denied the control surface of every enabled workspace manager:
open claude://*, Claude Desktop’s session registry (~/.claude/sessions) and its peer sockets and keys (/tmp/cc-socks). That keeps a round from driving another session through its manager by mistake; like the other denies, it is no boundary against a process that runs as you. A manager’s own CLI, such asorca, stays allowed in every session, so a session can open Orca’s browser or hand work to another worktree; a session that follows injected text already hasgh,git pushandcurl.
What the local endpoints authenticate
Section titled “What the local endpoints authenticate”- The cockpit listens on
127.0.0.1and requires the token in<state>/http.json(mode 0600) on every path, static files included.wloop openhands the token to the browser once; the page then keeps it in an HttpOnly,SameSite=Strictcookie. The agent and admin APIs are not served over TCP. The token is kept across restarts. - The MCP endpoint listens on
127.0.0.1:47800(mcp.port) and requires the key in<state>/mcp.key(mode 0600) in theX-Loop-Keyheader. It checks theHostheader and refuses any request with anOrigin.X-Loop-Sessioncarries the process handle from the environment of the session’s manager (one variable per enabled manager inworkspace_managersorder, such as${CLAUDE_CODE_HOST_SESSION_ID:-${ORCA_TERMINAL_HANDLE}}by default), and the daemon resolves it to the session. The handle is what the caller says it is: any holder of the key can record a turn for another session. When another process already holds the port, the daemon refuses to start and namesmcp.port, so sessions never send the key to it.
Both endpoints keep other local users and web pages out. Neither keeps out a process running as you, which can read the token and the key from the state directory.
What automatic rounds do on GitHub
Section titled “What automatic rounds do on GitHub”Automatic rounds act under your GitHub identity. The agent publishes reviews, inline comments and replies itself, through gh in its session, and an address round commits and pushes to your branch.
The approval window comes before a round starts, not before what the round publishes. The rules that a first review round never approves, that an unsure round holds its draft and asks, and what a follow-up may approve are instructions to the agent. What the agent can actually run is decided by your Claude Code permissions, plus the allows wloop adds for its rounds. No code checks a review before it goes out. Only drafts wait on a gate: Slack messages and reactions, and the comments that Act and the GitHub plugin’s nudge rule propose.
What reaches an agent from other people
Section titled “What reaches an agent from other people”- Address rounds read third-party comments. Reviews and review threads on your pull requests start address rounds, from any GitHub account that can comment on the repository, apart from the logins in
plugins.github.address_skip_reviewers. On a public repository that is anyone. The agent verifies each comment against the code, but the comment’s text reaches it, and so does anything the comment asks for. - Review rounds open the pull request’s checkout. An automated review round starts Claude Code in a worktree at the pull request’s head. Claude Code loads that checkout’s project settings, so a pull request that changes
.claude/settings.json(hooks, permissions),.mcp.json,CLAUDE.mdorAGENTS.mdcan run code or steer the agent as you. Claude Code’s folder trust is recorded for the repository and covers every pull request worktree of it; where project settings are off, trusting a blocked round from the cockpit trusts that worktree alone. Automated review rounds skip forks and authors outside the members, collaborators, owners andtrusted_authors, so the author needs push access to the repository; a round you start by hand has no such filter, but its session never loads the checkout’s project settings for such a pull request.
Merges and relations the loop runs
Section titled “Merges and relations the loop runs”The bundled task-fold-linked, task-relate-linked, task-correlate-merge-linked and task-correlate-relate merge or relate two tasks at once, without asking (see Tasks and views). What picks the pair is other people’s text: a review link in a Slack ask, or a model’s judgment over titles and item summaries. The residual risk is accepted: a crafted message can merge its task into the one pull request’s task it links, or relate two tasks; two tasks that each hold a pull request are never merged at once. Nothing is posted and nothing is lost: Split and Unrelate undo either, a pair you took apart is never merged or related again without you, and the timeline names the workflow that did it. Pin a wait rule to those workflows’ merge and relate gates to have them ask instead.
Starting new work
Section titled “Starting new work”wloop start gives an agent the brief of a task you captured and, with --repo, a fresh worktree of that repository. It adds two powers:
- An agent chooses which repository to clone. A repository no workspace manager knows is cloned into the project’s
projects_dir. That stays harmless until the clone is trusted: Claude Code loads an untrusted folder’s settings and hooks only after you accept its trust dialog, so the start blocks on trust as any round does. trust_clonestrusts a clone as it is made. Withtrust_clones = trueon aprojects[]entry, a repository of that project is trusted in the agent home in the same call that clones it, so its hooks and settings run as you. Thematchmust name an owner (acme/*, never*); a folder that already existed, or one cloned earlier, is never trusted this way. A clone whose trust fails is removed, so the start’s retry clones it again.
The residual risk is accepted: an injected brief can make an agent clone and trust a repository of an owner you listed. Two limits remain. Start runs only on tasks you captured (task.captured_by_user); a task derived from a Slack thread carries other people’s text and is refused. And the trusted owners are the ones you list, your own organization.
The omnibox agent call
Section titled “The omnibox agent call”A free-text request in the omnibox runs one read-only agent call (see Turns). It reads a snapshot of your open tasks, views and repositories in a directory written for that call and removed after it (or, after a crash, at the daemon’s next start). Its only tools are Read, Grep and Glob, granted on that directory alone; the agent runs in an empty directory beside it: no shell, no MCP server, no checkout, and no other call’s snapshot. The call stops after two minutes. Task titles and notes in the snapshot are other people’s text, and your request may quote some too, so the prompt marks both as untrusted data. The call writes nothing: its proposal is not a gate and resolves none, it is never stored, and what it proposes runs only when you confirm it in your client, through the same requests a click sends, so a proposed review is drafted and waits for your approval like any other. Proposed tasks and units the snapshot lacks, and operations the omnibox’s vocabulary does not run, are dropped before you see them. Its cost counts against headless.per_call_usd and headless.per_day_usd.
What agents read from Slack
Section titled “What agents read from Slack”Every agent session the MCP server serves reads Slack as you, within what your user token can read:
slack_readreads a stored unit, or the thread of any channel message whose ref or permalink the agent holds, private channels and Slack Connect channels included; a direct message or a group DM only when wloop tracks it.slack_searchruns Slack’s search over every channel you can read, and returns the permalink, author, time and first line of each match. An agent needs no permalink handed to it to find a channel’s messages: one query reaches any channel you are in. Direct messages and group DMs are left out unless tracked, the same lineslack_readdraws, and the result says how many it left out.
Reading is as you, like posting: wloop keeps no channel from a session, and the line on direct messages is a workflow control, not a barrier, since the agent can reach your token by the paths above. What an agent reads this way is other people’s text, and reaches it as data it may follow: a message found by search can ask for anything, and no such text approves a draft. plugins.slack.post_allowlist bounds where any post lands; turning the Slack plugin off (plugins.slack.enabled = false) removes these tools.
The settings that limit it
Section titled “The settings that limit it”Two settings control what reaches an agent from other people:
- an instruction to the agent to treat comments by other accounts as untrusted data, and to stop and alert you when one asks for something suspicious;
- two switches you can turn off: automatic address rounds, and loading project settings in review sessions.
Both are on by default. The instruction is always on. The two switches are configuration keys whose value is a predicate over the pull request’s facts; where the predicate does not hold, the behavior is off. They read the owner, the repository, the author and the visibility, and intents.<intent>.project_settings.when also reads the author association and the labels. For public repositories, keep both to private ones:
[workflows.address]only_when = 'pr.visibility == "private"'
[intents.review.project_settings]when = 'pr.visibility == "private"'pr.visibility is empty until a pull request has been read in full, so these plan nothing for one not read yet. Narrow by owner, repository or author the same way (pr.owner != "acme", pr.author != "renovate[bot]"). Where project settings are off, review sessions start with --setting-sources user: your settings, hooks and MCP servers load, the checkout’s .claude/, .mcp.json and CLAUDE.md do not, and the agent reads CLAUDE.md and AGENTS.md as text. Claude Desktop cannot start such a session, so it runs in the next manager of workspace_managers that can (Orca), and fails closed when none can. In Claude Desktop, the allows and denies wloop adds for a round are written into the worktree’s .claude/settings.local.json, so they apply only where the checkout’s project settings load. See the configuration reference.
Distribution
Section titled “Distribution”Releases ship unsigned binaries with a checksums.txt, without a signature or build provenance. The Homebrew cask removes the com.apple.quarantine attribute from the binary after install, which turns off Gatekeeper’s check for it. wloop app install signs the app bundle ad hoc, in your home directory.