---
title: How to monitor a login page without leaking the password
description: A monitor behind a login needs a password or token. Where it lives, who can read it back and what shows up in results decides whether it leaks.
canonical: https://watchfor.io/blog/monitor-login-page-without-leaking-credentials
---

# How to monitor a login page without leaking the password

2026-10-20 · WatchFor Team · Security · security, synthetic-monitoring, authentication, playwright

A monitor behind a login needs a password or token. Where it lives, who can read it back and what shows up in results decides whether it leaks.

The check on the orders API had been red for ten minutes, and someone on the
team did the reasonable thing: opened the failing result, copied it and pasted
it into the team channel with "anyone know why this is 401?".

The answer was in the paste. So was the bearer token, sitting in the echoed
request headers, now in a chat history that half the company can search and
that nobody was ever going to clean up.

Nobody did anything wrong there. The monitor needed the token to do its job,
the result showed what was sent, and copying a result is what you do when you
want help. That is the uncomfortable part of monitoring anything behind a
login: the monitor has to know a secret, and every place the monitor writes
something down is a place that secret can end up.

This post is about the places we decided a secret should never end up, and
how to set up a check on a login page or a token-protected API so it doesn't.

## Where a check's secret tends to leak

When you give a monitor a credential, it can escape through more doors than
the one you are watching:

- **The settings form.** If the field shows the saved value, anyone with
  access to the monitor — or a screenshot of it — has the password.
- **The API.** If reading a monitor's config returns the token, every script,
  integration and AI agent with a read key has it too.
- **Check results.** Servers echo requests back: debug endpoints, error pages,
  `Set-Cookie` headers, a JSON body with an `access_token`. If results are
  stored verbatim, the secret is stored too.
- **The audit trail.** A log of "who changed what" that records the old and
  new value of a password field is a password log.
- **Copies.** A token pasted into fifty monitors is a token nobody will rotate,
  because rotating it means fifty edits.

## Saved, not shown

For HTTP, API and MCP checks, the basic or digest password, the bearer token
and the value of every credential header — `Authorization`,
`Proxy-Authorization`, `Cookie`, `X-API-Key`, and any header with *token*,
*secret* or *key* in its name — are **write-only**. You type the value once.
After that the form shows it as saved, with a **Replace** button:

![The Authentication section of an HTTP check: the bearer token and an X-Api-Key header both show "•••••• saved" with a Replace button](/blog/monitor-login-page-without-leaking-credentials-saved-fields.webp)

The value is stored encrypted for that one monitor and is never sent back to
the browser or the API, not to the organization's owners either. Leaving the
field as it is keeps the saved value; there is no way to "reveal" it, because
the dashboard never has it to reveal.

Reading a monitor through the REST API or the MCP server returns these fields
as `[redacted]`. That matters more than it sounds: an agent or a script that
reads a monitor, changes the interval and writes the config back will send
`[redacted]` back with it. We treat that as "keep what's saved", so the round
trip neither leaks the token nor quietly replaces it with the word
"redacted".

## A saved secret stays with the address it was entered for

A credential is entered for a particular server. If someone later edits the
check to point somewhere else — another host, another port, plain HTTP instead
of HTTPS, a different basic-auth username — the saved value does not follow.
The form asks for the credential again before the change can be saved. The
same happens if certificate verification is switched off, which would let the
check hand the secret to whoever answers on that address.

The reason is simple: changing a URL should never be a way to send someone
else's token to a server you control. Nobody has to remember that rule; the
form enforces it.

## One secret, many checks: `{{NAME}}`

Most teams have a handful of credentials behind dozens of checks: one API
token for every endpoint of an API, one monitoring account for the login and
the checkout. Typing the token into each check is how it ends up never being
rotated.

Instead, add it once under **Settings → Variables & secrets** and refer to it
by name:

![The Variables & secrets settings: LOGIN_PASSWORD and ORDERS_API_TOKEN are secrets shown as dots, LOGIN_USERNAME is a plain variable, and the token is used by one check](/blog/monitor-login-page-without-leaking-credentials-variables.webp)

