Skip to main content
Governance packs give you curated, framework-aligned policy with one 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 non-bypassable 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.

The six gates

Declaw evaluates Rego at six enforcement gates. Each gate is its own Rego package — write your rules in the package that matches the action you want to govern:
GatePackageGovernsWhen it runs
cmddeclaw.platform.cmdEvery command the agent (or a sub-agent) runsBefore the command executes
networkdeclaw.platform.networkEvery outbound connectionPer egress connection, at the proxy
contentdeclaw.platform.contentThe request body on LLM egress (model, scan findings)On intercepted LLM traffic (opt-in)
lifecycledeclaw.platform.lifecycleSandbox provisioning (tier, template, requested features)At sandbox create
ptydeclaw.platform.ptyInteractive PTY sessionsAt PTY creation
volumedeclaw.platform.volumeVolume mountsOnce per attached volume, at create
The gates also enforce differently, which matters when you test:
  • cmd deny → the command is rejected before it runs (surfaces as an HTTP 403 / raised exception, not a non-zero exit code).
  • network deny → the command runs, but the outbound connection is dropped at the proxy (e.g. curl exits non-zero with a reset/timeout — there is no 403).
Because the agent runs inside the declaw microVM, these gates are not advisory — they are the only path the agent’s actions can take.

The deny-only model

You only ever write deny rules:
package declaw.platform.cmd

deny contains msg if {
    input.action.command == "rm"
    msg := "rm is not permitted by org policy"
}
Do not redeclare default allow. Each platform package already derives allow in Go purely from “zero denials” — an allow rule you write is ignored, and redeclaring default allow collides with the platform module your rules are compiled alongside. Express everything as deny contains msg if { ... }. An allowlist is just “deny anything not in the approved set” (see the allowlist pattern).
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.

CustomPolicyConfig

FieldTypeDescription
enabledboolActivates custom-policy evaluation for the sandbox.
inline_regostrA single Rego module string (one package). Simplest option when you only need one gate.
inline_moduleslist[str]A list of independent Rego module strings — one per package. Use this to govern multiple gates at once (you cannot put two packages in one inline_rego string).
policy_refstrReference a built-in pack or a published bundle. One of name@version (e.g. owasp-llm-top10@v1), sha256:<hex>, or blob:<key>.
default_denyboolFail behavior when the evaluator itself errors. true = an evaluator error denies the action (fail-closed — recommended for hard security gates). false = the action is allowed on evaluator error (fail-open — for advisory rules).
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

from declaw import Sandbox
from declaw.security import SecurityPolicy
from declaw.security.custom_policy import CustomPolicyConfig

# (a) Command denylist — block ad-hoc network tools at the cmd gate.
#     Only the listed binaries are blocked; everything else runs normally.
CMD_DENYLIST = """\
package declaw.platform.cmd

blocked := {"curl", "wget", "nc", "ncat"}

deny contains m if {
    input.action.command in blocked
    m := sprintf("command '%s' blocked by custom policy (no ad-hoc network tools)",
                 [input.action.command])
}
"""

sbx = Sandbox.create(
    template="python",
    security=SecurityPolicy(
        custom_policy=CustomPolicyConfig(
            enabled=True,
            default_deny=True,        # fail-closed: deny if the evaluator errors
            inline_rego=CMD_DENYLIST,
        ),
    ),
)

# (b) Multi-package policy via inline_modules — cmd + network in one shot.
CMD_MODULE = (
    "package declaw.platform.cmd\n\n"
    'deny contains m if { input.action.command == "rm"; m := "no rm allowed by org policy" }'
)
NET_MODULE = (
    "package declaw.platform.network\n\n"
    'deny contains m if { endswith(input.action.destination, ".ru"); m := "no .ru egress" }'
)

sbx = Sandbox.create(
    security=SecurityPolicy(
        custom_policy=CustomPolicyConfig(
            enabled=True,
            default_deny=True,
            inline_modules=[CMD_MODULE, NET_MODULE],
        ),
    ),
)

# (c) Reference a built-in governance pack.
sbx = Sandbox.create(
    security=SecurityPolicy(
        custom_policy=CustomPolicyConfig(
            enabled=True,
            policy_ref="owasp-llm-top10@v1",
        ),
    ),
)
import { Sandbox, createSecurityPolicy } from "@declaw/sdk";

// (a) Command denylist — block ad-hoc network tools at the cmd gate.
const CMD_DENYLIST =
  "package declaw.platform.cmd\n\n" +
  'blocked := {"curl", "wget", "nc", "ncat"}\n\n' +
  "deny contains m if {\n" +
  "    input.action.command in blocked\n" +
  "    m := sprintf(\"command '%s' blocked by custom policy (no ad-hoc network tools)\",\n" +
  "                 [input.action.command])\n" +
  "}";

let sbx = await Sandbox.create({
  template: "python",
  security: createSecurityPolicy({
    customPolicy: {
      enabled: true,
      defaultDeny: true,        // fail-closed: deny if the evaluator errors
      inlineRego: CMD_DENYLIST,
    },
  }),
});

// (b) Multi-package policy via inlineModules — cmd + network in one shot.
const CMD_MODULE =
  "package declaw.platform.cmd\n\n" +
  'deny contains m if { input.action.command == "rm"; m := "no rm allowed by org policy" }';
const NET_MODULE =
  "package declaw.platform.network\n\n" +
  'deny contains m if { endswith(input.action.destination, ".ru"); m := "no .ru egress" }';

sbx = await Sandbox.create({
  security: createSecurityPolicy({
    customPolicy: {
      enabled: true,
      defaultDeny: true,
      inlineModules: [CMD_MODULE, NET_MODULE],
    },
  }),
});

