Policy guardrails and break-glass
Runtime policies keep agents from running dangerous tools. During an outage you sometimes need a short, audited bypass, then the guardrails back on.
This sample walks that journey on an existing agent:
- Create a small Rego deny and attach it (first in shadow, then enforcing)
- Set the conflict rule to
any_deny_blocks(if any policy says no, the tool is blocked) - Dry-run
evaluatePolicyso you see the deny before going live breakGlasswith a reason and duration (1–480 minutes) only. Aiden records who opened the glass from your API token (activated_byon the response)- Confirm with
getBreakGlass, thenendBreakGlassinfinally
When you need this
- Ship a fix that a deny policy is blocking. Open the glass for a bounded window, run the fix, close it.
- Prove a deny before enforcing. Attach with
shadow: true, evaluate, then flip to enforcing. - Several policies on one agent. Call
whenPoliciesDisagreeso the tie-break is explicit. Preferany_deny_blocksfor production.
How to run
export STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>
pip install stackgen-sdk==0.4.0
python examples/automation/policy-break-glass.py --agent my-oncall-agentexport STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>
npm install stackgen-sdk@0.4.0 tsx
npx tsx examples/automation/policy-break-glass.ts --agent my-oncall-agentexport STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>
go run ./examples/go/automation/policy-break-glass.go --agent my-oncall-agentUse an agent that already exists in the workspace. The sample creates and deletes its own policy; it does not register a new agent.
Minimal
from stackgen import StackgenClient, StackgenConfig
client = StackgenClient(
StackgenConfig(
base_url="https://app.stackgen.com",
api_token="stackgen_…",
workspace_id="<workspace-uuid>",
)
)
policy = client.aiden.create_policy({
"name": "deny-emergency-marker",
"type": "logic",
"rego_source": "...", # full source is in the complete script on this page
})
client.aiden.attach_policy("my-oncall-agent", policy_id=policy["id"], shadow=True)
client.aiden.when_policies_disagree("my-oncall-agent", "any_deny_blocks")
try:
# Request body is only reason + duration_minutes.
# activated_by appears on the response, filled from your API token.
opened = client.aiden.break_glass(
"my-oncall-agent",
reason="sev-1: unblock kubectl while we patch the deny",
duration_minutes=30,
)
print(opened["active"], opened.get("activated_by"))
finally:
client.aiden.end_break_glass("my-oncall-agent")
client.aiden.detach_policy("my-oncall-agent", policy["id"])
client.aiden.delete_policy(policy["id"])import { StackgenClient, StackgenConfig } from "stackgen-sdk";
const client = new StackgenClient(
new StackgenConfig({
baseUrl: "https://app.stackgen.com",
apiToken: "stackgen_…",
workspaceId: "<workspace-uuid>",
}),
);
const policy = await client.aiden.createPolicy({
name: "deny-emergency-marker",
type: "logic",
rego_source: "...", // full source is in the complete script on this page
});
await client.aiden.attachPolicy("my-oncall-agent", {
policyId: String(policy.id),
shadow: true,
});
await client.aiden.whenPoliciesDisagree("my-oncall-agent", "any_deny_blocks");
try {
// Request body is only reason + durationMinutes.
// activated_by appears on the response, filled from your API token.
const opened = await client.aiden.breakGlass("my-oncall-agent", {
reason: "sev-1: unblock kubectl while we patch the deny",
durationMinutes: 30,
});
console.log(opened.active, opened.activated_by);
} finally {
await client.aiden.endBreakGlass("my-oncall-agent");
await client.aiden.detachPolicy("my-oncall-agent", String(policy.id));
await client.aiden.deletePolicy(String(policy.id));
}policy, err := client.Aiden.CreatePolicy(ctx, map[string]any{
"name": "deny-emergency-marker",
"type": "logic",
"rego_source": "...", // full source is in the complete script on this page
})
if err != nil { return err }
policyID := fmt.Sprint(policy["id"])
if _, err = client.Aiden.AttachPolicy(ctx, "my-oncall-agent", map[string]any{
"policy_id": policyID,
"shadow": true,
}); err != nil { return err }
if _, err = client.Aiden.WhenPoliciesDisagree(ctx, "my-oncall-agent", "any_deny_blocks"); err != nil {
return err
}
defer client.Aiden.EndBreakGlass(ctx, "my-oncall-agent")
defer client.Aiden.DetachPolicy(ctx, "my-oncall-agent", policyID)
defer client.Aiden.DeletePolicy(ctx, policyID)
// Request body is only reason and duration minutes.
// activated_by appears on the response, filled from your API token.
opened, err := client.Aiden.BreakGlass(ctx, "my-oncall-agent",
"sev-1: unblock kubectl while we patch the deny", 30)
if err != nil { return err }
fmt.Println(opened["active"], opened["activated_by"])
return nilConflict rules (plain language)
| Rule | Meaning |
|---|---|
any_deny_blocks |
If any attached policy says no, the tool is blocked |
any_allow_wins |
If any policy says yes, the tool is allowed (rare) |
first_matching_policy |
Use the first policy that returns a decision |
Notes
durationMinutes/duration_minutesmust be between 1 and 480.- Always call
endBreakGlass/end_break_glassin afinally. The timer still expires if you forget, but ending early shortens the unprotected window. - There is no
activatedByargument. Activate sends only reason and duration; Aiden setsactivated_byon the response from the authenticated API token.
Complete scripts
- Python:
examples/automation/policy-break-glass.py - TypeScript:
examples/automation/policy-break-glass.ts - Go:
examples/go/automation/policy-break-glass.go
#!/usr/bin/env python3
"""Attach a deny policy, dry-run it, open break-glass for an outage, then put
the guardrails back.
Pass only reason + duration for break-glass. Aiden records who opened it from
your API token (``activated_by`` on the response). Prefer ``any_deny_blocks``
when several policies can disagree: one deny stops the tool.
Usage:
export STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>
python examples/automation/policy-break-glass.py --agent my-oncall-agent
"""
from __future__ import annotations
import argparse
import os
import sys
import uuid
from datetime import datetime, timezone
from stackgen import StackgenClient, StackgenConfig
DENY_MARKER = "sdk-btg-emergency-marker"
WIRE_TO_RULE = {
"deny_overrides": "any_deny_blocks",
"permit_overrides": "any_allow_wins",
"first_applicable": "first_matching_policy",
"any_deny_blocks": "any_deny_blocks",
"any_allow_wins": "any_allow_wins",
"first_matching_policy": "first_matching_policy",
}
DENY_REGO = f"""package policy
import rego.v1
default allow := true
allow := false if {{
endswith(input.tool.name, "_execute_parallel")
some c in input.tool.arguments.commands
contains(c.command, "{DENY_MARKER}")
}}
deny_reason := "sdk sample: emergency marker denied without break-glass" if {{
not allow
}}
"""
def require_env(name: str, *fallbacks: str) -> str:
for key in (name, *fallbacks):
value = (os.environ.get(key) or "").strip()
if value:
return value
raise SystemExit(f"missing env {name}")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--agent", required=True, help="existing agent name")
args = parser.parse_args()
agent_name = args.agent
client = StackgenClient(
StackgenConfig(
base_url=require_env("STACKGEN_URL"),
api_token=require_env("STACKGEN_TOKEN"),
workspace_id=require_env("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT"),
)
)
suffix = uuid.uuid4().hex[:8]
policy_name = f"sdk-btg-deny-{suffix}"
policy_id = ""
shadow_attached = False
attached = False
glass_open = False
previous = client.aiden.get_agent(agent_name)
previous_rule = WIRE_TO_RULE.get(
str(previous.get("combining_algorithm") or previous.get("combiningAlgorithm") or "")
)
created = client.aiden.create_policy(
{
"name": policy_name,
"description": "SDK sample: deny emergency marker command",
"type": "logic",
"rego_source": DENY_REGO,
}
)
policy_id = str(created.get("id") or "")
if not policy_id:
raise SystemExit(f"create_policy missing id: {created!r}")
print(f"created policy id={policy_id}")
try:
client.aiden.attach_policy(
agent_name, policy_id=policy_id, shadow=True, priority=10
)
shadow_attached = True
print("attached in shadow (logged, not enforced)")
client.aiden.when_policies_disagree(agent_name, "any_deny_blocks")
print("conflict rule: any_deny_blocks")
agent_info = client.aiden.get_agent(agent_name)
agent_id = str(agent_info.get("id") or "").strip()
if not agent_id:
raise SystemExit(f"get_agent missing id: {agent_info!r}")
sample_input = {
"agent": {"id": agent_id, "name": agent_name},
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": {
"name": "shell_execute_parallel",
"arguments": {
"commands": [{"command": f"printf '{DENY_MARKER}'"}],
},
},
}
eval_shadow = client.aiden.evaluate_policy(
{"rego_source": DENY_REGO, "input": sample_input}
)
print(f"evaluate (shadow path) outcome={eval_shadow.get('outcome')}")
client.aiden.detach_policy(agent_name, policy_id)
shadow_attached = False
client.aiden.attach_policy(
agent_name, policy_id=policy_id, shadow=False, priority=10
)
attached = True
print("reattached enforcing")
eval_live = client.aiden.evaluate_policy(
{"rego_source": DENY_REGO, "input": sample_input}
)
print(f"evaluate (enforcing) outcome={eval_live.get('outcome')}")
# duration_minutes is 1–480. Keep the sample short; end_break_glass in finally.
client.aiden.break_glass(
agent_name,
reason=f"sdk sample outage bypass {suffix}",
duration_minutes=15,
)
glass_open = True
state = client.aiden.get_break_glass(agent_name)
print(
"break-glass "
f"active={state.get('active')} "
f"activated_by={state.get('activated_by')} "
f"reason={state.get('reason')}"
)
if not state.get("active"):
raise SystemExit(f"expected break-glass active: {state!r}")
if not state.get("activated_by"):
raise SystemExit(
f"expected activated_by from API token on response: {state!r}"
)
finally:
cleanup_errors: list[str] = []
def cleanup(label: str, fn) -> None:
try:
fn()
except Exception as exc: # noqa: BLE001
cleanup_errors.append(f"{label}: {exc}")
print(f"{label} failed: {exc}", file=sys.stderr)
if glass_open:
cleanup("end break-glass", lambda: client.aiden.end_break_glass(agent_name))
if not cleanup_errors:
print("ended break-glass")
if (attached or shadow_attached) and policy_id:
cleanup(
"detach policy",
lambda: client.aiden.detach_policy(agent_name, policy_id),
)
if "detach policy" not in " ".join(cleanup_errors):
print("detached policy")
if policy_id:
cleanup("delete policy", lambda: client.aiden.delete_policy(policy_id))
if not any(item.startswith("delete policy") for item in cleanup_errors):
print(f"deleted policy id={policy_id}")
if previous_rule and previous_rule != "any_deny_blocks":
cleanup(
"restore combining algorithm",
lambda: client.aiden.when_policies_disagree(agent_name, previous_rule),
)
if not any(item.startswith("restore combining") for item in cleanup_errors):
print(f"restored conflict rule: {previous_rule}")
elif previous_rule is None:
print(
"combining algorithm left as any_deny_blocks "
"(agent record had no previous rule)",
file=sys.stderr,
)
if cleanup_errors:
raise SystemExit("; ".join(cleanup_errors))
if __name__ == "__main__":
main()/**
* Attach a deny policy, dry-run it, open break-glass for an outage, then put
* the guardrails back.
*
* Pass only reason + duration for break-glass. Aiden records who opened it from
* your API token (`activated_by` on the response). Prefer `any_deny_blocks`
* when several policies can disagree: one deny stops the tool.
*
* Usage:
* export STACKGEN_URL=https://app.stackgen.com
* export STACKGEN_TOKEN=stackgen_…
* export STACKGEN_WORKSPACE_ID=<workspace-uuid>
* npx tsx examples/automation/policy-break-glass.ts --agent my-oncall-agent
*/
import { parseArgs } from "node:util";
import { randomUUID } from "node:crypto";
import { StackgenClient, StackgenConfig, type PolicyConflictRule } from "stackgen-sdk";
/** Marker substring the sample deny Rego matches. Safe under break-glass. */
const DENY_MARKER = "sdk-btg-emergency-marker";
const WIRE_TO_RULE: Record<string, PolicyConflictRule> = {
deny_overrides: "any_deny_blocks",
permit_overrides: "any_allow_wins",
first_applicable: "first_matching_policy",
any_deny_blocks: "any_deny_blocks",
any_allow_wins: "any_allow_wins",
first_matching_policy: "first_matching_policy",
};
const DENY_REGO = `package policy
import rego.v1
default allow := true
allow := false if {
endswith(input.tool.name, "_execute_parallel")
some c in input.tool.arguments.commands
contains(c.command, "${DENY_MARKER}")
}
deny_reason := "sdk sample: emergency marker denied without break-glass" if {
not allow
}
`;
function requireEnv(name: string, ...fallbacks: string[]): string {
for (const key of [name, ...fallbacks]) {
const value = (process.env[key] || "").trim();
if (value) {
return value;
}
}
throw new Error(`missing env ${name}`);
}
async function main(): Promise<void> {
const { values } = parseArgs({
options: {
agent: { type: "string" },
},
});
if (!values.agent) {
throw new Error("require --agent <existing-agent-name>");
}
const agentName = values.agent;
const client = new StackgenClient(
new StackgenConfig({
baseUrl: requireEnv("STACKGEN_URL"),
apiToken: requireEnv("STACKGEN_TOKEN"),
workspaceId: requireEnv("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT"),
}),
);
const suffix = randomUUID().slice(0, 8);
const policyName = `sdk-btg-deny-${suffix}`;
let policyId = "";
let shadowAttached = false;
let attached = false;
let glassOpen = false;
const before = await client.aiden.getAgent(agentName);
const previousRule =
WIRE_TO_RULE[String(before.combining_algorithm ?? before.combiningAlgorithm ?? "")];
const created = await client.aiden.createPolicy({
name: policyName,
description: "SDK sample: deny emergency marker command",
type: "logic",
rego_source: DENY_REGO,
});
policyId = String(created.id ?? "");
if (!policyId) {
throw new Error(`createPolicy missing id: ${JSON.stringify(created)}`);
}
console.log(`created policy id=${policyId}`);
let cleanupSummary = "";
try {
await client.aiden.attachPolicy(agentName, {
policyId,
shadow: true,
priority: 10,
});
shadowAttached = true;
console.log("attached in shadow (logged, not enforced)");
await client.aiden.whenPoliciesDisagree(agentName, "any_deny_blocks");
console.log("conflict rule: any_deny_blocks");
const agentInfo = await client.aiden.getAgent(agentName);
const agentId = String(agentInfo.id ?? "").trim();
if (!agentId) {
throw new Error(`getAgent missing id: ${JSON.stringify(agentInfo)}`);
}
const sampleInput = {
agent: { id: agentId, name: agentName },
timestamp: new Date().toISOString(),
tool: {
name: "shell_execute_parallel",
arguments: { commands: [{ command: `printf '${DENY_MARKER}'` }] },
},
};
const evalShadow = await client.aiden.evaluatePolicy({
rego_source: DENY_REGO,
input: sampleInput,
});
console.log(`evaluate (shadow path) outcome=${evalShadow.outcome}`);
await client.aiden.detachPolicy(agentName, policyId);
shadowAttached = false;
await client.aiden.attachPolicy(agentName, {
policyId,
shadow: false,
priority: 10,
});
attached = true;
console.log("reattached enforcing");
const evalLive = await client.aiden.evaluatePolicy({
rego_source: DENY_REGO,
input: sampleInput,
});
console.log(`evaluate (enforcing) outcome=${evalLive.outcome}`);
// durationMinutes is 1–480. Keep the sample short; endBreakGlass in finally.
await client.aiden.breakGlass(agentName, {
reason: `sdk sample outage bypass ${suffix}`,
durationMinutes: 15,
});
glassOpen = true;
const state = await client.aiden.getBreakGlass(agentName);
console.log(
`break-glass active=${state.active} activated_by=${state.activated_by} reason=${state.reason}`,
);
if (!state.active) {
throw new Error(`expected break-glass active: ${JSON.stringify(state)}`);
}
if (!state.activated_by) {
throw new Error(
`expected activated_by from API token on response: ${JSON.stringify(state)}`,
);
}
} finally {
const cleanupErrors: string[] = [];
const cleanup = async (label: string, fn: () => Promise<unknown>) => {
try {
await fn();
} catch (err) {
cleanupErrors.push(`${label}: ${err instanceof Error ? err.message : err}`);
console.error(`${label} failed:`, err);
}
};
if (glassOpen) {
await cleanup("end break-glass", () => client.aiden.endBreakGlass(agentName));
if (!cleanupErrors.length) console.log("ended break-glass");
}
if ((attached || shadowAttached) && policyId) {
await cleanup("detach policy", () => client.aiden.detachPolicy(agentName, policyId));
if (!cleanupErrors.some((item) => item.startsWith("detach policy"))) {
console.log("detached policy");
}
}
if (policyId) {
await cleanup("delete policy", () => client.aiden.deletePolicy(policyId));
if (!cleanupErrors.some((item) => item.startsWith("delete policy"))) {
console.log(`deleted policy id=${policyId}`);
}
}
if (previousRule && previousRule !== "any_deny_blocks") {
await cleanup("restore combining algorithm", () =>
client.aiden.whenPoliciesDisagree(agentName, previousRule),
);
if (!cleanupErrors.some((item) => item.startsWith("restore combining"))) {
console.log(`restored conflict rule: ${previousRule}`);
}
} else if (!previousRule) {
console.error(
"combining algorithm left as any_deny_blocks (agent record had no previous rule)",
);
}
if (cleanupErrors.length) {
cleanupSummary = cleanupErrors.join("; ");
}
}
if (cleanupSummary) {
throw new Error(cleanupSummary);
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});// Attach a deny policy, open break-glass, then restore guardrails.
//
// Documented at https://appcd-dev.github.io/stackgen-sdk/code-samples/policy-break-glass/
//
// go run ./examples/go/automation/policy-break-glass.go --agent my-oncall-agent
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"flag"
"fmt"
"os"
"strings"
stackgen "github.com/appcd-dev/stackgen-sdk/clients/go"
)
const denyRego = `package policy
import rego.v1
default allow := true
allow := false if {
endswith(input.tool.name, "_execute_parallel")
some c in input.tool.arguments.commands
contains(c.command, "sdk-btg-emergency-marker")
}
deny_reason := "sdk sample: emergency marker denied without break-glass" if {
not allow
}
`
// envOr returns the first non-empty environment variable among keys.
func envOr(keys ...string) string {
for _, key := range keys {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
}
return ""
}
func main() {
agent := flag.String("agent", "", "existing agent name")
flag.Parse()
if strings.TrimSpace(*agent) == "" {
fmt.Fprintln(os.Stderr, "--agent is required")
os.Exit(2)
}
base, token, org := envOr("STACKGEN_URL"), envOr("STACKGEN_TOKEN"), envOr("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT")
if base == "" || token == "" || org == "" {
fmt.Fprintln(os.Stderr, "set STACKGEN_URL, STACKGEN_TOKEN, and STACKGEN_WORKSPACE_ID")
os.Exit(2)
}
client, err := stackgen.NewClient(stackgen.Config{BaseURL: base, APIToken: token, WorkspaceID: org})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var buf [4]byte
_, _ = rand.Read(buf[:])
ctx := context.Background()
policy, err := client.Aiden.CreatePolicy(ctx, map[string]any{
"name": "deny-emergency-marker-" + hex.EncodeToString(buf[:]),
"description": "SDK sample: deny emergency marker command",
"type": "logic",
"rego_source": denyRego,
})
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
policyID, _ := policy["id"].(string)
fmt.Printf("created policy id=%s\n", policyID)
defer func() {
_, _ = client.Aiden.EndBreakGlass(context.Background(), *agent)
_, _ = client.Aiden.DetachPolicy(context.Background(), *agent, policyID)
_, _ = client.Aiden.DeletePolicy(context.Background(), policyID)
}()
if _, err := client.Aiden.AttachPolicy(ctx, *agent, map[string]any{
"policy_id": policyID,
"shadow": true,
"priority": 10,
}); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
if _, err := client.Aiden.WhenPoliciesDisagree(ctx, *agent, "any_deny_blocks"); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
opened, err := client.Aiden.BreakGlass(ctx, *agent, "sdk sample outage bypass", 15)
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
fmt.Printf("break-glass active=%v activated_by=%v\n", opened["active"], opened["activated_by"])
state, err := client.Aiden.GetBreakGlass(ctx, *agent)
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
fmt.Printf("confirmed active=%v reason=%v\n", state["active"], state["reason"])
}