| name | mantis-reproduce |
| description | Generates and runs crash reproducers to verify security flaws. Use when viable findings exist and you need to write and execute a script or payload to verify the crash. Don't use for code auditing or patching. |
Reproducer (/mantis-reproduce)
System Goal
Integration Test Engineer. Designs crash reproducers or inputs and executes them
inside isolated sandbox environments to empirically verify bugs.
Command Definition
- Command:
/mantis-reproduce [--reattack] [--finding_id=<uuid>] [--force] [--target_root=<path>] [--state_root=<path>] [--snapshot_root=<path>] [--snapshot_id=<SNAPSHOT_ID>] [--snapshot_pinned=<true|false>]
- Description: Generates and runs crash reproducers to verify security
flaws.
- Parameters:
--reattack: When executing as part of patch verification to isolate
re-attack outcomes.
--finding_id: The specific finding UUID to reproduce. Must be provided
and is required when --reattack is specified.
--force: Override/bypass eligibility checks for targeted normal runs.
--target_root: Path to the root of the target codebase under test
(defaults to .). AUTHORITATIVE when supplied — overrides --snapshot_root
(Block A step 1a); the sentinel check is skipped for this tree (e.g. a
patched shadow during re-attack verification).
--state_root: Path to the root of the Mantis state directory containing
workspace/ (defaults to .).
--snapshot_root: Root of the pinned immutable code snapshot for this pass.
Consumed by Block A (Step 0) when --target_root is not supplied.
--snapshot_id: The SNAPSHOT_ID string of the pinned snapshot, consumed by
Block A (sentinel) and Block B (snapshot match check) in Step 0.
--snapshot_pinned: When false (set by mantis-patch during re-attack on
a patched shadow), the reproduce sub-agent MUST skip the snapshot
sentinel/match check for this invocation — the --target_root tree is
authoritative and sentinel-exempt (Block A step 1a).
Input/Output Contract
- Reads:
state_root/workspace/findings/ (viable/conditional findings).
target_root/ (Repository source files to analyze trigger paths).
state_root/workspace/archive/.repro_attempts.json.
state_root/workspace/.mantis_state.json (to track current loop pass).
- Writes:
- PoC reproduction files (e.g.
poc_[uuid].py or crash_[uuid].payload
inside state_root/workspace/reproducers/).
- If run normally: updates findings in-place under
state_root/workspace/findings/ (sets "repro_status",
"repro_file_path", "run_command", "repro_output", and appends
history). Updates status to "VALID" if provisionally valid.
- If run with
--reattack: updates findings in-place under
state_root/workspace/findings/ (sets "reattack_status",
"reattack_file_path", "reattack_run_command", "reattack_output",
"reattack_variants", and appends history with stage "reattack"). Does
not modify "repro_*" fields or "status". Exception: may atomically
downgrade patch_status per INV-1 in Step 6 (never persist
VERIFIED_SECURE alongside a non-failed_to_bypass reattack_status).
- Updates
state_root/workspace/archive/.repro_attempts.json atomically.
- Stamps
"repro_snapshot_id" / "reattack_snapshot_id" on updated findings
and stores .repro_attempts.json values as {count,last_snapshot} objects
(bare integers still read correctly).
- Preconditions:
- Findings must exist in
state_root/workspace/findings/.
- Sandbox/container runtime environment must be available.
- Idempotency Guarantee:
- Updates findings in place. Uses
state_root/workspace/archive/.repro_attempts.lock file locking and atomic
temporary file swaps ( on
) to guarantee
concurrency safety and retry stability.
Instructions
Step 0: Locator Resolution + Snapshot Match (run first)
LOCATOR RESOLUTION (before reading ANY target code or artifact):
0. ROLE: If this skill NEVER reads target source (report, calibrate, reflect),
you are a FINDINGS-ONLY stage: skip steps 2-6; still read active_snapshot from
state for provenance/annotation; NEVER stop merely because a code root is unset.
1. Determine CODE_ROOT, in this priority order:
a. If --target_root is passed on THIS invocation, CODE_ROOT = --target_root.
It is AUTHORITATIVE and OVERRIDES SNAPSHOT_ROOT and the state fallback
(used when a caller hands you a prepared tree, e.g. a patched shadow).
b. Else if --snapshot_root (or SNAPSHOT_ROOT) is passed, use it.
c. Else read state_root/workspace/.mantis_state.json (state_root from
--state_root if passed, else ./workspace/... relative to the current dir)
-> active_snapshot.root / .snapshot_id / .snapshot_pinned.
d. Else (no arg AND no readable active_snapshot): CODE_ROOT = current directory,
treat snapshot_pinned = false (MODE-OFF). Do NOT stop.
2. SENTINEL CHECK (only if snapshot_pinned is true AND you did NOT take path 1a):
verify CODE_ROOT/.mantis_snapshot_id exists and equals SNAPSHOT_ID. If missing
or different -> STOP "snapshot sentinel mismatch". (A --target_root tree (1a) is
deliberately mutated and is sentinel-EXEMPT.)
3. PATH FIELDS:
- SNAPSHOT-RELATIVE (read under CODE_ROOT): code_paths entries; plan target_files
that are file paths. Strip ONLY a trailing ":<digits>". A code_paths entry
containing "://" is a URL/endpoint, NOT a file read. A code_paths entry that is
NOT of the form <existing-path>:<integer> is a non-source LOCATOR
(symbol/offset/endpoint): only check that the artifact/symbol exists; skip ALL
line-range and line-existence logic.
- STATE-RELATIVE (read/write under state_root/workspace, NEVER prefix CODE_ROOT):
kb_references, repro_file_path, reattack_file_path, helper scripts, report
files, and all state/findings JSON.
4. Never WRITE under CODE_ROOT when snapshot_pinned is true. Any command that
compiles, generates, or writes artifacts MUST run in a PRIVATE SHADOW copy
(mktemp -d from CODE_ROOT), never with cwd=CODE_ROOT. Read-only inspection may
cd into CODE_ROOT.
5. VCS-METADATA CARVE-OUT: history-log extraction and any VCS diff/blame command
run in the LIVE repository root (which still has .git/.hg/.repo), NOT CODE_ROOT
(the snapshot copy strips VCS metadata). Do NOT stop merely because CODE_ROOT
lacks .git/.hg/.repo.
6. Every shell command uses ABSOLUTE paths and sets its own working directory on
that call. Do NOT assume the working directory persists between calls.
[!NOTE] CURRENT-PASS CHECK (defensive; the binding guarantee is on the
harness per mantis-pipeline-adapter Scenario 2): if active_snapshot is
present AND active_snapshot.pass != state.pass_number, treat the snapshot as
STALE for this pass — STOP "stale active_snapshot: pass mismatch" or degrade
as HALT (snapshot_pinned effectively false: no authoritative verdicts, Block
B NOT_MATCHED, reproduce not_attempted). This catches a custom harness that
preserved active_snapshot across the Stage 15 pass increment without
re-pinning. The reference meta-agent re-pins every pass, so this check never
fires there. Block B itself cannot detect this (it is snapshot_id-only, not
pass-aware).
SNAPSHOT MATCH CHECK for finding F (decides MATCHED vs NOT_MATCHED):
1. If snapshot_pinned is false -> NOT_MATCHED. Stop.
2. Read F.discovery_commit:
- missing OR empty OR the literal "MIXED" -> NOT_MATCHED.
- not exactly equal to SNAPSHOT_ID -> NOT_MATCHED.
- exactly equal to SNAPSHOT_ID -> MATCHED.
There is no other route to MATCHED; never fuzzy-compare. The global "default the
field and proceed" backward-compat rule does NOT apply to discovery_commit:
absent = NOT_MATCHED. (There is NO separate "dirty" gate: a dirty tree's
SNAPSHOT_ID already embeds the working-tree content hash, so within-pass findings
MATCH and cross-pass bare-commit findings do not.)
Notes: When invoked by the patcher with --target_root=<shadow> (a patched
copy), Block A step 1a makes that shadow the authoritative CODE_ROOT and SKIPS
the sentinel check (the shadow is deliberately mutated). Otherwise CODE_ROOT is
the pinned snapshot and the sentinel MUST match. Stamp repro_snapshot_id
(normal run) or reattack_snapshot_id (--reattack) = the current SNAPSHOT_ID
(from --snapshot_id or state active_snapshot.snapshot_id) on every finding
you update.
Write a Proof-of-Concept Reproduction Script (Repro) or raw input payload file
that reproduces a confirmed security flaw.
Execute the reproduction stage under these constraints:
-
Load Viable Findings:
- If
--finding_id is supplied:
- Load only that finding's file
(
state_root/workspace/findings/<uuid>.json). Exit if it does not exist.
- If
--reattack is specified: Enforce the expected patch workflow
state for the loaded finding:
- The finding's
"status" must be "VALID" or "PROVISIONALLY_VALID".
- The finding's
"repro_status" must be "reproduced".
- The finding's
"patch_status" must NOT be "MITIGATION_PROPOSED".
(VERIFIED_SECURE IS allowed: C5 below atomically downgrades it when
the re-attack outcome is not failed_to_bypass.)
- Exit with an error if these conditions are not met, explaining the
invalid state.
- If
--reattack is NOT specified (Targeted Normal Run):
- If
--force is NOT specified, enforce standard eligibility filters:
- The finding's
"status" must be "VALID" or
"PROVISIONALLY_VALID".
- The finding's
"production_viability" must be "VIABLE",
"SAMPLE_OR_TEST", or "CONDITIONAL_VIABLE".
- Exit with an error if these conditions are not met, explaining the
invalid state.
- If
--force is specified, bypass these eligibility checks.
- If
--finding_id is not supplied:
- Constraint: Exit if
--reattack is specified (it requires
--finding_id).
- Read the JSON files in the
state_root/workspace/findings/ directory.
- Strict Eligibility Filter (Normal Runs): Include only findings where:
"status" is "VALID" or "PROVISIONALLY_VALID".
"production_viability" is "VIABLE", "SAMPLE_OR_TEST", or
"CONDITIONAL_VIABLE" (or skip this viability filter if not checking
viability, but always check status).
Step 3a: Variant Hunting (re-attack only, MANDATORY)
When invoked with --reattack, you MUST author and execute N ≥ 3
boundary-mutated variant inputs in addition to re-running the original PoC.
The schema (schema.json) literally calls this the "variant-hunting re-attack"
— merely re-running the original PoC is insufficient. Over-narrow patches that
guard the exact PoC bytes are the dominant auto-repair failure mode; variant
hunting is the zero-infra-cost defense against them.
Legacy findings: Findings with absent reattack_variants (created before
this rule existed) are re-verified under the ≥3 variant requirement on their
first --reattack pass. This is intentional — legacy VERIFIED_SECURE verdicts
are upgraded to the stricter gate — but expect one-time churn across archived
findings on the first pass that runs variant hunting.
What to generate (bug-class-aware):
-
Memory-safety bugs (buffer overflow, OOB read/write, UAF, integer
overflow): Author at least 3 of:
- Off-by-one:
len = bound, len = bound + 1, len = bound - 1.
- Size mutations:
len ± 1, len = 0, len = SIZE_MAX, sign flips on
signed lengths.
- Alternate paths to the same sink: If the vulnerable sink is reached via
multiple call sites, author a variant reaching it through a different path
(e.g., different API endpoint, file format variant, or protocol command).
- Type confusion / width mismatch: Exploit a different type path to the
same sink.
-
Non-memory-safety bugs (logic, auth, injection, SSRF, path traversal):
Hunt for actual variants of the same class:
- Alternate endpoints/parameters: Try
/api/v2/echo when PoC targets
/api/v1/echo, or alternate parameter names.
- Equivalent payloads:
..%2fetc%2fpasswd, ..\\..\\, URL-encoding,
double encoding, unicode normalization variants.
- Auth boundary variants: Different roles, empty/null tokens, alternate
privilege-escalation paths.
- Injection variants:
'; EXEC--, UNION SELECT, blind variants,
alternate injection points.
-
Parallel variant generation (if subagents available): You SHOULD spawn
subagents to author and test variants in parallel. Each subagent gets one
mutation strategy, writes its variant PoC, and reports whether it triggered.
Aggregate all results before setting reattack_status. If subagents are
unavailable, do them sequentially.
Execution: Write each variant as a separate script in
state_root/workspace/reproducers/ (e.g., reattack_variant_[uuid]_[N].py).
Execute each against the patched shadow (--target_root) using the same
isolation constraints as Step 3. Record each variant in the reattack_variants
array using EXACTLY the schema keys
{"description": "...", "triggered": true/false} (Step 6).
Verdict rule: reattack_status = "failed_to_bypass" requires a non-empty
reattack_variants array containing ≥ 3 valid variant inputs that ALL failed to
trigger the bug on the patched shadow. An empty or short set makes "all variants
failed" vacuously true — this is FORBIDDEN: if fewer than 3 meaningful variants
can be constructed after genuine effort, cap at VERIFICATION_INCOMPLETE
(history note insufficient_variants), NEVER failed_to_bypass. The [] case
may ONLY coexist with a non-failed_to_bypass status (e.g., C5 baseline failure
that halted before variant hunting).
Variant validity guardrail: A variant counts as a bypass (triggered = true
causing bypassed_patch) ONLY if it satisfies BOTH:
- Same vulnerability class: The variant reproduces the original bug class —
same sink function / sanitizer signature / crash type. A junk mutant (e.g.,
len=SIZE_MAX causing an unrelated OOM, an alternate endpoint 404-ing then
erroring, or a completely different crash) does NOT count as a bypass.
Discard it (set triggered = false with a description noting it was invalid)
and continue. For non-memory bugs (logic, auth, injection), compare the same
sink function or behavior (e.g., same unauthorized action succeeds, same
injection executes, reaches the same sink function) — NOT a ±line window
against a pre-patch line number (patches shift lines, and non-memory bugs
often have no precise sink line). If you cannot positively confirm a
triggering variant is a DIFFERENT bug, count it as a bypass (fail-closed:
prefer a false VERIFICATION_FAILED over a false VERIFIED_SECURE).
- Valid input per Step 4: The variant must be a valid exercise of the
public API or internal invariants — it must not rely on artificial harness
tricks (e.g., private-function direct calls with custom-allocated buffers)
that bypass the library's execution invariants.
Ideally, confirm each triggering variant still triggers on the unpatched
shadow (same baseline the original PoC ran against) to prove it exercises the
original bug rather than an artifact. If the unpatched shadow is unavailable
(e.g., snapshot mismatch), require the same-sink evidence (same sanitizer frame,
same crash address, same logic failure) as corroboration.
If ANY valid variant triggers the bug, set reattack_status = "bypassed_patch"
and apply INV-1 (downgrade VERIFIED_SECURE → VERIFICATION_FAILED).
-
Strict Public-API & Internal Invariant Constraints:
- Your crash reproducer should interact with the codebase through
public-facing APIs wherever possible, or strictly respect the library's
global execution invariants (such as allocator padding) to avoid generating
artificial, non-viable crashes.
- Do not declare a finding as "reproduced" if the crash can only be achieved
by compiling a direct-call harness that feeds a private/static function a
custom-allocated buffer (e.g.,
malloc(15)) that bypasses the library's
guaranteed allocator wrappers (e.g., png_malloc(rowbytes + 48)).
- If a crash cannot be triggered through the public API or with standard
allocation padding, classify the finding as
"failed_to_reproduce" due to
"Internal Invariant Protection."
-
Functional & Crash-Aware Validation: Analyze the output such as stdout,
stderr, and exit codes to classify reproduction success depending on the bug
class:
Before classifying ANY negative outcome (failed_to_reproduce, or in
--reattack mode failed_to_bypass), apply this gate:
REACHED-SINK EVIDENCE GATE (mechanical):
Each reproducer produces REACHED-SINK EVIDENCE via ONE channel, recorded in repro_hints:
(a) script/source harness -> write the exact bytes MANTIS_REACHED_ENTRYPOINT to a
sidecar file $SENTINEL_FILE and flush+fsync (or unbuffered write) BEFORE
invoking the sink. (A file survives a crash that truncates buffered stdout.)
(b) binary / firmware / raw-payload -> reached-sink evidence is a captured
crash backtrace or sanitizer frame (ASan/UBSan/MSan/TSan) that
explicitly names the target sink function (target-produced tracing). A
marker written by a wrapper you author BEFORE invoking the target is
SETUP EVIDENCE ONLY: it proves "launch attempted," not "sink reached,"
and does NOT qualify as reached-sink evidence. If no in-path marker
(channel a) and no target-produced backtrace/sanitizer trace
(channel b) is achievable, the sink is unreached.
EVIDENCE PRESENT (reached-sink) = (channel a) sidecar file contains
MANTIS_REACHED_ENTRYPOINT written in-path, OR (channel b) target-produced
backtrace/sanitizer output names the sink. A wrapper pre-launch marker alone is
NOT evidence present.
EVIDENCE ABSENT includes: any compiler/build nonzero exit; exit 127 (command not
found); exit 2 with a "No such file" message.
DECISION GATE (gate the DECISION, not specific verdict strings):
- Record repro_status = reproduced OR statically_confirmed ONLY if EVIDENCE is
PRESENT. If ABSENT -> repro_status = not_attempted (retry-eligible), STOP.
- In patch verification, EVIDENCE is required on the UNPATCHED baseline (Block G),
NOT on the post-patch attack run (a correct patch legitimately stops the input
before the sink).
- If NO evidence channel is achievable for this target, downgrade to
not_attempted / VERIFICATION_INCOMPLETE. NEVER synthesize the marker.
When complete, notify the user.