Keep secrets out of logs, reports, and captures
Tests often need credentials: an API token in a request header, a password typed into a login form, a key in a .env file. Doc Detective uses the real values to run your steps. Everything it writes shows them as ***NAME*** instead: logs, reports, results files, and the GitHub Action’s issues.
Masking is on by default. Your steps, assertions, and routing conditions still compare real values, so a login test that checks what a user typed keeps working.
What Doc Detective masks
A value is masked when it came from one of these places:
- A
$NAMEthat Doc Detective substituted, in a step, the config, or an OpenAPI example. That includes a variable a step set withvariables. - A variable loaded from your project’s
.envfile, aloadVariablesfile, or a credential file (apiKeyFileorapiTokenFile), even if no step uses it. - A variable whose name contains
TOKEN,SECRET,PASSWORD,API_KEY,PRIVATE_KEY, orCREDENTIAL, in any case, even if no step uses it.
Doc Detective never masks a value shorter than 4 characters, or the values true, false, and null. So a variable such as DEBUG=1 doesn’t erase every 1 in a report. Other variables in your environment, such as HOME or GITHUB_REF_NAME, aren’t masked unless a step uses them.
At the debug log level, the run lists the variables it masks, by name only:
When two variables hold the same value, the mask shows the first one’s name. That debug line and the run’s hint list both names.
Show a value that isn’t secret
Some values a run uses aren’t secret, such as a base URL. To show one, list its name in publicVariables. * matches any characters.
You can set publicVariables on the config, on a spec, or on a test, including an inline test in a doc. A name listed anywhere is public for the whole run. One variable has one value across a run, so once any test shows it, masking it elsewhere protects nothing. A spec or a test can’t make a name secret again.
On Windows, names match without regard to case, the way Windows environment variables do.
For a one-off run, set it through DOC_DETECTIVE_CONFIG. ["*"] turns masking off:
When a run masks a variable whose name doesn’t look secret, it suggests listing it:
A path or URL built from a masked value is masked in reports too. If a report can’t link a file whose path holds a variable’s value, make that variable public.
For every config option, see the config reference.
Forms Doc Detective catches
Doc Detective masks a value in each form that commonly reaches output:
Saved files
When runShell, runCode, httpRequest, or runBrowserScript saves its output with path, the file holds ***NAME*** in place of each secret value. A later run masks its new output the same way before it compares the two with maxVariation. So a saved file still matches when the secret behind it changes.
A file saved before version 5 holds the real value, so its first masked comparison can report a variation. Save it again with overwrite, or delete it, and the next run compares masked output.
saveCookie files are the exception. They hold the real cookie, because loadCookie reads them back. A cookie saved with variable holds the real value too. See What masking can’t catch.
Screenshots and recordings
Masking text can’t change pixels, so in a browser Doc Detective hides secrets on the page itself:
- Typed secrets. A
typestep, or thetypeoption of afindstep, can type a secret into a field. The field then shows dots for the rest of the page’s life. Later screenshots and recording frames show the dots too. The field still holds the real value, so the form works. - Screenshots. Before each browser screenshot, Doc Detective blurs every element whose text or form value holds a secret. That covers
screenshotsteps and automatic screenshots. It takes the capture, then removes the blur. The page never receives your secrets. Doc Detective reads the page’s text and decides itself which elements to blur, so a page’s own scripts can’t collect them.
Because the blur changes the image, a reference screenshot taken before version 5 that showed a secret differs from new captures. Retake it.
These captures can’t hide a secret:
- page text that shows a secret while a recording runs, because only screenshots blur;
- text inside iframes or shadow DOM, text drawn on a canvas, and text from CSS pseudo-elements such as
::before; - a secret split across sibling elements whose parent has no text of its own, such as
<div><span>hun</span><b>ter2</b></div>; - native app and device screenshots and recordings.
What masking can’t catch
- Transformed values. A value that a command hashes, splits, reverses, or encodes some other way isn’t masked.
- Variables no rule covers.
runShellandrunCodecommands inherit the whole environment. A command can print a variable no step substituted. Doc Detective masks it only when a.envorloadVariablesfile set it, or when its name looks secret. A secret-looking name counts only for variables present when the run started, such as CI secrets, and for variables a step set withvariables. - Saved cookies. A
saveCookiefile holds real cookie values, becauseloadCookiereads them back. Keep these files out of version control. A cookie saved withvariableisn’t masked until a later step substitutes it as$NAME, even when the variable’s name looks secret. Don’t print the variable or list the environment inrunShellorrunCodecommands. - Collisions. A secret that matches ordinary text, such as a short dictionary word, also masks that text.
- Steps you run from code.
getRunner().runStepin the JavaScript API returns a step’s real result to your program, so mask anything you print from it yourself.runTests()returns masked results.