out-of-project-guard0.1.0
omp safety hook: any tool call that touches a local path outside the session workspace requires explicit user approval (fail-closed).
Built for omp
The author tagged this package omp-package.
Reports 0
No reports yet.
Readme
out-of-project-guard
Safety hook for omp: any tool call that touches a local filesystem path outside the session workspace requires explicit user approval.
omp install npm:out-of-project-guard # from npm
omp install ./out-of-project-guard-0.1.0.tgz # from a local tarball
- Hook file in this package:
hooks/pre/out-of-project-guard.ts— the only runtime file. - Once installed, omp loads it from
<plugins>/node_modules/out-of-project-guard/hooks/pre/out-of-project-guard.tsvia its native hook discovery (hooks/pre/*.ts), so it applies to every session started after the install. Noconfig.ymlentry is needed. - Disable for one run:
omp --no-extensions; disable permanently withdisabledExtensions: [hook:pre:out-of-project-guard:out-of-project-guard]in~/.omp/agent/config.yml(the capability id ishook:<pre|post>:<tool>:<name>, where<tool>and<name>both derive from the filename). - Project override: drop a copy at
<repo>/.omp/hooks/pre/out-of-project-guard.ts. - Self-contained: no config file, no state file, no environment variable. Everything it needs is the session it is loaded into (cwd, workspace roots, the UI). The only switch is the disable id above or the project override.
Workspace roots (never prompt)
- session
cwd(symlinks resolved) --add-dir//add-dir/workspace.additionalDirectoriesroots, read live from the session header- nearest ancestor of
cwdcontaining.git— skipped when that repository also contains$HOME(a home-directory repo would otherwise disable the guard)
Everything else prompts. Paths are compared after realpath, so a symlink inside the
project pointing outside still prompts, and /tmp vs /private/tmp does not false-positive.
Prompt
Three-way selector (ctx.ui.select), only when ctx.hasUI:
| Choice | Effect |
|---|---|
| Allow once | This call runs; the next outside access prompts again |
| Allow for this session | Grants every offending target's directory for the rest of the process |
| Deny | Call blocked; the same target is refused without prompting until the session ends |
The block reason returned to the model names the path, the workspace roots, and the exit
routes (/add-dir, /outside-access), so it can ask the user instead of looping.
Fail-closed cases
Blocked, never silently allowed:
- Headless / print / RPC / subagent contexts where
hasUIis false - Prompt dismissed or answered with an unrecognized label
- Hook handler throws
- Handler exceeds
extensionHandlers.toolCallTimeoutMs(runner-enforced, default 30s). Open dialogs pause that budget, so a human can take as long as they need to answer.
Coverage
Known path arguments per tool (read, write, edit, glob, grep, find, ast_grep,
ast_edit, lsp, debug, security_scan) plus a generic key scan (path, paths, file,
filePath, dir, cwd, root, target, destination, old_path, ...) for any other tool,
including MCP tools. Free text is also tokenized for absolute, ~, $HOME, and .. paths in:
bash.command(cat /etc/hosts,cd ../..)edit.input(absolute paths inside apply-patch / hashline / sloppy patches)eval.code(Python/JS bodies referencing absolute paths)browser.url(file:///...)
Never prompted: harness-handled URI schemes (memory://, skill://, artifact://,
https://, ssh://, ...) and execution-only prefixes under /dev, /proc, /sys,
/bin, /sbin, /usr/bin, /usr/sbin, /usr/local/bin, /opt/homebrew/bin.
/outside-access
/outside-access— workspace roots, live session grants, live denials/outside-access clear— drop all grants and denials (state is per-process, so a restart clears it too)
Known limits
$VAR/...shell paths other than$HOMEare not expanded; the hook cannot know the value, socat $TMPDIR/secretis not flagged.~user/...is flagged but unresolved.- Command substitution (
cat "$(cat /etc/path-list)"), shell loops, and paths built at runtime insideevalcode are not statically visible. write.contentis not scanned (it would flag absolute paths quoted inside generated files).security_scanis listed with its path arguments, but scanning a whole repository is not path-visible when the target comes from a plan id.- Grants and denials live in process memory: they do not survive a restart, and subagents share the parent session's state.
- The hook is not a sandbox. It gates the tools it can see; code that runs inside
evalcan still open any file the process can read.
License
MIT © 2026 Zenon