Under the hood
The first process never runs the page’s code. The second runs nothing else, and the operating system takes its network away before the window opens.
Has the network and never runs the page. It reads the tags from the file on disk before any script runs, shows the popup, and runs the proxy for papp-net.
It starts the second process in the OS sandbox: Seatbelt on macOS, bwrap --unshare-net on Linux. The kernel refuses sockets and DNS to it and to every program it starts.
Before it opens the window, the sandboxed process proves it is offline: on macOS it must fail to bind a loopback port, on Linux its network must hold only lo. If it cannot, it refuses. No flag skips this.
Its only way to files is window.papp, which checks every path against the grants, links resolved. The OS also limits writes to the granted folders, for the page and every program it runs.
The tags
One value per <meta>, read from the file on disk. A running page cannot add one, and an unknown tag stops it from opening. With no tags, a page sees its own folder and nothing else.
report.papp<meta name="papp-read" content="~/logs">
<meta name="papp-write" content="./reports/*.md">
<meta name="papp-net" content="api.example.com:443">| Tag | Allows | For example | In the popup |
|---|---|---|---|
| Files | |||
papp-read | See a folder and its subfolders. | ~/logs | plain |
papp-write | See, change and delete files there. | ~/notes/*.md | yellow |
| Network | |||
papp-net | Send and receive data with one server, through the proxy. | api.example.com:443 | plain |
papp-socket | Talk to the program behind one socket file. | /var/run/docker.sock | red |
| Programs | |||
papp-run | Run one program. It can read every file but keys and logins. | git | red |
papp-env | Give its programs one variable. | AWS_PROFILE | plain |
| Devices | |||
papp-use | Use one of notifications, sound, the clipboard, location, the camera, the microphone or the screen. | camera | yellow |
papp-shortcut | Answer a key combination in every app. | CommandOrControl+Shift+N | yellow |
| Everything | |||
papp-node | Full Node: any file, any program, as you. Only for pages you trust. | full | red |
The popup
Out of reach
The OS hides ~/.ssh, ~/.gnupg, ~/.aws, cloud and GitHub logins, saved passwords and browser profiles from the page and every program. Only a tag that starts inside one opens it, and that line is red.
The proxy looks up names itself and refuses any that lead to this computer or your network: the router, a VPN such as Tailscale, cloud metadata. Every request through it, allowed or refused, goes to egress.log.
The window shows only the page: a link does not navigate and a frame does not load another file. Each page keeps its own storage, and on Linux it sees only its own processes.
Its one network call is the update check, at most once a day, and you can turn it off. No telemetry, no crash reports, nothing that runs at login.
Where it stops
These are the gaps that matter most. SECURITY.md lists every one.
papp-run program can read every file but keys and logins, and takes any arguments. That is why its line is always red.papp-node page has no network of its own, but it can launch an app that does, or write files that run later. Trust it as you would a script.git clone, curl and USB sticks leave no mark, so Apargon treats such a page as yours: no site in the popup, and no popup at all if it has no tags.pnpm test runs every check in the real app, on macOS and Linux, and fails if one fails: the offline refusal, the proxy’s refusals, the hidden keys.
Completely free and open source, under the MIT license. Read every line on GitHub.
Also for Intel Mac · Linux (.deb)