Skip to main content

pretext

Turn a one-page persona into a runnable social-engineering exercise.

Narrative, opening message, escalation path, and the red flags defenders should have caught, generated, validated, then rendered where you need it.

ci python license

pretext rendering a simulation in the terminal

Authorized training only. Everything produced is a fictional exercise. Domains, names, and systems are invented unless you say otherwise. pretext never sends anything to a target; it writes the material and the analysis. Run an exercise against your own people only after your security team agrees to it, and check the lures before sending.

Why

Security-awareness decks are generic ("you receive an email with a typo"), so staff learn to spot a template rather than a technique. A useful exercise is grounded in one role at one company: what that person controls, who they trust, and what an operator would do next.

Doing that by hand costs an afternoon per persona. pretext takes a YAML profile and produces the exercise in one call, defender's side included, so training value does not depend on whoever wrote the scenario remembering to add it.

What you get

Section Content
Pretext narrative The operation, attacker-side: harvested detail, forged artifact, why contact lands today
Opening message Usable verbatim, in the sender's voice, carrying the sending address and one lure
Escalation path 3-6 stages across channels, each handing over a new prop or introducing a new person
Red flags Observable tells tied to this exercise, each with a defender action, including one only clear in hindsight

docs/running-an-exercise.md covers authorization, delivery, scoring and the debrief, which is the half the tool cannot do.

Every output carries a disclaimer. sample-output.md is a complete Markdown export; docs/demo.png is the terminal view.

Install

uvx pretext-sim generate persona.yaml     # no install
pipx install pretext-sim                  # or a permanent command

Python 3.10+. The distribution is pretext-sim; the command is pretext. For the source tree: uv sync --extra dev then uv run pretext.

Quickstart

pretext example -o persona.yaml            # fictional profile to edit
pretext validate persona.yaml              # schema check, no API call
pretext generate persona.yaml --dry-run    # resolved config + exact prompt, no call
pretext generate persona.yaml -f markdown -o exercise.md

Point it at any OpenAI-compatible chat completions endpoint with a flag, env var, or .env:

export PRETEXT_API_KEY=sk-...
export PRETEXT_BASE_URL=http://localhost:11434/v1   # local proxy, if you prefer
export PRETEXT_MODEL=gpt-oss:20b

Or name a known host instead of the URL: --provider openai|openrouter|venice|ollama|lmstudio (PRETEXT_PROVIDER), which fills the base URL. An explicit --base-url wins. Local providers need no key.

pretext config shows what resolves, with the key masked.

Profile

Only four fields are required (name, role, company, communication_style). The rest sharpen the result.

name: Dana Iyer
role: Senior Payroll Analyst
company: Northwind Logistics
communication_style: Brisk and formal, mixes payroll shorthand with chatty asides
department: People Operations
tenure: 3 months
reports_to: Marta Kwan
systems: [Workday, the payroll mailbox]
notes: New enough to the team to be unsure of process.

Optional: department, tenure, reports_to, systems, notes. Unknown keys are rejected so typos surface early. Fixture personas live in tests/fixtures/profiles.

Command reference

Command Purpose
generate PROFILE Produce one exercise
validate PROFILE Validate without calling the API
example Print the bundled fictional profile
batch DIR One exercise per *.yaml in DIR (files starting _ are skipped)
doctor Probe the endpoint: config, TLS, reachability, model, JSON mode
schema Print the JSON Schema model output must satisfy (--all adds the profile)
config Show resolved settings, key masked

Flags on generate:

Flag Effect
--channel Force the opening channel (email, phone, sms, chat, in_person, linkedin)
--extra Extra direction for the model
--format rich (default), markdown, json
--out Write to a file; plain text unless a format is chosen
--sections Render only some of pretext,opening,escalation,red_flags
--from-file Replay a saved response; no API call, no key
--dry-run Print resolved config and prompt, send nothing; --out writes the prompt
--usage Token counts on stderr
--base-url, --model, --api-key Override endpoint settings
--temperature, --max-tokens, --timeout, --retries, --seed Sampling and transport
--no-color Plain terminal output

Exit codes: 0 success, 1 bad profile/config/API/schema failure, 2 usage error, 130 interrupted.

--from-file accepts a --format json export, a bare simulation object, or a raw chat completion body, so formatting work stays free and offline.

Diagnose first

pretext doctor              # config, TLS, /models, JSON mode
pretext doctor --full       # also runs one real simulation through the schema

