Skip to main content

scamscript: reverse-engineer the playbook, not the story

terminal demo: analyzing a script, then ranking a directory
Recorded against a local stub endpoint, so no API spend.

scamscript

CI Python License Scope

A reverse-engineering analyzer for leaked or public scam call scripts and vishing transcripts. Point it at a script and a model takes the fraud apart: the persona, the hook that catches you, how trust is manufactured, the pressure applied, what happens when you hesitate, and how to counter that exact playbook.

Security research and fraud-awareness training only. Output is a model-produced reading of a captured script, not legal advice and not attribution. A script is evidence of a pattern; the live scam that reuses it will differ in details.


Why

Scripts leak. Call centres dump their flows, victims publish transcripts, researchers collect samples. Reading them one by one teaches you that your account is at risk is bad, but not how a particular operation sequences trust into a transfer. This tool turns each artifact into a comparable record, so a corpus can be ranked and a defender's briefing can quote the actual mechanics instead of general advice.

Quickstart

pip install scamscript           # or, for development:
# git clone https://github.com/sjdsjadjajdjsa/scamscript && cd scamscript && pip install -e ".[dev]"

export API_KEY=...
export SCAMSCRIPT_BASE_URL=https://openrouter.ai/api/v1
export SCAMSCRIPT_MODEL=deepseek/deepseek-v4.1-flash-1017

scamscript --dry-run analyze --file examples/tech_support_scam.txt   # inspect the prompt, spend nothing
scamscript analyze --file examples/tech_support_scam.txt

Any OpenAI-compatible /chat/completions endpoint works. There is no baked-in provider or model: pass --base-url and --model, or set SCAMSCRIPT_BASE_URL and SCAMSCRIPT_MODEL.

scamscript --base-url https://openrouter.ai/api/v1 \
  --model deepseek/deepseek-v4.1-flash-1017 analyze --file script.txt

scamscript --base-url http://localhost:11434/v1 --model llama3.2 analyze --file script.txt

What it produces

Each script renders as a colour-coded panel:

╭──────────────────────────── tech_support_scam.txt ────────────────────────────╮
│ ATTACK TYPE   TECH SUPPORT SCAM     SOPHISTICATION  6/10                     │
│                                                                              │
│ OPENING HOOK                                                                 │
│   "Our telemetry shows your machine has been broadcasting error ..."         │
│                                                                              │
│ RED FLAGS                                                                    │
│   ✗ Unsolicited call claiming Windows telemetry                              │
│   ✗ Caller asks for a Remote Desktop session ID                              │
╰────────────────────────────────── 7 red flags ───────────────────────────────╯

Fields: attack_type, persona, opening_hook, trust_building_moves, urgency_tactics, escalation_ladder, target_demographics, red_flags, defense_summary, sophistication_score.

The panel also carries a quote check: every quoted claim in opening_hook and red_flags is compared against the source, and lines that are paraphrased or simply not there are marked. Use --strict-quotes to make a missing line fail the run, and --no-verify to skip it.

Batch mode ranks a whole directory into one markdown report. A redacted report from the sample corpus is at docs/sample-report.md.

Commands

Command Purpose
analyze --file Break down one script
analyze --dir --batch Directory, aggregated report
batch <dir> Same as above, ranked
report --run-id Regenerate a report from a past run's log
history List previous runs
prune Delete old logs

Useful flags: --dry-run, --json, --redact, --top N, --recursive, --timeout, --retries, --max-tokens, --concurrency, --max-cost, --dedupe/--no-dedupe, --show-duplicates, --type, --no-color. --json puts one object per script on stdout and all Rich output on stderr, so it pipes:

scamscript --json batch examples | jq -s 'map(select(.event == "result")) | length'

Scale and spend

--concurrency N runs up to 16 analyses at once and keeps output in script order. --max-cost stops scheduling new scripts once the provider reports that much spend, marks the remainder skipped and still exits zero when nothing failed.

