Enumerating Internal Architecture Through a Container Registry
Container registries hold images. The registry catalogs what images exist, who built them, when they were pushed, and what version they are. In a well-configured environment, that catalog is accessible only to authenticated users with a need to know. When the registry is accessible without authentication, the catalog becomes a detailed map of everything the organization runs.
This is a case study from an assessment of a global infrastructure provider. The finding itself — an accessible registry — was medium severity in isolation. In context, it was one of the most valuable reconnaissance assets of the engagement, because the information it disclosed accelerated every other phase of testing.
Finding the Registry
Subdomain enumeration against the target organization returned a list of several hundred hostnames. Most resolved to IP addresses in the organization's production cloud environment. Working through the list, I looked for services that stood out from the expected pattern: API hosts, customer portals, documentation sites, monitoring dashboards.
One hostname, following a naming pattern that suggested internal infrastructure tooling, returned a 200 response on port 443 with a Content-Type header indicating an HTML page. The page title in the response was Harbor.
GET / HTTP/1.1
Host: registry.internal-infra.example.com
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
<title>Harbor</title>
Harbor is a self-hosted container registry. The organization was running it on a hostname that resolved publicly.
Testing for Anonymous Access
The first test was direct: does the interface load without authentication, and does it expose content?
Opening the registry in a browser presented the Harbor login page with no immediate disclosure. The login form itself was not the interesting part. Harbor's registry API, which backs both the web interface and Docker CLI interactions, is a separate question from whether the web UI requires login.
The Harbor API exposes a /api/v2.0/projects endpoint that lists all projects the authenticated user has access to. For anonymous access, the question is what that endpoint returns without any credentials.
GET /api/v2.0/projects HTTP/1.1
Host: registry.internal-infra.example.com
The response was immediate and extensive:
HTTP/1.1 200 OK
Content-Type: application/json
X-Total-Count: 47
[
{"id": 1, "name": "platform-core", ...},
{"id": 2, "name": "compute-api", ...},
{"id": 3, "name": "billing-service", ...},
...
]
Forty-seven projects. No authentication required.
What the Project List Disclosed
The project names were the first layer of information. Harbor projects typically correspond to organizational namespaces — a team, a product area, or a class of services. Reading through the forty-seven project names returned a detailed view of how the organization's engineering was structured:
Core platform services. Several projects with names following the pattern [service]-api or [service]-service mapped to the primary components of the customer-facing platform. The names corresponded to service areas visible from the customer documentation: compute, storage, networking, billing, identity.
Internal tooling and infrastructure. A separate cluster of project names referenced infrastructure components: deployment tooling, internal monitoring, build pipeline components, database management utilities. These were not customer-facing services — they were the machinery that the customer-facing services ran on.
Team namespaces. Several projects appeared to be team-level namespaces rather than specific service names — names that referenced functional groups within engineering rather than deployed services. These projects contained repositories for services owned by that team, and the repository names within them provided more detail about what each team was responsible for.
Environment and staging projects. A subset of projects had names indicating staging or experimental status: [service]-experimental, [service]-canary, staging. These disclosed that the organization used Harbor to manage pre-production images separately from production ones, and that certain services had distinct canary deployment workflows.
Reading the project list took about ten minutes. The output was a reasonable approximation of the organization's internal service map.
Drilling Into Repositories and Tags
Each project contains repositories. Each repository corresponds to a specific image — a service, a component, a build artifact. Querying the repositories within each project was the next step.
GET /api/v2.0/projects/compute-api/repositories HTTP/1.1
Host: registry.internal-infra.example.com
HTTP/1.1 200 OK
X-Total-Count: 12
[
{"name": "compute-api/scheduler", ...},
{"name": "compute-api/provisioner", ...},
{"name": "compute-api/health-checker", ...},
{"name": "compute-api/webhook-dispatcher", ...},
...
]
The compute-api project alone contained twelve repositories, each a separate component of the compute API system. This level of granularity went beyond the organization's public documentation, which described a single compute API without disclosing its internal component architecture.
Moving to image tags for a repository:
GET /api/v2.0/projects/compute-api/repositories/scheduler/artifacts HTTP/1.1
Host: registry.internal-infra.example.com
The response included every image pushed to that repository, with tags, push timestamps, and image digest values. The tag history disclosed:
Version history. Tags like v2.1.0, v2.1.1, v2.2.0-rc1 showed the release cadence for this specific component. A gap of several months between versions followed by a rapid sequence of minor releases suggested a period of intensive development that corresponded to a feature set the organization had recently announced publicly.
Branch names. Several tags were clearly generated from git branch names: feature-multi-region-support-build-204, fix-rate-limiter-edge-case-build-312, refactor-scheduling-algorithm-build-89. These branch names disclosed ongoing development priorities in more detail than any external documentation.
Environment names. Tags in the format prod-20241108-v2.1.0, staging-20241107-v2.2.0-rc1 revealed the organization's environment naming conventions and confirmed which version was deployed to production at specific points in time.
Build system information. The tag format was consistent across repositories — [environment]-[date]-[version] or [branch-name]-build-[number]. The consistency indicated a centralized build pipeline that generated tags programmatically. The build number format revealed roughly how many total builds had been produced by the pipeline, which in turn revealed information about team size and development velocity.
Cross-Referencing With Other Findings
A registry listing has standalone value as reconnaissance. Its value multiplies when combined with other exposed assets from the same organization.
Earlier in the assessment, subdomain enumeration had found an exposed OpenAPI specification for the compute API. The spec described the API surface from the outside — endpoints, parameters, authentication requirements. The registry listing described the same service from the inside — its internal component structure, how it was organized, and what the distinct modules were called.
Together, the two sources provided a more complete picture than either alone. The OpenAPI spec described a POST /compute/instances/provision endpoint with a set of parameters. The registry listing showed that behind that endpoint, a component called provisioner was responsible for the operation. Understanding which component handled which API surface area — from a combination of external API documentation and internal registry structure — created a more detailed testing map than either source provided independently.
The registry listing also added value through the service names it revealed. Internal service names that appear in error messages, log entries, or stack traces — which sometimes leak through API responses — are easier to interpret when you already know the name is a legitimate internal service rather than an artifact of the specific request.
What the Organization's Registry Configuration Looked Like
Working through the implications of the exposure, the configuration that produced it was consistent with a specific Harbor setup pattern.
Harbor separates the question of who can access the registry from the question of which projects are visible. The global "Allow anonymous access" setting permits unauthenticated users to interact with the API. Projects marked as "Public" are visible to anonymous users; projects marked as "Private" should require authentication. In theory, an organization could expose specific public projects while keeping others private.
The behavior in practice was different. The /api/v2.0/projects endpoint with anonymous access returned all forty-seven projects regardless of their individual public/private setting. The project listing was global — it disclosed the existence and names of all projects, including those marked private. Only the repository contents within private projects were restricted; the project names themselves were not.
This is a Harbor behavior worth understanding: the project listing endpoint enumerates all projects to anonymous users when anonymous access is enabled at the registry level, even if individual projects are marked private. An organization that enabled anonymous access to expose a single public project for legitimate external use inadvertently exposed the names of all forty-seven projects.
The Harbor documentation notes this distinction, but it is not prominent, and the practical implication — that project names in an otherwise-private registry become visible to the world — requires reading the documentation carefully rather than relying on the per-project visibility setting.
Remediation
The remediation was straightforward:
Disable anonymous access. For a registry containing primarily internal projects, anonymous access provides no benefit and significant exposure. Disabling it at the registry level prevents unauthenticated access to the project listing, repository contents, and image metadata regardless of individual project settings.
Restrict network access. An internal container registry serving internal services has no reason to be reachable from the public internet. Placing the registry behind a network access control that limits connectivity to internal networks, VPN endpoints, and specific external infrastructure removes the exposure regardless of application-level configuration.
Audit public project settings. For organizations that legitimately need anonymous access to specific projects, auditing which projects are marked public and confirming that only intended projects are accessible to external users prevents the misconfigured-scope problem.
The Registry as Reconnaissance Infrastructure
What made this finding disproportionately valuable was not what it directly enabled — accessing the registry without authentication did not grant access to the images themselves or to any production system. What it provided was context: a detailed, accurate picture of how the organization's systems were structured internally.
That context reduces the time from "I have access to an endpoint" to "I understand what this endpoint connects to and what its internal components are called." In a complex platform with dozens of services, that reduction in time is significant. The reconnaissance phase that might have taken days of patient observation and inference took hours because the registry had already done the organizational mapping.
This is the pattern with information disclosure findings: they do not enable attacks directly. They accelerate everything else. The container registry that shows you forty-seven internal service names, twelve components within one of those services, and the branch names of active development work has handed you a roadmap that no amount of external probing could easily produce.
For a related finding — exposed source maps that disclosed the entire frontend codebase of a different organization — see the post on 13 megabytes of secrets in plain sight. For the technical background on information disclosure through source map files, see the source map exposure article.