Process jail and network policy
The process jail lets you limit the permissions of commands that Vis runs: which files, environment variables and network connections are available to them. It is optional and disabled by default. These limits apply to managed processes, not trusted extension code. The Python sandbox also has its own permission checks.
When to use
- The agent runs code you have not reviewed, such as tests from an unfamiliar repository. Enable the jail so those commands do not run with your full permissions.
- Commands must not read secrets such as
.envfiles. Deny specific files and filter environment variables. - Commands may reach only the hosts you approve, or may only read from them. Set network egress rules, down to method, path and port.
- A development server inside the jail must accept connections. Open inbound development ports.
- A command is blocked and you need to know why. Diagnose the effective policy.
Enable the jail
The jail is disabled by default. Enable it in configuration:
jail:
enabled: true
After changing security settings, run /reload. Each active session uses the
new policy on its next message. The agent can inspect the effective policy in
session["access"], including is_jailed, filesystem modes, network rules and
changes_require.
Shell availability is separate. toggles.shell: false removes shell(...), but it does not change
the policy for other managed processes.
What is confined
| Execution path | Enforcement |
|---|---|
shell(...) and its child processes |
OS process jail plus gateway egress policy |
python_execution |
CPython filesystem, process and socket guards while the jail is enabled. HTTP then uses the gateway policy |
Python extension code and its ordinary subprocess calls |
trusted host code, outside the session jail |
A trusted extension can opt into confinement with vis.jailed_shell(...), which reads
the merged configuration at every spawn. Project extension files are executable plugins
and require the same review as build scripts. A confined child can be given the exact
paths it needs on top of the session roots with the allow_read_write and unix_connect
options. An extension declares its fixed toolchain paths, such as a JDK or a package cache,
as vis.Grant values. Vis gives them only to that extension's confined children. See
Extension API.
Filesystem access
Declare more roots under workspace.filesystem. Then allow them by id under
jail.filesystem.allow:
workspace:
filesystem:
- id: sibling
path: ~/sibling-repository
description: repository used by this project
access: read-write
draft: copy-and-apply
- id: reference
path: ~/reference-data
access: read-only
- id: m2
path: ~/.m2
access: read-only
search: false
jail:
enabled: true
filesystem:
allow: [sibling, reference, m2]
The active workspace and temporary directories are writable. Allowed roots use
their declared access. Other than runtime access below, unlisted roots are not
available to jailed children. Dependency caches require explicit access.
Vis automatically gives jailed processes read-only access to recognized Java installations. It finds
them through the host JVM, the host JAVA_HOME and the first absolute java executable on the host
PATH. Detection resolves symlinks and requires a Java installation layout. It does not run
launchers, scan other versions or grant full toolchain-manager directories.
These grants are frozen in the policy snapshot of the session. They appear with descriptions under
session["access"]["filesystem"]["process_read_only"]. Default searches skip them. They do not
become workspace roots, and they give Python filesystem tools no extra access. Explicit catalog
grants keep their access mode and search setting. Deny rules still win.
A language call or an environment: declaration can select JAVA_HOME. Then Vis puts the bin
directory of that JDK first on the child's PATH. It also gives the jailed child read-only access
to that directory. The launcher and every JVM that it starts then run the JDK you chose. This
includes tools.deps dependency preparation, which starts a bare java and never reads JAVA_HOME
itself.
If the environment also sets or unsets PATH, that setting wins, and Vis does not change the search
order. A JAVA_HOME whose bin has no executable java selects nothing.
Other per-call environment overrides do not add runtime grants. A different or
unrecognized toolchain needs an explicit grant. Host toolchain changes require
/reload before an existing session gains access.
Paths must be absolute or home-relative. Use when.os, when.exists or
optional: true for roots available only on some hosts. An id may remain in
allow when its conditional entry does not apply. An unknown id is an error.
search: false keeps a granted root out of default grep searches without blocking
an explicit path. draft controls isolated workspace copies independently of the OS
jail. Vis also grants ~/.vis read/write and excludes it from default searches.
Declare it explicitly only to change that access.
Deny specific files
Deny rules take individual files back out of every grant. List patterns under
jail.filesystem.deny_read and jail.filesystem.deny_write:
jail:
enabled: true
filesystem:
allow: [sibling, reference]
deny_read:
- .env
- "**/.env*"
- ~/secrets
deny_write:
- deploy/production
A pattern is absolute, home-relative, or relative to the workspace root. * matches
inside one path segment, ** crosses directories, and a rule that names a directory
covers everything under it.
Denial wins over allow lists, workspace roots and runtime grants. Vis matches a rule against the
normalized path, with symlinks resolved. So a denied file stays denied when you reach it through
another root, a relative path or a link. deny_read blocks both reading and writing. deny_write
blocks writing only.
The same rules cover host tools (cat, ls, grep, patch and the other readers and
writers), python_execution and every child process Vis starts. A call that touches
several paths is refused whole: no path in the batch is read or written.
A rule also covers files that appear later. On macOS, the pattern itself goes into the sandbox profile. So a secret written after the session started is denied as soon as it exists. On Linux, bubblewrap works with mount points, not patterns. A child cannot open the files that the pattern matched when the session started. Vis' own tools continue to refuse every path that the rule covers.
jail.enabled turns the OS sandbox on and off. The deny rules are separate configuration and stay
valid either way. With the jail off, Vis' own tools continue to refuse every path that a rule
covers. But nothing confines the commands and code that Vis starts, so a shell child or
python_execution can still open the file. When a rule must hold against code that Vis does not run
itself, keep the jail on.
Environment filtering
With the jail enabled, a child receives:
- Basic non-secret variables such as
PATH,HOME,LANG,TERM,TZ, andTMPDIR. - Values resolved from the project's
.env,.env.local, and top-levelenvironment:block. - The session's proxy and CA variables.
Other parent-process variables are excluded. Declare required variables in the
top-level environment block:
environment:
CI: {env: CI}
BUILD_TOKEN: {keychain: vis-build}
Pre-exec injection variables such as LD_*, DYLD_*, BASH_ENV, and PERL* are
refused because they could run before the jail is installed.
When the jail is disabled, children inherit the host environment. Project values override it.
Children never inherit NODE_OPTIONS from the environment that started Vis.
Terminal tools can set it to a preload file that they delete later, and then
every Node command fails. If your project needs Node options, declare
NODE_OPTIONS under environment: or in .env.
Network egress
Jailed processes use a session-authenticated gateway proxy for HTTP, HTTPS and proxy-aware TCP connections. The proxy resolves the host, checks session policy and connects to the validated address.
Without a jail.network block, public destinations are allowed. These protections
still apply:
- Link-local, cloud metadata, wildcard, and multicast addresses are blocked.
- Private IPv4 ranges, CGNAT, and IPv6 ULA require
allow_private: true. - Loopback services are allowed, except the gateway's control and proxy ports.
Use allowed_domains for an allowlist and denied_domains for explicit blocks.
Deny rules take precedence. A denied hostname also blocks its resolved
addresses. Wildcard entries match names. exclude_domains disables TLS
inspection for clients that pin certificates, but host, port and SSRF checks
still apply.
jail:
enabled: true
network:
allowed_domains:
- api.github.com
- "*.pypi.org"
denied_domains:
- blocked.example
allow_private: false
Method, path, and port rules
Rules narrow a host by HTTP method, path, and destination port:
jail:
enabled: true
network:
rules:
- host: api.example.com
access: read-only
allow:
- method: POST
path: /v1/issues/**
- host: db.example.com
access: full
ports: [5432]
access sets the allowed HTTP methods:
read-onlypermitsGET,HEAD, andOPTIONS.fullpermits all methods.nonepermits no methods.
methods can name an explicit method set, and allow adds method/path exceptions. ports
applies to HTTP CONNECT, SOCKS and ordinary HTTP.
The gateway inspects HTTPS using a temporary session CA. Common HTTP clients
receive CA environment variables, and managed JVMs receive a temporary trust
store. Raw TCP uses SOCKS5 on the same proxy port, with host and port checks
but no HTTP method or path checks. Programs that ignore proxy variables, such
as ssh, require explicit proxy configuration.
Inbound development ports
A confined server accepts connections on a port listed in jail.network.inbound_ports. On macOS,
the child shares the network stack of the host. So the operator's browser can reach a loopback
listener (localhost:5273) even when the port is not listed. Listing the port also opens it to
other hosts. On Linux, the child has its own network namespace, and the host reaches it only through
a listed port. Managed nREPL uses its own preselected loopback port and does not inherit this list.
jail:
enabled: true
network:
inbound_ports: [5273]
Project network filters
Trusted Python extensions can register gateway network_filters for HTTP
requests, responses and SOCKS connections. An exception denies the request.
/net-probe checks host policy and registered filters without opening a socket.
Inside python_execution, network_filter(...) and network_probe(...) test
session-local filters. Those filters do not change gateway network policy.
See Extension API for network_filters.
Platform enforcement
| host | enforcer | requirement |
|---|---|---|
| macOS | Seatbelt through bundled libvisjail.dylib |
included with the Vis Python runtime |
| Linux and WSL2 | embedded bubblewrap through bundled libvisjail.so |
included with the Vis Python runtime |
| WSL1 and other systems | no supported OS process jail | use a supported host for kernel confinement |
No system package, helper executable, PATH entry, or operator install is required. Vis
loads the platform library adjacent to libvispython. The library applies Seatbelt or
bubblewrap before the child command starts. On Linux, a filtered proxy policy
uses a private network namespace with no route, so it fails closed rather than exposing
direct egress.
If this host cannot enforce an enabled jail, Vis does not start the child. Vis also refuses when the session policy is missing. When Vis cannot tell which policy applies, it never falls back to an unconfined process.
Executables and macOS services
jail.deny_exec blocks named executables inside confined children:
jail:
enabled: true
deny_exec: [curl, wget]
Blocking an executable does not block every way to do what it does. To restrict those operations, use filesystem and network policy.
Jailed children cannot access the OS credential store by default, so gh,
git credential helpers and similar tools cannot retrieve credentials. Allow
access with:
jail:
enabled: true
keychain: true
On macOS this grants the Keychain services plus read access to the system and user keychain databases. On Linux it exposes the session D-Bus so the Secret Service (GNOME Keyring, KWallet) can respond.
Diagnose the effective policy
- Inspect
session["access"]. Do not infer access from the YAML file alone.session["access"]["filesystem"]["deny_read"]and["deny_write"]list the effective deny rules. - Run
/reloadafter a config edit, then send a message in each session that must adopt it. - Use
/net-probe METHOD URLor/net-probe host:portfor egress decisions. - If startup reports a missing enforcer, verify that the selected Python runtime
contains the matching
libvisjailplatform library. Without it, Vis refuses to start the child.
The policy snapshot resolves paths and symlinks when the session environment is
built. Live workspace roots can change within that snapshot, but editing vis.yml
cannot widen an existing environment until /reload invalidates it.
See also
- Configuration: the complete
workspace,jail,environmentand toggle keys. - Python sandbox: Python runtime permissions.
- Running a gateway: client connections and authentication.
