class

Wiretap::Config

Inherits Reference < Object

Holds all runtime configuration for Wiretap.

Do not instantiate this class directly. Access it through Wiretap.configure to mutate settings, or Wiretap.reset_config to restore defaults.

Wiretap.configure do |c|
  c.transcript_dir = "spec/fixtures/transcripts"
  c.record_mode = :none
  c.filter_headers << "X-Session-Token"
  c.normalize_url = ->(url : String) { url.gsub(/sk-[a-z0-9]+/, "[FILTERED]") }
end

Instance methods

filter_headers

Header names whose values are replaced with "[FILTERED]" before the interaction is saved to disk.

Matching is case-insensitive. Defaults to ["Authorization", "X-Api-Key", "X-Goog-Api-Key", "Api-Key"] - covering the common Authorization: Bearer pattern plus the non-standard auth headers used by Anthropic / most LLM APIs (X-Api-Key), Google Gemini (X-Goog-Api-Key), and Azure OpenAI (Api-Key). Append additional names as needed:

c.filter_headers << "X-Session-Token"

To replace the list entirely use filter_headers.replace(...), though this discards the defaults and should be done deliberately.

Source
ignore_suspected_secrets

Header names to exempt from the suspected-secret warning (see on_suspected_secret), even though their value looks like it could be a credential.

Use this for headers you've verified are safe - a randomly-generated idempotency key or trace ID, for example - rather than silencing the warning globally.

c.ignore_suspected_secrets << "X-Idempotency-Key"
Source
ignore_suspected_secrets=(ignore_suspected_secrets : Array(String))

Header names to exempt from the suspected-secret warning (see on_suspected_secret), even though their value looks like it could be a credential.

Use this for headers you've verified are safe - a randomly-generated idempotency key or trace ID, for example - rather than silencing the warning globally.

c.ignore_suspected_secrets << "X-Idempotency-Key"
Source
normalize_body

Optional proc applied to the request body before it is hashed for matching.

Use this to strip non-deterministic fields (timestamps, user IDs, UUIDs) so requests still match across machines and CI runs despite those fields changing on every call. This affects matching only: the real outbound request body is unaffected, and the body saved to the transcript is the raw, un-normalized body actually sent. If you need to redact sensitive fields from the saved transcript itself, that is a separate concern from matching (see filter_headers for the header equivalent).

c.normalize_body = ->(body : String) {
  parsed = JSON.parse(body).as_h
  parsed.delete("user")
  parsed.to_json
}
Source
normalize_body=(normalize_body : Proc(String, String) | Nil)

Optional proc applied to the request body before it is hashed for matching.

Use this to strip non-deterministic fields (timestamps, user IDs, UUIDs) so requests still match across machines and CI runs despite those fields changing on every call. This affects matching only: the real outbound request body is unaffected, and the body saved to the transcript is the raw, un-normalized body actually sent. If you need to redact sensitive fields from the saved transcript itself, that is a separate concern from matching (see filter_headers for the header equivalent).

c.normalize_body = ->(body : String) {
  parsed = JSON.parse(body).as_h
  parsed.delete("user")
  parsed.to_json
}
Source
normalize_url

Optional proc applied to the request URL before matching and saving.

Use this to scrub API keys or session tokens embedded in the URL path or query string. The real outbound request is unaffected.

c.normalize_url = ->(url : String) { url.gsub(/sk-[a-z0-9]+/, "[FILTERED]") }
Source
normalize_url=(normalize_url : Proc(String, String) | Nil)

Optional proc applied to the request URL before matching and saving.

Use this to scrub API keys or session tokens embedded in the URL path or query string. The real outbound request is unaffected.

c.normalize_url = ->(url : String) { url.gsub(/sk-[a-z0-9]+/, "[FILTERED]") }
Source
on_suspected_secret

Called when a header that is not in filter_headers has a value that looks like it could be a credential (a known API key format, or a long, high-entropy value on a header named like "key", "token", "secret" or "auth"). Receives the header name and its unfiltered value.

This is a warning, not a filter - the header is still saved as-is. Defaults to printing a warning to STDERR. Two ways to act on it:

  • If it's a real credential, add the header name to filter_headers so it's redacted (this also stops the warning, since filtered headers are never checked).
  • If it's a false positive, add the header name to ignore_suspected_secrets to silence just that header.

Override the proc itself to change how the warning is delivered, e.g. to raise instead of printing:

c.on_suspected_secret = ->(name : String, value : String) {
  raise "Unfiltered header '#{name}' looks like a secret" if ENV["CI"]?
}
Source
on_suspected_secret=(on_suspected_secret : Proc(String, String, Nil))

Called when a header that is not in filter_headers has a value that looks like it could be a credential (a known API key format, or a long, high-entropy value on a header named like "key", "token", "secret" or "auth"). Receives the header name and its unfiltered value.

This is a warning, not a filter - the header is still saved as-is. Defaults to printing a warning to STDERR. Two ways to act on it:

  • If it's a real credential, add the header name to filter_headers so it's redacted (this also stops the warning, since filtered headers are never checked).
  • If it's a false positive, add the header name to ignore_suspected_secrets to silence just that header.

Override the proc itself to change how the warning is delivered, e.g. to raise instead of printing:

c.on_suspected_secret = ->(name : String, value : String) {
  raise "Unfiltered header '#{name}' looks like a secret" if ENV["CI"]?
}
Source
record_mode

Default record mode applied to all Wiretap.intercept calls.

ModeBehaviour
:onceRecord if no transcript exists; strict replay if one does.
:alwaysAlways re-record, discarding any existing transcript.
:noneStrict replay only. Raise Error on any unmatched request.

Defaults to :once. Override per block via the mode: keyword on Wiretap.intercept.

Source
record_mode=(record_mode : Symbol)

Default record mode applied to all Wiretap.intercept calls.

ModeBehaviour
:onceRecord if no transcript exists; strict replay if one does.
:alwaysAlways re-record, discarding any existing transcript.
:noneStrict replay only. Raise Error on any unmatched request.

Defaults to :once. Override per block via the mode: keyword on Wiretap.intercept.

Source
transcript_dir

Directory where transcript JSON files are stored.

Defaults to "spec/transcripts". The directory and any intermediate parents are created automatically on first save.

Source
transcript_dir=(transcript_dir : String)

Directory where transcript JSON files are stored.

Defaults to "spec/transcripts". The directory and any intermediate parents are created automatically on first save.

Source