Skip to content

Configure Wraith API twins and scrubbing rules

Wraith uses TOML files per twin workspace to control behaviour, security policy, and drift handling.

twins/<name>/
├── wraith.toml # Twin behaviour, thresholds, generation settings
├── scrub.toml # Security scrubbing rules
└── drift.toml # (optional) Drift suppression / reclassification hints

wraith.toml and scrub.toml are created automatically by wraith init. drift.toml is optional; add it only when wraith check reports drifts you want to suppress or reclassify.

Runtime simulation (fault injection, latency, rate limiting, tracing) is configured via CLI flags on wraith serve, not through wraith.toml. See Simulation for the full reference.


FieldTypeDefaultDescription
namestringrequiredTwin name (directory name under twins/)
base_urlstringrequiredUpstream API base URL
specstringnot setOptional path to OpenAPI spec
FieldTypeDefaultDescription
modestring"reverse"Proxy mode: reverse or forward
portu168080Port the recording proxy binds to
FieldTypeDefaultDescription
portu168081Port the twin server binds to
session_modestring"header"Session isolation: header, cookie, or path
fidelitystring"synth"Response mode: strict or synth (permissive is reserved, not yet implemented)
deterministic_seedu6442Seed for deterministic random generation
debugboolfalseEnable debug response headers
strip_headersbooltrueStrip non-allowlisted response headers (vendor cf-*, x-cache, x-served-by, x-powered-by, nel, report-to, etc.). Set to false to replay every recorded header verbatim.
rewrite_self_urlsbooltrueRewrite absolute URLs whose host matches service.base_url (response bodies and Location:) to point back at the twin so clients don’t leak off following next-page or related-resource URLs.
FieldTypeDefaultDescription
modestring"none"none, recorded, scaled, or fixed
min_msu640Minimum simulated latency (ms)
max_msu645000Maximum simulated latency (ms)
multiplierf641.0Multiply recorded latency by this factor
FieldTypeDefaultDescription
max_entities_per_typeu6410000Max entities per resource type
max_total_state_mbu64100Max in-memory state (MB)
max_namespacesu641000Max concurrent session namespaces
request_drain_timeout_msu645000Graceful shutdown drain timeout (ms)
FieldTypeDescription
limitu64Max requests per window
window_secondsu64Window duration in seconds

Resolution policy for created / created_at / updated_at timestamp holes. real (the default) reads SystemTime::now() per request so sequential creates produce strictly-increasing timestamps — matches every real API. deterministic advances a seed-derived monotonic counter from base_epoch for byte-identical golden replay. fixed freezes the clock at base_epoch.

FieldTypeDefaultDescription
modestring"real"real, deterministic, or fixed
base_epochu641640995200Unix seconds. Starting value for deterministic; frozen value for fixed. Ignored when mode = real.

Replay cache for Idempotency-Key-style POST headers. Disabled by default so non-Stripe twins don’t inadvertently get the behavior surprise — opt in per twin.

FieldTypeDefaultDescription
enabledboolfalseMaster toggle
headerstring"Idempotency-Key"Header carrying the idempotency key
ttl_secondsu6486400Cache entry TTL. Stale entries are lazily evicted on lookup.
[serve.idempotency]
enabled = true
header = "Idempotency-Key"
ttl_seconds = 86400
FieldTypeDefaultDescription
on_errorstring"fail""fail" returns HTTP 500 + structured JSON envelope when a Lua handler raises. "fallback" falls through to the synth template engine (legacy pre-v0.6.0 behavior; hid handler bugs from conformance, opt-in only).
FieldTypeDefaultDescription
required_scoref640.90Minimum overall conformance score
session_pass_ratef640.95Minimum fraction of passing sessions
scoring_versionu321Scoring algorithm version
FieldTypeDefaultDescription
status_exact_matchbooltrueRequire exact HTTP status match
body_structuref640.90Min structural similarity [0, 1]
body_valuesf640.85Min value similarity [0, 1]
symbol_consistencyf641.0Required symbol/token consistency
header_conformancef640.80Min response header conformance

Per-route threshold overrides. Same fields as [diff.thresholds] but all optional.

[diff.overrides."POST /v1/charges"]
body_structure = 0.95
body_values = 0.95