// (c) Reference a built-in governance pack.
sbx = await Sandbox.create({
  security: createSecurityPolicy({
    customPolicy: { enabled: true, policyRef: "owasp-llm-top10@v1" },
  }),
});
import (
    "context"

    declaw "github.com/declaw-ai/declaw-go"
)

// (a) Command denylist — block ad-hoc network tools at the cmd gate.
const cmdDenylist = `
package declaw.platform.cmd

blocked := {"curl", "wget", "nc", "ncat"}

deny contains m if {
    input.action.command in blocked
    m := sprintf("command '%s' blocked by custom policy (no ad-hoc network tools)",
                 [input.action.command])
}
`

sbx, err := declaw.Create(ctx,
    declaw.WithTemplate("python"),
    declaw.WithSecurity(declaw.SecurityPolicy{
        CustomPolicy: &declaw.CustomPolicyConfig{
            Enabled:     true,
            DefaultDeny: true, // fail-closed: deny if the evaluator errors
            InlineRego:  cmdDenylist,
        },
    }),
)

// (b) Multi-package policy via InlineModules — cmd + network in one shot.
const cmdModule = `
package declaw.platform.cmd
deny contains m if { input.action.command == "rm"; m := "no rm allowed by org policy" }
`
const netModule = `
package declaw.platform.network
deny contains m if { endswith(input.action.destination, ".ru"); m := "no .ru egress" }
`

sbx, err = declaw.Create(ctx,
    declaw.WithSecurity(declaw.SecurityPolicy{
        CustomPolicy: &declaw.CustomPolicyConfig{
            Enabled:       true,
            DefaultDeny:   true,
            InlineModules: []string{cmdModule, netModule},
        },
    }),
)

// (c) Reference a built-in governance pack.
sbx, err = declaw.Create(ctx,
    declaw.WithSecurity(declaw.SecurityPolicy{
        CustomPolicy: &declaw.CustomPolicyConfig{
            Enabled:   true,
            PolicyRef: "owasp-llm-top10@v1",
        },
    }),
)

Allowlists with a deny-only engine

Since you can only write deny 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:
package declaw.platform.cmd

allowed := {"node", "npm", "ls", "cat", "echo"}

deny contains m if {
    not input.action.command in allowed
    m := sprintf("command '%s' not in allowlist", [input.action.command])
}
The same pattern at the network gate — an egress allowlist using glob.match for wildcard domains:
package declaw.platform.network

allowed_exact := {"registry.npmjs.org"}

deny contains m if {
    not glob.match("*.github.com", [], input.action.destination)
    not input.action.destination in allowed_exact
    m := sprintf("egress to '%s' is not in the allowed destination set",
                 [input.action.destination])
}

Input schema per gate

Each gate evaluates your rules against an input document. The key fields:
Gate (package)Key input fields
cmdinput.action.command (the binary, e.g. "curl" — not the full shell line), input.action.args (argument list)
networkinput.action.destination (target host or IP; may include a :port suffix — strip it with split(..., ":")[0] if matching the host)
contentinput.attributes.model (LLM model from the JSON body), input.attributes.destination (request hostname), input.attributes.scan_results.injection.is_injection, input.attributes.scan_results.toxicity.is_toxic, input.attributes.scan_results.pii, input.attributes.scan_results.code_security, input.attributes.scan_results.invisible_text
lifecycleinput.attributes.policy (the requested SecurityPolicy, e.g. input.attributes.policy.toxicity.enabled)
ptyinput.context.tier (and the shared context below)
volumeinput.action.mode ("copy" | "mount" | "mount-ro"), input.action.mount_path (absolute in-sandbox path)
Every gate also receives a shared context block: input.context.tier, input.context.account_id, input.context.sandbox_id, and input.context.template. Use it to scope a rule, e.g. forbid interactive shells on a locked-down tier:
package declaw.platform.pty

deny contains msg if {
    input.context.tier == "free"
    msg := "interactive PTY not available on the free tier"
}

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:
from declaw import Sandbox
from declaw.security import SecurityPolicy
from declaw.security.custom_policy import CustomPolicyConfig
from declaw.security.content_gate import ContentGateConfig

CONTENT_MODULE = """\
package declaw.platform.content

# Rule A — block if the ML scanner flagged a prompt-injection attempt.
deny contains m if {
    input.attributes.scan_results.injection.is_injection == true
    m := "request blocked: prompt injection detected by ML scanner"
}

# Rule B — model allowlist.
approved_models := {"gpt-4o", "gpt-4o-mini", "claude-3-5-sonnet-20241022"}

deny contains m if {
    input.attributes.model != ""
    not input.attributes.model in approved_models
    m := sprintf("model '%s' is not in the org-approved model list",
                 [input.attributes.model])
}
"""

sbx = Sandbox.create(
    security=SecurityPolicy(
        # Opt this sandbox into the content gate for these hosts so the
        # model-allowlist rule runs even with no ML scanner enabled.
        content_gate=ContentGateConfig(
            enabled=True,
            domains=["api.openai.com", "api.anthropic.com"],
        ),
        custom_policy=CustomPolicyConfig(
            enabled=True,
            default_deny=True,
            inline_modules=[CONTENT_MODULE],
        ),
    ),
)
The model is read from the request JSON body and exposed as 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 redeclared default 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.
A quick way to validate the gate behavior end-to-end is to run a command that should be blocked and confirm the failure mode matches the gate: a 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:, or blob: instead of inlining the Rego on every sandbox.
  • Network policies — the domain-allowlist / IP-CIDR layer that runs alongside the network gate.