Resources · CVE-2026-60004, Gitea, External perimeter, Asset discovery

Self-hosted Gitea on the perimeter: checking whether an old code host became an entry point

CVE-2026-60004 is in the CISA KEV catalog and Gitea has published an advisory. The external check is concrete: find Gitea instances that answer from the internet, establish their actual versions, and compare them with the affected ranges.

CISA added CVE-2026-60004 to its Known Exploited Vulnerabilities catalog, and Gitea published the advisory GHSA-rcr6-4jqh-j84m. For the external perimeter the resulting task is concrete: find Gitea instances that are reachable from the internet, establish their actual versions, and compare those versions with the ranges given in the advisory.

The CVE-2026-60004 entry in the KEV catalog changes how the work is prioritised. This is no longer a "update the old Git server at some point" item; it is a separate check of an external asset against a list of affected versions. We did not reproduce exploitation, and we will not derive an attack method from a CVE identifier alone. To make a decision about a specific host, two confirmed facts are enough: Gitea answers from outside, and the installed version falls inside the affected range from the advisory.

Why searching the corporate repository list does not work

The usual question to whoever owns development sounds like this: "where is our Git?" The answers name the corporate GitHub or GitLab, and sometimes one current Gitea. That is a list of platforms in use, not a list of external assets.

A self-hosted Gitea has to be looked for more broadly. It may have stayed on an old subdomain or a cloud IP address after the development process changed. A separate instance may have belonged to a team that no longer keeps projects there. So the absence of Gitea from the current development scheme says nothing about a DNS record, a public address and a running process.

In practice it looks like this: the known service has already been migrated, while the old host takes part in neither backups, nor patching, nor routine inventory. Meanwhile the external asset keeps answering. Checking only the agreed list of servers will miss it by definition, because forgotten systems are exactly what that list does not contain.

The check should therefore start from the organisation's external assets rather than from the name of the approved platform. First collect domains, subdomains and public IP addresses, then pick out Gitea candidates among the services that respond. Only after the product is confirmed does it make sense to move on to version and CVE. The reverse order, looking for the CVE only on servers that are already known, keeps the blind spot in place.

What exactly has to be discovered

On the first pass we are not looking for "a vulnerable Gitea". We are looking for an external code host that needs an owner and a version check. That distinction matters.

The fact that a URL answers does not confirm the product. The page title, the HTTP status code and generic headers should not become the only evidence either: they do not support a reliable decision about the affected version. External scanning yields a candidate, and that candidate then has to be tied to a deployment: a virtual machine, a container, a cluster or another managed object.

For each candidate we keep a short record:

  • the external URL;
  • the results of A, AAAA and CNAME resolution;
  • the public IP address;
  • the confirmed product;
  • the actual version, or the identifier of the deployed artifact;
  • the owner;
  • the source the update will be applied from;
  • the match against the ranges in GHSA-rcr6-4jqh-j84m;
  • the external reachability status after the fix.

"Owner unknown" and "version unknown" are not grounds to close a finding. They describe an unfinished check. For CVE-2026-60004, priority cannot be assigned by the age of a subdomain or by the look of the interface: the version has to be established and compared with the primary advisory.

Where standalone instances turn up

We start with the zones the organisation owns now or used to own. That means checking more than the obvious Git-related names. The initial set should include old domains, surviving subdomains, public addresses of cloud resources, and addresses handed over to individual development teams.

For a name that is already known, the basic check looks like this:

host=gitea.old.example.com

dig +short A "$host"
dig +short AAAA "$host"
dig +short CNAME "$host"
curl -kI --connect-timeout 5 "https://$host/"

Here curl checks HTTP(S) reachability and nothing else. We do not use the headers it returns as proof that we are looking at Gitea. If an intermediate proxy answers, a single external response establishes neither the product behind it nor its version.

For a public IP address we run the same transport check, but we do not treat the absence of a suitable response at that address as proof that the service is absent. A virtual host may require a specific name. The link between a domain and an IP address therefore has to be preserved, rather than checking the two lists independently.

Separately, we match candidates against the configuration of external proxies and load balancers. The goal is to get from a public name to the deployment that serves it. Until that link exists, we have a discovered external address but no confirmed version.

Do not confuse discovery with CVE verification

