# Capabilities and security

URL: /docs/tour/capabilities
Markdown: /docs/tour/capabilities.md

> How Sakura Lang separates effects from authority, limits root handlers with sakura.toml, narrows authority for dependencies, and blocks injection and secret leaks.

> **Pre-alpha**
>
> The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates.

Most supply-chain attacks and injection bugs rely on code doing more than it should: a dependency that reads files it was never meant to see, a string from a request that reaches a SQL query, a token that ends up in a log. Sakura Lang uses effect checking, scoped handlers and explicit trust boundaries to make authority reviewable. These are language-level checks, not an OS sandbox for trusted FFI.

## Effects are requirements, handlers are authority [#effects-are-requirements-handlers-are-authority]

`Fs` in a signature says "this function may perform filesystem operations". It does not say which files. The **installed handler** decides that ([design §6.1](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)).

A handler for `Fs` is built from a `Dir` capability value. Every path is resolved relative to that directory, and symlink escapes are rejected. Authority lives in capability values that handlers close over, not in effect labels.

The effect row still matters: a function can only perform the effects in its row, and a pure function cannot touch the outside world at all. But the row is what a function *needs*. What it may actually *reach* is set by whoever installs the handler.

## Root authority comes from the host [#root-authority-comes-from-the-host]

The entry point is `pub fn main() -> Unit ! {…}`. The host supplies root handlers for exactly the std effects `main` declares, and nothing else. Only std effects may appear in `main`'s row (`V6010`), because only they have host root handlers.

Root handlers are limited by the `[permissions]` table in the project manifest:

```toml title="sakura.toml"
[permissions]
fs.read  = ["./data", "./templates"]
fs.write = ["./data/out"]
net      = ["api.anthropic.com:443", "localhost:5432"]
env      = ["DATABASE_URL", "ANTHROPIC_API_KEY"]
process  = []          # no subprocesses
```

`sakura run` passes this table as JSON in `SAKURA_PERMISSIONS`; the host filesystem, HTTP/serving and environment adapters enforce it. Running emitted JS directly without that variable does not establish this ceiling. Trusted FFI is outside this language-level boundary. There is no separate `Net` effect: the `net` list limits the network effects `Http` and `Serve` (`Sql.postgres` is not implemented), and `env` limits which variables `Env` can read. A `main` that declares an effect whose permission list is empty, such as `Http` with an empty `net` list, fails at **startup**, not at first use, and exits with status `75` (permission denied).

## Narrowing authority [#narrowing-authority]

`Dir` and `File` have no public constructors. `Fs.root()` supplies a directory capability, and `Dir.subdir` narrows it. `Http.scoped(hosts: List<String>)` and `Env.only(keys)` attenuate outer handlers by data; `NetScope`, `EnvScope`, and `SecretStore` are not current APIs.

Attenuating handlers are ordinary std functions: `Fs.rooted(dir)`, `Http.scoped(hosts)`, and `Env.only(keys)`.

This test narrows a scripted HTTP handler to one host, without using the network:

```sakura
import std/http.{Any, Http}
import std/request.{Method, Request}
import std/response.{Response}
import std/url.{Url}

test "only the allowed HTTP host is forwarded" {
  with Http.scripted([(Any, Response.status(200, "hello"))]),
      Http.scoped(["example.com:443"]) {
    let req = Request.of(Method.Get, Url.from("https://example.com/"))
    expect_eq(Http.send(req).status, 200)
    let denied = Request.of(Method.Get, Url.from("https://other.example/"))
    expect((try { Http.send(denied) }).is_err())
  }
}
```

The same attenuation wraps a host HTTP handler, which additionally enforces manifest permissions.

## Forged capabilities are rejected [#forged-capabilities-are-rejected]

A user `@external` cannot declare a parameter or result containing a capability from another package, or any `Handler<…>` (`V6001`, design §17.91). The check follows records, collections, options and function types; another package's `@resource` type also counts. An FFI package may declare externals over its own external types.

This declaration is intentionally rejected with `V6001`; no JavaScript is executed:

```sakura
import std/fs.{Dir}

@external(js, "./forge.js", "root")
fn forge() -> Dir
```

## Scoped escape rules [#scoped-escape-rules]