scamscript --concurrency 6 --max-cost 0.25 batch corpus/ --output report.md

The cap reads usage.cost from the response, so it works only where the provider reports cost and says so when none is sent. Spend is printed and logged per run. In-flight requests can overshoot by up to the concurrency, so keep headroom on a hard limit.

Attack types

attack_type is free prose, which is useful to read and useless to group. Every result also carries a closed-set slug (vishing, smishing, tech-support, refund, gov-impersonation, bec, romance, crypto, investment, utility, delivery, other). Reports show counts and mean scores per slug above the ranking, and --type filters to the ones you ask for:

scamscript batch corpus/ --type tech-support,refund --output report.md

The raw phrase is always kept next to the slug. An unrecognised description lands in other and the CLI names any filter token it did not recognise.

Duplicates

Corpora are full of reprints. Each script is fingerprinted on its body: headers, casing, punctuation and whitespace are ignored, so copies collapse. The strongest copy is ranked and the rest are listed under it; the report counts what was folded and JSON records carry fingerprint and duplicate_of.

scamscript batch corpus/ --output report.md          # folds by default
scamscript batch corpus/ --no-dedupe --show-duplicates

Only byte-level twins fold. A copy with one changed account number stays separate, by design.

How it works

script ──▶ ingest (type/size guards, optional redaction)
        ──▶ analyst prompt ──▶ /chat/completions
        ──▶ JSON extraction ──▶ Pydantic schema
                 │ invalid / truncated
                 └──▶ one repair retry with the error fed back
        ──▶ Rich panel  |  JSONL record  |  ranked markdown
  • Validation. Replies are parsed tolerantly (fences, prose, string lists, 9/10 scores) and then validated. One malformed reply earns exactly one repair attempt; a second failure is reported, not retried forever.
  • Transport. Retryable statuses (408, 409, 425, 429, 5xx) and empty replies back off and retry within --retries; Retry-After is honoured. A finish_reason: length reply is treated as truncation and repaired with a shorter-answer instruction.
  • Reasoning models. If a provider returns only reasoning text with finish_reason: length, the client treats it as truncation, and when a provider parks the answer in reasoning with an empty content, that text is used as a last resort. Raise --max-tokens for models that think first.
  • Quote checking. Quoted claims are normalised (case, quotes, punctuation) and matched against the script. close means a paraphrase, missing means the line is not in the artifact. Verification never drops a finding, it only marks it for the reader.
  • Scoring. sophistication_score follows fixed anchors (1-2 one device, 5-6 staged trust and one deadline, 9-10 rehearsed objection handling and controlled transfers) so rankings compare across runs instead of drifting with prose.

Redaction and logs

--redact masks emails, card numbers, SSNs, phone numbers and long digit runs before the prompt is sent, before anything is logged, and inside the returned breakdown. Counts are logged. Regex masking is best effort: names, case IDs and spelled-out digits survive it.

Every request appends to a timestamped JSONL file under ~/.scamscript/logs/ (override with --home or SCAMSCRIPT_HOME). The API key is never written. Logs are the source for report, so a run replays without spending tokens, and prune --keep 200 --keep-days 30 keeps the store bounded. Directories are 0700 and log files 0600.

Install and configure

Python 3.12+. pip install scamscript for use, pip install -e ".[dev]" from a clone for tests. Release history is in CHANGELOG.md.

Setting Flag Env Default
API key --api-key API_KEY none, required unless --dry-run
Base URL --base-url SCAMSCRIPT_BASE_URL none, required
Model --model SCAMSCRIPT_MODEL none, required
State dir --home SCAMSCRIPT_HOME ~/.scamscript

Tests, CI and calibration

pytest

The suite mocks the HTTP layer with httpx.MockTransport; no network or key needed. CI runs the suite and a wheel build on Python 3.12 and 3.13.

Calibration is evidence, not vibes. scripts/calibrate.py measures first-attempt schema adherence, repair rate, latency, score spread and billed cost against a live endpoint:

