agentsec · Tools · tunescan

Offline · stdlib-only · MIT

tunescan: detect poisoned or backdoored fine-tuning data in AI agents


Offline poisoning and hygiene scanner for LLM fine-tuning datasets (OWASP LLM04 Data and Model Poisoning). Run it on a JSONL file before you upload it for fine-tuning. It looks for the fingerprints a poisoned or careless dataset leaves behind: a rare trigger phrase that always maps to one fixed answer, an outside domain the completions keep pointing at, injected instructions, hidden Unicode, dangerous commands taught as answers, conflicting labels, embedded secrets, heavy duplication, and malformed records.

What it detects

Offline poisoning and hygiene scanner for LLM fine-tuning datasets (OWASP LLM04 Data and Model Poisoning). Run it on a JSONL file before you upload it for fine-tuning. It looks for the fingerprints a poisoned or careless dataset leaves behind: a rare trigger phrase that always maps to one fixed answer, an outside domain the completions keep pointing at, injected instructions, hidden Unicode, dangerous commands taught as answers, conflicting labels, embedded secrets, heavy duplication, and malformed records.

Standard library only. No network calls. Secret values are masked in every output format.

Rules

RuleSeverityWhat it flags
TS01highBackdoor trigger: a 1–3 word phrase found in a small share of prompts (at least --trigger-min, at most --trigger-rate) whose prompts are otherwise unrelated, yet 90%+ map to one completion that is rare elsewhere. Needs --min-records (default 20)
TS02mediumA domain that appears in 3+ completions but never in any prompt (promotion or exfil seeding). example.* and --allow-domain are skipped
TS03high (role tokens medium)Instruction-override phrasing ("ignore all previous instructions", "when you see 'X', always ..."), "don't tell the user", and fake chat-template delimiters (`<\
TS04high (critical with TAG text)Zero-width, bidi, and Unicode TAG characters; TAG payloads are decoded
TS05high (TLS-off medium)Completions that teach `curl \
TS06mediumThe same prompt with different short completions (conflicting labels)
TS07criticalSecret-shaped values: OpenAI/AWS/GitHub/Slack/Google key shapes, private key blocks, JWTs, credentialed connection strings, password=... labels (placeholders skipped)
TS08lowOne example repeated more than --max-dupes times (over-weighting)
TS09mediumBad JSON, unknown record shape, or no assistant turn
TS10infoInventory per file: record count, formats, and roles

Usage

python3 tunescan.py DATASET [...] [--json | --sarif] [--per-file]
                    [--max-dupes N] [--min-records N] [--trigger-min N] [--trigger-rate F]
                    [--allow-domain DOMAIN] [--min-severity LEVEL] [--fail-on LEVEL|none]
                    [--disable RULE] [--list-rules]

After pip install of the kit, the same options work as agentsec tunescan …. Every tool also accepts --help and almost every tool accepts --json.

Example output

examples/clean_chat.jsonl is the same support dataset without the poison and produces only the TS10 inventory line. examples/conflicting_labels.jsonl shows TS06 on a sentiment dataset. All data is synthetic and every secret is a FAKE-EXAMPLE-... value.

cd tools/tunescan
python3 examples/make_examples.py      # regenerates the synthetic datasets
python3 tunescan.py examples/poisoned_chat.jsonl --min-severity medium

SARIF and CI

agentsec tunescan path/to/input --sarif > tunescan.sarif

# GitHub Actions (composite action ships with the repo)
- uses: dunkleschitz/agentsec@v0
  with:
    tools: tunescan
    paths: path/to/input
    fail-on: high

SARIF 2.1.0 can be uploaded to GitHub code scanning. The composite action writes per-tool reports under agentsec-sarif/ by default.

OWASP mapping

Heuristic mapping of what this tool helps a reviewer look for. Not an official OWASP endorsement, and not complete coverage of any item. Sources: OWASP Top 10 for LLM Applications 2025 and OWASP Top 10 for Agentic Applications 2026.

Known limits

prompts. Semantic triggers (a topic or style rather than a token), triggers spread across many spellings, clean-label poisoning, and triggers in more than --trigger-rate of the data are missed. Very templated datasets can produce false positives; tune --trigger-rate or disable it.

works. Pair it with an evaluation run on the trained model.

Full source, tests, and fixtures: tools/tunescan/ in the repository. Install the kit with pip install git+https://github.com/dunkleschitz/agentsec.git.