Protecting the console¶
/admin is the one door a person uses, and it holds the keys to every stored
production credential. /mcp must be reachable by your agents; /admin must be
reachable by as few people as possible. This page is the layered answer, from
what is on by default to what you add when the console is on the internet.
What is always on¶
- Sign-in is rate limited, checked before the password, so a locked-out client learns nothing from the answer.
- Every page carries hardening headers: a nonce-based Content-Security-Policy
with
default-src 'none', frames denied,nosniff, a same-origin referrer policy andno-store. State-changing requests must come from the console's own origin. - Sessions expire: 8 hours idle, 24 hours at most. Changing a password signs that user out of every other browser.
- Accounts arrive by invitation. An admin mints a one-time link (shown once;
only its hash is stored, good for 72 hours) and whoever opens it chooses their
own username and a password of at least 12 characters. The seeded
admin/adminmust be changed on first sign-in.
A second factor¶
Under Security every user can enrol an authenticator app: scan a QR code (or type the key), confirm one code, and save the ten recovery codes shown once. Each TOTP code and each recovery code signs in exactly once. Wrong codes count against the same rate limit as wrong passwords, so a known password does not buy unlimited guesses at six digits.
Make it mandatory:
A user without a second factor is then held at enrolment; an invitee goes there straight from the invitation. For a lost phone with no recovery codes left, an admin can reset another user's second factor from the Users page — that signs the user out everywhere and sends them back through enrolment.
Passkeys¶
Also under Security: add a passkey — Touch ID, Windows Hello, a security
key, or your password manager. A passkey signs in on its own, with no password
and no code, and cannot be phished: it answers only the console's own hostname.
It counts as the second factor, so a user with a passkey and no authenticator
app satisfies PRODPEEK_CONSOLE_REQUIRE_MFA. After a password, the
second-factor page accepts a passkey that belongs to that user. The server holds
only public keys and refuses an authenticator whose counter does not advance.
PRODPEEK_PASSKEY_RP_ID is optional and defaults to the console's hostname;
set a parent domain only if the same passkeys must work on several subdomains.
Behind a reverse proxy¶
Name the proxy, or every visitor shares its one login window:
PRODPEEK_TRUSTED_PROXIES=private # Traefik / Caddy / nginx on a Docker network
PRODPEEK_TRUSTED_PROXIES=private,cloudflare # …with Cloudflare in front
PRODPEEK_TRUSTED_PROXIES=10.0.0.5 # or the proxy's address, or a CIDR
The forwarded chain is walked right to left, skipping proxies you named; the
first address that is not one of them is the client. CF-Connecting-IP is
believed only when the hop that sent it really is a Cloudflare edge.
Cloudflare Access in front, verified at the origin¶
With the hostname proxied through Cloudflare (orange cloud), Zero Trust Access
puts Cloudflare's own sign-in in front of /admin before Prodpeek's login page is
ever served — with its bot protection, rate limiting and WAF in the same place.
The free plan covers up to fifty users.
- In Zero Trust → Access → Applications, add a self-hosted application for
your-host/admin. Policy: Allow the emails that may use the console. Use a one-time PIN or any identity provider you already have. - Add a second application for
your-host/admin/staticwith a Bypass policy for everyone: the stylesheet is public, and the/droppage a customer opens needs it. - Leave
/mcp,/api/v1,/mcp-adminand/dropout of Access. Agents hold keys, and a customer pasting a credential has no Access account. - Copy the application's audience tag from its overview page, then — and only once the record is actually proxied — tell Prodpeek to verify it:
PRODPEEK_ACCESS_TEAM=yourteam # the part before .cloudflareaccess.com
PRODPEEK_ACCESS_AUD=e91cc2c6… # the application's audience tag
Prodpeek then refuses any /admin request without a valid Access assertion
(RS256 against the team's published keys, audience, issuer, expiry). That is
what makes Access real: a request that bypasses Cloudflare and reaches the
origin's address directly is refused at the origin too.
Warning
Set PRODPEEK_ACCESS_* after the hostname is proxied. Before that no
request carries an assertion, and nobody can reach /admin.
If you invite a user while Access is on, add their email to the Access policy as well — otherwise their invitation link stops at Cloudflare's sign-in.
Only from your machine¶
Two further layers, independent of each other, both in Cloudflare Access:
- A fixed address: a Require rule with your IP range on the console's Access policy.
- A client certificate: Access mutual TLS with a certificate installed in your laptop's keychain. No agent software, and nothing else can even start the TLS handshake to the console.