Skip to content
You are reading v0.1, which is not the current release. Go to v0.4.

std::permissions

What the manifest allows, as data the program carries.

[permissions] in khora.toml says which paths a package may read and write. The answer itself lives in std::permissions::grants, which khora build writes; this module is the part that decides what an answer means, and std::fs’s real handlers consult it before touching a file.

The grants are compiled in, not read at run time. A file the program consults for its own permissions is a file an attacker edits; a list the compiler wrote into the binary is one nothing at run time can widen.

Why this is a file you can read rather than a check inside the compiler

Section titled “Why this is a file you can read rather than a check inside the compiler”

The compiler contributes data. Everything that decides whether a path is allowed is here, in Khora, and can be read, tested and stepped through – which is the whole reason the enforcement is not a pass in khora-types that nobody outside the compiler can see. The design document is explicit about it: “ordinary Khora in a module you can read, not a second enforcement engine hidden in the compiler”.

Everything. A missing [permissions] table grants everything, which is opt-in tightening rather than a tax on starting – the same bargain Rust makes with unsafe. The default below is the one a program with no manifest, or no [permissions.fs], compiles with, and khora build narrows it only when the manifest asks.

Functions: granted · granted_name · granted_host · normalized

pub fn granted(grants: List<String>, path: String) -> Bool

Whether any grant covers path.

The glob dialect is .gitignore’s, because it is the one everybody already has in their fingers:

  • * matches within one path segment and stops at a separator.
  • ** matches across separators, so data/** covers data/a/b.txt – though not data itself, which is .gitignore’s reading too.
  • Everything else matches itself.

Separators are normalized first, so a grant written with / covers a Windows path spelled with \\. granted_path in khora-manifest answers the same question for the compiler and the two have to agree; the tests name the cases where agreement is not obvious.

pub fn granted_name(grants: List<String>, name: String) -> Bool

Whether any grant covers name.

For things that are not paths – an environment variable, a command – where there are no segments and so * spans everything. DATABASE_* covers DATABASE_URL, and * covers all of them.

granted_name in khora-manifest answers the same question for the compiler, and the two are tested against one table.

pub fn granted_host(grants: List<String>, host: String) -> Bool

Whether any grant covers host, which is name or name:port.

Two rules, and both are the reading that costs a newcomer least:

  • * spans dots, so *.internal covers db.eu.internal and not only db.internal. That is what a Content-Security-Policy origin means by it; the one-label reading belongs to TLS certificates, and surprising somebody into a denied connection is the worse failure.
  • A grant with no port covers every port, which is what Deno’s --allow-net=example.com does. :* says the same thing explicitly.
pub fn normalized(path: String) -> String

One spelling of a path, so a grant and a request can be compared.

Two things are levelled, and both were reported as traps by somebody who hit them:

  • \\ becomes /, so a grant written with forward slashes covers a path Windows spelled with back ones.
  • A . segment is dropped, so ./data/** grants data/foo.txt and data/** grants ./data/foo.txt. Before this, the ./ was significant on both sides: two readers of capabilities.md wrote the example’s read = ["./data/**"] into a manifest, opened data/foo.txt, and were refused by a grant that looks like it says yes.

.. is deliberately not resolved. Dropping a/../b to b is only correct if a exists and is a directory rather than a link, which is a question about the filesystem — and a normalizer that guessed would widen a grant, which is the one direction a permission check must never be wrong in. A path containing .. is compared as written and will usually be refused, which is the safe answer.

Nor is anything resolved against a working directory or a package root. A grant is matched against the path the program passes, so a program that builds its paths from its own arguments needs a grant wide enough to cover what its users will type. The permissions design note records why that is the boundary and what it costs a CLI.

Khora 0.1.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource