pathsafe is a tiny local-first TypeScript CLI and library for answering one safety question before a tool touches disk: is this path allowed inside this root, and why?
It is designed for CLIs, generators, local agents, and file tools that need deterministic path-boundary checks with readable decisions.
- Explicit root containment checks
- Allow and deny globs, with deny precedence
- Symlink policies:
follow,refuse, orignore .pathsafe.jsonconfig loading- JSONL batch checks
- Explainable decision objects with reason codes
- Dependency-light TypeScript library and CLI
npm install pathsafeFor local development:
npm install
npm testpathsafe check ./src/index.ts --root . --allow 'src/**' --deny '**/*.secret'Human output:
ALLOW ./src/index.ts (src/index.ts): ALLOW_MATCH - Allowed by pattern src/**.
JSON output:
pathsafe check ./src/index.ts --root . --allow 'src/**' --jsonBatch JSONL:
{"path":"src/index.ts"}
{"path":"../outside.txt"}pathsafe batch --root . --input batch.jsonl --jsonCreate .pathsafe.json:
{
"root": ".",
"allow": ["src/**", "docs/**", "README.md"],
"deny": ["**/.env", "secrets/**"],
"symlinkPolicy": "refuse"
}CLI flags override config values.
import { checkPath } from "pathsafe";
const decision = checkPath("src/index.ts", {
root: process.cwd(),
allow: ["src/**"],
deny: ["**/*.secret"],
symlinkPolicy: "refuse"
});
if (!decision.ok) {
console.error(decision.reason, decision.message);
}follow(default): use the real path when possible and enforce containment on the resolved target.refuse: deny if any existing path segment is a symlink.ignore: evaluate the lexical path without resolving symlinks.
Use refuse for conservative file-write tools. Use follow when reading existing files and you want target containment. Use ignore only when a caller has already handled symlink risk.
ALLOW_MATCHDENY_MATCHNO_ALLOW_MATCHOUTSIDE_ROOTSYMLINK_REFUSEDINPUT_MISSINGROOT_MISSINGCONFIG_ERROR
pathsafe is a local validation helper, not an OS sandbox. It does not provide kernel isolation, ACL management, or race-free filesystem authorization. For sensitive writes, validate as close as possible to the actual operation and prefer symlinkPolicy: "refuse".