Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.sandbox.jail

OS-level process CONTAINMENT — the 'jail' — that wraps the shell executors' argv so an allowed child is physically confined to the session workspace roots and, when network is off, cannot open a socket. This is a real containment boundary — not a cooperative name/argv check, which can be walked around since argv[0] is bash and the real binary hides inside the -lc string; the jail constrains what the child can DO once it runs, regardless of what a script inside it tries (curl, python -c, /dev/tcp — all hit the same wall).

POLICY, NOT GUARDS. The jail is driven by a declarative policy compiled from vis.yml + the LIVE session roots, not by hand-written guard functions. The policy is a plain VALUE passed per spawn (never a process-global singleton, so many concurrent sessions in one gateway never stomp each other). Its shape:

{:roots-fn (fn [] [root-strings]) ; live session RW roots, re-read/spawn :net-enabled? <bool> ; whole shell-child network on/off :allow-read-write [<path> …] ; full read+write grant :deny-write [<path> …] ; protect within writable (deny wins) :allow-read [<path> …] ; additional read-only paths :deny-read [<path> …] ; protect a read region (deny wins) :deny-exec [<path> …] ; readable but never executable :keychain? <bool> ; the OS credential store is reachable :inbound-ports [<int> …] ; ports a child may ACCEPT on from ; other hosts (loopback is always open) :env-values {<NAME> <value>} ; RESOLVED project env (.env + ; environment:) with ONE call's own ; env delta merged on, per spawn :env-removals #{<NAME> …} ; names THAT call asked to UNSET :inherit-host-env? <bool>} ; jail.environment: inherit — the child ; also keeps the operator's ambient env

The filesystem model mirrors Anthropic's sandbox-runtime:

  • WRITE is allow-only: denied everywhere except the session roots + tmp + :allow-read-write; :deny-write wins.
  • READ is default-deny here (workspace-focused, stronger than srt's read-everywhere default): system code/config + RW paths + :allow-read are readable; :deny-read wins.

This namespace owns WHAT a session's child may do: it turns the session's configuration, live roots, proxy endpoint and call environment into one platform-neutral policy value and the complete child environment. HOW the operating system enforces that value belongs to com.blockether/vis-python-runtime (spawn-process! with :policy): the per-platform enforcement, the already-confined marker and the refusal on a host that cannot enforce all live there, beside the process launcher, so no enforcement text is assembled here.

OS-level process CONTAINMENT — the 'jail' — that wraps the shell executors'
argv so an allowed child is physically confined to the session workspace roots
and, when network is off, cannot open a socket. This is a real containment
boundary — not a cooperative name/argv check, which can be walked around since
argv[0] is `bash` and the real binary hides inside the `-lc` string; the jail
constrains what the child can DO once it runs, regardless of what a script
inside it tries (curl, python -c, /dev/tcp — all hit the same wall).

POLICY, NOT GUARDS. The jail is driven by a declarative *policy* compiled from
vis.yml + the LIVE session roots, not by hand-written guard functions. The
policy is a plain VALUE passed per spawn (never a process-global singleton, so
many concurrent sessions in one gateway never stomp each other). Its shape:

  {:roots-fn     (fn [] [root-strings])  ; live session RW roots, re-read/spawn
   :net-enabled? <bool>                  ; whole shell-child network on/off
   :allow-read-write [<path> …]           ; full read+write grant
   :deny-write       [<path> …]           ; protect within writable (deny wins)
   :allow-read       [<path> …]           ; additional read-only paths
   :deny-read        [<path> …]           ; protect a read region (deny wins)
   :deny-exec        [<path> …]           ; readable but never executable
   :keychain?        <bool>               ; the OS credential store is reachable
   :inbound-ports    [<int> …]            ; ports a child may ACCEPT on from
                                          ; other hosts (loopback is always open)
   :env-values       {<NAME> <value>}     ; RESOLVED project env (`.env` +
                                          ; `environment:`) with ONE call's own
                                          ; `env` delta merged on, per spawn
   :env-removals     #{<NAME> …}          ; names THAT call asked to UNSET
   :inherit-host-env? <bool>}             ; `jail.environment: inherit` — the child
                                          ; also keeps the operator's ambient env

The filesystem model mirrors Anthropic's sandbox-runtime:
  - WRITE is allow-only: denied everywhere except the session roots + tmp +
    `:allow-read-write`; `:deny-write` wins.
  - READ is default-deny here (workspace-focused, stronger than srt's
    read-everywhere default): system code/config + RW paths + `:allow-read`
    are readable; `:deny-read` wins.

This namespace owns WHAT a session's child may do: it turns the session's
configuration, live roots, proxy endpoint and call environment into one
platform-neutral policy value and the complete child environment. HOW the
operating system enforces that value belongs to `com.blockether/vis-python-runtime`
(`spawn-process!` with `:policy`): the per-platform enforcement, the
already-confined marker and the refusal on a host that cannot enforce all live
there, beside the process launcher, so no enforcement text is assembled here.
raw docstring

call-env-valuesclj

(call-env-values env)

Resolve ONE call's env delta into {NAME value-or-nil}, where nil means UNSET that name for this child. A DELTA: it is merged over the project environment (config/child-environment-values), never a replacement for it, so a workspace .env still reaches a child whose call names one variable.

A value is either a LITERAL (string/number/boolean) or a SOURCE map — the same {env|dotenv|keychain|command|literal} shape environment: declares. That split is not style: this map is an ARGUMENT, so a literal is written into the session journal and the transcript for good. Literals are for SWITCHES (NODE_ENV, RUST_LOG, PYTHONHASHSEED); a secret names its source and only the child ever sees the value.

Every refusal is LOUD and names the key: a name that is not a variable name, a [[pre-exec-hijack?]] name (which the jail would drop anyway, silently), a map naming no source, and a source that produced nothing — an explicit request for ONE variable that resolved to nothing is an error here, not the quiet :unset a standing declaration is allowed.

Resolve ONE call's `env` delta into `{NAME value-or-nil}`, where nil means
UNSET that name for this child. A DELTA: it is merged over the project
environment (`config/child-environment-values`), never a replacement for it,
so a workspace `.env` still reaches a child whose call names one variable.

A value is either a LITERAL (string/number/boolean) or a SOURCE map — the
same `{env|dotenv|keychain|command|literal}` shape `environment:` declares. That
split is not style: this map is an ARGUMENT, so a literal is written into the
session journal and the transcript for good. Literals are for SWITCHES
(`NODE_ENV`, `RUST_LOG`, `PYTHONHASHSEED`); a secret names its source and
only the child ever sees the value.

Every refusal is LOUD and names the key: a name that is not a variable name,
a [[pre-exec-hijack?]] name (which the jail would drop anyway, silently), a
map naming no source, and a source that produced nothing — an explicit
request for ONE variable that resolved to nothing is an error here, not the
quiet `:unset` a standing declaration is allowed.
sourceraw docstring

child-env-additionsclj

(child-env-additions policy)

What an UNCONFINED child gets ON TOP of the inherited host environment: the resolved project environment (workspace .env + environment: declarations) plus this session's proxy + CA variables. They apply whether or not the jail is enabled — the project says where a variable comes from, and the jail only decides what ELSE a child may keep.

What an UNCONFINED child gets ON TOP of the inherited host environment: the
resolved project environment (workspace `.env` + `environment:` declarations)
plus this session's proxy + CA variables. They apply whether or not the jail
is enabled — the project says where a variable comes from, and the jail only
decides what ELSE a child may keep.
sourceraw docstring

declared-envclj

(declared-env policy)

The policy's RESOLVED project environment, as string pairs: the workspace's .env/.env.local with the operator's environment: declarations on top (config/child-environment-values). Every value already came from the source that produced it, so the jail hands them over verbatim — a confined child can read .env out of the workspace it was granted anyway, so withholding those values would confine nothing. A [[pre-exec-hijack?]] name is dropped even here, whether it came from a declaration or from a project .env: it would run code in the UNCONFINED detacher/enforcer hops, so nothing can buy it back.

The policy's RESOLVED project environment, as string pairs: the workspace's
`.env`/`.env.local` with the operator's `environment:` declarations on top
(`config/child-environment-values`). Every value already came from the source
that produced it, so the jail hands them over verbatim — a confined child can
read `.env` out of the workspace it was granted anyway, so withholding those
values would confine nothing. A [[pre-exec-hijack?]] name is dropped even
here, whether it came from a declaration or from a project `.env`: it would
run code in the UNCONFINED detacher/enforcer hops, so nothing can buy it
back.
sourceraw docstring

env-differenceclj

(env-difference running requested)

Variable NAMES whose value differs between the env a live process is running with and the one a new start asked for. Both sides are FINGERPRINTS, so this compares digests and answers names — the only thing either side may keep.

Variable NAMES whose value differs between the env a live process is running
with and the one a new start asked for. Both sides are FINGERPRINTS, so this
compares digests and answers names — the only thing either side may keep.
sourceraw docstring

env-fingerprintclj

(env-fingerprint values)

{NAME "<digest>"} for one resolved delta — its SHAPE without its values. This is what a status prints and what a REUSED process is compared against, and both of those are read by a model and written to a log, so the value itself can never appear: a name set from a keychain must compare equal to itself and to nothing else. An unset name fingerprints as "unset".

`{NAME "<digest>"}` for one resolved delta — its SHAPE without its values.
This is what a status prints and what a REUSED process is compared against,
and both of those are read by a model and written to a log, so the value
itself can never appear: a name set from a keychain must compare equal to
itself and to nothing else. An unset name fingerprints as "unset".
sourceraw docstring

env-mismatch-refusalclj

(env-mismatch-refusal id running requested)

{:message :differing} when a REPL is already running with an env OTHER than the one this start named, else nil. Every language pack answers this same refusal, because repl_start must mean ONE thing across languages: a live REPL is reused, never silently replaced, and an env it was not started with is a different REPL. Names and digests only — a value never reaches it.

`{:message :differing}` when a REPL is already running with an env OTHER than
the one this start named, else nil. Every language pack answers this same
refusal, because `repl_start` must mean ONE thing across languages: a live
REPL is reused, never silently replaced, and an env it was not started with
is a different REPL. Names and digests only — a value never reaches it.
sourceraw docstring

jailed-child-envclj

(jailed-child-env policy)

The COMPLETE environment for a confined child: an allowlist of non-secret operator variables, plus the policy's resolved project environment (the workspace's .env plus the environment: declarations), plus this session's proxy + CA variables. Every API key / token / credential the operator happens to have exported is DROPPED — an AMBIENT variable a child needs is named in environment: — and so is every [[pre-exec-hijack?]] name, which would run code in the unconfined launcher before the jail exists.

jail.environment: inherit (:inherit-host-env?) replaces the allowlist with the operator's WHOLE ambient environment, secrets included: filesystem, network, exec and Mach confinement are unchanged, but ambient secrecy is given up on purpose. The [[pre-exec-hijack?]] scrub still applies — that one is not confinement of the child, it is the jail's own installation.

Returns nil when the policy is not enforcing — the caller keeps the parent environment and merges child-env-additions instead (unjailed platforms/jail.enabled: false), so non-confined behavior is unchanged.

The COMPLETE environment for a confined child: an allowlist of non-secret
operator variables, plus the policy's resolved project environment (the
workspace's `.env` plus the `environment:` declarations), plus this session's
proxy + CA variables. Every API key / token / credential the operator happens
to have exported is DROPPED — an AMBIENT variable a child needs is named in
`environment:` — and so is every [[pre-exec-hijack?]] name, which would run
code in the unconfined launcher before the jail exists.

`jail.environment: inherit` (`:inherit-host-env?`) replaces the allowlist with
the operator's WHOLE ambient environment, secrets included: filesystem,
network, exec and Mach confinement are unchanged, but ambient secrecy is
given up on purpose. The [[pre-exec-hijack?]] scrub still applies — that one
is not confinement of the child, it is the jail's own installation.

Returns nil when the policy is not enforcing — the caller keeps the parent
environment and merges [[child-env-additions]] instead (unjailed
platforms/`jail.enabled: false`), so non-confined behavior is unchanged.
sourceraw docstring

keychain-denial-hintclj

(keychain-denial-hint {:keys [disabled? keychain?]} output)

One actionable line when a command's output shows a Keychain lookup THIS jail denied, else nil. Silent when the jail is off (:disabled?) or the keychain is already granted (:keychain?) — the failure is then a real Keychain miss and naming the sandbox would send the caller the wrong way.

One actionable line when a command's output shows a Keychain lookup THIS jail
denied, else nil. Silent when the jail is off (`:disabled?`) or the keychain
is already granted (`:keychain?`) — the failure is then a real Keychain miss
and naming the sandbox would send the caller the wrong way.
sourceraw docstring

keychain-denial?clj

(keychain-denial? output)

True when captured output shows a macOS Keychain lookup that failed the way a denied Mach lookup fails. Pure text test; keychain-denial-hint decides whether confinement is the explanation. ONE output string, because a command runs under a pty where stdout and stderr are one stream.

True when captured output shows a macOS Keychain lookup that failed the way a
denied Mach lookup fails. Pure text test; `keychain-denial-hint` decides
whether confinement is the explanation. ONE `output` string, because a command
runs under a pty where stdout and stderr are one stream.
sourceraw docstring

language-process-policyclj

(language-process-policy base loopback-port)

Derive a managed-language jail policy from a session's base policy. It keeps filesystem confinement, adds read-only runtime installations plus Vis-owned log directories, and replaces the shell proxy endpoint with this session's attributed language-process endpoint.

Dependency caches enter through the shared workspace.filesystem catalog and are already present on the base policy's read/write roots; this pass only adds the read-only language runtimes/toolchains plus Vis-owned log directories.

Direct network access is disabled. CA-aware runtimes receive the combined PEM bundle, while JVM children also receive an ephemeral PKCS12 truststore. loopback-port permits only the managed nREPL's selected listener port.

Derive a managed-language jail policy from a session's base policy. It keeps
filesystem confinement, adds read-only runtime installations plus Vis-owned log
directories, and replaces the shell proxy endpoint with this session's
attributed language-process endpoint.

Dependency caches enter through the shared `workspace.filesystem` catalog and
are already present on the base policy's read/write roots; this pass only adds
the read-only language runtimes/toolchains plus Vis-owned log directories.

Direct network access is disabled. CA-aware runtimes receive the combined PEM
bundle, while JVM children also receive an ephemeral PKCS12 truststore.
`loopback-port` permits only the managed nREPL's selected listener port.
sourceraw docstring

prepare-session-jail!clj

(prepare-session-jail! {:keys [session-id jail-policy-fn]})

Bind the language surface's live session env to the managed-process contract. Missing session identity or policy fails closed before a language handler can start a REPL or project test process. Safe and idempotent per dispatch.

Bind the language surface's live session env to the managed-process contract.
Missing session identity or policy fails closed before a language handler can
start a REPL or project test process. Safe and idempotent per dispatch.
sourceraw docstring

process-environmentclj

(process-environment policy)
(process-environment policy extra)

Build the COMPLETE child environment for policy, then overlay trusted host-owned extra. Confined children start from the scrubbed allowlist; disabled policies preserve the ambient environment and apply removals.

Build the COMPLETE child environment for `policy`, then overlay trusted
host-owned `extra`. Confined children start from the scrubbed allowlist;
disabled policies preserve the ambient environment and apply removals.
sourceraw docstring

proxy-envclj

(proxy-env policy)

Environment additions for a child of an enabled policy. When a gateway proxy endpoint is present, common proxy and CA variables cover curl/git/Python/Bun/ etc.; managed JVM children additionally receive proxy plus ephemeral truststore properties through JAVA_TOOL_OPTIONS. The already-confined marker is not ours: the runtime stamps it on every child it confines.

Environment additions for a child of an enabled policy. When a gateway proxy
endpoint is present, common proxy and CA variables cover curl/git/Python/Bun/
etc.; managed JVM children additionally receive proxy plus ephemeral
truststore properties through JAVA_TOOL_OPTIONS. The already-confined marker
is not ours: the runtime stamps it on every child it confines.
sourceraw docstring

python-worker-policyclj

(python-worker-policy base run-directory control-socket boot-read-paths)

Add host-owned worker state, boot files and its exact control socket to a session policy. When present, the session-attributed compatibility proxy replaces the shared authenticated endpoint for stacks that omit proxy auth.

Add host-owned worker state, boot files and its exact control socket to a
session policy. When present, the session-attributed compatibility proxy
replaces the shared authenticated endpoint for stacks that omit proxy auth.
sourceraw docstring

register-session-jail!clj

(register-session-jail! session-id policy-fn)

Register (or replace) this session's live base jail-policy function.

Register (or replace) this session's live base jail-policy function.
sourceraw docstring

repl-policyclj

(repl-policy base loopback-port)

Derive the managed-nREPL variant for its selected loopback listener port.

Derive the managed-nREPL variant for its selected loopback listener port.
sourceraw docstring

runtime-policyclj

(runtime-policy {:keys [roots-fn net-enabled? allow-read-write allow-read
                        deny-write deny-read deny-exec proxy-port loopback-port
                        keychain? unix-connect]
                 :as policy})

The platform-neutral confinement VALUE the runtime compiles, from a session policy: the LIVE session roots via :roots-fn plus :allow-read-write are read-write, :allow-read read-only, the deny lists win, :unix-connect names exact local control sockets, egress is the session proxy when one is up, otherwise open or off with :net-enabled?, and inbound is the managed listener port plus :inbound-ports. Called per spawn, so each child gets the CURRENT live roots without re-reading model-writable config.

The platform-neutral confinement VALUE the runtime compiles, from a session
policy: the LIVE session roots via `:roots-fn` plus `:allow-read-write` are
read-write, `:allow-read` read-only, the deny lists win, `:unix-connect` names
exact local control sockets, egress is the session proxy when one is up, otherwise
open or off with `:net-enabled?`, and inbound is the managed listener port plus
`:inbound-ports`. Called per spawn, so each child gets the CURRENT live roots
without re-reading model-writable config.
sourceraw docstring

session-process-spawn!clj

(session-process-spawn! session-id argv directory)
(session-process-spawn! session-id
                        argv
                        directory
                        {:keys [loopback-port env] :as opts})

THE managed-language launch contract. Resolve session-id atomically, derive its REPL/test policy, merge this call's environment delta, and spawn through spawn!. Unknown, disposed, or failing sessions are denied before spawn.

Options additionally accept :loopback-port, :env, and every spawn! option. The returned value is a java.lang.Process.

THE managed-language launch contract. Resolve `session-id` atomically, derive
its REPL/test policy, merge this call's environment delta, and spawn through
[[spawn!]]. Unknown, disposed, or failing sessions are denied before spawn.

Options additionally accept `:loopback-port`, `:env`, and every [[spawn!]]
option. The returned value is a `java.lang.Process`.
sourceraw docstring

spawn!clj

(spawn! argv directory policy)
(spawn! argv
        directory
        policy
        {:keys [environment extra-environment pty? merge-stderr? rows columns]})

Spawn argv through the runtime-owned process boundary. An enabled policy becomes a runtime-policy value the runtime enforces (it refuses the spawn on a host that cannot); a disabled policy, or a process that is itself already confined, still gets libvisjail's detached process group, PTY and lifecycle implementation with no second layer.

Options: :directory, an exact :environment or trusted :extra-environment, :pty?, :merge-stderr?, :rows, and :columns.

Spawn `argv` through the runtime-owned process boundary. An enabled policy
becomes a [[runtime-policy]] value the runtime enforces (it refuses the spawn
on a host that cannot); a disabled policy, or a process that is itself already
confined, still gets libvisjail's detached process group, PTY and lifecycle
implementation with no second layer.

Options: `:directory`, an exact `:environment` or trusted
`:extra-environment`, `:pty?`, `:merge-stderr?`, `:rows`, and `:columns`.
sourceraw docstring

supported?clj

(supported?)

True when this host can confine a child at all.

True when this host can confine a child at all.
sourceraw docstring

unenforceable-reasonclj

(unenforceable-reason)

Nil when supported?, else the runtime's explanation of the platform gap.

Nil when `supported?`, else the runtime's explanation of the platform gap.
sourceraw docstring

unregister-session-jail!clj

(unregister-session-jail! session-id)

Drop this session's registered jail policy (loop dispose).

Drop this session's registered jail policy (loop dispose).
sourceraw docstring

with-call-envclj

(with-call-env policy overrides)

policy with ONE call's resolved delta merged over its project environment. Names set to nil become :env-removals: a confined child's environment is built from nothing so they are simply never added, while an UNCONFINED child inherits this process' environment and must have them removed before the complete map crosses the native spawn boundary.

A nil policy is a spawn with no jail at all, and the delta still applies — a caller cannot lose its own variables by running where the jail is off.

`policy` with ONE call's resolved delta merged over its project environment.
Names set to nil become `:env-removals`: a confined child's environment is
built from nothing so they are simply never added, while an UNCONFINED child
inherits this process' environment and must have them removed before the
complete map crosses the native spawn boundary.

A nil policy is a spawn with no jail at all, and the delta still applies —
a caller cannot lose its own variables by running where the jail is off.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close