Skip to main content
Let an ACP agent edit code with --allow-write while every write is validated, contained, and applied atomically.
The SafeEditPipeline runs a propose → approve → apply workflow: it confines writes to the workspace, rejects symlink escapes, claims the apply transition atomically, and writes via a temp-file rename.

Quick Start

1

Enable writes on the ACP server

Writes are denied by default; --allow-write routes them through the safe-edit pipeline.
2

Propose, approve, apply from Python

Guarantees

_validate_path() rejects any target that escapes the workspace or traverses a symlink. It raises ValueError with one of these exact messages (users grep for them):
  • resolves outside the workspace — Path {..} is outside workspace {..}
  • traverses a symlinked parentRefusing to write through symlinked parent {..} (possible containment bypass)
  • traverses a parent that exists but is not a directory — Parent {..} is not a directory
  • is itself a symlinkRefusing to write through symlinked target {..}
The parent-symlink check defends against a propose→apply TOCTOU: even though resolve() follows symlinks, an attacker can swap an intermediate workspace-owned directory for a symlink between the two calls so a later pathname-based write escapes containment. Rejecting any symlinked parent means the only writable path is one made entirely of real directories the workspace owns.

Atomic APPROVED → APPLIED Transition

apply_edit() claims the APPROVED status atomically so two concurrent applies of the same proposal cannot both succeed. The pipeline holds an RLock (self._lock) around _proposals mutations and status transitions. apply_edit() checks the proposal is APPROVED and flips it to APPLIED inside the lock (a tentative claim), then does the disk work outside the lock; a second concurrent apply_edit() finds the status is no longer APPROVED and returns False.

Atomic File Write

The final write goes through _atomic_write_text (a sibling temp-file + fsync + rename), so an interrupted apply never leaves a half-written or truncated file.

Per-Workspace Pipeline

get_safe_edit_pipeline(workspace=...) returns a distinct SafeEditPipeline per resolved workspace so concurrent agents cannot leak proposals across workspaces.
The factory caches instances in a module-level _pipelines: Dict[Path, SafeEditPipeline] keyed by the resolved workspace path. A previous single global silently bound every caller to the first caller’s workspace; the per-workspace cache removes that cross-talk.

Full Flow

Error Taxonomy

Best Practices

Set --workspace (or ACPConfig(workspace=...)) to the smallest project root that covers your edits so _validate_path() rejects anything outside it.
ACP is read-only by default. Add --allow-write only for sessions that need to edit files, and prefer manual approval for risky changes.
Call get_safe_edit_pipeline(workspace=...) rather than sharing a single instance so concurrent agents keep separate proposal state.

ACP

Connect IDEs and editors to PraisonAI agents.

Security Best Practices

Injection defense, audit logging, and protected paths.

Auto-Generator Safety

Safe defaults for praisonai --auto.

Tools

Add custom tools to your agent.