Skip to main content
A policy bundle is a set of OPA Rego modules you publish once to your account and then reference from any sandbox by a short policy_ref. Bundles are versioned and immutable: a published name@version always resolves to the exact same Rego, so a sandbox config can pin a policy that can never silently change underneath it. This page covers the admin side — publishing bundles, referencing them, managing the registry, and setting a non-bypassable account-wide floor. For writing the Rego itself, see Authoring custom policy. For curated, framework-aligned bundles you can enable without writing any Rego, see Governance Packs.
Custom policy bundles build on declaw’s non-bypassable platform floor (which already blocks living-off-the-land commands, kernel-module loading, and cloud-metadata/IMDS access). Every bundle is compile-checked against that floor at publish time. A bundle can only add denials — it can never relax the platform defaults.

Publish a bundle

POST /admin/accounts/<account_id>/policy-bundles with a name, a version, and the Rego modules that make up the bundle:
curl -X POST https://api.declaw.ai/admin/accounts/<account_id>/policy-bundles \
  -H "X-Admin-Secret: $ADMIN_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-baseline",
    "version": "v3",
    "modules": [
      "package declaw.platform.cmd\n\ndeny[msg] {\n  input.command == \"curl\"\n  msg := \"curl is not allowed\"\n}",
      "package declaw.platform.net\n\ndeny[msg] {\n  input.host == \"169.254.169.254\"\n  msg := \"metadata egress blocked\"\n}"
    ]
  }'
The response confirms the stored content and gives you both ways to reference it:
{
  "account_id": "<account_id>",
  "name": "acme-baseline",
  "version": "v3",
  "content_hash": "3b1f…e9a2",
  "policy_ref": "acme-baseline@v3",
  "sha256_ref": "sha256:3b1f…e9a2"
}
FieldMeaning
content_hashThe sha256 hex of the bundle’s canonical bytes — the bundle’s content address.
policy_refThe mutable-pointer reference (name@version) you set on a sandbox or account floor.
sha256_refThe content-addressed reference (sha256:<hex>) — an immutable pin to these exact bytes.

Validation rules

  • Compile-checked. The modules are compiled together with the platform floor at publish time, so a bundle that does not compile is rejected with 400 before it is stored — never at sandbox-create time.
  • Size limit. A bundle’s canonical bytes may not exceed 1 MiB. Larger bundles are rejected at publish (they would otherwise be unresolvable on the read path).
  • Versions are immutable. A name@version is write-once. Re-publishing the same content is idempotent (returns the same content_hash), but re-publishing a name@version with different content returns 409 Conflict — the existing sha256_ref is included in the error so you can see what is already pinned there. To ship a change, publish a new version (acme-baseline@v4).
Because versions are immutable, treat @vN like a Git tag, not a branch. Bump the version for every change; never try to “update” a published version in place.

Reference a bundle from a sandbox

A sandbox references a bundle through custom_policy.policy_ref. There are three reference forms, all resolving against declaw-controlled storage only (there is no arbitrary URL fetch):
FormScopeUse it for
name@version (e.g. acme-baseline@v3)Account-scoped, with a platform fallback for built-in packsProduction. Human-readable, versioned, isolated to your account.
sha256:<hex>Content-addressed, globalImmutable pins / reproducibility — the bytes can never change, regardless of the registry.
blob:<key>Global, raw object under the policies/ prefix onlyEscape hatch for bundles placed in the policy bucket out-of-band.
from declaw import Sandbox, SecurityPolicy, CustomPolicyConfig

sandbox = Sandbox.create(
    security=SecurityPolicy(
        custom_policy=CustomPolicyConfig(
            enabled=True,
            policy_ref="acme-baseline@v3",
        ),
    ),
)
import { Sandbox } from "declaw";

const sandbox = await Sandbox.create({
  security: {
    customPolicy: { enabled: true, policyRef: "acme-baseline@v3" },
  },
});
sb, err := declaw.CreateSandbox(declaw.SandboxConfig{
    Security: &declaw.SecurityPolicy{
        CustomPolicy: &declaw.CustomPolicyConfig{
            Enabled:   true,
            PolicyRef: "acme-baseline@v3",
        },
    },
})