API_KEY=... SCAMSCRIPT_BASE_URL=https://openrouter.ai/api/v1 \
  python scripts/calibrate.py --dir examples --repeat 3 \
  --model deepseek/deepseek-v4.1-flash-1017

Recorded passes with their numbers are in docs/calibration.md: three rubric repeats, a three-model comparison, an adversarial set and end-case stability runs. The current rubric reaches both ends of the scale, and quote checking flags the invented lines a plain reading would take as evidence.

The adversarial set lives in examples/adversarial/ and has its own harness:

API_KEY=... SCAMSCRIPT_BASE_URL=... python scripts/injection_eval.py --model ...

Format held in 7/7 cases; embedded demands were obeyed in 1 of 6. The fence protects the schema, not the classification.

Layout

src/scamscript/
  cli.py        command wiring, exit codes
  client.py     OpenAI-compatible client, retries, truncation
  analysis.py   per-script loop, one repair retry
  ingest.py     file and directory reading with guards
  models.py     Pydantic schema, tolerant JSON extraction
  prompts.py    analyst prompt and score rubric
  redact.py     identifier masking
  render.py     Rich panels and summary
  report.py     ranked markdown aggregation
  store.py      JSONL log, run discovery, retention
  verify.py     quote checking against the source
tests/          schema, retry, redaction, verification, client and CLI tests
examples/       six fictional transcripts, plus adversarial/ fixtures
scripts/        calibration and injection harnesses
docs/           calibration record, sample report, banner, demo

Limits

  • Breakdowns are model readings; a bad prompt or a weak model produces confident nonsense. Read opening_hook and red_flags against the source before quoting anything.
  • Redaction is regex-based and will miss names and identifiers without a fixed shape.
  • Duplicate folding ignores formatting only; a single changed digit keeps a copy separate.
  • Type slugs are keyword-matched, so an unusual description falls to other rather than inventing one.
  • The cost cap depends on provider-reported usage and can overshoot by the in-flight count.
  • Analysis is English-centric: the prompt and schema assume English artifacts.
  • Quote checking compares text, not meaning. A legitimate paraphrase left in quotes is flagged; an accurate paraphrase without quotes is skipped rather than judged.

Scope and disclaimer

For security research and fraud-awareness training only. Not legal advice, not an attribution tool, no warranty. Keep API keys out of commits; the key is read from the environment.

MIT licensed, see LICENSE. Contribution notes in CONTRIBUTING.md, vulnerability reports in SECURITY.md.

Metadata

Release files for scamscript 0.6.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for scamscript 0.6.1
File Size Uploaded Provenance Actions
scamscript-0.6.1.tar.gz 410.8 kB With provenance Details for scamscript-0.6.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for scamscript 0.6.1
File Interpreter ABI Platform Actions
scamscript-0.6.1-py3-none-any.whl Python 3 none any Details for scamscript-0.6.1-py3-none-any.whl

Total release size: 447.8 kB

Release files / scamscript-0.6.1.tar.gz

Download URL scamscript-0.6.1.tar.gz
Size 410.8 kB
Tags Source
SHA-256 checksum
How to use checksums
ecdcd195077d38fd852599759b4818c64f5ea8999f0878b39765c58c04e6aba0
BLAKE2b-256 checksum
How to use checksums
b97449d038ee00a49baa8875484b889d01879df311d755cbfbb25c86d3e84dc7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 2, 2026.

Transparency log

Release files / scamscript-0.6.1-py3-none-any.whl

Download URL scamscript-0.6.1-py3-none-any.whl
Size 37.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ea94d5a259320742843a9c1a7e7ccef88d3414ad8fc882391fbc69806665cdce
BLAKE2b-256 checksum
How to use checksums
2edeb6f51cfaac695c93eb9c3e36c0ae3baa2bd5f2ad590b35a92f31daeb4b70
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 2, 2026.

Transparency log

Security for scamscript 0.6.1

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for scamscript-0.6.1.tar.gz

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 Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page