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 path

Proving the log was not edited

An audit log matters only if somebody who did not write it can believe it. This one chains its rows together with hashes, so a changed row can be detected. This lesson changes one and catches it.

Every row stores a hash of its own contents and the hash of the row before it. Break either and the chain stops agreeing with itself.

Example
import os, tempfile
from agent_control_plane import AgentKernel, PolicyEngine, FlightRecorder

db = os.path.join(tempfile.mkdtemp(), "audit.db")
recorder = FlightRecorder(db_path=db)
policy = PolicyEngine(); policy.add_constraint("support", ["lookup_order"])
kernel = AgentKernel(policy_engine=policy, audit_logger=recorder)

for tool in ["lookup_order", "delete_order"]:
    kernel.intercept_tool_execution("support", tool, {})
recorder.flush()
print(recorder.verify_integrity())

Valid, and it says how many entries it checked. That number matters: a log that has been truncated rather than edited still verifies, so the count is the part that catches deletion.

Editing a row

Now the attack. Somebody with access to the database decides the blocked refund should read as allowed, and edits it in SQLite directly, going nowhere near the toolkit.

Example
import sqlite3
recorder.close()

connection = sqlite3.connect(db)
connection.execute("UPDATE audit_log SET policy_verdict='allowed' WHERE policy_verdict='blocked'")
connection.commit()
connection.close()
print("edited")

The row now says the refund was allowed. Read it back through query_logs and it looks entirely ordinary, because nothing about the row itself is obviously wrong.

Catching it

Example
checked = FlightRecorder(db_path=db).verify_integrity()
print("valid:", checked["valid"])
print("error:", checked["error"])
print("first tampered id:", checked["first_tampered_id"])

Caught, and the row is named. The stored content hash covers the verdict along with the rest of the row, so changing the verdict alone is enough to break it.

This is evidence of tampering, not prevention of it. Anyone who can write to the file can still destroy the log, and anyone who can delete the last few rows leaves a chain that still verifies. The protection is that a quiet edit in the middle stops being quiet.

To make it worth more than that, the database has to live somewhere the agent's own tools cannot reach. A governed agent with a file-writing tool and a policy that forgot to protect the audit path can rewrite its own history, and lesson 6's protected-path check is what stops it.

Try it yourself
  • Delete the last row instead of editing it and see whether the log still verifies.
  • Edit tool_args rather than the verdict and confirm the content hash still catches it.
  • Add a row of your own with plain SQL and find out which check fails.

You understood something today that you didn't yesterday.