Override field classifications for specific JSON paths. Use hole-style paths (no body. prefix - it’s added automatically).

FieldTypeRequiredDescription
classifystringyes"generated", "timestamp", "constant", or "echoed"
valuesstring[]noAllowed values when classify = "enum"
[diff.fields]
# Force exact comparison on computed fields (not auto-suppressed)
"total" = { classify = "constant" }
"summary.total_value" = { classify = "constant" }
# Treat a field as a timestamp (type-only comparison)
"expires_at" = { classify = "timestamp" }

User-supplied classifications always override auto-detected ones.

Suppress specific divergences by route, path, and/or category. Supports * wildcards.

FieldTypeRequiredDescription
routestringnoRoute pattern (e.g. "POST /repos/*")
pathstringnoJSON path (e.g. "body.created_at")
categorystringnoDivergence category (e.g. "value_mismatch")
reasonstringyesHuman-readable explanation
[[diff.suppress]]
path = "body.created_at"
reason = "twin uses placeholder timestamps"
[[diff.suppress]]
route = "POST /repos/*/statuses/*"
category = "value_mismatch"
reason = "commit status fields are state-dependent"

Suppressed divergences are excluded from scoring but listed by --show-suppressed.

FieldTypeDefaultDescription
max_iterationsu3210Max agent optimisation iterations
token_budgetu64200000LLM token budget per generation run
time_budget_minutesu3230Time limit for generation run
regression_tolerancef640.0Acceptable conformance regression
FieldTypeDefaultDescription
entropy_thresholdf644.5Shannon entropy cutoff for symbol detection
min_string_lengthu324Ignore strings shorter than this
exclude_urlsbooltrueDon’t symbolise URL-like values
exclude_natural_languagebooltrueDon’t symbolise natural language
field_name_hintingbooltrueUse field names to guide symbolisation
FieldTypeDefaultDescription
min_exchanges_per_routeu323Min exchanges before pattern extraction
low_confidence_thresholdf640.20Below this, route is flagged low-conf
array_modestring"schema"schema or element
array_lengthstring"median"Length policy for variable-length arrays. median (default; back-compatible) collapses bimodal corpora — use p75, p90, or max for catalog / search APIs.
drop_empty_array_responsesboolfalseWhen true, responses whose every array is empty are excluded from anti-unification per status group (only when at least one non-empty response exists for that group — never prunes to zero).
max_array_representativesinteger or "all"8Distinct elements retained per variable-length array. Integer N keeps a deterministic sample of up to N elements in first-seen order. "all" retains every distinct element.
# Recommended config for bimodal / search APIs
[generate.anti_unification]
array_length = "p90"
drop_empty_array_responses = true
max_array_representatives = "all" # or a bound like 200

Recovers request → response correlation when a route’s response depends on a request field (a parent id, a useCase scope, a search filter). Without this, synth collapses every value to one global representative.

FieldTypeDefaultDescription
modestring"off"off (inert; pre-v0.8.0 behavior), manual, or auto. auto additionally detects keys for unruled routes when one strongly predicts the response.

Explicit per-route rules. Honored under both manual and auto.

FieldTypeRequiredDescription
routestringyes"METHOD /path/pattern" matching the synthesized path
fieldsstring[]yesRequest-body JSON paths ($.a.b.c). Multiple fields form a composite key.
[generate.request_keying]
mode = "manual"
[[generate.request_keying.route]]
route = "POST /v1/assets/actions/search"
fields = ["$.bulksearchv1AssetsInput.filter.parentId"]
FieldTypeDefaultDescription
numeric_idsbooltrueCollapse /users/123 -> /users/:id
prefix_pattern_idsbooltrueCollapse prefix-style IDs
value_flow_confirmationbooltrueUse value-flow graph to confirm
structural_alignmentboolfalseExperimental structural alignment
FieldTypeDefaultDescription
algorithmstring"decision_tree"decision_tree or rule_list
max_depthu324Max decision tree depth
z3_minimizationboolfalseUse Z3 to minimise guards
FieldTypeDefaultDescription
cross_route_unificationbooltrueUnify types across routes
enum_max_valuesu325Max distinct values before non-enum
enum_min_samplesu323Min samples to confirm enum type
FieldTypeDefaultDescription
default_runnerstring?not setDefault LLM runner name
fallbackstring[][]Fallback runner chain
air_gappedboolfalseDisable all network-based runners
FieldTypeDefaultDescription
commandstringrequiredRunner executable
argsstring[][]Command-line arguments
formatstring?not setOutput format (json, etc.)
FieldTypeDefaultDescription
sample_strategystring"risk_weighted"risk_weighted, random, coverage
budget_requestsu64500Max requests per refresh cycle
FieldTypeDefaultDescription
max_sessionsu641000Max recording sessions retained
max_total_size_mbu645000Max total recording size (MB)
retention_daysu6490Delete recordings older than this
max_body_size_mbu6410Max body size per exchange (MB)
FieldTypeDefaultDescription
enabledboolfalseForward unmatched requests to upstream
allowstring[][]Route patterns allowed for passthrough