Exits 1 on any failure and prints the provider's own error, so a wrong model name or a blocked key shows up before a batch run wastes retries.

Batch runs

For a whole team, point batch at a directory and it writes one file per profile, continuing past failures:

pretext batch profiles/ --out-dir packet/ --format markdown

Progress and a wrote 7/9 summary go to stderr, the failure reasons are listed, and the exit code is 1 if anything failed. Endpoint flags are the same as generate; --sections, --format and --usage apply to the run as a whole.

Configuration

First hit wins:

  1. Flags (--base-url, --model, --api-key, --provider)
  2. Environment: the same names under PRETEXT_ (PRETEXT_API_KEY, PRETEXT_MODEL, ...)
  3. .env in the working directory
  4. OPENAI_API_KEY for the key, then https://api.openai.com/v1 and gpt-4o-mini

The key is never printed and never written to output.

How it works

profile.yaml ─> Profile ─> prompt ─> POST /chat/completions ─> extract JSON
                                                                    │
     rich | markdown | json  <─ render <─ validate <────────────────┘

Model output is untrusted. It is parsed out of fences or prose, validated against a strict pydantic schema, and only then rendered. Escalation stages must run 1..n with no gaps, red_flags needs at least two entries, and unknown keys are rejected, so a loose model fails loudly instead of producing a half-filled exercise.

Diagnostics go to stderr, keeping stdout pipeable: pretext generate p.yaml -f json | jq.

Releasing

Tagging vX.Y.Z runs release.yml: it checks the tag against __version__, gates on lint, format, types and tests, builds, then publishes a release whose notes come from CHANGELOG.md. Version bumps and changelog sections land before the tag.

Development

uv run pytest --cov=pretext # offline: MockTransport and a stubbed client
uv run ruff check src tests
uv run mypy

The suite needs no key. conftest.py chdirs each test into a temp directory so your own .env cannot leak in, and pins the terminal width so Rich reflow stays stable.

scripts/capture_corpus.py refreshes tests/fixtures/corpus, which tests/test_corpus.py checks for structural promises.

AGENTS.md documents the layout, the hardening rules, and the traps for agent contributors. CONTRIBUTING.md covers process; security reports follow SECURITY.md.

Status

Pre-1.0: the CLI surface and JSON schema can still move between minor versions; CHANGELOG.md records what changed.

Output has been checked against gpt-4o-mini, gemini-2.5-flash, claude-haiku-4.5 and llama-3.3-70b through OpenRouter, plus venice-uncensored-1-2 and qwen3-235b-a22b-instruct on Venice. All six held the JSON contract and used distinct channels. Weak models will still need prompt tuning, and the suite covers the pipeline rather than any one model's prose.

Not planned: delivery to targets, click tracking, an SMTP client. pretext writes the exercise; sending stays your job.

License

MIT. See LICENSE.

Metadata

Release files for pretext-sim 0.3.2

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

Source distribution (sdist)

Source distribution for pretext-sim 0.3.2
File Size Uploaded Provenance Actions
pretext_sim-0.3.2.tar.gz 589.0 kB With provenance Details for pretext_sim-0.3.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pretext-sim 0.3.2
File Interpreter ABI Platform Actions
pretext_sim-0.3.2-py3-none-any.whl Python 3 none any Details for pretext_sim-0.3.2-py3-none-any.whl

Total release size: 612.8 kB

Release files / pretext_sim-0.3.2.tar.gz

Download URL pretext_sim-0.3.2.tar.gz
Size 589.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a6290df4f1b8ea2e45b73bfc97c9dd722c947d21097992436a78a4d8a1dd270a
BLAKE2b-256 checksum
How to use checksums
8afdc4a156d900304423e64f56a2bb639faec7ab326e09ec46530bbaca94dd86
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 1, 2026.

Transparency log

Release files / pretext_sim-0.3.2-py3-none-any.whl

Download URL pretext_sim-0.3.2-py3-none-any.whl
Size 23.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
29d5578131d9ea5aaafab2135da920ee2776a5ee6428a38cf72b3887ca481482
BLAKE2b-256 checksum
How to use checksums
02d3cc6cc698c4843f5a4cd727c883422d468ecc097bafbc30f18808218abf27
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 1, 2026.

Transparency log

Security for pretext-sim 0.3.2

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 pretext_sim-0.3.2.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 1, 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.

Release history Release notifications | RSS feed

This release

0.3.2 This release

2 release files Full provenance

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