An external type, a handler, an `@resource` type, or a value transitively containing one is **scoped**. Closures also carry the scoped locals they capture ([design §6.3](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). Scoped values may be passed down the call stack, stored in scoped records, and returned from ordinary functions. They may not flow into:

- a `Task.spawn` closure whose `Scope` outlives the `with` that produced the value (there are no detached tasks in v0),
- a top-level `let`,
- a value returned from the `with` body that introduced the scope.

A violation is `V6002`, including an explicit `return` out of the `with`. Calls returning function-containing values conservatively retain scoped argument captures.

`V6003` rejects a resource escaping the callback that borrows it, or being stored in handler `var` state. The callback rule also applies to wrapper functions and to a callback parameter whose type contains a resource, such as a record with a `File` field (design §17.89). This example is intentionally rejected:

```sakura
import std/fs.{File, Fs, FsError, Mode}
import std/path.{Path}

fn keep_open(path: Path) -> File ! {Fs, Fail<FsError>} {
  Fs.with_file(path, Mode.Read, fn(file) { file })
}
```

Return ordinary data read inside the callback instead. Files are finalized on callback exit. v0 does not check affine use counts; runtime file operations still reject access after closure.

## Dependency authority (design; package tooling planned) [#dependency-authority-design-package-tooling-planned]

The design calls for computed package authority and `sakura deps capabilities`; that command and multi-package tooling are not implemented. Imports do not grant authority: callers install handlers around dependency calls. Adding an effect to a dependency's signature requires its callers to account for that effect.

The implemented [build manifest](/docs/agents/build-manifest) inventories user externals alongside other trusted components. It is not yet the planned dependency authority graph.

## Untrusted data [#untrusted-data]

`of`, `check`, `trust`, `len` and refined-target `validate` work today. A validation target must be a known refinement of the wrapped base; otherwise the compiler reports `V5003`. Using `Untrusted.validate` as a function value is also rejected. See [contracts](/docs/tour/contracts#validating-untrusted-input).

`Untrusted<T>` wraps data from outside the deployment: raw request bodies, file contents read as text (`Fs.read_text`), response text (`Response.text`), and free-text LLM output (`Llm.generate_text`). Environment variables are not in that list: they are set by the operator and gated by the manifest's `env` list, so `Env.get(key)` returns a plain `Option<String>`. `Untrusted<T>` supports:

| Operation                        | Result                                                            |
| -------------------------------- | ----------------------------------------------------------------- |
| `.validate()`                    | `Result<V, Violation>` for any refined type `V`, named by annotation: `let e: Email = Untrusted.validate(u)?`. |
| `.check(clause, pred)`                   | `Result<T, Violation>`.                                           |
| `.len()`                         | The length, without trusting the content.                         |
| `.trust(@trust("reason"))`       | `T`. An explicit, greppable escape hatch.                         |
| `Untrusted.of(t)`                | Wraps a value, for tests.                                         |

Validating against a refinement is the normal path:

```sakura
type Email = String where String.len(it) <= 254 && String.contains(it, "@")
error InvalidEmail

fn read_email(raw: Untrusted<String>) -> Email ! {Fail<InvalidEmail>} {
  match raw.validate() {      // V = Email, inferred from the return type
    Ok(email) => email,
    Err(_) => fail InvalidEmail,
  }
}
```

`Untrusted<T>` is `Show`, so it can be logged. Schema-decoded values (`Request.json`, `Json.decode`, `Llm.generate`) are already validated against the target type's refinements and come back as `T`, not `Untrusted<T>`.

## Tagged templates never splice [#tagged-templates-never-splice]

`sql"…"`, `html"…"` and `url"…"` are implemented tagged templates; `shell` is reserved but has no std module. The design uses tagged templates ([design §4.11](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). A tagged string desugars to a call to the tag module's `template` function, with the literal parts and the interpolated values passed separately:

```text
sql"select * from users where email = {email}"
  ==> sql.template(["select * from users where email = ", ""], [SqlValue.from(email)])
```

Interpolated values are **never spliced raw**. Each one is type checked against the tag's parameter type. For `sql` that is `SqlValue`, which becomes a bound parameter. `html` escapes text by construction. This query binds a string as a parameter:

```sakura
import std/sql.{Sql, SqlError}

type Post = { id: Int, title: String }

fn search(q: String) -> List<Post> ! {Sql, Fail<SqlError>} {
  Sql.query(sql"select id, title from posts where title = {q}")
}
```

`q` reaches the database as a parameter value, never as SQL text. Direct `Untrusted<String>` SQL interpolation currently fails with `V2032` because `SqlValue.from` requires `Schema`; validate the input into a refined string before passing it here. The source index describes broader support than this binary accepts.

HTML interpolations have element type `Html`. Existing `Html` values compose as markup; other `Show` values convert through `Html.from` and are escaped text (design §17.77). `Untrusted<String>` interpolated directly is escaped in its shown wrapper form; `Html.untrusted` displays just the escaped contents:

```sakura
import std/html.{Html}

test "HTML composition keeps text escaped" {
  let heading = html"<h1>{"Tom & Jerry"}</h1>"
  let input = Untrusted.of("<script>")
  let page = html"<main>{heading}{Html.untrusted(input)}</main>"
  expect_eq(Html.to_string(page), "<main><h1>Tom &amp; Jerry</h1>&lt;script&gt;</main>")
  expect_eq(Html.to_string(Html.from(3)), "3")
}
```

`Html.raw(markup, @trust("reason"))` is the explicit unescaped entry point; there is no `TrustedHtml` type. Its reason is included in the trust audit.

This holds because the template's result types are `pub opaque type`s. `Query<R>` and `Html` are exported by name only: other modules can pass them around, but constructing one outside its declaring module is `V1006`, so callers must use the module APIs. `Html.raw` makes bypassing escaping an explicit trust decision.

## Secrets [#secrets]

`Secret<T>` wraps credentials. `Env.secret("KEY")` returns `Option<Secret<String>>`. A secret is not `Show`, `Eq`, `Ord`, or `Schema`, so it cannot be interpolated, logged, snapshotted, or serialized. Interpolating one is `V2031`. Only std sinks accept it, such as `Http.bearer`:

```sakura
import std/http.{Http}
import std/request.{Request}
import std/response.{HttpError}
import std/log.{Level}
import std/url.{Url}
import std/decimal.{Decimal}

pub fn call_billing(amount: Decimal) -> Unit ! {Env, Http, Log, Fail<HttpError>} {
  let token = Env.secret("BILLING_TOKEN").expect("BILLING_TOKEN")
  let req = Http.bearer(Request.post_json(Url.from("https://billing/charge"), amount), token)
  Log.event(Level.Info, "charging {amount}", [])          // ok: Decimal is Show
  // Log.event(Level.Info, "token {token}", [])            // V2031: Secret<String> is not Show
  let _ = Http.send(req)
}
```

Adapters that genuinely need the raw value use `Secret.expose(s, @trust("reason"))`. [`sakura audit`](/docs/agents/audit) lists every user `@trust` site with its reason. This prevents accidental disclosure, not deliberate leakage.

## The trusted computing base [#the-trusted-computing-base]

Some things are assumed, and Sakura Lang says so. The trusted computing base is the compiler, the runtime, the std root handlers, FFI bindings marked `@external`, every `@trust` site, the host platform, and later the SMT solver ([design §6.6](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). [`sakura build --manifest`](/docs/agents/build-manifest) inventories trusted compiler/runtime/std bindings and user trust sites, including a hash of the emitted runtime. It does not hash every trusted component. Each item has the `trusted` status in the [guarantee model](/docs/tour/contracts#the-guarantee-model).

Compiler fixes never widen authority silently. A fix that would change authority carries `impact: authority_change` and is never applied by `sakura fix` automatically. See [Diagnostics](/docs/agents/diagnostics).

## What the compiler checks [#what-the-compiler-checks]

- A function performs only the effects in its row. Every performed operation has an installed handler.
- `main` declares only std effects (`V6010`). Root handlers are limited by `[permissions]`; a denied permission stops the program at startup (exit `75`).
- Prelude `Trust` is compiler-known. `@trust("reason")` is accepted only directly in a trusted sink argument; other uses report `V6001`.
- Scoped capability/resource escape checks (`V6002`/`V6003`) and foreign-capability FFI checks (`V6001`) are implemented. The broader M3 gates and maintainer security review remain separate requirements.
- `Secret<T>` is never shown, logged, snapshotted, or serialized (`V2031`).
- Tagged templates receive interpolations as values, never raw text.

## Not in v0 [#not-in-v0]

- **Information-flow control.** `Secret<T>` stops accidents, not a determined leak (design §1.1).
- **Declared capability ceilings for packages.** The design calls for computed package authority; package tooling is not implemented. Mandatory ceilings in `sakura.toml` for published packages are planned for v1 (design §16).

## Next [#next]

[Memory model](/docs/tour/memory): value semantics, reference counting, and in-place reuse.
