# Dependencies and capabilities


A dependency in Xo can only do what you let it. This page shows the tools,
in the order you are likely to use them, with the output the toolchain
prints. Every command and message below was run with the current
toolchain; only the long file paths are shortened.

The running example is a shop that depends on a billing library,
`github.com/acme/billing`.

## Capabilities: authority starts at main

A program gets its authority as values: `main(os: Os)` receives the
network, files, clock, environment, and processes, and a library can use
one only if it is handed it. There is no global `open` or `dial`.

```xo
pub fn sync(net: Net) -> Bool uses net.dial {
  match net.dial("metrics.example.net:443") {
    Ok(_) => true
    Err(_) => false
  }
}
```

Every `pub` function declares its effects (`uses net.dial`) and the
compiler checks the body against them, so a library's signature tells you
what it can do. A function with no `uses` clause is pure. Writing a log
record is the `log` effect, and every call into Go is the `go` effect.

## Effect ceilings in xo.mod

A `uses` clause on a `require` line bounds what the dependency may do:

```
module example.com/shop
edition preview

require github.com/acme/billing v1.2.0 uses net.dial
```

`uses none` admits nothing, `uses net` admits `net.dial` and
`net.listen`. A call into the dependency whose function declares more is
an error at the call:

```
$ xo check .
main/main.xo:4:20: error XO1309: `region` of github.com/acme/billing v1.2.0 performs `go`, above the effect ceiling `uses net.dial` of that dependency in xo.mod
  note: xo.mod:4:1: the ceiling is declared here
  note: review why github.com/acme/billing needs `go`; then raise the ceiling, or require a version within it (decision 0086)
```

Ceilings are opt-in. A `require` line without `uses` has none, which is
where the effect lock comes in.

## The effect lock in xo.lock

`xo get` and `xo mod tidy` record, next to each module's hashes, the
effects its exported API can reach:

```
github.com/acme/billing v1.0.0 h1:gtG1lIS42/HaOPBMfYVCNbdaHfqlfYaS65G16dWPVAw=
github.com/acme/billing v1.0.0/xo.mod h1:auD5srgbLtmoFQa9b8RUSQb31JWBxoGs3RQZoaW4FqE=
github.com/acme/billing effects none
```

When an update reaches further, the update fails and nothing is written:

```
$ xo get github.com/acme/billing@v1.1.0
xo.lock:6:1: error XO1317: github.com/acme/billing v1.1.0 reaches effects that xo.lock does not record for it: `net.dial` (locked: none, now: net.dial)
  note: the API of the selected version declares effects the version locked before did not; review why it needs them (decision 0156)
  note: to accept them: `xo mod accept-effects github.com/acme/billing` (or `xo get --accept-effects github.com/acme/billing@v1.1.0`); otherwise stay on the previous version
```

This needs no setup: the lock is on for every module `xo get` records.
After reviewing the new version, accept it, and `xo get` prints what
changed in its API:

```
$ xo get --accept-effects github.com/acme/billing@v1.1.0
github.com/acme/billing v1.0.0 => v1.1.0
  github.com/acme/billing v1.0.0 => v1.1.0:
    added fn github.com/acme/billing.sync
```

`xo check` and `xo build` never widen the lock; only `xo mod
accept-effects` and `xo get --accept-effects` do. On the publishing side,
`xo publish` rejects a minor or patch release that adds an effect to an
export (`XO1310`).

## Vouches for Go and C code

Go code (`use go` of a package that is not pure) and C code (`use c`)
can reach files and the network without a capability, so the checker
cannot see what they do. In a dependency, each of these escape hatches
needs a `vouch` line in **your** xo.mod, for the exact version you
reviewed. `xo get` refuses an update that brings an escape hatch you have
not vouched for, before it writes xo.mod or xo.lock:

```
$ xo get --accept-effects github.com/acme/billing@v1.2.0
cache/github.com/acme/billing@v1.2.0/billing.xo:1:1: error XO1316: `use go "os"` of github.com/acme/billing v1.2.0 is not vouched for by the consuming module: Go code reaches files, the network, and processes without a capability
  note: after reviewing the code, add to xo.mod: vouch github.com/acme/billing v1.2.0 go "os"
```

`--accept-effects` accepts new effects only; it never stands in for a
vouch. After reviewing the code, add the line and run `xo get` again:

```
require github.com/acme/billing v1.1.0

vouch github.com/acme/billing v1.2.0 go "os"
```

