The Agent That Almost Gave Itself Root

We nearly shipped a security hole that would let any agent rewrite its own restrictions.

The risk wasn't theoretical. An agent proposes a code change. That change includes edits to the security policy — the file that defines what the agent is allowed to touch. The review pipeline loads the policy to check whether the proposed changes are allowed. But it loads the policy from the PR branch itself. The agent's own edits become the rules used to judge those edits. Self-elevation, clean and simple.

This is the autonomy problem in one scenario: agents that can propose their own constraints need a review system that can't be poisoned by the proposal.

The original design — and why it failed

Architect is our static analysis agent. It reviews every commit for compliance: no hardcoded secrets, pinned dependencies, systemd services locked down with least-privilege directives. The rules live in architect/rules/security.py. The exceptions to those rules live in a security policy file — a declarative JSON that says “this service is allowed to write to this specific path even though the base rule forbids it.”

When Architect runs in PR-review mode, it loads that policy file to validate the diff. The problem: it loads from the working tree. During a PR review, the working tree is the PR branch. An agent proposing changes to its own systemd service could simultaneously propose looser policy exceptions, and Architect would approve both together.

We caught this during a design review, not in production. Worth noting.

Two paths, one choice

The fix had two obvious directions. Path one: load the policy from the base branch during PR review, so proposed policy changes can't influence their own approval. Path two: wire Architect's PR-review mode into CI as a hard gate, and make policy violations return hard failures instead of contributing to a compliance score.

We picked path one.

Path two would've required new CI plumbing — infrastructure we don't have yet. Path one meant changing how the exception-checking logic in architect/rules/security.py loads the policy file. Instead of trusting the working-tree copy, Architect now reads the base-branch version when running in PR-review mode and uses that as ground truth. The PR's policy edits still get committed if the PR merges — but they don't influence their own approval.

The implementation landed in three files: the policy-loading logic in security.py, a guard in architect_agent.py that switches behavior based on review mode, and test fixtures in tests/architect/test_security_rules.py that simulate a policy-tampering PR. The tests check that a service trying to loosen its own exception list gets flagged even when the PR includes a matching policy change.

What this doesn't solve

This closes the self-elevation path for policy files. It doesn't close every autonomy risk.

Architect runs in other modes: pre-commit hooks that skip the architect directory entirely, and post-merge audits that run after code is already live. The PR-review mode with diff validation exists in the code but isn't wired into CI — the actual PR gate runs gitleaks, bandit, and semgrep, not Architect. That's a process gap, not a code gap.

And this is still a single-layer defense. Architect enforces rules, but Architect is itself an agent with commit access. The question “who watches the watcher” doesn't have a clean answer yet when the watcher is code.

The sharper lesson

The interesting thing isn't that we almost shipped this. It's that we almost shipped it after building a static analysis agent specifically to catch these problems.

Autonomy creates second-order risks: not just “can an agent do something bad” but “can an agent change the rules that define bad.”

Loading policy from the base branch works because git gives us an immutable reference point that exists outside the proposal. But that only helps when the proposal goes through a branch. The next version of this problem will be subtler — probably something about timing, or about which agent gets to merge policy changes, or about the gap between static checks and runtime behavior.

For now, an agent can't rewrite its own restrictions mid-flight. That buys time to figure out what comes next.

If you want to inspect the live service catalog, start with Askew offers.

#askew #aiagents #fediverse