Agent Governance Toolkitagent-governance-toolkit 4.1.0 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
23 small wins to finish your pathNext lesson

Stopping an agent from editing its own policy

Every rule so far was added by a method call. An agent that can run code can make method calls too, which makes the policy engine itself a target.

The attack is one line long and needs no cleverness at all.

Example
from agent_control_plane import PolicyEngine, AgentKernel

policy = PolicyEngine()
policy.add_constraint("support", ["lookup_order"])
kernel = AgentKernel(policy_engine=policy)
print("before:", kernel.intercept_tool_execution("support", "delete_order", {}) is None)

policy.add_constraint("support", ["lookup_order", "delete_order"])
print("after: ", kernel.intercept_tool_execution("support", "delete_order", {}) is None)

The agent granted itself the tool it was denied. Any code running in the same process can do this, which includes a tool that executes code and anything that got there through a dependency.

Freezing

Example
policy = PolicyEngine()
policy.add_constraint("support", ["lookup_order"])
policy.freeze()

try:
    policy.add_constraint("support", ["delete_order"])
except RuntimeError as exc:
    print(exc)

After freeze every mutating method raises. The message says the call is irreversible, and it means it: there is no unfreeze, by design, because one would be the first thing an attacker called.

Freezing does more than set a flag. The engine's internal dictionaries are swapped for read-only views, so reaching past the methods and assigning to them directly fails as well.

Example
print("frozen:", policy.is_frozen)
try:
    policy.state_permissions["support"] = {"delete_order"}
except TypeError as exc:
    print("direct assignment:", exc)

Checking still works normally after freezing; only changing is blocked. So the shape of a governed program is: build the policy at start-up, freeze it, then serve.

Who changed what

Every change is recorded as it happens, including the freeze itself, which gives you a record of how the policy reached the state it is in.

Example
for entry in policy.mutation_log:
    print(entry["operation"], entry["details"])

That log is in memory and disappears with the process. Part 5 builds the durable record, the one that survives a restart and can be shown to somebody who does not trust you.

Try it yourself
  • Call freeze twice and check it does not complain.
  • Freeze before add_constraint and watch the set-up fail instead of the attack.
  • Try set_agent_context after freezing, which matters because lesson 15 changes context at runtime.

This is what real progress feels like.