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”.
What it says when nothing is configured
Section titled “What it says when nothing is configured”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.
API at a glance
Section titled “API at a glance”Functions: granted · granted_name · granted_host · normalized
Functions
Section titled “Functions”granted
Section titled “granted”pub fn granted(grants: List<String>, path: String) -> BoolWhether 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, sodata/**coversdata/a/b.txt– though notdataitself, 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.
granted_name
Section titled “granted_name”pub fn granted_name(grants: List<String>, name: String) -> BoolWhether 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.
granted_host
Section titled “granted_host”pub fn granted_host(grants: List<String>, host: String) -> BoolWhether 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*.internalcoversdb.eu.internaland not onlydb.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.comdoes.:*says the same thing explicitly.
normalized
Section titled “normalized”pub fn normalized(path: String) -> StringOne 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/**grantsdata/foo.txtanddata/**grants./data/foo.txt. Before this, the./was significant on both sides: two readers ofcapabilities.mdwrote the example’sread = ["./data/**"]into a manifest, openeddata/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