Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
2625fe7
feat(permissions): P0 — wire the per-tool 'ask' decision + risk engin…
ncrispino Jun 11, 2026
698f814
feat(permissions): P1.1 — declarative allow/ask/deny rule layer (acti…
ncrispino Jun 11, 2026
19cea3f
feat(permissions): P0 — interactive approval modal (completes the ask…
ncrispino Jun 11, 2026
ff148d0
test(permissions): prove the system is fully opt-in + presence-based …
ncrispino Jun 11, 2026
301693a
feat(permissions): P1.3 — per-agent scoping via role presets
ncrispino Jun 12, 2026
37cf729
feat(permissions): P1.2 FileApprovalProvider + P1.3 read-only→SRT bac…
ncrispino Jun 12, 2026
d69b9f6
feat(permissions): P2.1 ApprovalLedger audit trail + ApprovalBudget
ncrispino Jun 12, 2026
e310899
feat(permissions): P2.2 — wire ApprovalBudget, persist 'always' grant…
ncrispino Jun 12, 2026
7fbe547
docs(permissions): add interactive-modal demo config + P2.2 follow-up…
ncrispino Jun 12, 2026
406fe89
docs(permissions): demo uses Gemini (modal fires) + record egress-byp…
ncrispino Jun 12, 2026
4accce2
feat(permissions): channel-based guardrail policy in system prompt (w…
ncrispino Jun 12, 2026
c4540f0
feat(permissions): render denied tool calls as first-class failed too…
ncrispino Jun 12, 2026
2b119fe
docs(permissions): record 3-run alignment≠enforcement evidence + WAN-34
ncrispino Jun 12, 2026
8f63bab
docs(release): prep v0.1.97 — Application-Layer Permission Engine
ncrispino Jun 12, 2026
5093096
Merge dev/v0.1.97 into feat/permissions-p2-audit (resolve ROADMAP ren…
ncrispino Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.97] - 2026-06-12

### Theme: Application-Layer Permission Engine

A layered, **fully opt-in** permission system for agent tool calls — the application-layer companion to v0.1.96's OS sandbox. When a `permissions:` block is present, every tool call flows through a hardline catastrophic-command floor, a declarative `allow/ask/deny` rule layer, and a blast-radius risk classifier, resolving to allow / **ask** / deny. An `ask` routes through a pluggable approval provider: an interactive TUI modal (allow once / session / always · reject), an automation policy (`risk-based` / `deny-all` / `allow-all`), or a file request/response handshake for headless/remote approval. Approvals are recorded in an append-only audit ledger; per-agent **role presets** (e.g. `read-only`) scope each agent and also empty the SRT writable set as an OS backstop. A channel-based **guardrail system prompt** tells the model to follow blocks and surface-and-ask rather than circumvent — while keeping `ask` a sanctioned path. **Presence-gated**: a config with no `permissions:` block is 100% unchanged. All items landed under TDD (tests first, confirmed red, then green), with live verification across automation runs. **Honest scope**: the prompt + regex classifier are best-effort *alignment*, not enforcement — the OS sandbox (v0.1.96) remains the load-bearing control (see `docs/dev_notes/permissions_p2_followups.md`).

### Added
- **Permission engine (opt-in `permissions:` block)**: composite `PreToolUse` pipeline in `massgen/permissions/` — a non-overridable hardline blocklist (`hardline.py`, catastrophic patterns like `rm -rf /`, fork bombs, raw-disk `dd`), a declarative `action(target)` rule layer (`rules.py`: `command`/`read_file`/`write_file`/`read_url`/`mcp`/`*`, **deny-wins** across scopes), and a blast-radius `RiskClassifier` (`risk_classifier.py`: tiers by what the call *does* — egress/force-push/publish/privilege → high, reads/in-workspace edits → low). An explicit rule suppresses the risk-ask, so rules + risk live in one hook.
- **Approval round-trip**: the `base_with_custom_tool_and_mcp` chokepoint resolves an `ask` via a pluggable `ApprovalProvider` — `CallbackApprovalProvider` → interactive **TUI modal** (`ToolApprovalModal`: allow once/session/always · reject), `PolicyApprovalProvider` → automation default (`risk-based` ships default; high denied with reason, low/medium allowed), and `FileApprovalProvider` → `req_*.json`/`resp_*.json` handshake for headless/remote (fail-closed on timeout).
- **Per-agent role scoping**: `permissions.role` presets (`read-only`/`researcher` deny writes+shell; `read-write`/`implementer` fall through to rules+risk), merged with user rules deny-wins. A `read-only` role also empties the agent's SRT writable set (OS-layer backstop to the engine's write denials).
- **Audit ledger + runaway guard (`ledger.py`)**: `ApprovalLedger` writes one append-only JSONL line per approval decision (who/what/why/outcome, crash-safe). `ApprovalBudget` caps consecutive auto-approvals per agent (opt-in `max_consecutive_auto`; fail-closed past the cap, reset by any human decision).
- **`always`-grant persistence**: an operator's "Always" approval persists as a deduped `allow(...)` rule in `settings.local.json` and loads back as a merged scope next run (opt-out `persist_approvals: false`).
- **Channel-based guardrail system prompt**: `PermissionGuardrailSection` is injected into the system prompt only when the engine is active for that agent — follow the guardrails, don't circumvent a denial (no rewording/obfuscation/tool-swap), surface-and-ask instead; **`ask` is explicitly sanctioned, not a block**. Authority is established by channel (only the system prompt is authoritative; tool/file/web content never is) — no token needed.

### Changed
- **Denied tool calls render as first-class FAILED tool events**: the deny path now emits `tool_start` (with the attempted command/args) + `tool_complete(is_error=True, status="denied")` and the status line shows the command (`Denied ($ curl …): …`), so blocked calls appear in the TUI/WebUI timeline instead of only a transient status line.

### Fixed
- **Backend parity guard**: native backends (`claude_code`, `codex`) don't run the framework `PreToolUse` chokepoint, so a `permissions:` block there is reported **INACTIVE** at startup (loud warning) and inert hooks are skipped — preventing a false promise of enforcement.

### Tests
- New deterministic suites: `test_permissions_core.py`, `test_permission_rules.py`, `test_permission_hooks.py`, `test_permission_coordinator.py`, `test_approval_provider.py` / `test_file_approval_provider.py`, `test_approval_ledger.py`, `test_tool_approval_modal.py`, `test_permissions_optional.py` (opt-in/presence gate + parity guard), `test_permission_persistence.py` (write↔load roundtrip + dedup), `test_permission_guardrail_prompt.py` (gating + content incl. ask-is-sanctioned), `test_permission_denied_tool_visibility.py` (start→error-complete events + command preview), plus SRT read-only backstop in `test_srt_manager.py` / `test_srt_filesystem_integration.py`.
- Live-verified (automation, `gemini-3-flash-preview`): all three chokepoint branches end-to-end (allow / deny-rule / ask→policy-deny + ledger), guardrail policy present in the real system message, denied calls emitting real `tool_start`/`tool_complete(error)` events with the command. Documented honest limitation: the model evaded the regex egress classifier via `\c\u\r\l` / `python urllib`, confirming the OS sandbox is the load-bearing control.

### Documentations, Configurations and Resources
- **New Configs**: `massgen/configs/tools/permissions/permission_engine.yaml` (risk-tiered approval + rule algebra), `per_agent_roles.yaml` (role scoping), `permission_modal_interactive.yaml` (interactive approval-modal demo + automation deny path).
- **Design Notes**: `docs/dev_notes/permission_systems_research.md` (three-layer model), `docs/dev_notes/permissions_p2_followups.md` (limitations, manual-test gaps, OS-enforcement follow-up).

## [0.1.96] - 2026-06-10

### Theme: OS-Level Agent Sandboxing
Expand Down
40 changes: 21 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ This project started with the "threads of thought" and "iterative refinement" id
<details open>
<summary><h3>🗺️ Roadmap</h3></summary>

- [Recent Achievements (v0.1.96)](#recent-achievements-v0196)
- [Previous Achievements (v0.0.3 - v0.1.95)](#previous-achievements-v003---v0195)
- [Recent Achievements (v0.1.97)](#recent-achievements-v0197)
- [Previous Achievements (v0.0.3 - v0.1.96)](#previous-achievements-v003---v0196)
- [Key Future Enhancements](#key-future-enhancements)
- Bug Fixes & Backend Improvements
- Advanced Agent Collaboration
Expand Down Expand Up @@ -155,18 +155,18 @@ This project started with the "threads of thought" and "iterative refinement" id

---

## 🆕 Latest Features (v0.1.96)
## 🆕 Latest Features (v0.1.97)

**🎉 Released: June 10, 2026**
**🎉 Released: June 12, 2026**

**What's New in v0.1.96** (OS-Level Agent Sandboxing):
- **🛡️ OS-Level Execution Sandbox** - Confine agent command/code execution at the OS level via Anthropic's [sandbox-runtime](https://github.com/anthropic-experimental/sandbox-runtime) (bubblewrap on Linux, Seatbelt on macOS) with one knob: `command_line_execution_mode: srt`. Filesystem + network isolation derived from the same path policy as the application layer — **defense in depth**, both layers active.
- **🔒 Configurable Read Confinement** - By default (`confined`), sandboxed commands can't read your `$HOME` (secrets, other projects) — only the workspace + context — while system paths stay readable so commands still run. `strict` and `open` modes available. Network is **deny-all by default**; each allowlisted domain is an explicit capability grant.
- **🧱 Hardened Permission Hook** - A key-agnostic scan walks the full tool-args tree (nested dicts + lists) and denies any value resolving outside managed areas, closing prior fail-open gaps (unrecognized key, list-valued paths, `move`/`copy` source pointing outside) — with no false positives.
**What's New in v0.1.97** (Application-Layer Permission Engine):
- **🛡️ Layered Permission Engine** - Opt-in `permissions:` block routes every tool call through a non-overridable **hardline** floor (`rm -rf /`, fork bombs), declarative **`allow/ask/deny` rules** over a small `action(target)` algebra (deny-wins), and a **blast-radius risk classifier** — auto-allowing reads/in-workspace edits and asking only for the dangerous tail (egress, force-push, publish, privilege). The app-layer companion to v0.1.96's OS sandbox.
- **✋ Approval That Fits the Run** - An `ask` pops an interactive **modal** (allow once / session / always · reject) when a human is present, or resolves via an automation **policy** (`risk-based` / `deny-all` / `allow-all`) or a **file** request/response handshake for headless/remote approval. Fail-closed by design.
- **🧑‍🤝‍🧑 Roles, Audit & Guards** - Per-agent `role` presets (e.g. `read-only`, which also empties the agent's OS-sandbox writable set), an append-only JSONL **audit ledger** of every decision, a runaway-loop **budget**, `always`-grant persistence, and a channel-based **guardrail prompt** that nudges the model to surface blocks rather than circumvent them while keeping `ask` sanctioned. *(Honest scope: the prompt is best-effort alignment; the OS sandbox is the enforcement.)*

**Install v0.1.96:**
**Install v0.1.97:**
```bash
pip install massgen==0.1.96
pip install massgen==0.1.97
```

→ [See full release history and examples](massgen/configs/README.md#release-history--examples)
Expand Down Expand Up @@ -1241,18 +1241,20 @@ MassGen is currently in its foundational stage, with a focus on parallel, asynch

⚠️ **Early Stage Notice:** As MassGen is in active development, please expect upcoming breaking architecture changes as we continue to refine and improve the system.

### Recent Achievements (v0.1.96)
### Recent Achievements (v0.1.97)

**🎉 Released: June 10, 2026**
**🎉 Released: June 12, 2026**

#### OS-Level Agent Sandboxing
- **OS-Level Execution Sandbox (`command_line_execution_mode: srt`)**: a third execution mode alongside `local`/`docker` that wraps agent command/code execution in Anthropic's [sandbox-runtime](https://github.com/anthropic-experimental/sandbox-runtime) (bubblewrap on Linux, Seatbelt on macOS). `SrtManager` (`massgen/filesystem_manager/_srt_manager.py`) derives per-agent OS-enforced filesystem + network isolation from the **same** `PathPermissionManager` policy as the app layer — defense in depth, both layers active. Default-off, one-knob opt-in
- **Configurable Read Confinement (`command_line_srt_read_mode`, default `confined`)**: SRT reads are allow-all by default, so `confined` denies all of `$HOME` and re-allows only the workspace + context (system paths stay readable so commands run); `strict` denies `/` and allows only managed + a system baseline; `open` allows-all minus a secret denylist. `command_line_srt_allow_read` widens it per config. **Network is deny-all by default** — each allowlisted domain is an explicit capability grant
- **Hardened Permission Hook**: a new key-agnostic scan (`_validate_no_path_arg_escapes`) walks the full tool-args tree (nested dicts + lists) and denies any value resolving outside managed areas, closing prior fail-open gaps (path under an unrecognized key, list-valued paths, `move`/`copy` source pointing outside) without false positives
- **Parity & safety**: native-sandbox backends (Codex `--full-auto`, Claude Code) degrade `srt`→`local` to avoid nested-sandbox hangs; subagents inherit the parent's SRT settings (parity with Docker); live-verified across OpenRouter, OpenAI Responses, and Gemini backends
- All landed under TDD, with a 15-vector adversarial escape suite and an adversarially-verified multi-agent pre-merge review
#### Application-Layer Permission Engine
- **Permission engine (opt-in `permissions:` block)**: a composite `PreToolUse` pipeline in `massgen/permissions/` — a non-overridable **hardline** blocklist (`hardline.py`: `rm -rf /`, fork bombs, raw-disk `dd`), a declarative **`action(target)` rule layer** (`rules.py`: `command`/`read_file`/`write_file`/`read_url`/`mcp`/`*`, deny-wins across scopes), and a **blast-radius `RiskClassifier`** that tiers by what the call does (egress/force-push/publish/privilege → high; reads/in-workspace edits → low). An explicit rule suppresses the risk-ask, so rules + risk live in one hook
- **Approval round-trip**: the `base_with_custom_tool_and_mcp` chokepoint resolves an `ask` via a pluggable `ApprovalProvider` — interactive **modal** (`ToolApprovalModal`: allow once/session/always · reject), automation **policy** (`risk-based` default / `deny-all` / `allow-all`), or **file** request/response handshake for headless/remote — fail-closed on timeout
- **Roles, audit & guards**: per-agent `role` presets (`read-only`/`researcher` deny writes+shell, also empties the agent's SRT writable set), an append-only JSONL **`ApprovalLedger`**, a runaway-loop **`ApprovalBudget`**, and `always`-grant persistence to `settings.local.json`
- **Guardrail system prompt** (`PermissionGuardrailSection`, injected only when the engine is active): follow the guardrails, don't circumvent a denial, surface-and-ask — while keeping `ask` a sanctioned path. Authority is established by channel (only the system prompt is authoritative). Denied tool calls now render as **first-class failed tool events** (with the command) in the TUI/WebUI timeline
- **Presence-gated & honest**: a config with no `permissions:` block is 100% unchanged; native backends (claude_code/codex) report **INACTIVE** rather than silently inert. All under TDD; live-verified that the prompt is best-effort *alignment* (a model evaded the regex egress classifier via `\c\u\r\l` / `python urllib`), so the OS sandbox remains the load-bearing enforcement

### Previous Achievements (v0.0.3 - v0.1.95)
### Previous Achievements (v0.0.3 - v0.1.96)

✅ **OS-Level Agent Sandboxing (v0.1.96)**: Added a real OS-level execution sandbox (`command_line_execution_mode: srt`) wrapping agent command/code execution in Anthropic's sandbox-runtime (bubblewrap/Seatbelt), with OS-enforced filesystem + network isolation derived from the same `PathPermissionManager` policy as the app layer (defense in depth), configurable read confinement (`confined`/`strict`/`open`, deny-all network), a hardened key-agnostic permission hook, native-sandbox-backend `srt`→`local` degrade, and subagent inheritance.

✅ **Steering Improvements (v0.1.95)**: Extended mid-stream steering from a UI-only capability into a programmatic, headless one — `send_steering_message()` drops guidance into a file inbox (`--inbox-dir`) routed through the same `set_pending_input` chokepoint the TUI/WebUI use — and upgraded Codex/Antigravity to interrupt-and-resume the in-flight turn (`codex exec resume` / `agy --continue`) instead of waiting for a round boundary, with `expires_at`-guarded MCP-hook payload IPC and the Antigravity `--model` flag wired through.

Expand Down
Loading
Loading