The Build Should Know What Your Code Trusts

A maintainer pushes a commit to a package you have never heard of, a transitive dependency of a dependency of a thing you actually use. Their account got phished, or they sold the package to someone who waited six months. Either way that one commit now runs on millions of machines, and most of them keep running it for weeks before anyone notices. We usually find out from a blog post.

Nothing in the build ever knew it could happen. The compiler watched a logging library reach out and touch the filesystem and said nothing, because as far as it was concerned, touching the filesystem is just what code does. No model of authority, no record of what the program was allowed to reach. The build produced an artifact and reported its size, its symbols, its timing, and stayed silent on the one question that matters after a compromise: what was this code ever permitted to do?

Today’s defenses are reasonable, and that is the problem

The standard defenses are good engineering responses to a hard problem. Package signing verifies that the bytes you downloaded are the bytes the maintainer published. Lockfiles pin exact versions and hashes, so your build is reproducible and a silent swap gets caught. Reputation and download counts give a rough prior on whether serious people maintain a package. Audits, advisories, and scanners catch known-bad versions after they are discovered. Together, these raise the cost of an attack and shrink the window in which it works.

They share one assumption: that a trusted maintainer stays trustworthy. Signing proves the bytes came from the maintainer; it cannot tell you the maintainer’s intentions changed. Reputation is earned over years and spent in a single commit. The entire model is transitive trust, and transitive trust has a brutal property: compromising one popular maintainer compromises everyone downstream at once, with valid signatures the whole way. The defenses are fragile in the same place, because they all answer “did this come from who I think?” and none answer “what is this allowed to do once it runs?”

That second question is the one I want the build to answer with certainty.

Authority should live in the import graph

In Omega, the language Cathedral is built in, the externally visible things a piece of code can do are tracked through the type system as effects: reading or writing files, opening sockets, reading the clock, asking for randomness, spawning a process, mapping memory, calling out to a host, talking to a device. Each is a named effect, and the compiler computes them for every function and propagates them through the call graph.

The names are deliberately small and stable: filesystem_io, network_io, clock_read, random_read, process_spawn, device_io, and a handful more. They are language-level vocabulary, so a Linux syscall, a Windows API call, and a test harness all report the same effect when they do the same kind of thing. The compiler represents the whole set as a bitset and does subset checks fast, but the part you care about is simpler: a function with no effects, and no calls to anything with effects, is provably doing no input or output at all. That is a checked fact, not a comment or a convention.

Because effects propagate, the import graph stops being a list of names and becomes a map of authority. If a deep dependency performs network access, every function that can reach it carries network_io, all the way up. There is no level at which the authority quietly vanishes.

// Declared effects are a ceiling. This function promises filesystem
// access and nothing else, and the compiler enforces the promise
// against everything it transitively calls.
machine Grep::search(folder: Folder, pattern: String) -> Vec<Match>
requires
    folder in Folder::Readable
effects
    filesystem_io
{
    // ...
}

That effects filesystem_io line is a ceiling. The function may reach filesystem behavior and nothing else. If a refactor or an updated dependency causes search to suddenly want the network, the declared set no longer covers the reached set, and the build fails. The authority changed, so the graph changed, so the compiler noticed.

A logging library cannot grow filesystem access in secret

You depend on a logging library. It writes to standard error and nothing else, so its effect ceiling is stderr_io. Three releases later, a compromised maintainer adds a few lines that open files and exfiltrate them. In today’s world, those lines compile, ship, and run, and you learn about it later.

In Omega, those lines reach filesystem_io. That effect propagates up out of the library and changes the effect set the package reports at its public surface. The declared ceiling was stderr_io; the reached set is now larger. The package no longer builds against its own contract. The new authority cannot hide three layers down, because there is no “down” where authority disappears. Every layer carries what it touches.

This is what I mean when the language and the package manager become one security system. The package manager reads a fact the compiler proved, instead of running a heuristic over source text and hoping. A package can be required to declare its effects, and the package manager enforces that declaration with real certainty.

Effects are a ceiling, authority flow is the blast radius

Effects answer “what classes of behavior may occur.” That is most of the value, but Omega goes one step finer, because two libraries with the identical filesystem_io effect can have wildly different blast radii.

One only ever writes into a folder you hand it. The other prompts the user for a folder, or opens an absolute path out of thin air, reaching files you never gave it. Same effect, very different power. So Omega also tracks authority flow: power-bearing values like a writable Folder are followed through the program. The build report says whether the code merely accepts authority from its caller, derives a narrower handle from it (opening one file inside a folder you provided), or acquires fresh authority on its own through a host prompt or an ambient surface.

// Accepts a writable folder from the caller. Uses it. Acquires nothing.
machine Thumbnailer::write_cache(cache: Folder, image: Image)
requires
    cache in Folder::Writable
effects
    filesystem_io
{
    Filesystem::write_bytes(cache, "thumb.bin", image.thumbnail_bytes());
}

The report for that function says, in plain terms: accepts a writable folder, uses it, acquires nothing. A build policy can then set a ceiling over the flow itself: this package may use the filesystem only through folders the caller supplied, and may not acquire one by prompting or by reaching an absolute path. A pure parser, a math library, a serializer, a deterministic simulation: none should accept any authority or reach any effect at all, and now you can require exactly that and have it checked.

Make the strict thing the default

Once authority is visible and enforceable, the right default is to forbid almost everything and make overrides loud and deliberate. A package can be required to prove it has no host calls, no filesystem, no network, no clock, no randomness, no input or output at all. A store can refuse to list an application that wants to mint its own filesystem authority instead of going through approved surfaces. A library marked deterministic can be required to prove it has no clock and no entropy, because nondeterminism would show up as an effect and give it away.

None of this asks every programmer to become a security engineer. The compiler infers effects and authority flow on its own; you write ordinary code, and the facts come out the other side. The work is in the language having a vocabulary for authority in the first place, then refusing to let anyone hide it.

The mental model is this: if nobody is allowed to conceal authority, nobody can slip in just a little of it, because the moment they try, the graph changes and the build tells you.