Popy is a macOS-first local trust broker for files arriving from browsers and AI coding agents. Its job is narrow: keep policy-sensitive raw bytes inert until the user approves them, while making ordinary downloads and safe inspection feel normal.
Popy has two policies:
- Standard (default): ordinary documents, images, archives, and media save normally. Literal native binaries and scripts are held for approval.
- Strict: every raw file is held. Fixed safe views such as sanitized text or sandbox-rendered images remain available without raw access.
There is no filesystem watcher. Popy protects explicit ingress through the
browser extension, popy CLI, MCP server, and agent adapters. It does not claim
OS-wide interception of unrelated programs.
For an agent, the useful interface is intentionally small:
popy_open(url)fetches, verifies, and returns a safe view in one visible tool call. It never returns a raw path.popy_save(url, to)saves immediately under Standard when the artifact is not executable; otherwise it returnsapproval_required.popy_approve(id, to)is the explicit raw trust transition.
The old popy_fetch and popy_read_text MCP names remain available for one
compatibility release only when POPY_ENABLE_LEGACY_MCP=1.
flowchart LR
B["Chromium extension"] -->|"bounded JSON"| N["popy-native"]
N --> D["popyd trust broker"]
A["Codex / Claude / Cursor"] --> M["3-tool MCP adapter"]
M --> C["popy CLI"]
C --> S["sealed stage + signed sidecars"]
D --> S
D --> L["SQLite artifact ledger + policy"]
B --> O["browser OPFS"]
B --> L
U["browser dashboard"] -->|"approve / delete"| D
popyd owns shared policy and artifact lifecycle. The native host transports
control JSON only, never file bytes. Browser fallback files land under
Downloads/popy-quarantine, are explicitly adopted by the broker, inspected by
content, and either materialized or held.
The local stage remains mode-restricted and indexer-excluded. Artifacts have
SHA-256 metadata and HMAC-signed sidecars. Raw reads re-verify identity, size,
signature, and content hash. PDF/image derivation stays in the restricted
popy-render child.
Browser extension:
npm install
npm run buildLoad dist/ as an unpacked Chromium extension and note its extension ID.
Native components on macOS:
brew install cmake curl sqlite3
cmake -S popyd -B popyd/build -DCMAKE_BUILD_TYPE=Release
cmake --build popyd/build -j
cmake --install popyd/build --prefix "$HOME/.local"
sh "$HOME/.local/share/popy/dist/install-macos.sh" EXTENSION_ID "$HOME/.local"The last command registers the per-user launch agent and native-messaging host for Chrome, Chromium, Brave, and Edge. The manifest is scoped to the supplied extension ID.
Linux still builds the CLI/broker and includes a systemd user unit, but the integrated installer and browser bridge are currently macOS-first.
Normal browser use needs no new ritual. Under Standard, ordinary downloads are saved automatically. If a binary or script arrives, Popy shows it in the dashboard with its hash, detected type, origin, and destination; approve or delete it there.
CLI:
popy save https://example.com/report.pdf --to ~/Downloads/report.pdf
popy save https://example.com/tool.sh --to ~/Downloads/tool.sh
popy approve ARTIFACT_ID --to ~/Downloads/tool.sh
popy policy --set strict
popy status --jsonLower-level fetch, read, verify, release, and resign commands remain
for compatibility and debugging. New integrations should use save, approve,
and the three MCP tools.
Codex uses Popy through its MCP server:
codex mcp add popy -- python3 "$HOME/.local/share/popy/mcp/server.py"Also copy the rules from
$HOME/.local/share/popy/dist/AGENTS.popy.md into the applicable AGENTS.md so
shell downloads are routed through the MCP interface.
Claude Code and Cursor adapter snippets are installed under
$HOME/.local/share/popy/dist/. Their pre-shell hook denies or rewrites direct,
simple downloader commands; complex downloader invocations fail closed with a
clear popy save instruction.
Popy is not antivirus and does not decide that a file is benign. It controls when raw bytes acquire a normal executable or handler-visible filename.
It protects:
- browser downloads captured by the extension;
- agent downloads made through Popy's MCP tools or guarded adapters;
- verified reads and releases from the private stage;
- explicit browser fallback adoption.
It does not protect:
- arbitrary programs that bypass every Popy entry point;
- malicious content after the user explicitly approves raw access;
- browser or kernel compromise;
- remote URLs merely mentioned in text but never fetched through Popy.
Origin allowlists no longer bypass byte-derived policy. Direct native-messaging access is restricted by Chromium's extension-origin manifest and the broker's same-user Unix socket.
src/ Chromium extension
background/ capture and lifecycle routing
offscreen/ streaming validation, hashing, OPFS
dashboard/ shared approval surface
lib/native/ bounded native-broker client
popyd/
src/broker/ SQLite ledger, policy, JSON service
src/cli/ popy commands
src/store/ sealed stage, signed sidecars, release
src/render/ restricted safe derivation child
mcp/server.py popy_open / popy_save / popy_approve
dist/ service, native host, and agent adapters
See docs/ARCHITECTURE.md for component-level details.