google/policy
Inspect and edit the per-workspace tool-policy overlay (.lha/policies.jsonl) that gates destructive or sensitive tool calls.
npx skills add https://github.com/google/adk-samples --skill policy
The policies_guard callback consults a JSONL policy file on every tool
call. Policies live in two layers:
horizon/guardrails/default_policies.jsonl (read-only,ships with the agent). Provides baseline blocks for catastrophic operations
(literal dd if=/dev/zero, mkfs, fork bombs, >/dev/sd*, nc -l/ncat -l/socat
bind listeners) and credential reads (cat ~/.ssh/id_rsa, cat ~/.aws/credentials).
The seed no longer carries brittle substring/regex rules for rm -rf or
chmod -R — those are now classified by an argv-structural parser
(horizon/guardrails/command_safety.py) that lexes the command into tokens and
inspects structure instead of pattern-matching raw strings.
.lha/policies.jsonl under the workspaceroot. This is the file you edit. The overlay is appended to
the seed — new rules add restrictions on top of the defaults.
Mtime-cached, so edits take effect on the next tool call.
earlier — you cannot remove seed rules; only the overlay can be
edited.
If the user is asking about a *one-time* approval for a blocked
call, they want policy_grant, not this skill.
One JSON object per line. Blank lines and # comments are skipped.
Every rule must include canonical_tool_name; that field gates which
tool the rule applies to.
A rule may carry one or more of these fields. They are evaluated
independently — set as many as you need on a single rule.
| Field | Type | Effect |
|---|---|---|
| destructive | true | Always block the tool. |
| destructive_commands | {arg: [substring, ...]} | Block when the string arg contains any substring (case-insensitive). |
| destructive_commands_regex | {arg: [regex, ...]} | Block when any regex matches the string arg (case-insensitive). Use when substrings are too blunt. Tenant-authored regexes (overlay + grant rules) are validated for length and nested-quantifier patterns before compilation; malformed regexes are skipped with a warning. |
| destructive_paths | [prefix, ...] | Block when a path-shaped arg (path, file_path, target_path) starts with any prefix. |
| destructive_path_patterns | [fnmatch-glob, ...] | Block when a path-shaped arg matches any fnmatch glob. Use for per-user paths like */.ssh/*. |
Before the overlay/seed rules run, command_safety.py lexes shell commands
into argv tokens (quote- and operator-aware via stdlib shlex) and inspects
structure. It returns a verdict: "deny" (catastrophic, always blocks),
"ask" (risky, blocks children/headless, prompts the root agent via an
interactive approval card), or None (no opinion). Examples:
rm -rf /, rm -rf /etc, rm -rf $HOME, rm -rf /*, etc.(recursive force-delete of system/home roots or their subdirectories).
find . -delete, git push --force, chmod -R 777 .,sudo apt install ..., curl <url> | bash.
On an "ask" verdict, the root agent sees an interactive four-button
approval card; the child/headless chain treats it as a hard deny (no
regression in unattended contexts). This replaces the fragile substring/regex
rules that shipped in older seeds — the new seed (default_policies.jsonl)
only carries literal catastrophic commands + credential reads.
The root agent's interactive approval can be set to auto-approve
demotable-ask verdicts (ONLY) via /yolo (toggles between default and yolo
modes). YOLO mode auto-approves the Layer-D interactive ask; it does not
bypass the exfil guard or Layer-C hard-deny rules (catastrophic
+ credential reads + seed overlays). Use it when you trust the session and want
fewer prompts for risky-but-non-catastrophic operations. Approval mode is
per-session state, not persisted.
Permission rules in .lha/permissions.jsonl or granted via the interactive
approval card may include a commandPrefix, commandRegex, or argsPattern
to narrow blanket allow rules. Overlay and grant rules (source =
"overlay" or "grant") that target terminal or process but carry no
such narrowing field are rejected at load time — you cannot grant a blanket
"always allow terminal" from the overlay or a session approval; only the
default seed may carry that. This prevents accidental over-granting.
A policy block returns {"error": ..., "confirmation_required": True}. The
agent should either narrow the call, ask the user, or — if the user explicitly
approves — record a session grant via policy_grant.
To force a *prompt* (not a hard block) or block a specific command for the
user, use the permission layer instead: add an ask_user or deny rule to
.lha/permissions.jsonl (see docs/permission-model.md). This overlay is
hard-block only.
read_file(".lha/policies.jsonl")
If the file does not exist, the user has no overlay rules yet — only
the seed is active. To see the seed too:
read_file("horizon/guardrails/default_policies.jsonl")
Read the existing overlay, append the new JSON object as one line,
write the whole file back. Always end the file with a trailing
newline.
existing = read_file(".lha/policies.jsonl") # may be missing → treat as ""
new_rule = {"canonical_tool_name": "terminal",
"destructive_commands": {"command": ["rm -rf node_modules"]}}
body = (existing.rstrip("\n") + "\n" if existing else "") + json.dumps(new_rule) + "\n"
write_file(".lha/policies.jsonl", body)
If the parent .lha/ directory does not exist, write_file will
create it.
Read the overlay, drop the targeted line (0-based index into the
overlay, ignoring blank/comment lines), write back. Confirm the
target with the user before removing — overlay rules typically exist
because the user (or you on the user's behalf) added them to plug a
gap.
lines = [l for l in read_file(".lha/policies.jsonl").splitlines()
if l.strip() and not l.lstrip().startswith("#")]
removed = lines.pop(target_index)
write_file(".lha/policies.jsonl", "\n".join(lines) + ("\n" if lines else ""))
User: "Block npm publish from the terminal."
.lha/policies.jsonl (returns "" if missing). {"canonical_tool_name": "terminal", "destructive_commands": {"command": ["npm publish"]}}
npm publish fromterminal. The next attempt will be blocked until the rule is
removed."
horizon/guardrails/default_policies.jsonl — it's theshipped seed and is read-only from the agent's perspective.
skipped with a warning) — but always double-check your rule
parses by reading the file back after writing.
Take google/policy from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.