In an HTTP, API or MCP check, type `{{NAME}}` wherever the value would go —
the username, the password, the bearer token, a header value such as
`Bearer {{ORDERS_API_TOKEN}}`, or the request body. The check stores the
reference as you wrote it; the secret itself stays encrypted and is filled in
on the probe, for each request. A few properties fall out of that:

- **Rotation is one edit.** Change the secret and every check that uses it
  sends the new value on its next run. The **Used by** column shows how many
  checks that is before you touch it.
- **A deleted secret fails loudly.** If a check refers to a name that no
  longer exists, it fails and says which name is missing, instead of sending
  the literal text `{{NAME}}` to your server.
- **Secrets are never shown again.** A plain variable (a username, a URL) is
  visible to members; a secret can be replaced but never read back, by
  anyone.

Browser checks read the same list as `process.env.NAME`, so a Playwright
script never has to contain a password. Only the variables a script actually
references travel with its runs. If you are writing one,
[monitoring a login with 2FA](/blog/monitor-a-2fa-login-with-playwright) and
[a checkout flow, step by step](/blog/monitor-checkout-flow-with-playwright)
show it end to end.

## What the result shows when the server echoes your token

Back to the paste in the team channel. Here is the same kind of check in
WatchFor, against an endpoint that echoes every request header back in its
response body:

![A probe result for a check against an echo endpoint: the badge says 3 sensitive values were redacted on the server, and the echoed authorization header reads "Bearer •••••• redacted"](/blog/monitor-login-page-without-leaking-credentials-redacted-result.webp)

The server did echo the token. The result doesn't contain it. Before a check
result is stored, and again before it is shown, anything shaped like a
credential is replaced: `Authorization` and `Set-Cookie` values, tokens and
keys in URLs and query strings, session ids, JWTs and well-known API-key
formats inside a response body. The result says how many values that touched,
so you know it happened and nobody has to wonder whether **Copy JSON** is safe
to paste.

The [activity log](/docs/organization/activity-log) follows the same rule: it
records that a password or token changed, never what it changed from or to.

## Where we can't help, honestly

Redaction recognises secrets by shape and by the exact values you saved. It
can't know everything:

- **Values derived from a secret.** A session token the site issues after
  sign-in, a hash, a password split across two fields — these don't look like
  the secret you saved. In browser checks, `Cookie` and `Authorization`-style
  headers are redacted in the trace, but a token returned in a response body
  is kept in the trace's network view. If that worries you, turn the trace off
  for that check.
- **A page that displays the secret.** If the page you're checking prints the
  password in clear text, it can appear in the trace's film strip.
- **People who can run code.** A browser check runs with the secrets its
  script names, and a script can send a value anywhere. So anyone who can edit
  or test-run a script — owners, admins, API keys with the write scope — can
  use a secret, even though none of them can read it. Give write keys only to
  tools you trust.

The [browser check docs](/docs/monitors/browser-checks/variables) go through
these limits in detail.

## A setup that holds up

If you only take a few things from this:

1. **Use a dedicated monitoring account** for every login you check, with the
   least access that still exercises the path. If its password ever leaks, it
   opens a read-only dashboard, not your admin panel.
2. **Put every credential in Variables & secrets** and refer to it as
   `{{NAME}}` or `process.env.NAME`. Rotate it there.
3. **Prefer a token over a password** where the service supports it, and give
   the token only the scope the check needs.
4. **Keep traces on while you set up, then decide.** For a check whose pages
   show sensitive data, turn the trace off once the check is stable.
5. **Share results freely.** That one is the point of all of the above.

For the general picture of keeping keys out of code, chat and git, see
[Secrets Management Explained](/blog/secrets-management). The exact field
rules for HTTP, API and MCP checks are in
[Web checks → Credentials and variables](/docs/monitors/web#credentials-and-variables).

---

Canonical page: https://watchfor.io/blog/monitor-login-page-without-leaking-credentials · All articles: https://watchfor.io/blog/all.md · Site guide: https://watchfor.io/llms.txt
