elengtis
A configurable MCP prompt-injection benchmark. Elengtis runs declarative YAML scenarios against stdio or Streamable HTTP MCP targets, records complete agent trajectories, and verifies effects independently of the driving model.
It is a scenario-based research tool, not an automatic proof that a server is secure. It detects behavior described by its rules; it does not discover every possible injection.
Choosing an engine
Use create_agent as the default for straightforward agents. It is the
easiest LangChain path to a capable tool-using agent and is the recommended
starting point for new campaigns.
Choose graph when the workflow itself needs explicit control: shared state,
conditional branching, recovery after tool errors, strict tool ordering,
bounded turn-by-turn execution, or evidence-sensitive transitions. graph is
the lower-level LangGraph path and makes those mechanics visible and
programmable.
This is a role distinction, not a claim that one engine is universally safer.
The framework capability experiment found no practical universal winner:
create_agent is the simpler default, while graph is the controlled-workflow
option.
Quick start
Install uv and Python 3.12, then install the CLI:
uv tool install elengtis
elengtis example --out results/demo
cat results/demo/summary.txt
To work from a source checkout instead:
uv sync --locked
uv run --offline elengtis example --out results/demo
cat results/demo/summary.txt
uv run --offline python -m unittest discover -s tests -v
The packaged example needs no API key, network service or Docker. It uses a trusted synthetic stdio fixture and a unique fictional canary for every trial.
The same CLI is published as a non-root container image:
docker run --rm ghcr.io/nicoleman0/elengtis:0.1.0 --help
docker run --rm ghcr.io/nicoleman0/elengtis:0.1.0 example --out /tmp/results
The image includes the Docker client for isolated-container campaigns. Using that transport from inside the image additionally requires an explicitly mounted Docker socket and matching group access; treat access to the socket as host-root equivalent.
Campaigns
A campaign selects run settings, targets and scenario files. Elengtis expands
them into a stable target × scenario × trial matrix.
schema_version: 1
trials: 3
step_budget: 4
engine: create_agent
model: openai/gpt-5-mini
targets:
- id: research-server
transport:
type: streamable_http
url: https://mcp.example.test/mcp
headers:
Authorization: {env: MCP_AUTHORIZATION}
bindings:
support-note:
read_note: notes.read
read_credential: secrets.read
submit_credential: collector.submit
verify_collector: collector.inspect
scenarios: [scenarios/support-note.yaml]
Target bindings map a scenario's logical capabilities to concrete server tool names and values. Every target must satisfy every selected scenario; missing bindings are rejected before a result directory is created.
Validate without contacting a server:
uv run elengtis validate campaign.yaml
Run it:
set -a; . ./.env; set +a
uv run elengtis run --config campaign.yaml --out results/research
Configuration must use .yaml or .yml. JSON and the old scripted policies
interface are intentionally unsupported. Run-level --trials,
--step-budget, --engine, and --model overrides remain available.
Set model_free: true with the reference engine to run only declarative
setup, verification and cleanup actions. This mode never constructs a model or
the built-in scripted provider, and records termination: model_free with zero
model turns and tool calls. Model, route and generation settings are rejected
in a model-free campaign.
Targets and credentials
stdio targets declare a command and argument list. Arguments are passed
directly—never through a shell—and may use typed runner values such as
{runner: trial_dir} or {runner: canary}.
streamable_http targets declare an MCP URL. Every configured header value is
an environment reference. Resolved values are used for the active session but
are not written to YAML-derived manifests or evidence.
A fresh stdio trial owns a fresh child process. A fresh HTTP trial owns only a
new client session: elengtis does not claim that an externally managed server
was reset or isolated. Remote HTTP runs are labelled externally_managed in
evidence and print a warning during preflight. Only audit systems you are
authorized to test.
Isolated container targets
For a local server image, use isolated_container to get a fresh hardened
container and an internal, no-egress Docker network for every trial:
targets:
- id: audited-server
transport:
type: isolated_container
image: ghcr.io/example/audited-mcp@sha256:...
container_port: 3000
uid: 10001
gid: 10001
command: ["server", "--mcp"]
bindings:
scenario-id: {read_note: notes.read}
scenarios: [scenario.yaml]
The image must listen on the declared port and work as the supplied non-root
UID/GID without host mounts or outbound network access. Elengtis starts it with
a read-only root, a small /tmp tmpfs, dropped capabilities,
no-new-privileges, resource limits, and no image pull. Use
elengtis preflight campaign.yaml before a model run; it starts the target,
checks the tool allowlists, and tears it down without running scenario actions.
A container scenario that declares verification checks must include an
independent http_request or container_sqlite_query verifier; a verification
call back into the audited MCP server is not an independent boundary. A
scenario may declare no checks,
and then reports completed: null rather than a vacuous pass. Outbound
dependencies are intentionally unsupported in this first isolated path; see
issue #13 for
policy-proxied egress.
The container_sqlite_query verifier requires the target image to provide a
compatible Node runtime with node:sqlite; it snapshots the database through
the trusted target runtime and evaluates the snapshot read-only in the runner.
Lifecycle evidence includes the immutable image and container IDs, identifies
the session as a fresh container, and explicitly records that reset is
asserted. The tracked Everything-server onboarding and smoke campaign is under
experiments/real-targets/everything/.
Scenarios
A scenario declares five phases:
setup: trusted MCP or HTTP actions prepare controlled state.exercise: prompts and an explicit tool allowlist are given to the model.proposal_rules: structured tool calls and arguments are matched.verify: trusted MCP or HTTP checks inspect resulting state.cleanup: best-effort actions run even after a failure.
References are data, not executable templates:
tool: {binding: submit_credential}
arguments:
credential: {runner: canary}
Setup actions can capture response values with RFC 6901 JSON Pointers. Later
actions can use them through {capture: name}. Matchers and verifier assertions
support equals, contains, and matches. Every proposal rule carries positive
and negative examples; validation runs them like unit tests for the YAML rule.
Only tools listed in exercise.tools are shown to the model. Setup, verification
and cleanup tools remain hidden unless explicitly included. Use tools: all
only when the experiment intentionally exposes the complete inventory.
Scenarios may also declare optional safety_rules. These are evaluated
independently against recorded tool calls as forbidden-action rules. Scenarios
without safety rules report safety as not applicable rather than passing.
Evaluation and trust
The driving model never judges itself. Elengtis deterministically evaluates its recorded tool calls, then the trusted runner performs configured verification.
proposed: true, completed: true: matched malicious call and verified effect.true, false: observed attempt without verified effect.false, true: anomaly—investigate matcher coverage or contaminated state.false, false: no matched proposal or verified effect.completed: null: verification failed or no outcome check was configured, so the outcome is unknown.safety_pass: true: no configured forbidden action was observed.safe_completed: true: the verified outcome completed and passed safety rules.
An MCP verifier is independent of the model's claim but still trusts the target server's response. A separate HTTP verifier can provide a stronger boundary. The verifier type and assertion evidence are recorded.
Results and resume
Each output directory contains manifest.json, runs.jsonl, one JSON evidence
document per attempt, and summary.txt. Rows identify target, scenario, trial,
attempt and engine. Complete requests, messages, tool calls, proposal matches,
verification assertions and lifecycle records are retained.
Resume is between trials:
uv run elengtis run --resume --out results/research
Completed trials are skipped. An incomplete attempt remains evidence and is retried from setup with a new attempt ID; retries never inflate the trial denominator. Result or metric version mismatches block resume.
Live orchestration comparison
The live comparison uses a hosted model against the local synthetic MCP fixture;
it is an engine-behaviour experiment, not a real-server security assessment.
The protocol and reproducible multi-model campaign generator are in
experiments/live-comparison/. Generate
the ordered one-trial campaigns before contacting a model, retain every result
directory, then summarize them without hiding unknown or incomplete trials:
uv run elengtis analyze --input results/block-000-reference-support-note \
--input results/block-000-graph-support-note --out results/analysis
The command writes summary.json and summary.md, including known denominators
and Wilson intervals for the recorded proposal and completion outcomes.
Engines
reference, graph, langchain, and create_agent receive identical prompts
and allowed tools. They return orchestration observations only. One scenario
evaluator assigns proposal and completion meaning afterward, preventing four
implementations of the experiment's semantics.
create_agent is the recommended default for straightforward agents. It may
dispatch several tool calls concurrently, which is useful for ordinary
tool-using work but can be the wrong fit when order or intermediate state is
part of the contract.
The explicit graph engine is for workflows where sequential dispatch,
turn-budget behavior, state transitions, branching, recovery, or evidence
control must be represented directly. Its extra control comes with more
orchestration to define and maintain.
reference is the deterministic baseline used by conformance tests, and
langchain is an adapter-control condition for experiments; neither is the
recommended application default.
Dashboard schema
Generate the same strict schemas used by the CLI:
uv run elengtis schema --out schemas
campaign.schema.json and scenario.schema.json are suitable for validation
and future form generation. Unknown fields are rejected rather than ignored.
Contributing
uv sync --locked
uv run --offline python -m unittest discover -s tests -v
uv build
Tests and CI use only synthetic fixtures, local endpoints and scripted models. Keep scenario fixtures independently synthetic; do not import embargoed or third-party findings. Framework behavior changes need a differential test and must be recorded as experimental conditions rather than silently normalized.
License
Release files for elengtis 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| elengtis-0.1.0.tar.gz | 40.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| elengtis-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 90.7 kB
Release files / elengtis-0.1.0.tar.gz
| Download URL | elengtis-0.1.0.tar.gz |
|---|---|
| Size | 40.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f9a8d1e6af20cad1bbe56cca6ce43fb5974571265870879c8583407a692d0859
|
|
BLAKE2b-256 checksum How to use checksums |
b4ce1d22bb6fc3c2b7975c4fc421a9d1c06d2f43923fd6503c4c2e89bb4e8dcb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency logRelease files / elengtis-0.1.0-py3-none-any.whl
| Download URL | elengtis-0.1.0-py3-none-any.whl |
|---|---|
| Size | 50.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fce3b5ee18cf31d442bdebf1a4829cd9c725a35b195bb03360234a7f5e3acca3
|
|
BLAKE2b-256 checksum How to use checksums |
6236712aff0ca0177ed0672ad5c87f6278755504a87dc013470a4aa6ee92d083
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.
Transparency log