Skip to main content
A policy can be a program instead of a class. A profile names a list of programs for each method it answers at, and the runtime each one runs on. A program reads the same ctx a coded policy reads, as JSON, and prints its answer. Three places take a script:

A profile’s policy

workspace.yaml
guard.py
Each entry names its program and its runtime: a name, or {name, config} when the runtime needs settings, as in a runtimes entry. A method’s programs are asked in order, up to the first deny, and each answer counts as if a policy of its own gave it (when answers disagree): an ask from one program and a route from another both hold. A method with no program is never asked and costs nothing. In code, a profile takes the same block, with a ScriptSource for each program:

The program

A program runs the way any program runs on its runtime. Mirage never edits it:
  • Input: ctx as JSON, in the environment variable MIRAGE_POLICY_CTX and on stdin. On monty, which has no sys.stdin, stdin arrives as a stdin variable, so read the environment variable to run anywhere. MIRAGE_POLICY_CTX is reserved, and the session never sees it. It is the only variable Mirage passes the program: the session’s variables stay out of its environment. A runtime may add its own, as wasi adds PYTHONHOME, PYTHONPATH and PYTHONDONTWRITEBYTECODE.
  • Session: the program starts in the session’s working directory, so os.getcwd() is the session’s. The session’s variables are in ctx["env"], not in os.environ. An import never loads a module from the session’s directory.
  • Answer: printed on stdout. Nothing, or null, is no opinion.
A deny or an ask without a reason gets the default one. Anything else on stdout, such as a debug line, fails the program; write those to stderr.

ctx

Every field a coded policy reads, under the Python names, as plain data. For cat notes.txt | wc -l in /data, pre_execute gets:
pre_command gets one entry of commands, with the session fields beside it: Every ctx also has session_id, cwd, env, profile and mounts, the mount roots the session can see. A runtime’s script: also gets runtime: its name and captures.

Files and the session

A program reads the workspace as the session it judges for:
  • open(), os.listdir() and os.stat() go through the workspace. A hidden path does not exist, path rules apply, and a write is refused (EROFS).
  • Its own reads are not judged again by its profile’s pre_vfs.
  • Monty has no os.path, os.walk or Path.glob; use Path.exists(), Path.is_dir() and Path.iterdir(), which run on every runtime.

When a program fails

A program that exits non-zero, runs past 10 seconds, prints anything but an answer its method can give, or names a runtime that cannot run here refuses the action. The reason names the profile and what went wrong, such as profile 'reviewer' policy exited 1, and the program’s stderr goes to the log. Silence on failure would let through exactly what the policy is there to judge. route_policy: and runtime scripts are deployment settings, so the same mistakes fail the line with a routing error instead.

Runtimes

Each runtime is the policy’s own, built from the name and settings the profile gives and never shared with the agent. wasi needs a CPython WASI build: its folder as config: {home: ...}, or in MIRAGE_WASI_HOME. Monty runs a program in about a millisecond. wasi starts CPython on every run, 100 to 300 ms, so it suits the methods asked less often; pre_vfs, asked for every file call from outside a line, is usually monty’s.