omp packages

@jocax/security-guard0.2.0

Rule-based guard for pi tool calls: built-in rules for dangerous commands and sensitive files, YAML path tiers and bash patterns, project scope, and an audit log

jocax Published 7 days ago MIT ~177 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

@jocax/security-guard

Rule-based guard for pi tool calls. It blocks dangerous commands, protects sensitive files, asks before a tool reaches outside the project, and applies the rules you write per project.

What

The extension checks every tool call before it runs and decides once: allow, ask you, or block.

Rules come from three places and apply together. The strictest result wins.

Source Where Active
Built-in the package always
Global file ~/.pi/security-guard.yaml when the file exists
Project file .pi/security-guard.yaml when the file exists

A rules file can add protection. It cannot remove a built-in rule or a rule of the other file.

How a call is decided

bash:

  1. A pattern that blocks matches: blocked.
  2. The command starts with rm or mv and an argument is a no-delete path: blocked.
  3. A pattern that asks matches: dialog.
  4. Otherwise allowed.

read, write, edit:

  1. Zero-access path: blocked.
  2. Read-only path, for write and edit: blocked.
  3. Confirm path: dialog.
  4. read of a trusted read root: allowed.
  5. Path outside the project: dialog.
  6. Otherwise allowed.

grep, find, ls: allowed in a trusted read root, dialog outside the project, otherwise allowed. The path tiers are not checked for these three tools.

A call the rules allow can still go to the model stage, which is off unless switched on.

Why

A pi agent has full system access. One bad prompt or one wrong command can delete a home directory, overwrite a file of secrets, or pipe a download into a shell. The guard stops the known cases, asks about the doubtful ones, and leaves ordinary work inside the project alone.

Install

Install the package from npm:

{
  "source": "npm:@jocax/security-guard"
}

Or try it from a local checkout without installing:

pi -e ./src/index.ts

Usage

The guard loads at pi startup. The footer shows 🛡️ Guard: <n> rules, followed by + model (<model>) when the model stage is on.

Command

Command Description
/security-guard Mode, rule counts, the model stage, and the audit totals with the five most recent entries

Built-in rules

Blocked, 18 patterns:

Group Example
Recursive force-delete of /, the home directory or a system directory rm -rf /
Writes to a block device, filesystem and swap creation dd if=… of=/dev/sda, mkfs.ext4 …
Fork bomb, recursive chmod 777 or chown on / chmod -R 777 /
Download piped into a shell curl https://example.com/x.sh | bash

Dialog, 16 patterns:

Group Example
Recursive delete, rmdir rm -rf node_modules
sudo, stopping a service, shutdown, reboot sudo systemctl stop nginx
chmod 777, recursive chmod or chown chmod -R 755 dir
Force push, hard reset, git clean git push --force origin main
curl piped to a command, wget -O curl https://example.com | jq .

Dialog on read, write and edit, 16 path globs: environment files, credentials.*, *.pem, *.key, SSH private keys, .git/config, .ssh/config, .aws/credentials, .npmrc, secrets.yml, secrets.yaml, .secrets.

Project scope

read, write, edit, grep, find and ls run without a dialog inside the project, the directory pi was started in. A path outside the project opens a dialog.

Pi's own sources can be read without a dialog: the pi SDK directory, ~/.pi/ and .pi/. This covers read, grep, find and ls. Writing there still asks when the path is outside the project.

Model stage

An optional second stage asks a decision model about the calls the rules allow. It is off unless AE_PI_SECURITY_GUARD_MODEL_ENABLED=true and needs a decision provider configured through @jocax/decision-core, see its configuration. A call the rules block or ask about never reaches the model.

Check Runs on Asks Outcome
bash a bash call the rules allow what the command does to the machine, and whether it aims to destroy allow, dialog, block
write a write or edit call the rules allow what kind of file it is, and whether the content holds a real credential allow, dialog, block
screen a read or bash result whether the output carries instructions aimed at the agent a warning banner above the output
  • The questions and thresholds live in src/model-questions.ts. A rules file or variable cannot change them.
  • Model unreachable or failing: a bash or write check opens a Model Unavailable dialog; a screened result stays unchanged and is recorded as model-error.
  • Switched on but not configured: a warning at session start, and the rules still apply.
  • Cost: with all three checks on, the guard adds one model call per allowed bash, write and edit call and one per read and bash result. AE_PI_SECURITY_GUARD_MODEL_STAGES=bash write turns off the screen check, the most frequent one.

Audit log

Every block, every confirmed dialog and every denied dialog is recorded in the session. A flagged result and a result the model could not screen are recorded too. /security-guard shows the totals and the five most recent entries; the flagged and unscreened totals appear on a second line when either is non-zero. With AE_PI_EVENT_CORE_ENABLED=true every audit entry is also reported on the package event channel (@jocax/event-core), with the tool call it belongs to. The demo kit turns this on; /security-guard counts only audit entries.

Configuration

Rules file

Create .pi/security-guard.yaml in the project or ~/.pi/security-guard.yaml for all projects. Start from the example:

cp software/packages/security-guard/assets/example-rules.yaml .pi/security-guard.yaml

The guard reads the files when a session starts.

paths:
  # read, write and edit blocked
  zero-access:
    - "**/secrets/**"

  # write and edit blocked
  read-only:
    - "**/node_modules/**"

  # rm and mv blocked
  no-delete:
    - "**/*.md"

  # read, write and edit ask
  confirm:
    - "**/*.secret"

bash:
  patterns:
    # ask: true opens a dialog
    - regex: "npm\\s+(uninstall|remove|rm)"
      ask: true
      label: "npm uninstall"

    # no ask: blocked
    - regex: "npm\\s+publish"
      label: "npm publish"

The parser reads a subset of YAML: two-space indentation, lists, quoted and unquoted strings, true and false, and comments on their own line. A file that fails to parse is ignored with a warning.

Glob syntax. A glob is tested against the absolute path.

  • * matches within one path segment
  • **/ matches zero or more directories
  • a leading ~ is the home directory

Environment variables

Variable Values Default Description
AE_PI_SECURITY_GUARD_ENABLED false unset false turns the guard off
AE_PI_SECURITY_GUARD_MODE block-abort, block-continue block-abort Wording of the reason the model receives for a blocked call
AE_PI_SECURITY_GUARD_ALLOW_COMMANDS space-separated prefixes unset A command that starts with a prefix skips the built-in bash patterns and the model bash check. Patterns from rules files and no-delete paths still apply
AE_PI_SECURITY_GUARD_TRUSTED_READ_PATHS space-separated directories unset Extra directories that may be read outside the project without a dialog
AE_PI_SECURITY_GUARD_MODEL_ENABLED true unset true turns the model stage on; any other value leaves it off
AE_PI_SECURITY_GUARD_MODEL_STAGES space-separated subset of bash write screen all three Checks that run. Unknown names are dropped with a warning
AE_PI_SECURITY_GUARD_MODEL_HEADLESS block, allow block What a model dialog does when there is no UI
AE_PI_SECURITY_GUARD_MODEL_TIMEOUT_MS positive integer of ms 5000 Budget for one model call. Overrides AE_PI_DECISION_CORE_TIMEOUT_MS
AE_PI_EVENT_CORE_ENABLED true unset true also reports every audit entry as a package event (see @jocax/event-core)

A rules file cannot loosen the guard. The only settings that do are AE_PI_SECURITY_GUARD_ENABLED=false, AE_PI_SECURITY_GUARD_ALLOW_COMMANDS, AE_PI_SECURITY_GUARD_TRUSTED_READ_PATHS and AE_PI_SECURITY_GUARD_MODEL_HEADLESS=allow.

Modes

  • block-abort: the model receives a short reason, 🛡️ Security Guard BLOCKED: <rule>.
  • block-continue: the reason also tells the model to avoid the operation, find another approach and continue with other tasks.

Print mode

pi -p has no dialog. Every call the rules would ask about is denied and recorded as denied. A model stage dialog blocks instead, unless AE_PI_SECURITY_GUARD_MODEL_HEADLESS=allow lets the call through.

Examples

# Built-in rules
pi -e software/packages/security-guard/examples/basic.ts

# With a rules file
pi -e software/packages/security-guard/examples/custom-rules.ts

# With the model stage (export the variables listed in the file first)
pi -e software/packages/security-guard/examples/model-stage.ts

Run the demo

just demo-web security-guard   # browser
just demo security-guard       # herdr

The sandbox copy gets a .env, a config/credentials.json and a .pi/security-guard.yaml adding one zero-access and one read-only rule on top of the always-active built-ins. The decision-model stage stays off.

Every scenario is two turns, one guarded call each. pi runs the tool calls of one assistant message as a parallel batch and holds each call's result until the batch's last dialog is answered, so one dialog at a time keeps the panel readable. In the hook panel, a dialog's row shows only its title and the answer (→ yes / → no) — the browser collapses the message's line breaks.

Scenario "Secrets ask, then stay out of reach"

Step Do this Watch
1 Send the first prompt A confirm dialog titled 🛡️ Security Guard — Sensitive File names Environment file (.env)
2 Allow it The row turns ran; under it a security-guard confirmed read ".env" event
3 Send the second prompt No dialog. The call fails with 🛡️ Security Guard BLOCKED: zero-access path: **/config/credentials.json, and the row carries a blocked event
4 Type /security-guard Mode: block-abort, rule counts per tier, and an audit tail with both decisions

Scenario "Destructive command"

Step Do this Watch
1 Send the first prompt A confirm dialog titled 🛡️ Security Guard names Force-delete directory (rm -rf) and shows Command: rm -rf build
2 Deny it Blocked with … Force-delete directory (rm -rf) (denied by user), and the audit records denied
3 Send the second prompt A confirm dialog names Hard reset (git reset --hard) — a built-in rule, not git-workflow's
4 Allow it The reset runs and the audit records confirmed

Scenario "Writing outside the project"

Step Do this Watch
1 Send the first prompt A confirm dialog titled 🛡️ Security Guard — Outside Project names the file and the sandbox directory it escapes
2 Deny it Blocked with … outside project (denied by user), and the audit records the scope tier
3 Send the second prompt The same write inside the project runs with no dialog

Every decision also arrives on the package event channel with the tool call's id, which is why the browser shows it under that call's row rather than only in the timeline. Reloading the page while a dialog is open restores the timeline and reopens the dialog.

Needs herdr 0.9.3 or newer and one reachable model provider — see demo-kit.

Development

npx vitest run software/packages/security-guard
npx tsc --noEmit -p software/packages/security-guard/tsconfig.json

# Live model stage suites, opt-in
AE_PI_SECURITY_GUARD_MODEL_LIVE=ollama npx vitest run software/packages/security-guard/tests/model-live.test.ts
AE_PI_SECURITY_GUARD_MODEL_LIVE=typesafe npx vitest run software/packages/security-guard/tests/model-live.test.ts