policy_ref. When you need rules specific to your threat model — a command denylist, an egress allowlist, a model allowlist — you can author them yourself in OPA Rego and attach them to a sandbox with CustomPolicyConfig.
Your rules are additive to declaw’s platform floor (which already blocks living-off-the-land commands, kernel-module loading, device/storage operations, and cloud-metadata/IMDS access). You write deny rules that tighten policy; you can never relax a platform default.
Parts of that floor are enforced below the policy layer and hold for anything running in the sandbox; parts are enforced at the API surface. Where each gate runs sets out which is which — worth reading before you rely on a rule.
The seven gates
Declaw evaluates Rego at seven enforcement gates. Each gate is its own Rego package — write your rules in the package that matches the action you want to govern:pty and stdio are two doors to the same capability — an interactive shell.
Denying one does not deny the other, so a policy meant to forbid interactive
sessions has to deny both packages.
The gates also enforce differently, which matters when you test:
cmddeny → the command is rejected before it runs (surfaces as an HTTP 403 / raised exception, not a non-zero exit code).networkdeny → the command runs, but the outbound connection is dropped at the proxy (e.g.curlexits non-zero with a reset/timeout — there is no 403).
The deny-only model
You only ever writedeny rules:
deny is a partial set, and partial-set rules are additive across modules — so your denials compose with the platform defaults (and with each other) without ever being able to remove a platform denial.
Where each gate runs
The gates are not all enforced in the same place, and the difference decides what each one can see.network is the clearest case of the first kind: egress is intercepted in the
host’s network namespace, so an egress rule holds for every process in the
sandbox no matter how it started.
cmd, pty and stdio are evaluated by envd, on its API surface. An agent
running inside the sandbox that spawns a child directly — subprocess.run(),
os.system(), a shell pipeline — never crosses that surface, so those gates do
not see it. Commands your code sends through the SDK are gated; commands a
running process invents for itself are not.
This is worth internalising if you are running an autonomous agent: a cmd
denylist constrains what your application asks declaw to run, not everything the
agent’s own process may do once started.
What still applies to those in-sandbox processes:
- Egress and content rules, because they are enforced outside the guest.
-
The syscall floor, where enabled —
init_module,mount,pivot_root,ptrace,bpf,kexec,memfd_createand the kernel keyring are refused by the kernel itself and inherited by every child process. It is a fixed platform control, not expressible in Rego, and it is enabled on Declaw Cloud; self-hosted operators turn it on per deployment. Note this overlaps the command floor deliberately, at two different levels. Thecmdgate refuses the commands (insmod,modprobe,mount,mknod) and applies to every sandbox, but only on the API surface. The syscall floor refuses the syscalls those commands would make, no matter how they are invoked, and is inherited by children. Intent and effect, guarded separately. -
The microVM, which is the actual isolation boundary. A process that already
has execution inside the sandbox is contained by the VM — not by the
cmdgate.
stdio_stdin) even where policy does not
evaluate it, so in-sandbox activity remains observable when it is not blockable.
CustomPolicyConfig
inline_rego, inline_modules, and policy_ref can be combined — all sources are concatenated with the platform defaults before evaluation. Use inline_rego for the single-package case and inline_modules when your policy spans gates.Worked examples
Allowlists with a deny-only engine
Since you can only writedeny rules, an allowlist is expressed as “deny anything not in the approved set”. Combine it with default_deny=True so an evaluator fault also blocks:
glob.match for wildcard domains:
Input schema per gate
Each gate evaluates your rules against aninput document. The key fields:
What input.context contains, per gate
Gates also receive a context block — but how much of it is populated depends
on where the gate runs, so a rule keyed on the wrong field silently never fires.
input.context.tier is therefore usable only in the lifecycle and volume
gates. A cmd, pty or stdio rule that tests input.context.tier == "free"
compares "" against "free", never matches, and denies nothing — it looks like
a working policy and is not.
Content gate
The content gate (declaw.platform.content) sees the decrypted LLM request body — the model name and the ML scanner’s findings — so you can enforce a model allowlist or write cross-signal rules that combine multiple scan results.
This gate runs inside the body-scanning path, so by default it only fires when a scanner is already intercepting that domain. To run a model-allowlist rule without enabling an ML scanner, opt the sandbox in with ContentGateConfig and list the LLM API hosts to intercept:
input.attributes.model. Rule A and Rule B are independent — either firing blocks the request before it reaches the upstream LLM.
Content-gate caveats. The gate is only evaluated when the connection is intercepted — either by an active ML scanner on the domain or by an explicit
ContentGateConfig entry for that host (per-destination network rules are unaffected; that gate always runs). Also, scan_results.pii.count reflects whole-body (non-JSON) text; for JSON LLM bodies, value-level PII is scanned and redacted separately, so don’t gate on pii.count for JSON traffic.Testing and fail-closed behavior
Your inline Rego is compile-checked at sandbox create. If a module fails to parse or compile (a syntax error, a redeclareddefault allow, an unknown built-in), the create request fails with an error describing the problem — invalid policy never reaches a running sandbox.
At runtime, default_deny controls what happens if the evaluator itself errors (engine unreachable, evaluation timeout):
default_deny=True(fail-closed) — an evaluator error denies the action. Recommended for any hard security gate (denylists, allowlists, model gating).default_deny=False(fail-open) — the action is allowed on evaluator error. Acceptable only for advisory rules where availability outweighs the missed check.
cmd-gate denial surfaces as a 403 / raised exception, while a network-gate denial lets the command run but drops the connection (use a resolvable host so the test exercises the gate and not a DNS failure).
Next steps
- Governance packs — curated, framework-aligned policy (OWASP, NIST, EU AI Act, and more) you enable with a single
policy_ref, with per-denial compliance evidence. - Policy bundles — publish your own modules as a versioned, reusable bundle and reference it by
name@version,sha256:, orblob:instead of inlining the Rego on every sandbox. - Network policies — the domain-allowlist / IP-CIDR layer that runs alongside the
networkgate.