See what a hostile web page can learn about your internal GitLab
Open this page and point it at a GitLab you can reach. Through nothing but the browser, it fingerprints the version, then shows which CVEs that version is exposed to and which of those a malicious website might reach with no login. On recent Chrome, reaching an internal address also needs the visitor to accept a local-network permission prompt.
What this is, and why it matters
Many organisations run GitLab as the internal, free alternative to GitHub and assume a VPN or an IP allowlist keeps it safe. That assumption is the gap.
The drive-by CORS route, precisely
By default GitLab answers cross-origin requests to /api/* and
/api/graphql from any origin, with
Access-Control-Allow-Origin: *. So a page on
evil.com can read anonymous JSON from
git.internal.example through the browser of anyone who merely
visits evil.com. No login, and no click beyond the visit (on
recent Chrome, an internal target also needs the visitor to accept a
local-network permission prompt; see below). The visitor's browser is the
bridge from the attacker's public page to the internal host.
evil.com.https://git.internal/api/*; the browser sends it from the visitor's allowlisted network.Access-Control-Allow-Origin: *, so the attacker's page can read the response.- A GitLab on a public IP protected only by an IP allowlist is reachable this way: the request comes from the visitor's allowlisted browser, not the attacker. The attacker sees what an anonymous user inside the allowlist would see, which is enough to fingerprint the version.
- The wildcard is set without credentials, so even if a browser attached the visitor's session cookie, the attacker's page could not read the response. What it can read is anonymous data only, and this tool never counts a CVE as reachable when the real trigger needs a login.
- Chrome 142 and later put a public page reaching a private or loopback address behind a Local Network Access permission prompt. Firefox, Safari, older Chrome, Chrome where an administrator has turned the control off, and any GitLab on a public IP are not gated by it. Nor is a hostile page that is itself served from the internal network (a compromised wiki or SharePoint page, for example).
What the page does, step by step
- Proves the channel. It runs harmless cross-origin probes and shows the public REST API answering (readable), the version endpoint answering but needing a token, and the GraphQL schema reading cross-origin.
- Fingerprints the build. GitLab ships Webpack chunks whose filenames embed a content hash. The page requests a bundled set of these as preload resources and watches load versus 404; nothing is executed. A random-hash control probe first checks the server does not answer every path. A full match identifies a specific frontend build. The anonymous GraphQL Query-schema SHA is shown for reference; it is not yet used in matching.
- Lists exposure. Only once a version is identified does it reveal the CVEs that version is exposed to, each marked vulnerable or indeterminate.
- Flags the drive-by subset. It then flags which CVEs have a likely or conditional anonymous browser path, from a review of the public advisories and reproductions. None of these paths has been tested from a browser.
What it can and cannot resolve (honest limits)
Some patch releases ship a byte-identical frontend because the patch was
backend only. Unauthenticated client-side fingerprinting therefore resolves
to a build class (the set of patches that share one
frontend), not always a single patch. Every collected build is told apart
from every other, but a patch released after the corpus was collected can
serve the same assets as the newest collected patch of its minor line, so
for the three minor lines GitLab still patches, that later patch is kept
as a candidate rather than ruled out. CVE ranges
come from NVD, corrected where they contradict GitLab's own advisory. The
exact patch is available from the authenticated /api/v4/version
endpoint, so the page accepts an optional admin-approved read-only token and
uses it when present. This is a defensive demonstration for authorised testing
of your own infrastructure: it fingerprints and prioritises, it does not
weaponise, and it contacts no third party at runtime.
- 1 Point at a GitLab
- 2 Prove the CORS channel
- 3 Fingerprint the version
- 4 See the exposure
1. Point it at a GitLab
Enter an origin you are authorised to assess, then press Enter (or leave the
field) to run the anonymous probes. Nothing is
contacted while you type. If you add a token, only Enter or Run starts a
probe: confirm the host it may go to first. This page
is served from , so every probe below is a
genuine cross-origin request, exactly as a hostile site would make.
2. The CORS channel, proven live
These are the only two cross-origin-readable surfaces the tool uses:
/api/* and asset presence. Everything else is built on them.
Public REST API
/api/v4/projects?visibility=public&per_page=1Version REST API
/api/v4/version: CORS-readable, but requires a tokenBuild fingerprint
3. What this instance is
Resolving…
4. CVE exposure for the detected version
| Published | CVE | CVSS | Severity | Vulnerable? | Browser/API assessment | Links |
|---|
3. This instance's API is closed to cross-origin reads
A GitLab answered at this address (a known build asset loaded from it), but the
browser blocked the cross-origin read of its /api:
it does not return Access-Control-Allow-Origin for this page's
origin. A hostile website therefore cannot read this instance's API through a
visitor's browser, which closes the channel that the readable-response browser
paths in this tool depend on. That is a real improvement, but it is
not a clean bill of health:
- The version can still be fingerprinted. Asset presence needs no CORS, which is how this page recognised the instance.
- Requests can still be sent, just not read. "Blind" triggers that never need to read a response (a cross-origin form POST or GET, such as some denial-of-service bugs) are not stopped by CORS, and nor are CSRF-style side effects.
- If the CORS policy is ever relaxed, or a reverse proxy or CDN in front adds a permissive
Access-Control-Allow-Origin, the readable channel returns at once. Re-run this check after any such change. - An attacker who is already on the network, or who can get a victim to run a tool rather than just open a page, is a different threat model this tool does not measure.
To confirm independently, inspect the response headers on
/api/v4/projects and check for an Access-Control-Allow-Origin
that matches an arbitrary origin.
3. Your browser blocked this probe (Local Network Access)
The probes to never left your browser. Every
channel failed at once and the target is a private or loopback address, which
is the signature of Chrome's Local Network Access permission
(enforced from Chrome 142), not of the target refusing cross-origin reads.
This is a control in your browser, not a verdict on the target. It is not evidence that the instance is safe. Grant the permission and run the probes again:
- Click Allow on Chrome's local network permission prompt, then press the button below. If you already clicked Block, open this page's site permissions, allow local network access, and re-run.
- This control applies only to recent Chromium reaching a private or loopback address. It does not apply to a GitLab on a public IP, nor to Firefox or Safari, nor to older Chrome or Chrome where an administrator has turned it off. In those cases the drive-by path is unaffected.
- If the address is simply unreachable from this browser (wrong host, down, or firewalled), it looks the same here. Check the address and try again.
3. Could not reach this address from your browser
Nothing at answered, not even with an error
page. This is not a verdict on the target. Common causes:
- The address is plain
http://. This page is served over HTTPS, and browsers block an HTTPS page from fetching plain-HTTP addresses (mixed content). Try thehttps://address. - A typo, a host that is down, or a name that only resolves on a VPN or internal network you are not on.
- On recent Chrome, an internal host name can be blocked by the Local Network Access permission. If you saw a prompt about devices on your local network, allow it and re-run.
- A firewall or proxy dropping the connection.
3. Inconclusive: no GitLab recognised at this address
A server answered at , but none of the known
GitLab builds was recognised there.
No GitLab API was readable cross-origin either.
That is not proof the instance is safe.
It does send a permissive
Access-Control-Allow-Origin, so its responses are readable
cross-origin, but it did not answer like a GitLab API.
- You entered a path (
). Enter the GitLab root instead, for example, unless GitLab really is installed under that path. - The build may be newer than the bundled corpus, or customised. If the API is closed to cross-origin reads as well, an approved token will not help either; check the
Access-Control-Allow-Originheader on/api/v4/projectsdirectly. - The address may not be a GitLab at all.
Want help closing this down?
If any of this is relevant to your estate, or you would like advice on the mitigations that shut the drive-by path and harden the browser-facing attack surface around your GitLab, ControlPlane would be glad to help.
Disclaimer and liability
This tool fingerprints a GitLab build and lists the CVEs recorded against that version. It is a prioritisation aid, not a verdict. We have not live-tested every known exploit against every version, so reality may vary: an instance may have other mitigation controls in place that this tool cannot see from the outside, and there may be exploits that are unknown to us. The real exposure could therefore be better, or worse, than what is shown here. Always confirm any individual finding with your own configuration, edition, authentication and feature-prerequisite review.
Pull requests are welcome: if you can improve the corpus, the CVE mapping or the browser-reachability assessments, please contribute. The tool is provided as-is, with no warranty of any kind. ControlPlane accepts no liability for any use of this tool, or for any decision taken on the basis of its output. Use it only against GitLab instances you are authorised to assess.