An open, offline-verifiable admission layer that stops instruction-privilege
escalation in coding agents by enforcing a plan-first information-flow policy.
A research prototype with a rigorous audit — not a turnkey production defense.
LLM coding agents run over untrusted web pages, docs and tool output while
holding authority over security-sensitive resources (deploy keys, CI configs,
secret stores). Two structural gaps are documented in the literature:
The harnesses themselves are evaluated across 13 attack objectives in 6 real
coding-agent frameworks (arXiv:2608.27299).
agent-harness-defense
is an admission layer: before a change is applied,
you call run_admission()
with an explicit description of what the agent
proposes to do, and it returns a verdict — admit this, deny that, and why. It is
not a runtime firewall watching a live agent. It is a library you (or the
harness you integrate) invoke.
The decision core is a dual-lattice information-flow control (IFC) engine.
Each datum in the plan carries two labels:
When an action depends on low-integrity data (a repo README, say), the action
inherits that distrust even if the content contains no trigger word. That is
exactly what lets it catch prompt injection that the v0.1 heuristic (trigger
phrases) could not see. As a second layer, the v0.1 heuristic (trigger phrases +
forbidden paths) is retained as a backup signal, not the primary mechanism.
The planner emits one Plan
per query. evaluate_plan
applies a componentwise
lattice join over the depends_on
graph:
SourceTag
(SYSTEM / USER / TOOL_RESULT / REPO_TEXT / ENV / DATA) is the
per-fragment origin label. Reads are classified by path: reading repo text
yields UNTRUSTED integrity (the prompt-injection surface); reading a system file
yields SYSTEM.
write
that depends on an untrusted read
inherits UNTRUSTED integrity and
is write
sourced from env.SECRET
to a public sink is write
that depends only on SYSTEM/USER input is admitted.The public Signetry/eval
IPI corpus models the agent writing the malicious artifact to disk. We add
INCIDENT_REPORT_INJECTION
: the plan reads anREADME.md
and writes incident-report.md
sourced from env.SECRET
,This is verified, not asserted: test_v01_would_have_missed_this
re-runs the v0.1
scan over the materialized repo and confirms escalation_detected is False
— the
eval is non-vacuous. The suite covers 3 scenarios: the 2 Signetry IPI scenarios
(README injection, CLAUDE.md scope expansion) plus this own secret-leak scenario.
Each one has a test proving v0.1 would have let it through and the new engine does not.
An independent audit (fresh clone, clean venv) reproduced a real defect introduced
during the build: _step_initial_label
returned (PUBLIC, SYSTEM)
for every
read, so propagation through depends_on
only worked via magic prefixes baked
into value_source
. The fix (_classify_read_path
) derives the read label from
the path. A regression test (test_read_propagates_untrusted_via_depends_on
) now
fails if the bug returns. The audit also flagged a CI regression the fix caused
(a bandit
B108 and a ruff format
miss), both closed before merge.
This is the part most security posts omit. Stated plainly:
Plan
(what the agent will read,
write, and where each value comes from) must be supplied Plan
says step B depends on step A, the engine propagates the label. But nothing
analyzes disk to detect "this file literally cites that other file" on its own.
If the caller declares dependencies wrong, the engine cannot know — which is why
assert_plan_matches_materialized
exists, but it is a LoopStateMonitor
— which is still the substring heuristic,
not the lattice.KNOWN_ISSUES.md
does not whitewash
anything), and a real audit trail where a propagation bug was found and fixed
before publish. That already puts the repo above the median of security projects
shipped to GitHub without external scrutiny.Plan
generator) hits a wall, and that
burns credibility fast.Plan
from real agent
calls. Without it, "just use it" is an empty promise.ifc.py
to learn how to use it.All claims are reproducible offline. The suite models both Signetry IPI scenarios
faithfully: the agent's obey()
step writes the attack artifacts to disk, so the
defense is exercised on real materialized state, not a mock. Three guard rails keep
the eval honest:
test_admission.py
) — fails if obey()
does not land the
artifact on disk.assert_plan_matches_materialized
) — fails if the declared
Plan
diverges from what the agent actually wrote.test_eval_catches_regression.py
) — monkey-patches
evaluate_plan
to admit everything and asserts the guard observes the broken
boundary.| Gate | Result |
|---|---|
pytest |
23 passed (0.29s) |
ruff check |
clean |
ruff format --check |
clean |
bandit -r agent_harness_defense -ll |
clean (B108 suppressed, justified) |
The package is not on PyPI — install from the repo:
git clone https://github.com/amurlaniakea/agent-harness-defense
cd agent-harness-defense
pip install -e ".[dev]" # ed-itable install; [dev] pulls pytest/ruff/bandit
pytest # 23 tests
ahd eval # run bundled IPI + AC-EVAL-1 scenarios
ahd run REPO --plan plan.yaml # evaluate a declarative Plan
Última actualización: 2026-08-29 — correcciones de instalación: el paquete no está en PyPI, instalar desde el repo con pip install -e ".[dev]"; el tag v0.2.0 tiene Release de GitHub con notas.
License: AGPL-3.0-or-later — Pedro Sordo Martínez
Implementation → independent audit on a clean clone → merge gated on green CI. Prototype, not a turnkey defense: read "What it does NOT do" before integrating.