Resolution precedence

A name@version ref is resolved at sandbox-create time as follows:
  1. Look up (your account, name, version) in the registry.
  2. If not found, fall back to the shared platform account (this is how built-in governance packs resolve, and how a platform team can publish org-wide baselines once).
  3. If still not found, the create fails with 400 (a genuinely missing ref is a client error, not a server error).
The resolved content_hash is then fetched from the content-addressed store and the Rego is appended to the sandbox’s policy. sha256: and blob: refs skip the registry entirely and read the object directly.
Versioned name vs. sha256 — which to use. Use name@version for production: it is account-scoped, readable, and the natural unit you bump and roll out. Use sha256:<hex> when you need an absolute immutable pin — the content address can never resolve to anything but those exact bytes, even if a registry pointer is later deleted or repointed. (The sha256: and blob: forms are content-addressed-global by design: they read the dedicated, non-tenant-writable policy bucket and ignore the account, so confidentiality of policy text under these forms rests on hash opacity, not access control — hashes are not secrets. For per-account isolation, use name@version.)

Manage published bundles

OperationRequest
List all bundles published under an accountGET /admin/accounts/<account_id>/policy-bundles
Fetch a single bundle’s metadataGET /admin/accounts/<account_id>/policy-bundles/<name>/<version>
Delete a bundle’s name@version pointerDELETE /admin/accounts/<account_id>/policy-bundles/<name>/<version>
curl https://api.declaw.ai/admin/accounts/<account_id>/policy-bundles \
  -H "X-Admin-Secret: $ADMIN_SECRET"
List and fetch return the same fields as publish — name, version, content_hash, policy_ref, sha256_ref, and created_at. A missing bundle returns 404.
Delete removes the pointer, not the content. Deleting a bundle removes only its name@version registry entry. The content-addressed object survives (it may be shared across versions and is verified by hash on every read), so any sha256:<hex> ref to the same bytes keeps resolving. To make a policy permanently unreferenceable you must stop referencing its content hash, not just delete the named pointer.

Set an account-wide policy floor

To enforce a policy on every sandbox an account creates — with no per-sandbox change and no way for a sandbox to opt out — set the account floor:
curl -X POST https://api.declaw.ai/admin/accounts/<account_id>/policy \
  -H "X-Admin-Secret: $ADMIN_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "policy_ref": "acme-baseline@v3",
    "inline_rego": "package declaw.platform.cmd\n\ndeny[msg] {\n  input.command == \"nc\"\n  msg := \"netcat is not allowed\"\n}",
    "default_deny": true
  }'
FieldMeaning
enabledMaster switch. When false (or the row is absent), the account uses only the platform defaults.
policy_refA published bundle (name@version / sha256: / blob:) to merge in as the floor.
inline_regoRego merged directly, without publishing a bundle first. Compile-checked at this call.
default_denyWhen true, the floor’s posture is deny-by-default for the gates it covers.
You can set policy_ref, inline_rego, or both — an enabled floor needs at least one. Inline Rego is compile-validated synchronously (a broken module returns 400 here); a policy_ref is validated lazily at sandbox-create time, since the referenced bundle may be published independently.
The floor is non-bypassable. The account floor is merged into every sandbox’s policy as an additional module at create time. A sandbox’s own custom_policy can add rules on top, but it cannot remove or override the account floor — bundle denials are additive and tighten-only.
The floor is resolved and frozen at sandbox-create time. Enabling or tightening the account policy does not retroactively apply to already-running sandboxes, and resume / snapshot-restore reuse the create-time policy rather than re-merging the current floor. Roll out a tightened floor with the expectation that it takes effect on newly created sandboxes.
  • Authoring custom policy — writing the Rego gates (cmd, net, content, lifecycle) that go into a bundle.
  • Governance Packs — curated, framework-aligned bundles (OWASP, NIST, EU AI Act, …) you can enable without writing Rego.
  • Compliance reporting — per-account denial evidence grouped by control, framework, and gate.