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"
}
| Field | Meaning |
|---|
content_hash | The sha256 hex of the bundle’s canonical bytes — the bundle’s content address. |
policy_ref | The mutable-pointer reference (name@version) you set on a sandbox or account floor. |
sha256_ref | The 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):
| Form | Scope | Use it for |
|---|
name@version (e.g. acme-baseline@v3) | Account-scoped, with a platform fallback for built-in packs | Production. Human-readable, versioned, isolated to your account. |
sha256:<hex> | Content-addressed, global | Immutable pins / reproducibility — the bytes can never change, regardless of the registry. |
blob:<key> | Global, raw object under the policies/ prefix only | Escape 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:
- Look up
(your account, name, version) in the registry.
- 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).
- 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
| Operation | Request |
|---|
| List all bundles published under an account | GET /admin/accounts/<account_id>/policy-bundles |
| Fetch a single bundle’s metadata | GET /admin/accounts/<account_id>/policy-bundles/<name>/<version> |
Delete a bundle’s name@version pointer | DELETE /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
}'
| Field | Meaning |
|---|
enabled | Master switch. When false (or the row is absent), the account uses only the platform defaults. |
policy_ref | A published bundle (name@version / sha256: / blob:) to merge in as the floor. |
inline_rego | Rego merged directly, without publishing a bundle first. Compile-checked at this call. |
default_deny | When 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.