Your login config file is published by spec, and it might be pointing at a box on your internal network
Every site that does "sign in with" anything publishes a small JSON file at /.well-known/openid-configuration. It is supposed to be public. An OpenID Connect client reads it before the first login to learn where the authorization endpoint is, where to swap a code for a token, and where the signing keys live. The whole protocol depends on that file being readable by strangers, so there is nothing to hide and nothing to lock down.
The problem is what some of those files say. Open one up and you usually see a tidy list of https://login.yoursite.com/... URLs. But every now and then the file was generated on a machine that didn't know its own public name, and the URLs inside point somewhere they shouldn't: https://auth.svc.internal/token, or https://10.4.7.21:8443/authorize, or a jwks_uri on a host that only resolves inside your VPC. The file is still public. The address it now advertises is not.
What the file is supposed to look like
A healthy discovery document is boring, and boring is the goal. Every endpoint resolves to a public production host you control or to your identity provider.
That is the version a scanner reads, shrugs at, and moves on from. It is genuinely fine. The check that matters fires on the other kind: the same file, same status, same Content-Type, but with one of those endpoints rewritten to a name or address that was never meant to leave the building.
The version that gives the game away
Here is the same request against a site whose discovery doc was templated from a staging or internal config and shipped to production unchanged.
Two of these endpoints just told a stranger the name of an internal service and the private IP of the box that holds your signing keys. Neither is reachable from where the attacker sits, and that is not the point. The point is that your public identity surface has a leak: it confirms you run an internal host called auth.svc.internal, it confirms a 10.4.7.x subnet, and it tells anyone who finds a foothold later exactly where to walk. It is the kind of detail that turns a generic phishing attempt into a targeted one, and a slow internal pivot into a fast one. It also tells the reader something useful on its own: the public config was generated from a non-production environment, so it is worth checking what else in production was templated the same way.
Why the check is narrow on purpose
There is a version of this check that would be lazy and wrong, and it's worth saying what it does not do. It does not flag a discovery doc just because an endpoint label contains the word "staging" or "dev." A host like sandbox.yoursite.com or demo.yoursite.com is very often a real, deliberate public environment, and calling that a misconfiguration would be a false alarm on a brand that did nothing wrong. So the finding stays quiet on those and fires only on the unambiguous case: an endpoint resolving to a private IP, a .internal / .local / .corp / .consul / .intranet hostname, or a localhost address. Those are not "looks non-prod," they are "cannot be public," and a public file should never name one.
The read is also strictly passive. It is a single GET of a file the OIDC spec already tells every client to fetch, parsed as JSON. No login is attempted, no token endpoint is called, no key is requested. The scanner reads what the protocol publishes and stops there.
Fixing it is a template fix, not a lockdown
You don't want to hide the discovery document, you want it to be true. Generate it from your production issuer URL so every endpoint resolves to a host the public is supposed to reach.
# discovery doc baked with internal/staging values token_endpoint: https://auth.svc.internal/token jwks_uri: https://10.4.7.21:8443/jwks.json # -> a public file naming internal infrastructure
Set the issuer URL to your real public identity host and let the provider derive the endpoints from it, which is what most of them do automatically. If you build the document by hand, stop copying endpoint URLs out of a development or staging config. Then check the deployed file: issuer, authorization_endpoint, token_endpoint, jwks_uri, and userinfo_endpoint should every one resolve to a host you're happy to see on the public internet. If any of them names an internal box, that's the line to fix.
Reading it from outside
A stranger settles this in one request. They fetch the discovery document the same way every OIDC client does, read the five endpoint URLs, and see whether any of them names a place that shouldn't be public. SurfaceCheckr does exactly that, from the outside, with no credentials: it pulls /.well-known/openid-configuration, confirms it's a real OIDC document, and flags it only when an endpoint resolves to a private IP, an internal-suffix hostname, or localhost, never on a normal public config and never on a deliberate public sandbox. It reads the file the protocol already hands out and goes no further. If you want the wider picture of what your own pages quietly volunteer, the internal hosts and dev notes hiding in your page source is the companion read, and the full passive-recon view is the frame around all of it.
Read next
- What your page source says when you're not looking: internal hosts, dev notes, emailsWhat an attacker sees before they touch your site
- Your OData API will hand a stranger the entire shape of your database, no login requiredWhat an attacker sees before they touch your site
- Your /metrics endpoint is a live readout of your internals, and it's publicWhat an attacker sees before they touch your site
- Your API is answering errors with stack traces, SQL errors, and internal pathsWhat an attacker sees before they touch your site
Find it before someone else does.
Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.