Skip to content

Instantly share code, notes, and snippets.

@mwolff44
Created August 18, 2026 05:24
Show Gist options
  • Select an option

  • Save mwolff44/20a51174c89b13f30f85a814126a3cca to your computer and use it in GitHub Desktop.

Select an option

Save mwolff44/20a51174c89b13f30f85a814126a3cca to your computer and use it in GitHub Desktop.
Greywall 0.3.7 - hardened security profile for the pi coding agent (~/.config/greywall/learned/pi.json). Replaces the built-in profile entirely: closes pi's persistence vectors (extensions, git, agents, bin, settings.json, auth.json) while keeping ~/.pi/agent writable for pi's proper-lockfile lock, without which the settings "packages" field is …
{
"schemaVersion": 1,
"network": {
// Pushed to greyproxy as session rules, removed when the run ends.
// Only open the providers you actually use.
"rules": [
{ "destination": "YOUR_PROVIDER", "port": "443", "action": "allow", "notes": "LLM provider API" },
{ "destination": "registry.npmjs.org", "port": "443", "action": "allow" },
{ "destination": "github.com", "port": "443", "action": "allow" },
{ "destination": "api.github.com", "port": "443", "action": "allow" },
{ "destination": "*.githubusercontent.com", "port": "443", "action": "allow" }
// pi.dev is deliberately absent: run pi with PI_OFFLINE=1
]
},
"filesystem": {
// A file in ~/.config/greywall/learned/ REPLACES the built-in profile
// entirely, base protections included, so this profile has to be
// self-contained. That is why the useful paths of the base profile are
// repeated here.
"allowRead": [
".",
"~/CLAUDE.md",
"~/AGENTS.md",
"~/.gitconfig",
"~/.gitignore",
"~/.config/git",
"~/.ssh/config",
"~/.ssh/known_hosts",
"~/.skills",
"~/.agents",
"~/.pi",
"~/.local/share/pnpm"
],
// ~/.pi/agent has to be writable, for one reason only: pi reads its
// settings through proper-lockfile, which creates the DIRECTORY
// ~/.pi/agent/settings.json.lock before every access. With a read-only
// parent that mkdir fails with EROFS, pi never opens its settings, so it
// ignores the "packages" field and no installed package is ever loaded.
// ~/.agents, ~/.skills and ~/AGENTS.md stay out of this list: persistent
// prompt injection closed.
"allowWrite": [
".",
"~/.pi/agent",
"~/.ssh/known_hosts"
],
// Write protection therefore no longer comes from the parent being absent
// from allowWrite, but from this list. A denyWrite path is a bind mount, so
// it is neither writable (EROFS), nor removable, nor replaceable with mv
// (EBUSY on both), and it applies recursively to a directory. Its only
// limit: a path that does not exist yet is not protected at all, because
// Greywall implements denyWrite with --ro-bind, which requires an existing
// path. Every entry below therefore points at something that exists.
//
// ~/.pi/agent/security/ stays writable on purpose, so that the audit log,
// its rotation and skill-approvals.json keep working.
"denyWrite": [
"~/.pi/agent/extensions",
"~/.pi/agent/git",
"~/.pi/agent/agents",
"~/.pi/agent/bin",
"~/.pi/agent/settings.json",
"~/.pi/agent/models-store.json",
"~/.pi/agent/auth.json",
"~/.pi/agent/security/security-policy.json",
"~/.pi/agent/security/command-rules.json",
"~/.pi/agent/security/injection-rules.json",
"~/.pi/agent/security/protected-paths.json",
"~/.pi/agent/security/audit-config.json",
"~/.pi/agent/security/allowed-external.json"
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment