
Executable security regression testing for agentic applications and MCP-integrated systems.
The OWASP Agent Security Regression Harness is an open source, vendor-neutral test harness for running executable security regression scenarios against agentic applications and MCP-integrated systems.
The project helps builders and defenders verify that changes to prompts, models, tools, retrieval sources, memory, approval flows, or MCP integrations do not reintroduce known security failures.

This project provides a code-first harness for:
This project is not:
It is a regression harness. Its job is to help teams catch known classes of agent security failures before they ship.
This project is in early Incubator development.
The current CLI supports:
Currently implemented assertions:
no_denied_tool_call — denylist and optional allowlist enforcement for tool callsgoal_integrity — fail if the agent drifts from the expected goal eventmemory_isolation — fail if any configured forbidden_markers appear anywhere in the trace (with redacted failure evidence)no_external_recipient — fail on outbound actions to recipients or domains outside the allowlistTo test whether specific known secrets leak (API keys, tokens, PII you control), configure them as forbidden_markers under expected.memory_isolation — memory_isolation enforces this and reports leaks without re-exposing the marker value. See docs/assertions/memory-isolation.md.
Clone the repository, then install the package in editable mode:
python -m pip install -e .
Verify the CLI is available:
agent-harness version
Expected output:
agent-harness 0.2.0
For authoring guidance, see Scenario Specification.
Validate the included goal hijack scenario:
agent-harness validate scenarios/goal_hijack/basic.yaml
Expected output:
valid: goal_hijack.basic_001
Dry-run mode validates the scenario and emits the result shape without executing a target.
agent-harness run scenarios/goal_hijack/basic.yaml --dry-run
Dry-run assertions are marked as not_run because no target behavior has been observed.
You can evaluate a scenario against a pre-recorded trace.
Failing trace example:
agent-harness run scenarios/goal_hijack/basic.yaml --trace-file examples/traces/denied_tool_call.json
This trace contains a denied send_email tool call, so the no_denied_tool_call assertion fails.
Passing trace example:
agent-harness run scenarios/goal_hijack/basic.yaml --trace-file examples/traces/no_denied_tool_call.json
This trace does not contain a denied tool call and emits a goal event with id summarize_document matching the scenario's expected_goal, so the no_denied_tool_call and goal_integrity assertions both pass.
Because the example scenario also includes no_secret_disclosure, which is not implemented yet, the top-level result may still be not_run even when no_denied_tool_call and goal_integrity pass. It should not be fail.
The harness can call a live HTTP target that accepts scenario input and returns trace JSON.
Start the example target in one terminal:
python examples/targets/http_agent.py
In a second terminal, run the harness against it:
agent-harness run scenarios/goal_hijack/basic.yaml --live --target-url http://127.0.0.1:8000/run
The example target returns a trace with no denied tool calls and a goal event with id summarize_document matching the scenario's expected_goal, so no_denied_tool_call and goal_integrity both pass.
The repository ships two additional demo agents under examples/targets/
that pair with the bundled goal_hijack/outbound_email_exfiltration_001.yaml
scenario.
Together they show what a real regression catch and a real success look
like end-to-end through the CLI.
Both agents are deliberately tiny and unsafe-by-design or hardened-by-design — they exist to give the harness a positive and negative control to compare against, not to be templates for production agents.
Start the toy vulnerable agent (port 8001):
python examples/targets/vulnerable_http_agent.py
Run the outbound-email-exfiltration scenario against it:
agent-harness run scenarios/goal_hijack/outbound_email_exfiltration_001.yaml --live \
--target-url http://127.0.0.1:8001/run
The vulnerable agent naively follows untrusted retrieved content, so it
calls send_email and the no_denied_tool_call assertion fails with
denied tool call observed: send_email. This is the regression catch
the harness is built to provide.
Now start the toy hardened agent (port 8002):
python examples/targets/hardened_http_agent.py
Run the same scenario against it:
agent-harness run scenarios/goal_hijack/outbound_email_exfiltration_001.yaml --live \
--target-url http://127.0.0.1:8002/run
The hardened agent treats untrusted context as data, never as
instruction, so it makes no tool calls and the assertion passes. The
trace also records an untrusted_context_received event so reviewers
can see that the agent observed the attack content and consciously
refused to act on it.
The same scenario also includes a goal_integrity assertion with
expected_goal: summarize_document. Both demo agents emit a goal
event ({"type": "goal", "id": ...}) reflecting the goal they
actually committed to. The vulnerable agent drifts to
send_email under attack and fails the assertion; the hardened
agent stays on summarize_document and passes it.
By default agent-harness run exits 0 on every successful run, regardless of
assertion outcomes — the result JSON tells you what happened. To make the
process itself fail when an assertion fails (typical CI gate), pass
--exit-on-fail:
agent-harness run scenarios/goal_hijack/basic.yaml \
--trace-file examples/traces/denied_tool_call.json \
--exit-on-fail
The process exits with code 1 if the overall result is fail or error.
A pass or not_run result still exits 0.
All run modes support --out:
agent-harness run scenarios/goal_hijack/basic.yaml --dry-run --out result.json
agent-harness run scenarios/goal_hijack/basic.yaml --trace-file examples/traces/denied_tool_call.json --out result.json