Skip to content

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 and no-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 / admin must 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:

PRODPEEK_CONSOLE_REQUIRE_MFA=true

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.

  1. 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.
  2. Add a second application for your-host/admin/static with a Bypass policy for everyone: the stylesheet is public, and the /drop page a customer opens needs it.
  3. Leave /mcp, /api/v1, /mcp-admin and /drop out of Access. Agents hold keys, and a customer pasting a credential has no Access account.
  4. 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.