Pi writable-area audit

Date: 2026-08-30 00:52 UTC
Repository: $HOME/Documents/dotfiles
Scope: .local/bin/pi.sh, the Pi runner image, the parallel-agent extension, current Podman mounts, and the relevant upstream documentation.

Executive summary

The current design is close, but the full-home read-only bind currently in the working tree is the wrong protection for this machine. In this environment it also exposes nested Podman storage mounts, some of which are still writable.

The simplest fix is:

  1. Remove the full $HOME:$HOME:ro bind. Keep the earlier selective-mount design already present in HEAD.
  2. Stop passing the host Podman API socket by default. Make it an explicit opt-in only for tasks that need nested Podman/Testcontainers.

This preserves the normal workflow: Pi can edit the current workspace, Git metadata, Pi state, and published reports. It avoids adding Gondolin, Landlock, another user, or a second workflow.

There is one unavoidable boundary: a process cannot both write a host directory and be protected from a mistaken command that targets that same directory. For a completely protected audit, use a read-only workspace plus disposable state or a copy-on-write workspace. That should be a separate audit mode, not a replacement for the current coding workflow.

How the current path works

Findings, ordered by impact

1. Full-home bind exposes writable Podman storage — high

Path: .local/bin/pi.sh, the uncommitted --volume "$HOME:$HOME:ro" line.

Evidence: A disposable Podman run using the same full-home bind was inspected without changing files. The container could see $HOME/.local/share/containers/storage/overlay and overlay-containers; both reported writable with test -w. The current host has that storage on a separate read-write mount. A read-only parent bind did not make those nested mounts read-only here.

Why it matters: A broad rm -rf "$HOME" may encounter many nested mountpoints, and a targeted command can definitely operate on the exposed writable storage. This can delete or damage images, container metadata, merged filesystems, or running-service data. The full-home bind also makes every user-readable home file visible to Pi, including files that only needed to be inaccessible rather than read-only.

Smallest next step: Remove the full-home bind and restore the selective mounts from HEAD/commit fd1f26f. Do not rely on :ro on a parent directory to protect separate submounts.

2. The Podman socket is a full host control interface — high

Path: .local/bin/pi.sh, the /run/podman socket bind and CONTAINER_HOST/DOCKER_HOST variables.

Evidence: The wrapper exposes the host user's rootless Podman API. Podman's official documentation says the API grants full access to all Podman functionality, permits arbitrary code execution as the API user, and has no ability to limit or audit that access.

Why it matters: Even if the host storage path is not directly visible, an agent with the socket can ask the host Podman service to create, remove, inspect, or run containers and mount host paths. :ro on the socket file is not an API permission filter.

Smallest next step: Make the socket optional. Normal pi.sh runs should omit the socket and both API environment variables. Add a small explicit --podman wrapper option for the uncommon tasks that need nested Podman/Testcontainers. If those tasks are not needed, remove the socket support entirely.

3. The remaining writable binds are intentional, not protected — medium

Paths: workspace and Git common directory; ~/.pi; $XDG_STATE_HOME/pi; $XDG_DATA_HOME/published-sites; project .pi/agent when running from the dotfiles repository.

Evidence: These are explicitly mounted rw in pi.sh, because the normal workflow needs them. A controlled disposable test showed that rm -rf "$HOME" receives read-only errors at nested writable mount roots and did not delete the test files. A direct command aimed inside a writable mount did delete its test file.

Why it matters: The broad typo is helped by mount boundaries, but no mount can protect a directory that Pi must be allowed to modify. A command such as rm -rf "$HOME/.pi/*" remains destructive by design.

Smallest next step: Keep these mounts for normal coding. For unattended/read-only audits, use a separate mode with no host rw binds except a disposable copy-on-write workspace, and export/review the result afterward if needed.

4. “Isolated” parallel workers are not OS-isolated — medium

Path: .pi/agent/extensions/agents.ts.

Evidence: RpcWorker.start() spawns pi directly with cwd and inherited environment. It does not call pi.sh or create another Podman container. The extension also passes the parent session's active tools to every worker.

Why it matters: One worker can affect the same writable mounts used by the parent and sibling workers. A prompt saying “read-only” is not an enforcement mechanism.

Smallest next step: Rename/document this as process-level parallelism. For audit jobs, start Pi with a read-only tool allowlist where possible (read, grep, find, ls); this is only a tool restriction, not a security boundary. The container mount changes above remain the real protection.

Recommended minimal layout

Keep the existing rootless Podman runner and its useful hardening:

Use only selective host mounts:

This is a small rollback plus one opt-in switch, rather than a new sandbox architecture.

Alternatives considered

Checks performed

Sources

Accessed 2026-08-30 UTC: