omp packages

@tduyng/pi-guard0.0.4

Secret redaction, cross-session path reservations, git safety, and a rejection guard. Three of the four add no schema at all.

tienduy-nguyen Published 4 hours ago Apache-2.0 ~405 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

@tduyng/pi-guard

Four guards for the ways a session ends badly. Three are hooks and spawn no schema, so a session that never needs them pays nothing.

Guard Failure it prevents
reserve A peer session edits a file this session is working on
cloak A key file lands in the transcript and goes to the provider
git-interceptor Git opens an editor and hangs the turn, or --no-verify skips a hook
reject-guard One unhandled promise takes down the session

Only sessions that load reserve pay for it, at 215 tokens.

Sessions in one folder

Pi serializes writes inside one process. Two sessions in one folder do not see each other, so the second overwrites a file the first is halfway through reshaping.

A session writes what it holds to .pi/reservations/<session>.json. A tool_call hook refuses edit and write against a path a live peer holds, and it checks bash too, so sed -i, git checkout and a redirect into a reserved path are turned away while a read-only command passes.

reserve({action: "reserve", paths: ["extensions/pi-goal/src/"]})
reserve({action: "list"})
reserve({action: "release"})

A directory reserves its whole subtree, an exact path reserves one file, and release with no paths gives everything back. The refusal names the holder, so the other session is one that can be inspected.

Liveness is process.kill(pid, 0), so a killed session takes its reservations with it. No heartbeat and no daemon. Writes go through a temp file and a rename, so two sessions reserving at once cannot leave a half-written file.

A mutation that names no path is invisible to this, so git add -A and a bare git commit still take whatever is staged. Two sessions committing in one repository also race on .git/index.lock, and no reservation covers that. Commit from a subagent with isolation: "worktree" instead, whose checkout is its own.

Secrets in the transcript

A tool_result hook cleans read results on their way into the model. Three checks, each switchable:

Check Catches Replacement
Vendor keys AWS, OpenAI, Anthropic, Stripe, GitHub, Slack, GitLab, Google, npm, SendGrid, JWTs, Bearer First 4 characters, stars, [REDACTED:type]
PEM blocks -----BEGIN ...----- certificates and keys [REDACTED:PEM TYPE]
Sensitive paths .pem, .crt, .p12, SSH keys, .aws/credentials, .npmrc, .netrc, .docker/config.json, .kube/config The whole read becomes [REDACTED:Protected File (...)]

Secrets have known prefixes, so AKIA, sk-, ghp_, glpat-, xoxb-, AIza and a dozen more are matched as literals with a word boundary. An earlier version scanned by entropy and flagged nix store hashes and sha256 digests in derivations, so that check is gone.

Custom rules go in <agent-dir>/cloak.json or .pi/cloak.json. A project file overrides the scalar keys and appends its patterns.

Key Default Meaning
enabled true Master switch
cloakCharacter * Masking character
cloakLength null Fixed replacement length, null keeps the original
tryAllPatterns true Apply every matching rule, not just the first
patterns [] Rules with filePattern (glob), cloakPattern (regex), optional replace
vendorKeys true Built-in prefix detection
pemBlocks true PEM detection
sensitiveFileTypes true Whole-file redaction

When two matches overlap, the first rule applied wins. A custom pattern replaces only its matched spans, while a sensitive file type replaces the whole read. /cloak-status shows whether cloak is on, how many custom patterns loaded, and which built-in checks are active.

Git in a non-interactive turn

GIT_EDITOR=true, GIT_SEQUENCE_EDITOR=true and GIT_MERGE_AUTOEDIT=no go on process.env. Pi builds a child's environment by spreading process.env, so one assignment covers the bash tool, bg, and every other spawn.

The extension blocks a bash command containing --no-verify, because the agent should fix what the hook complains about or ask. The check is string matching, best effort, and not a security boundary.

The rejection that kills a session

Pi installs an uncaughtException handler and no unhandledRejection handler, so one unhandled rejected promise tears down the session. The guard adds the missing listener, logs the reason with a stack trace to stderr prefixed [reject-guard] unhandled rejection (session kept alive):, and the turn continues.

It installs once. /reload re-evaluates the module, and a flag on globalThis stops a second registration from logging every rejection twice.

Install

pi install npm:@tduyng/pi-guard

Local checkout: pi install /path/to/dotfiles/pi/agent/extensions/pi-guard

All four guards activate on install. Add cloak.json when a rule fires too widely or misses a secret shape.

Limits

  • Cloak reads the read result on its way into the model. It does not touch the outbound provider payload, the assistant's reply, or any tool other than read, and it does not redact a file pasted into a prompt.
  • A reservation is advisory between pi sessions. A shell opened by hand is outside it.
  • --no-verify blocking can be worked around by anyone who wants to.