A twin becomes an overlay when wraith.toml carries a [base] section. Without it, the twin is a “root” twin and overlay code paths are inert. See Overlays for the full reference.

FieldTypeRequiredDescription
artifactstringyesArtifact id of the provider twin
digeststringyesContent digest of the provider’s model (sha256:<64 hex>) — pins the overlay to a specific base version
model_schema_versionu32yesSchema version of the provider twin’s model
scrub_policy_hashstringyesHash of the provider twin’s scrub policy

Optional metadata describing the overlay twin.

FieldTypeRequiredDescription
ownerstringyesOwner of the overlay (e.g. team name) — required by wraith doctor / wraith lint
descriptionstringnoOverlay purpose and scope
requiresstring[]noDeclared dependencies on overlay artifacts (name@sha256:<64 hex>)

Permitted modifications. Defaults prevent accidental data loss; only set when explicitly opting into more invasive changes.

FieldTypeDefaultDescription
add_routesbooltrueAllow adding new routes
add_variantsbooltrueAllow adding disjoint variants on existing routes
add_schema_extensionsbooltrueAllow extending the schema
add_fixture_setsbooltrueAllow adding new fixture sets
add_fault_profilesbooltrueAllow adding new fault profiles
add_lua_handlersboolfalseAllow adding Lua handlers
override_variantsboolfalseAllow overriding existing variants
override_fixturesboolfalseAllow overriding existing fixtures
override_lua_handlersboolfalseAllow overriding existing Lua handlers

An override_* capability requires the matching add_* to also be enabled — wraith lint flags this as capability-inconsistent.


FieldTypeDefaultDescription
scrub_auth_headersbooltrueAuto-scrub Authorization, Cookie
scrub_cookiesbooltrueAuto-scrub Set-Cookie values
tokenize_modeTokenizeMode"hmac"Default action: hmac, redact, mask

User-defined scrub rules are applied in order after built-in rules.

FieldTypeRequiredDescription
scopeScrubScopeyesheader, cookie, jsonpath, regex, query_param
matchstringyesPattern to match (name, JSONPath, or regex)
actionScrubActionyestokenize, mask, redact, or pseudonymize
replacementstringnoRequired when action is mask
apply_toApplyTarget[]noheader_values, body, query

pseudonymize produces a deterministic user_<base62> HMAC envelope. Idempotent — re-running scrub on already-pseudonymized values is a no-op. Useful for username-shaped values in URL path segments or response bodies where you want a stable but obfuscated identifier.

Default PII detection runs on every recorded body and outbound response (wraith doctor audits recordings; wraith serve rescrubs outbound bodies). Covers email, phone, SSN, credit card (with Luhn check), name fields (via key-name + cardinality + value-shape heuristics), and git-author blobs (Bob <[email protected]>).

FieldTypeDefaultDescription
detectbooltrueMaster toggle. When false, wraith doctor skips the PII audit pass entirely (other doctor checks still run).
allowliststring[][]JSONPath-style glob patterns that bypass PII detection. * matches one path segment; leading $ is anchor-stripped; matching is suffix-based.
default_actionstring"tokenize"Action emitted when writing scrubbed PII. tokenize, redact, or reject. Note: parsed and stored for round-trip; not yet enforced at scrub-write time.
fields.alwaysstring[][]JSON paths unconditionally treated as PII regardless of cardinality detection. Used when auto-detection misclassifies a real PII field as enum (e.g. a small fixture where every entity has "Alice").