This work has four different outcomes:

  1. An external URL or IP address has been found.
  2. Gitea has been confirmed as the service behind it.
  3. The exact version of the deployment has been established.
  4. The version has been compared with the affected versions in GHSA-rcr6-4jqh-j84m.

Jumping from the first item straight to the fourth does not work. The presence of Gitea does not by itself prove that CVE-2026-60004 applies. And the absence of a version in the external response does not prove the opposite.

The version is better taken from the deployment side. If Gitea runs in a container, we first record the image:

docker inspect --format '{{.Config.Image}}' <container>

For a deployment in Kubernetes, we check the images in use:

kubectl -n <namespace> get deploy <deployment> \
  -o jsonpath='{..image}{"\n"}'

For a service on a virtual machine, we look at the actual startup configuration:

systemctl cat gitea

These commands do not replace version confirmation. An image tag may not carry the version, and a service configuration may point only to the path of an executable. In that case the check continues against the artifact itself and the deployment data. "Looks like an old version" is not something a decision can rest on.

After that we open the Gitea advisory itself and compare the installed version with the ranges listed there. Version numbers should not be carried over from an old ticket, an email thread or a screenshot: the working source is the current text of the advisory.

Why prioritisation by exploitability belongs here

A list of external Gitea instances can contain both affected and unaffected deployments. Sorting simply by "the service is reachable from the internet" does not answer which host to check first.

CVE-2026-60004 is present in the KEV catalog. That is a separate priority signal, and it applies after the version has been established rather than instead of it. The chain looks like this:

external asset
-> confirmed Gitea
-> installed version
-> version falls inside a GHSA-rcr6-4jqh-j84m range
-> CVE-2026-60004 is listed in KEV

It is the last pair of conditions that separates a specific, urgent check from a general list of outdated services. If the version does not fall inside an advisory range, the presence of the CVE in KEV does not make that instance affected. If the version is unknown, the result stays incomplete.

The CISA record can be pulled out of the published JSON without searching the whole file by hand:

curl -fsSL \
  https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json |
jq '.vulnerabilities[] | select(.cveID == "CVE-2026-60004")'

We store the fields of the record together with the task, including the required action and the due date from the catalog. That way KEV is not paraphrased, and the primary source is not replaced by an internal wording.

Where the external check ends

External scanning answers whether an asset is reachable and whether it can be tied to a specific service. It does not always show the installed version, and it does not confirm that a fix was applied inside the system.

So the finding has to reach the owner of the deployment with a precise data set: URL, IP address, time of the check, product confirmation and the reason for the priority. The answer "we do not use that any more" does not close an external asset. What closes it is either confirmed removal of external reachability, or a version check plus the action required by the advisory.

After the change we repeat the check from outside. If the service was retired, the URL and the IP address should not be left in a "presumably switched off" state. If the deployment was kept, the actual version has to be established again and matched against the advisory again. We do not call an asset fixed on the strength of a closed ticket.

A check you can run today

Take the list of corporate Git platforms and set it aside for a moment. Separately, collect the old domains, the live subdomains and the public cloud IP addresses associated with development teams. For every address that responds, confirm the product, find the deployment behind it and record the exact version.

Then open GHSA-rcr6-4jqh-j84m, compare the versions you found with the affected versions, and check CVE-2026-60004 in the KEV catalog JSON separately. The result should not be a list of URLs. It should be a table with a row per asset and these columns: asset, owner, version, advisory match, external reachability, action.

A check like this should not stay a one-off. A single pass confirms the state only at the moment it ran, and a forgotten Gitea comes into view precisely because it dropped out of the current process. Continuous discovery of external assets brings such hosts back into the inventory, which is what SeguriScan, the IntruForce platform, is built for, and KEV-based prioritisation points manual verification at the versions affected by CVE-2026-60004. Today it is enough to start with one old domain and one list of cloud IP addresses, and to leave the work open until every Gitea you find has an owner and a confirmed version.

Your first step

Request an initial review of your company.

Share your company website and a work email. We'll send your request to the IntruForce team. Tell us if you want to discuss a specific assessment instead.

  • Nothing to install
  • No access to your internal network
  • You approve active testing separately
Only if you’d prefer us to reply there.

This form sends a review request to the IntruForce team. Sending it commits you to nothing; our team replies to the work email you provide.