Scaffold project fence.jsonc with vendored starter template
Pass 3: when pi-fence loads in a git project that has no fence.jsonc,
prompt the user to scaffold one tuned for agent workflows. The template
is self-contained (no `extends`) because fence's slice-merge is
append-only — a child config cannot remove inherited rules, so the only
way to narrow the over-broad `**/*.pem` deny in fence's `code` template
(which breaks `mise install go|node|...` on tarball extraction of test
fixtures) is to author the policy from scratch.
Diff vs. fence's `code` template:
- network.allowedDomains adds *.jdx.dev, nodejs.org, dl.google.com,
static.rust-lang.org, releases.hashicorp.com
- filesystem.allowWrite adds ~/Library/Caches/** (mise cache on macOS)
- filesystem.denyWrite scoped to ./**/* instead of **/* so package
managers can extract tarballs containing .pem/.key test fixtures
New surface:
- policy/starter-template.ts — vendored JSONC string + version stamp
- policy/scaffold.ts — decideScaffold() + scaffoldStarterConfig()
- index.ts — maybeOfferScaffold() on session_start; /fence scaffold
slash command for users who declined the prompt
Verified end-to-end: `mise install go@1.23.4` succeeds (was failing
mid-tar on crypto/tls/testdata/example-cert.pem), project-tree
denyWrite still fires for .env / *.pem / *.key, unrelated domains
still 403.
See README §"Project scaffolding" for the rationale, drift trade-off,
and resync recipe; PLAN §"Pass 3 (shipped)" for the design summary.
Initial commit — pi-fence v0.1.0
Single-config sandbox extension for Pi, enforcing one shared
fence.jsonc across two layers:
1. Bash subprocesses, wrapped via the `fence` binary
(Seatbelt on macOS, bubblewrap+Landlock on Linux).
2. Pi's own file tools (read/write/edit/grep/find/ls), gated
by an in-process tool_call interceptor.
Both layers consume the same resolved config from `fence config show`
so they cannot disagree.
Features:
- Bash + user_bash wrapping with --no-fence escape hatch
- Footer status state machine (PLAN.md §12)
- Structured block payload back to the LLM (PLAN.md §6)
- Interactive grant prompt: [once / session / persist / deny]
with default-deny, realpath shown first, 500ms anti-fatfinger,
persist 2nd-confirmation
- Session grants persisted via pi.appendEntry, restored on
fork/resume
- Persist-to-disk grants edit-or-scaffold the project-root
fence.jsonc; jsonc-parser preserves comments, ajv 2020-12
validates, atomic write via tmp+rename, last-3 .bak rotation
- Mandatory denyWrite floor (PLAN.md §11) for ~/.pi/agent/{extensions,
skills,prompts}/**, ~/.config/fence/**, audit log, install dir
- Audit log at ~/.pi/agent/pi-fence.log with daily rotation,
7-day retention
- Per-turn rate limiter (3+ denials → batch confirm)
- Footgun lint: cwd===$HOME, secret canaries, top-level **,
brace expansion, bare ** segments
- Slash commands: /fence, /fence reload, /fence init, /fence log
See PLAN.md for the build contract and design decisions.
See README.md for usage and trade-offs.
Initial commit — pi-fence v0.1.0
Single-config sandbox extension for Pi, enforcing one shared
fence.jsonc across two layers:
1. Bash subprocesses, wrapped via the `fence` binary
(Seatbelt on macOS, bubblewrap+Landlock on Linux).
2. Pi's own file tools (read/write/edit/grep/find/ls), gated
by an in-process tool_call interceptor.
Both layers consume the same resolved config from `fence config show`
so they cannot disagree.
Features:
- Bash + user_bash wrapping with --no-fence escape hatch
- Footer status state machine (PLAN.md §12)
- Structured block payload back to the LLM (PLAN.md §6)
- Interactive grant prompt: [once / session / persist / deny]
with default-deny, realpath shown first, 500ms anti-fatfinger,
persist 2nd-confirmation
- Session grants persisted via pi.appendEntry, restored on
fork/resume
- Persist-to-disk grants edit-or-scaffold the project-root
fence.jsonc; jsonc-parser preserves comments, ajv 2020-12
validates, atomic write via tmp+rename, last-3 .bak rotation
- Mandatory denyWrite floor (PLAN.md §11) for ~/.pi/agent/{extensions,
skills,prompts}/**, ~/.config/fence/**, audit log, install dir
- Audit log at ~/.pi/agent/pi-fence.log with daily rotation,
7-day retention
- Per-turn rate limiter (3+ denials → batch confirm)
- Footgun lint: cwd===$HOME, secret canaries, top-level **,
brace expansion, bare ** segments
- Slash commands: /fence, /fence reload, /fence init, /fence log
See PLAN.md for the build contract and design decisions.
See README.md for usage and trade-offs.
Initial commit — pi-fence v0.1.0
Single-config sandbox extension for Pi, enforcing one shared
fence.jsonc across two layers:
1. Bash subprocesses, wrapped via the `fence` binary
(Seatbelt on macOS, bubblewrap+Landlock on Linux).
2. Pi's own file tools (read/write/edit/grep/find/ls), gated
by an in-process tool_call interceptor.
Both layers consume the same resolved config from `fence config show`
so they cannot disagree.
Features:
- Bash + user_bash wrapping with --no-fence escape hatch
- Footer status state machine (PLAN.md §12)
- Structured block payload back to the LLM (PLAN.md §6)
- Interactive grant prompt: [once / session / persist / deny]
with default-deny, realpath shown first, 500ms anti-fatfinger,
persist 2nd-confirmation
- Session grants persisted via pi.appendEntry, restored on
fork/resume
- Persist-to-disk grants edit-or-scaffold the project-root
fence.jsonc; jsonc-parser preserves comments, ajv 2020-12
validates, atomic write via tmp+rename, last-3 .bak rotation
- Mandatory denyWrite floor (PLAN.md §11) for ~/.pi/agent/{extensions,
skills,prompts}/**, ~/.config/fence/**, audit log, install dir
- Audit log at ~/.pi/agent/pi-fence.log with daily rotation,
7-day retention
- Per-turn rate limiter (3+ denials → batch confirm)
- Footgun lint: cwd===$HOME, secret canaries, top-level **,
brace expansion, bare ** segments
- Slash commands: /fence, /fence reload, /fence init, /fence log
See PLAN.md for the build contract and design decisions.
See README.md for usage and trade-offs.
Scaffold project fence.jsonc with vendored starter template
Pass 3: when pi-fence loads in a git project that has no fence.jsonc,
prompt the user to scaffold one tuned for agent workflows. The template
is self-contained (no `extends`) because fence's slice-merge is
append-only — a child config cannot remove inherited rules, so the only
way to narrow the over-broad `**/*.pem` deny in fence's `code` template
(which breaks `mise install go|node|...` on tarball extraction of test
fixtures) is to author the policy from scratch.
Diff vs. fence's `code` template:
- network.allowedDomains adds *.jdx.dev, nodejs.org, dl.google.com,
static.rust-lang.org, releases.hashicorp.com
- filesystem.allowWrite adds ~/Library/Caches/** (mise cache on macOS)
- filesystem.denyWrite scoped to ./**/* instead of **/* so package
managers can extract tarballs containing .pem/.key test fixtures
New surface:
- policy/starter-template.ts — vendored JSONC string + version stamp
- policy/scaffold.ts — decideScaffold() + scaffoldStarterConfig()
- index.ts — maybeOfferScaffold() on session_start; /fence scaffold
slash command for users who declined the prompt
Verified end-to-end: `mise install go@1.23.4` succeeds (was failing
mid-tar on crypto/tls/testdata/example-cert.pem), project-tree
denyWrite still fires for .env / *.pem / *.key, unrelated domains
still 403.
See README §"Project scaffolding" for the rationale, drift trade-off,
and resync recipe; PLAN §"Pass 3 (shipped)" for the design summary.
Scaffold project fence.jsonc with vendored starter template
Pass 3: when pi-fence loads in a git project that has no fence.jsonc,
prompt the user to scaffold one tuned for agent workflows. The template
is self-contained (no `extends`) because fence's slice-merge is
append-only — a child config cannot remove inherited rules, so the only
way to narrow the over-broad `**/*.pem` deny in fence's `code` template
(which breaks `mise install go|node|...` on tarball extraction of test
fixtures) is to author the policy from scratch.
Diff vs. fence's `code` template:
- network.allowedDomains adds *.jdx.dev, nodejs.org, dl.google.com,
static.rust-lang.org, releases.hashicorp.com
- filesystem.allowWrite adds ~/Library/Caches/** (mise cache on macOS)
- filesystem.denyWrite scoped to ./**/* instead of **/* so package
managers can extract tarballs containing .pem/.key test fixtures
New surface:
- policy/starter-template.ts — vendored JSONC string + version stamp
- policy/scaffold.ts — decideScaffold() + scaffoldStarterConfig()
- index.ts — maybeOfferScaffold() on session_start; /fence scaffold
slash command for users who declined the prompt
Verified end-to-end: `mise install go@1.23.4` succeeds (was failing
mid-tar on crypto/tls/testdata/example-cert.pem), project-tree
denyWrite still fires for .env / *.pem / *.key, unrelated domains
still 403.
See README §"Project scaffolding" for the rationale, drift trade-off,
and resync recipe; PLAN §"Pass 3 (shipped)" for the design summary.