The detection chain is fields.always (forced) → allowlist (suppressed) → cardinality-detected enum_paths (skipped) → default name-key allowlist → heuristic classification. allowlist is the highest-precedence suppression layer.

[pii]
detect = true
allowlist = [
"$.country_code",
"$.timezone",
]
[pii.fields]
always = [
"$.author.email",
"$.viewer.name",
]

These 11 rules are always active regardless of user configuration:

ScopePatternAction
headerauthorizationtokenize
headercookietokenize
headerx-api-keytokenize
query_paramapi_keytokenize
query_paramsecrettokenize
query_parampasswordtokenize
query_paramtokentokenize
query_paramaccess_tokentokenize
query_paramrefresh_tokentokenize
query_paramclient_secrettokenize
regex\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\bredact
[defaults]
scrub_auth_headers = true
scrub_cookies = true
tokenize_mode = "hmac"
[[rules]]
scope = "header"
match = "x-api-key"
action = "tokenize"
[[rules]]
scope = "jsonpath"
match = "$.card.number"
action = "mask"
replacement = "****"
[[rules]]
scope = "jsonpath"
match = "$.card.cvc"
action = "redact"
[[rules]]
scope = "regex"
match = "sk_test_[a-zA-Z0-9]{24}"
action = "tokenize"
apply_to = ["header_values", "body"]

Optional. Lives next to scrub.toml at twins/<name>/drift.toml. Controls how wraith check treats drifts that it classifies in the conformance report.

Every divergence emitted by wraith check carries a stable drift_id (a fingerprint derived from route + path + category + expected/actual values) and a drift_type (e.g. additive_optional_field, field_removed, status_code_shift). drift.toml lets you suppress or reclassify drifts by glob-matching any of those fields.

Absent file is a silent no-op. Parse or validation errors are logged as warnings and the check continues.

Drop divergences that match the rule from the report. Suppressed drifts are counted separately in drift_suppressed_count.

FieldTypeRequiredDescription
drift_idstringnoMatch a specific drift_id (exact or glob)
routestringnoRoute pattern (e.g. "GET /v1/users/*")
pathstringnoJSON path (e.g. "body.created_at")
drift_typestringnoDrift classification (e.g. "additive_optional_field")
reasonstringyesHuman-readable explanation

At least one matcher field is required in addition to reason.

[[suppress]]
drift_type = "additive_optional_field"
route = "GET /v1/users/*"
reason = "backend adds optional fields on schedule; not worth reclassifying"
[[suppress]]
drift_id = "drift-9f2c4b8e1a3d5f70"
reason = "known harmless field-order change in search responses"

Change a drift’s drift_type without suppressing it. The drift_id is recomputed from the new classification.

FieldTypeRequiredDescription
matchtableyesSame matcher fields as [[suppress]]
new_drift_typestringyesReplacement classification
reasonstringyesHuman-readable explanation
[[reclassify]]
match = { route = "POST /v1/jobs", path = "body.status" }
new_drift_type = "enum_extension"
reason = "upstream adds new enum values often; not a schema break"

Pairing with [[diff.suppress]] in wraith.toml

Section titled “Pairing with [[diff.suppress]] in wraith.toml”

[[diff.suppress]] (in wraith.toml) removes divergences before they are classified as drifts. Use it for divergences that are inherent to your twin (placeholder timestamps, generated IDs) and shouldn’t be reported at all.

[[suppress]] (in drift.toml) keeps divergences in the report but suppresses them at the drift layer. Use it when a drift has been reviewed and accepted, but you still want the raw divergence visible in the JSON output.


VariableDescription
WRAITH_HMAC_KEYHMAC key for deterministic tokenisation. Required in CI.
CIWhen "true", missing HMAC key is a hard error (exit 3).
  • Set WRAITH_HMAC_KEY to any secret string for reproducible tokens across sessions
  • Without it, wraith generates an ephemeral key (tokens differ between runs)
  • In CI (CI=true), a missing key causes exit code 3 (security violation)
  • Never commit the key to version control - wraith doctor checks for this