{
 "meta": {
  "title": "Capability System — The Crush Language Guide",
  "description": "Generated from crush/capabilities.md of The Crush Language Guide (https://docs.crushlang.org/crush/capabilities.html). Edit the guide, not this file: it is regenerated on every build.",
  "frontends": [
   "crush"
  ],
  "author": "The Crush Language Guide",
  "tags": {
   "source": "crush/capabilities.md",
   "guide_url": "https://docs.crushlang.org/crush/capabilities.html",
   "generator": "scripts/build-notebooks.py"
  }
 },
 "cells": [
  {
   "id": "crush-capabilities-md-1",
   "kind": {
    "type": "markdown"
   },
   "source": "# Capability System\n\nCrush programs can't touch the outside world directly. Every interaction with the\nhost — printing, reading a file, making an HTTP request, spawning a process,\nrunning another language — is a **capability call**, and a capability only exists\nif the host that is running the program has explicitly turned it on. There is no\nambient authority.\n\n> **Status: alpha.** The model below is real and enforced, but the set of\n> capabilities is small and the permission granularity is coarse (on/off per\n> group, with a directory root for the filesystem). Read\n> [What is not enforced](#what-is-not-enforced) before relying on it for\n> isolation.\n\n## Calling a capability\n\nA capability call looks like an ordinary dotted function call — **no `@` prefix**:\n",
   "state": {
    "status": "done"
   },
   "meta": {},
   "outputs": []
  },
  {
   "id": "crush-capabilities-1",
   "kind": {
    "type": "crush"
   },
   "source": "io.print(\"Hello, \", \"Crush\")\nlet upper = str.concat(\"a\", \"b\")\nprint(upper)\n",
   "state": {
    "status": "pending"
   },
   "meta": {
    "tags": [
     "guide",
     "runnable"
    ],
    "extra": {
     "guide": {
      "src": "crush/capabilities.md:19",
      "browser": "run",
      "expect_output": "Hello, Crush\nab\n"
     }
    }
   },
   "outputs": []
  },
  {
   "id": "crush-capabilities-md-3",
   "kind": {
    "type": "markdown"
   },
   "source": "`io.print(a, b, ...)` takes any number of arguments, joins them with no\nseparator, and appends a newline. The built-in `print(x)` takes exactly one.\n(The `@` sigil exists in Crush, but it introduces polyglot blocks and\nannotations, never capability calls — see [Syntax](https://docs.crushlang.org/crush/syntax.html#language-blocks).)\n\n## Always available\n\nThese work with no flags:\n\n| Capability | Description |\n|---|---|\n| `io.print(...)` | print to stdout |\n| `io.read()` | read one line from stdin (empty string at EOF) |\n| `str.concat(...)`, `str.len(s)` | concatenate / byte length |\n| `conv.chr(n)`, `conv.ord(s)` | codepoint ↔ one-character string |\n",
   "state": {
    "status": "done"
   },
   "meta": {},
   "outputs": []
  },
  {
   "id": "crush-capabilities-2",
   "kind": {
    "type": "crush"
   },
   "source": "print(conv.chr(65))\nprint(conv.ord(\"A\"))\nprint(str.len(\"héllo\"))\n",
   "state": {
    "status": "pending"
   },
   "meta": {
    "tags": [
     "guide",
     "runnable"
    ],
    "extra": {
     "guide": {
      "src": "crush/capabilities.md:47",
      "browser": "run",
      "expect_output": "A\n65\n6\n"
     }
    }
   },
   "outputs": []
  },
  {
   "id": "crush-capabilities-md-5",
   "kind": {
    "type": "markdown"
   },
   "source": "`io.read` reads from standard input:\n\n```crush\nlet name = io.read()\nio.print(\"Hello, \" + name)\n```\n\n## Enabling more capabilities\n\nWith the CLI, each *group* of capabilities is switched on by a flag on\n`crush-run run` (or `crush run`). Anything not switched on is simply **not\nregistered**, and calling it fails at run time with `unknown capability`:\n\n| Flag | Capabilities enabled |\n|---|---|\n| `--stdlib` | `str.*`, `math.*`, `conv.*`, `collections.*`, `json.*`, `path.*`, `regex.*`, `bytes.*`, `buffer.*`, `binary.*`, `result.*`, `time.format/parse`, `env.os/arch`, `system.*` — pure computation, see [Standard Library](https://docs.crushlang.org/crush/stdlib.html) |\n| `--fs` | `fs.read`, `fs.write`, `fs.exists`, `fs.list`, `text.head/tail/wc/cut/grep` |\n| `--env` | `env.get` |\n| `--time` | `time.now`, `time.now_ms`, `time.now_iso`, `time.elapsed`, `time.sleep` |\n| `--net` | `net.http_get`, `net.http_post` |\n| `--process` | `process.exec` |\n| `--crypto` | `crypto.sha256`, `crypto.random` |\n| `--db PATH` | `db.query`, `db.execute` on that SQLite file |\n| `--graphics` | `graphics.canvas/rect/circle/text/to_svg` |\n| `--bus` | `message_bus.publish/subscribe/recv` |\n| `--task` | `task.start/stop/list` |\n| `--akg` | `akg.write/read/search` |\n| `--polyglot` | `@python { }`, `@javascript { }`, `@bash { }` blocks — see [Polyglot](https://docs.crushlang.org/crush/polyglot.html) |\n\n`crush-run caps` prints the list for your build. Some groups (`--stdlib`,\n`--net`, `--db`, `--graphics`) are Cargo features of `crush-lang-sdk`; if your binary was\nbuilt without one, its flag prints `warning: --… requires the '…' feature (not\nenabled in this build)` and the group stays absent. Note that **`stdlib` is off by\ndefault** (crush-ast CRUSH-113); build with `--features stdlib`.\n\nWithout the flag, the call fails:\n",
   "state": {
    "status": "done"
   },
   "meta": {},
   "outputs": []
  },
  {
   "id": "crush-capabilities-3",
   "kind": {
    "type": "crush"
   },
   "source": "fn main() {\n    print(fs.read(\"data.txt\"))\n}\n",
   "state": {
    "status": "pending"
   },
   "meta": {
    "tags": [
     "guide",
     "expected-failure"
    ],
    "extra": {
     "guide": {
      "src": "crush/capabilities.md:100",
      "browser": "fail",
      "why": "this example shows a refusal",
      "expect_error": "unknown capability: fs.read"
     }
    }
   },
   "outputs": []
  },
  {
   "id": "crush-capabilities-md-7",
   "kind": {
    "type": "markdown"
   },
   "source": "With it, and with the filesystem confined to a directory:\n",
   "state": {
    "status": "done"
   },
   "meta": {},
   "outputs": []
  },
  {
   "id": "crush-capabilities-4",
   "kind": {
    "type": "crush"
   },
   "source": "print(fs.read(\"../outside.txt\"))\n",
   "state": {
    "status": "pending"
   },
   "meta": {
    "tags": [
     "guide",
     "needs-host"
    ],
    "extra": {
     "guide": {
      "src": "crush/capabilities.md:108",
      "browser": "host",
      "why": "uses `fs.read`, which the in-browser runtime does not provide",
      "expect_error": "path escapes sandbox root"
     }
    }
   },
   "outputs": []
  },
  {
   "id": "crush-capabilities-md-9",
   "kind": {
    "type": "markdown"
   },
   "source": "Filesystem paths must be relative and resolve **inside** `--fs-root` (default `.`):\nabsolute paths and `..` escapes are rejected. This is the one place the permission\nhas a scope.\n\n### Filesystem\n",
   "state": {
    "status": "done"
   },
   "meta": {},
   "outputs": []
  },
  {
   "id": "crush-capabilities-5",
   "kind": {
    "type": "crush"
   },
   "source": "print(fs.exists(\"missing.txt\"))\n",
   "state": {
    "status": "pending"
   },
   "meta": {
    "tags": [
     "guide",
     "needs-host"
    ],
    "extra": {
     "guide": {
      "src": "crush/capabilities.md:119",
      "browser": "host",
      "why": "uses `fs.exists`, which the in-browser runtime does not provide",
      "expect_output": "0\n"
     }
    }
   },
   "outputs": []
  },
  {
   "id": "crush-capabilities-md-11",
   "kind": {
    "type": "markdown"
   },
   "source": "| Capability | Arguments | Returns |\n|---|---|---|\n| `fs.read` | `path` | file contents (string) |\n| `fs.exists` | `path` | `1` / `0` |\n| `fs.list` | `dir` | array of names |\n| `fs.write` | `path, data` | nothing — **see the note below** |\n\n> **Known bug — `fs.write`.** It declares no return value, and the compiler emits\n> a `pop` after a call used as a statement, so any program that calls it ends with\n> `[runtime] stack underflow` (the write itself may still happen). Treat `fs.write`\n> as not usable from `.crush` source until this is fixed. Capabilities that\n> return nothing share the problem; `io.print` and `time.sleep` are unaffected.\n",
   "state": {
    "status": "done"
   },
   "meta": {},
   "outputs": []
  },
  {
   "id": "crush-capabilities-6",
   "kind": {
    "type": "crush"
   },
   "source": "fs.write(\"out.txt\", \"hello\")\nprint(fs.read(\"out.txt\"))\n",
   "state": {
    "status": "pending"
   },
   "meta": {
    "tags": [
     "guide",
     "needs-host"
    ],
    "extra": {
     "guide": {
      "src": "crush/capabilities.md:143",
      "browser": "host",
      "why": "uses `fs.read`, `fs.write`, which the in-browser runtime does not provide"
     }
    }
   },
   "outputs": []
  },
  {
   "id": "crush-capabilities-md-13",
   "kind": {
    "type": "markdown"
   },
   "source": "### Environment, time, processes, network\n",
   "state": {
    "status": "done"
   },
   "meta": {},
   "outputs": []
  },
  {
   "id": "crush-capabilities-7",
   "kind": {
    "type": "crush"
   },
   "source": "print(env.get(\"CRUSH_GUIDE_DEMO_UNSET\"))\nprint(time.now() > 0)\nprint(process.exec(\"echo\", \"hi\"))\n",
   "state": {
    "status": "pending"
   },
   "meta": {
    "tags": [
     "guide",
     "needs-host"
    ],
    "extra": {
     "guide": {
      "src": "crush/capabilities.md:151",
      "browser": "host",
      "why": "uses `env.get`, `process.exec`, `time.now`, which the in-browser runtime does not provide",
      "expect_output": "null\ntrue\n{\"exit_code\":0,\"stderr\":\"\",\"stdout\":\"hi\\n\"}\n"
     }
    }
   },
   "outputs": []
  },
  {
   "id": "crush-capabilities-md-15",
   "kind": {
    "type": "markdown"
   },
   "source": "- `env.get(name)` → string, or `null` if unset.\n- `time.now()` Unix seconds; `time.now_ms()`; `time.now_iso()`; `time.sleep(ms)`\n  (bounded by the wall-clock quota).\n- `process.exec(cmd, args)` — exactly **two** arguments (the command and one\n  argument string) — returns a JSON string with `exit_code`, `stdout`, `stderr`.\n- `net.http_get(url)` / `net.http_post(url, body)` — response body as a string;\n  size capped by `--net-max-response-bytes` (default 1 MiB).\n- `crypto.sha256(data)`, `crypto.random(n)` (base64, `n ≤ 4096`).\n\nA host capability that fails (missing file, refused connection, path escape)\naborts the program with `[runtime] unknown capability: <name>: <reason>` — the\n\"unknown capability\" wording is misleading there, and the error is not\ncatchable by [`try`/`catch`](https://docs.crushlang.org/crush/control_flow.html#what-catch-does-not-catch).\n\n## Resource limits\n\nSeparately from capabilities, the VM enforces **quotas**; exceeding one ends the\nprogram with a `[runtime]` error:\n\n| Quota | Default | CLI flag |\n|---|---|---|\n| instructions | 1,000,000 | `--max-steps` |\n| stack slots | 4,096 | `--max-stack` |\n| output bytes | 1 MiB | `--max-output` |\n| call depth | 256 | `--max-call-depth` |\n| wall-clock per polyglot subprocess | 30,000 ms | (set via `Quotas::max_wall_time_ms` when embedding) |\n\nOutput is buffered: if a program fails at run time, anything it printed before\nthe failure is **not** shown.\n\n## Embedding: declaring permissions\n\nWhen you embed the VM from Rust you choose the capability set yourself\n(`crush_lang_sdk::HostCaps`, `Runtime`, `ProgramBuilder`), and a CASM program\nnames the capabilities it is allowed to call in its manifest — a list of names:\n\n```json\n{\n  \"version\": \"1.0\",\n  \"manifest\": { \"permissions\": [\"io.print\"] },\n  \"functions\": { \"main\": { \"params\": [], \"locals\": [], \"body\": [] } }\n}\n```\n\nA capability call whose name is not both **registered** by the host and **listed**\nin the program's permissions is refused. See [Getting Started](https://docs.crushlang.org/getting-started.html)\nand [CASM structure](https://docs.crushlang.org/casm/structure.html). For `.casm` text run with `crush-run`,\n`--cap NAME` adds names to that list.\n\nThere is no path or URL scoping in the manifest (`\"fs.read\": [\"./data\"]` is not a\nthing): scoping today is the host's job — e.g. `--fs-root`.\n\n## What is not enforced\n\nBe honest with yourself about the isolation you actually get:\n\n- **Polyglot blocks run outside this capability model.** With `--polyglot`,\n  `@python { }` / `@javascript { }` / `@bash { }` blocks run as ordinary\n  subprocesses with the **host process's authority**: a Python block can read any\n  file the user can. The `fs`/`net`/`process` flags above do not constrain it. An\n  optional build-time sandbox (bubblewrap, via `buckets`) exists but is **off by\n  default** — see [Polyglot](https://docs.crushlang.org/crush/polyglot.html#sandbox-and-authority).\n- Quotas bound instructions and (for polyglot) wall-clock time, not memory.\n- `process.exec` runs any command the user can run; only enable it for programs\n  you trust.\n\n## Not implemented\n\nThe following capabilities appeared in earlier revisions of this guide but do not\nexist in the runtime: `io.eprint`, `fs.delete`, `sys.exec`, `sys.env`, `sys.args`,\n`sys.exit`, `net.get`, `net.post`, `net.http`, `type.of`, `console.print`,\n`array.length`, `map.keys`, `map.has_key`, and `module.load` (what `import`\nlowers to — crush-ast **CRUSH-110**). Use the replacements above\n(`env.get`, `net.http_get`, `conv.type_of`, `len`, …).\n\n## Next Steps\n\n- **[Standard Library](https://docs.crushlang.org/crush/stdlib.html)**\n- **[Polyglot Programming](https://docs.crushlang.org/crush/polyglot.html)**\n",
   "state": {
    "status": "done"
   },
   "meta": {},
   "outputs": []
  }
 ]
}