```
$ xo get --accept-effects github.com/acme/billing@v1.2.0
github.com/acme/billing v1.1.0 => v1.2.0
  github.com/acme/billing v1.1.0 => v1.2.0:
    added fn github.com/acme/billing.region
```

A dependency's own vouch lines are ignored: it cannot vouch for itself.
A C binding can carry effects in the vouch (`vouch <module> <version> c
"zlib.h" uses none`); an unvouched C binding is charged `ffi`, whatever
its own `uses` says. There is no automatic fix for `XO1316`: a vouch is a
human review, and an upgrade needs a new one. `xo check` and the build
report it too, for example after `xo mod tidy`, which records versions
without checking vouches.

## Narrowed capabilities

You can hand a library less than the whole capability. A narrowed
capability has the same type, and anything outside it fails with
`Denied` before any effect:

```xo
fn main(os: Os) {
  let api = os.net.only(["api.example.com"])
  match api.dial("metrics.example.net:443") {
    Ok(_) => os.stdio.println("connected")
    Err(e) => os.stdio.println("refused: ${e}")
  }
}
```

```
$ xo run ./main
refused: Denied(addr: "metrics.example.net:443")
```

The narrowing methods are `Net.only(hosts)`, `Env.only(names)`,
`Proc.only(commands)`, and for files `Fs.sub(dir)`, `read_only()`, and
`write_only()`. `Net`, `Env`, and `Proc` narrowing is checked at run time
on the Go backend; the native backends are planned.

## xo audit

`xo audit` prints one report of every dependency: its effects and
lock state, its escape hatches and the vouches that cover them, the
capabilities you narrowed, and which capabilities you pass into
dependencies. On a terminal it prints text; with `--json`, or when its
output is piped, it prints JSON (abridged):

```
$ xo audit --json .
{
  "module": "example.com/shop",
  "dependencies": [
    {
      "path": "github.com/acme/billing",
      "version": "v1.2.0",
      "effects": ["go", "net.dial"],
      "locked_effects": ["go", "net.dial"],
      "lock": "ok",
      "escape_hatches": [
        { "kind": "go", "target": "os", "line": 1, "vouched": true, "effects": ["go"] }
      ]
    }
  ],
  "attenuations": [
    { "file": "main/main.xo", "line": 4, "capability": "Net", "method": "only",
      "args": ["metrics.example.net"], "constant": true }
  ],
  "capabilities_passed": [
    { "file": "main/main.xo", "line": 5, "callee": "sync",
      "dependency": "github.com/acme/billing@v1.2.0", "capability": "Net",
      "attenuated": true, "via": "Net.only(metrics.example.net)" }
  ],
  "summary": { "dependencies": 1, "unvouched_escape_hatches": 0, "widened": 0,
               "unlocked": 0, "unattenuated_capabilities_passed": 0, "errors": 0 }
}
```

It exits 1 when the check has errors, so it can gate CI.

## Fetching and installing

No code runs when a module is fetched or built: Xo has no install hooks,
no build scripts, and no code that runs on import (a top-level statement
is a compile error). `xo.lock` pins the content hash of every module
version, and with `XO_SUMDB` set, the first fetch of each version is
checked against a signed checksum log.

## What this does not cover

- **Opt-in hardening.** Without a ceiling or a narrowed capability, a new
  dependency you hand `os.net` can use it. It shows in its signatures,
  in `xo.lock`, and in `xo audit`, but it is not blocked.
- **Your vouch is trusted.** Xo cannot see inside C or Go code. A vouch
  that says `uses none` for C that does I/O is not caught.
- **No effect, no check.** A function that returns a wrong answer, or
  loops forever, performs no effect. Tests, contracts, and timeouts are
  the defense there.
- **Not a sandbox.** This is checking at compile time plus capabilities
  at run time, inside one process.

Measured against 15 attack scenarios from the Backstabber's Knife
Collection taxonomy, these defenses block 10; the [post on supply-chain
safety](/blog/supply-chain-safety/) has the table. The full rules are in the
specification: [xo.mod](/docs/spec/core/#42-xomod),
[xo.lock](/docs/spec/core/#43-xolock), and the diagnostic pages for
[XO1309](/docs/diagnostics/xo1309/), [XO1316](/docs/diagnostics/xo1316/),
and [XO1317](/docs/diagnostics/xo1317/).

