Secrets and redaction
apifae record proxies your traffic, and traffic carries credentials. This page
says exactly what ends up on disk, what does not, and how to change it.
What is redacted
Request header values, unless the header is one of these:
accept, accept-encoding, accept-language, content-type,
content-length, user-agent, referer, origin, and anything starting
sec-fetch- or sec-ch-ua.
Everything else — authorization, cookie, x-api-key, and any custom header
your API uses — is written as <redacted>.
This is an allow-list on purpose. A deny-list only protects against headers
somebody thought of in advance, and a header named X-Acme-Session is exactly
the one nobody would have listed. Nothing reads these values back, so keeping
fewer of them costs nothing.
Request query values, when the parameter name contains any of api_key,
apikey, access_token, token, secret, signature, sig, password or
auth. Matched case-insensitively, as a substring, so apiKey and
user_password both match.
Query uses a deny-list rather than an allow-list because a query value usually
carries meaning you want when reading a capture — ?status=active&page=2 is
the request, not noise around it.
Bodies
When record --convert writes a mock, it walks the response body and replaces
string values whose key looks like a credential — a key containing token,
secret, password, apikey, credential, authorization or privatekey,
matched at any depth, case-insensitively, ignoring - and _.
- path: /login
method: POST
responses:
- status: 200
body:
access_token: <redacted>
refresh_token: <redacted>
expires_in: 3600
user:
email: a@b.c
Only strings are replaced. expires_in: 3600 and token_count: 5 keep their
values and their types — rewriting a number to "<redacted>" would hand a
consumer of the mock something it never expected.
The cost of substring matching is over-redaction: a field called tokenizer is
replaced too. Given the alternative is a token in a committed file, that is the
trade this makes.
Add your own keys with convert.redact_body_keys, or turn the whole thing off
with convert.redact_bodies: false or --no-redact.
What is not
Raw and binary bodies. A body stored as raw_body (text, XML, CSV) or
raw_body_base64 (images, gzip, protobuf) is never scanned. There are no keys
to match on, so the only option would be a regex over arbitrary text — the kind
of check that silently matches nothing and reports success.
record --convert names them instead:
Warning: 2 responses have non-JSON bodies (raw_body / raw_body_base64).
These are NOT scanned for secrets:
POST /oauth/token
GET /export.csv
Review before committing.
Recorded sessions. .apifae/recordings/ keeps the real response bodies on
purpose: a capture is meant to be a faithful record of what the API returned. It
is gitignored, and body redaction happens on the way into mocks/, which is the
directory you commit.
Traffic in flight. Redaction happens on the way to disk. Your API receives exactly the request your client sent, header for header. Recording never changes what the target sees.
What a redacted session looks like
request:
method: GET
path: /orders
headers:
user-agent: curl/8.7.1
authorization: <redacted>
accept: application/json
query:
api_key: <redacted>
status: active
The header names survive. You can see the request was authenticated and which header carried it; you cannot recover the credential.
Each session's metadata.yaml records whether it was redacted:
name: demo
request_count: 1
unique_endpoints: 1
bodies_dropped: 0
redacted: true
so a session you find later is not ambiguous.
Turning it off
For one run:
$ apifae record https://api.example.com --no-redact
For a workspace, in apifae.yaml:
record:
redact: false
The flag wins over the config. Either way record prints a warning when
redaction is off — at the start of the session and again at the end, because
the first one has scrolled away by then.
Keeping specific headers
Rather than turning redaction off wholesale, opt individual headers back in:
record:
keep_headers:
- x-request-id
- x-trace-id
authorization, cookie and proxy-authorization are never kept, whatever
this list says. If you genuinely need one of those on disk, use --no-redact
and be deliberate about it.
Redacting more query parameters
record:
redact_query:
- tenant_ref
Names here extend the built-in list and are matched the same way.
Recordings and git
apifae init adds .apifae/ to your .gitignore. If your workspace predates
that, add it yourself:
.apifae/
A redacted session is still a record of every path your client called and every body your API returned. That is usually fine to keep locally and rarely something to publish.