A simple typo of ghcr.io to ghrc.io would normally be a small goof. You’d typically get a 404 or similar error, finally work out the issue, fix it, and move along. But in this case, that typo appears to be doing something very malicious, stealing GitHub credentials.

What’s ghcr.io?

First, a quick bit of background. ghcr.io is an OCI conformant registry for container images and OCI artifacts used by a lot of projects. It’s part of GitHub and is a very popular image and artifact repository used by open source projects.

ghrc.io Is Just a Default Nginx

At first glance, ghrc.io is just a default nginx install:

$ curl -i https://ghrc.io/
HTTP/2 200
server: nginx
date: Fri, 22 Aug 2025 17:58:01 GMT
content-type: text/html
content-length: 615
last-modified: Tue, 23 Apr 2024 14:04:32 GMT
etag: "6627bff0-267"
strict-transport-security: max-age=31536000; includeSubDomains
accept-ranges: bytes

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Even checking other links gives a typical 404 error:

$ curl -i https://ghrc.io/404/
HTTP/2 404
server: nginx
date: Fri, 22 Aug 2025 17:58:04 GMT
content-type: text/html
content-length: 146
strict-transport-security: max-age=31536000; includeSubDomains

<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

Why Is It Malicious?

The concerning part comes in when looking at the OCI API’s. Those are all under the /v2/ prefix for legacy reasons. Looking at ghrc.io, suddenly it’s not acting like a default nginx install anymore:

$ curl -i https://ghrc.io/v2/
HTTP/2 401
server: nginx
date: Fri, 22 Aug 2025 17:56:36 GMT
content-type: application/json
content-length: 72
www-authenticate: Bearer realm="https://ghrc.io/token"

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required"}]}

Compare that to some other registries and you’ll see the 401 status, www-authenticate header, and error message look very similar:

$ curl -i https://ghcr.io/v2/
HTTP/2 401
content-type: application/json
docker-distribution-api-version: registry/2.0
strict-transport-security: max-age=63072000; includeSubDomains; preload
www-authenticate: Bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:user/image:pull"
date: Fri, 22 Aug 2025 17:51:36 GMT
content-length: 73
x-github-request-id: DA46:5B047:5EDB5D:66E5C2:68A8AE28

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required"}]}
$ curl -i https://registry-1.docker.io/v2/
HTTP/2 401
date: Sun, 24 Aug 2025 17:31:43 GMT
content-type: application/json
content-length: 87
docker-distribution-api-version: registry/2.0
www-authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io"
strict-transport-security: max-age=31536000

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":null}]}
$ curl -i https://quay.io/v2/
HTTP/2 401
date: Sun, 24 Aug 2025 17:32:18 GMT
content-type: text/html; charset=utf-8
content-length: 4
server: nginx/1.22.1
www-authenticate: Bearer realm="https://quay.io/v2/auth",service="quay.io"
docker-distribution-api-version: registry/2.0

true

The (optional) error message is defined by the OCI Distribution Spec along with the various OCI APIs under the /v2/ prefix. Authentication hasn’t been standardized by OCI, yet, but projects all use the token auth workflow currently defined by the distribution project.

The important detail is this www-authenticate header is telling OCI clients, like Docker, containerd, podman, and the various CRI’s used by Kubernetes, to send their user credentials to that https://ghrc.io/token API. There is no legitimate reason to configure this header on a default nginx install, and other parts of the server indicate that this is not a container registry.

What’s the Risk?

All signs point to this being a credential stealing typo-squatting attack. Credentials would be stolen only if you stored credentials for the ghrc.io registry, because clients won’t send credentials for a different host to ghcr.io.

Some scenarios that would result in credentials being leaked include:

  • Running docker login ghrc.io.
  • A GitHub action with uses: docker/login-action and with registry: ghrc.io.
  • Creating a Kubernetes secret with registry credentials for ghrc.io and then trying to pull an image from that typoed host.

Simply trying to push or pull an image to this registry without logging in will not leak credentials and will not leak any data other than your repository name. These commands default to trying to acquire an anonymous token, which will quickly fail.

What Should You Do?

If you’ve ever accidentally performed the login to the wrong server, you should change your password, revoke any PATs you used, and look for any potentially malicious activity in your GitHub account. An attacker could use it to push malicious images to your ghcr.io repositories, or they may gain access to your GitHub account directly depending on what login credentials were used.