Runtime filesystem isolation
For LLMs10 sections
Run the server so it can read exactly the corpus and cases it serves, write exactly the channels the operator opened, and reach exactly the network peers the operator named — with each boundary tied to a real mechanism or marked as operator policy.
Component law-mcp (stdio and --http). Scenario: a deployment host
serving one profile. All users, paths, and URLs below are synthetic
examples.
Applies to
Section titled “Applies to”| Branch | Coverage in this article |
|---|---|
MCP (law-mcp-server, stdio + HTTP) | full recipe: user, mounts, network, temp |
law serve | same OS mechanisms apply; serve paths (world, journal, token file) are in Deploy a private HTTP service and Configuration reference |
Prerequisites
Section titled “Prerequisites”- A checkout root containing the profiles directory and the
CLIR tree (real root markers), or an explicit
--root/LAW_MCP_ROOT(real). - A decision on the service user (no code default; the process runs as whoever starts it) and on which optional legs are enabled (journal socket, OTLP, Lens answers, neural appendix).
- Create a dedicated service user (operator-policy-example:
useradd --system --no-create-home law-svc). The server needs no root powers: it binds a high port by default (8722, real) and opens no privileged socket itself. - Mount the release read-only (operator-policy-example:
mount -o remount,ro /opt/law-dsl, or a read-only container layer). This holds only while the profile denies the writing facets: withediting/draftingallowed, the server writes case packages and author-tree files under the root (see the effects matrix). The explicit--export-neural-input PATH(real,main.rs) takes an operator-chosen path outside the release. - Grant read on the root, write nowhere by default
(operator-policy-example modes: root
0755 root:root, service user in no write group). The server reads profiles, the CLIR tree, and case directories (law.toml,law.lock, queries) under the root (real behavior). - Keep case packages inside the root. Real containment
(
execution.rs): the package path is canonicalized and must start with the canonical root and containlaw.toml; symlink escapes are refused withcase_ask.outside_root. Do not work around this with symlinks into foreign trees. - Open only the write channels you use (all real):
journald socket (datagrams; absent socket falls back to stderr),
stderr capture (your supervisor’s), OTLP receiver
(
LAW_MCP_OTLP_URL, spans only, no bodies), the export path if--export-neural-inputis used, plus — when the writing facets are allowed — the case/author trees the tools update. Workbench runs additionally create temp dirs and arequest.jsonunder the OS temp dir, soTMPDIRmust be writable (no other temp contract exists). - Name the allowed network explicitly. Real client-side rules
(
service_client.rs, shared by Lens answers and neural appendix): production requires HTTPS; plain HTTP only to loopback (127.0.0.1,localhost,[::1]); zero redirects; 30 s timeout; response caps 4 MiB default, 32 MiB for Lens answers. Credentials in the URL (@,?,#) are refused. The process otherwise opens no outbound connections: no telemetry, no update checks, no LLM calls. - Treat foreign drafts as untrusted input. Two touch points (real):
--export-neural-input PATHhands the search index input to a separate neural service at index-build time only; thelaw_searchneural appendix posts query text + lexical candidate ids to{LAW_NEURAL_URL}/v1/appendixand validates the reply (must carryneural+candidates, at most 8, each re-checked against the local index —reference/search.rs). Anything the service returns outside that contract is discarded with asearch.neural_bad.*reason, and the lexical answer stands.
Expected result
Section titled “Expected result”- Under a profile that denies the writing facets (
editing,drafting), the service user can start the server, serve calls, and emit journal/OTLP records, but cannot modify the release or any case input. With those facets allowed, case/author-tree writes and workbench temp files are the tools’ designed effect (see the effects matrix), not an isolation failure. - A
law_case_askcall whosepackageclimbs out of the root with parent-directory segments (created-example) is refused (outside_root/not_found, real codes); no file outside the root is opened. - With all
LAW_*_URLvariables unset, the process makes zero outbound connections (real: each leg is disabled without its URL).
Result check
Section titled “Result check”- Start read-only and call
law_search:neural.active:falsewith the unset-URL reason (real) proves no network attempt. - Set
LAW_ANSWERS_URL=http://created-example.invalid(non-loopback plain HTTP): publication tools fail withSERVICE_CONFIGhttps_required(real), proving the guard runs before any byte is sent. strace -f -e trace=%network(operator-policy-example) during a local-only call showsaccept/socketfor journald at most, and noconnectto foreign hosts.
Failures and diagnostics
Section titled “Failures and diagnostics”no law-dsl root found above …; pass --root(real): start from inside the checkout or setLAW_MCP_ROOT.bind on "0.0.0.0" without LAW_MCP_TOKEN is refused(real): set a token or confirm a deliberate open bind withLAW_MCP_PUBLIC=1(real escape hatch).401onPOST /mcp(real): missing/wrongAuthorization: BeareragainstLAW_MCP_TOKEN/--token.403(real): requestOriginnot in--allowed-origin/LAW_MCP_ALLOWED_ORIGINS.- Read-only root surprises: any write the profile’s facets allow
can fail for permission — case/author-tree writes (
editing,drafting), workbench temp files underTMPDIR, and--export-neural-input, not just the export flag. Diagnose by asking which tool attempted the write and checking its row in the effects matrix: a denied-by-filesystem write from an allowed facet means the mounts (step 2/5) are narrower than the profile, not that the tool misbehaved.
Support boundaries
Section titled “Support boundaries”- “Isolated” in this article means only: root containment for reads, the write channels step 5 enumerates (nothing wider), allowlisted outbound legs, and no ambient network use. Process sandboxing (namespaces, seccomp, containers) is the operator’s layer — the binary requests none.
- Immutable releases, the service user, directory modes, and firewall
rules are operator policy with reference-setting examples above;
the engine’s part is that read-only operation is sufficient only
for profiles denying the writing facets — with
editing/draftingallowed, the writable trees are part of the contract. - Case inputs are trusted once inside the root: filesystem ACLs, not
the engine, decide who may change a
law.toml.
Next step
Section titled “Next step”- Article 08 (Data flow, storage, and retention) for where each copy of the data rests; article 11 (Resource limits and cancellation) for the runtime limits that keep one tenant’s call from starving the others.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.