GENO

Blog/Security

No. 003

Fail closed: how capabilities work

David IachOctober 14, 20262 min read

Abstract

A Geno program cannot read a file, open a socket or start a process unless the command that runs it says so. Here is how capability gating works on each surface, including where it does not protect you.

Most computation in Geno is always allowed. Anything that touches the world outside the program needs a capability granted on the command line. If you do not grant it, the call fails at the moment it is made, naming the builtin that needed it.

$ geno run notes.geno
Runtime Error: Capability denied: 'fs_read_text' requires '--cap fs'

The ten capabilities

CapabilityUnlocks
printConsole output
clockTimestamps, timing, sleep_ms
randomRandom numbers
fsReading, writing and listing files
httpOutbound HTTP and HTTPS requests
regexRegular expressions
envEnvironment variables and CLI arguments
processRunning other programs
stdinReading standard input
serveListening for HTTP requests

The mapping from builtin to capability is declared once, in geno/builtin_manifest.py, and every execution surface derives its checks from it. A new effectful builtin cannot ship without saying which capability it needs.

Each surface, honestly

geno run compiles to Python and runs it in a separate, resource-limited process. print, clock and random work; everything else is denied.

geno run --unsafe --cap fs,print uses the direct interpreter with exactly the capabilities you list and nothing implicit.

geno run --json and the embedding API grant nothing unless asked. They run in your process with cooperative limits, so use the hosted server or your own worker process for untrusted code.

geno serve lets an operator decide which capabilities requests may ask for, and each request receives only the subset it asked for.

Compiled Python reads --cap from its own command line and denies everything without it.

Compiled JavaScript is the exception. --cap controls which APIs are wired up, but generated Node.js code is not a sandbox. Treat it as trusted code, which is how the security policy describes it too.

Granted does not mean unlimited

Granting fs, http or process still applies scoped policies in the interpreter and in compiled Python: filesystem roots, read-only modes, blocked private network addresses, redirect limits and response-size caps. The capability reference lists them per surface.

Why the command line?

Because that is where a person decides to run something. A model can write fs_read_text("/etc/passwd"). It cannot also grant itself the right to execute it, and the line that would grant it is short enough to read before pressing Enter.