Policy-as-Code

Steel evaluates authorization policies on every function call. Policies are written in Rego, loaded at runtime, and can be changed without redeploying your application.

What is Policy-as-Code?

Instead of hardcoding authorization logic in your application, Steel externalizes it into Rego policies. This means:

How Policies Work

When a plugin function is called, Steel constructs an input record from the call's metadata and arguments, then evaluates a Rego rule against it.

// Steel constructs this input for every call
{
    "object_id": 42,
    "facet_id": 1234567890,
    "method_id": 9876543210,
    "method_name": "execute",
    "positional": ["ls", "-la", "/tmp"],
    "named": { "verbose": true }
}

Your Rego policy can inspect any of these fields and decide whether to allow or deny the call.

Writing Policies

Allow a specific command

package data.steel.auth.allow

default allow = false

allow {
    input.method_name == "execute"
    input.positional[0] == "ls"
}

Restrict by role

package data.steel.auth.allow

default allow = false

allow {
    input.method_name == "execute"
    input.named.role == "admin"
}

Limit argument count

package data.steel.auth.allow

default allow = false

allow {
    input.method_name == "execute"
    input.positional[0] == "ls"
    count(input.positional) <= 3
}

Allow by stable method ID

package data.steel.auth.allow

default allow = false

allow {
    input.method_id == 1234567890
}

Default-Deny

Steel uses a default-deny model. If no policy rule exists for a call, it is denied. You must explicitly allow every capability you want to expose. There is no implicit permissiveness.

This is the opposite of most systems, where everything is allowed unless explicitly blocked. With Steel, you start from zero and grant only what you need.

Per-Plugin Policy Namespaces

Each plugin has its own policy namespace derived from its WIT world name. This means different plugins can have different authorization rules without conflicts.

// Policy for "steel:execute" world
package data.steel.execute.allow

// Policy for "steel:sanitize" world
package data.steel.sanitize.allow

Dynamic Policy Loading

Policies are loaded into the policy engine at runtime. This means:

Full Type Coverage

Steel includes a complete bidirectional conversion between its value types and the Rego engine. Every Wasm Component Model type — records, variants, lists, maps, tuples, flags, enums, options, results, and fixed-length lists — passes through Rego policies without loss.

This means your policies can inspect complex structured arguments, not just simple strings and numbers.

Rego Engine

Steel uses regorus, a pure-Rust implementation of the Rego policy language. No external dependencies, no network calls, no CGO. The policy engine runs entirely in-process with predictable performance.