Learn
A dependency cannot quietly grow teeth.
When you install a library that says it does arithmetic, nothing in Python, Go, Rust or Zig stops it from opening your files or reaching the network. Grasm writes that permission down and refuses the build when it is exceeded.
The attack
A trusted, pure library ships a patch release. Same name, same signature,
same return value — and it now reads /etc/passwd and writes it
out. Nothing about the call site changes. This is not hypothetical; it is the
shape of every dependency compromise of the last decade.
Only Grasm’s toolchain refuses. Everywhere else the tampered code builds, ships and runs; detection is left to runtime sandboxing or to a reviewer noticing — neither of which the toolchain does.
How it works
Two halves. The first is the effect row, which is inferred, so it cannot be wrong about the body:
// the row is worked out from the body, then checked against what you wrote
fn load(path: str) -> str ! IoError / {io} {
try read_file(path)
}
The second is the manifest, which is the ceiling for the whole package:
[package]
name = "mathlib"
version = "1.0.1"
capabilities = [] # this package may perform no effect at all
Add the read_file and the package performs io,
which exceeds []. The error names the exact place:
error: package `mathlib` performs `io` but its manifest only permits nothing
help: either add the capability to that package's manifest, or stop it
from performing the effect
performed: `io` at mathlib/src/main.gr:2:9
The maintainer can raise the ceiling — but the manifest is in the
diff, and a package that used to declare [] and now declares
["io"] is one line a consumer can see, refuse, or gate in CI.
That is the whole design: make the change visible where a human is already
looking.
What this does not do
This is a build-time check, not a sandbox. It
constrains what code in a Grasm package can be written to do. It does
not contain a process that is already running, it does not survive
unsafe, and it says nothing about a compromised compiler or
operating system.
unsafeandextern fnreach C, and C is outside this. That is why the tier is called Tier 3, why every call must sit inside anunsafeblock, and why the package must additionally declare theunsafecapability — so a consumer can refuse the whole package by reading one line.exitperforms no effect and can still end the process. So can a panic and an endless loop. A ceiling bounds what a dependency can reach, never how long it runs.
Prior art
This stands on proven ground. Austral pioneered
capability-secure modules, Wyvern did capability-safe modules before that, and
the effect row is Koka’s. What no one had shipped is
the combination, with a gate that proves it:
security_bench.sh runs the real attack against four toolchains on
every green build, and three of them wave it through.