Enterprise GitLab With the Front Door Wide Open
The organization operated across four continents, built infrastructure to serve millions of simultaneous viewers, and maintained a development organization large enough that centralizing version control was an operational necessity. The GitLab instance had been running for several years. It contained the kind of accumulated history that develops when a platform becomes genuinely central to how engineering teams work: not just application code, but deployment automation, network configuration templates, internal tooling, and the small-scale scripts that solve problems quietly and get forgotten after the problem is solved.
The assessment was scoped to the organization's external-facing infrastructure. The GitLab instance appeared during subdomain enumeration, resolved to a public IP address, and returned an HTTP 200 to unauthenticated requests. The login page loaded normally.
So did the registration page.
Finding the Open Door
Subdomain enumeration surfaces hostnames that resolve publicly, and a GitLab instance is recognizable immediately from its response headers, page structure, and the distinctive styling of its authentication pages. The instance ran on a standard subdomain — not obscure, not hidden, simply present.
The first check after identifying a self-hosted GitLab instance is always the registration path. A GET request to /users/sign_up returns one of two things: the registration form, indicating open sign-up, or a redirect to the login page, indicating that registration has been restricted or disabled. This instance returned the registration form.
The form requested a username, email address, and password. No invite code. No domain restriction. No administrator approval step. The sign-up button was present and functional.
A test account was created using an email address outside the organization's domain. The account creation succeeded immediately. No verification delay, no approval queue, no administrative notification. The new account was active.
What the Account Could Access
The first page after login on a GitLab instance is the dashboard, which displays recent activity and, more relevantly, project listings. The explore page at /explore/projects lists all projects with visibility set to Internal — GitLab's model where content is accessible to any authenticated user, distinct from both Public (accessible without authentication) and Private (accessible only to explicitly added members).
The explore page returned results. Many of them.
GitLab's internal visibility level is intended for intranet-style use: anything that should be accessible to anyone within the organization but not to the general public. When the organization controls who can register, internal visibility behaves correctly — it restricts content to organizational members. When open registration is enabled, internal visibility becomes effectively public: any visitor who creates an account qualifies as an authenticated user and receives access to everything marked internal.
The accessible repositories included:
Infrastructure as code. Terraform configurations and Ansible playbooks that defined cloud resource layouts, network topology, security group rules, and instance provisioning scripts. These files contained references to internal service names, region configurations, and naming conventions that mapped the organization's infrastructure in significant detail.
Deployment automation. Scripts and configuration files that automated application deployment across environments. Several of these contained environment-specific settings embedded as constants, including paths to internal configuration stores and references to service accounts used by deployment processes.
Internal tooling. A collection of smaller repositories containing monitoring integrations, internal API clients, log processing utilities, and migration scripts for historical database operations. The code quality and documentation indicated these were working tools used regularly by the engineering team, not abandoned experiments.
CI/CD configurations. Pipeline definitions that described how code was built, tested, and deployed. Several pipelines referenced environment variable names that pointed to credential storage, and some older pipeline files contained inline values that had been replaced by variable references in newer iterations but remained in the repository's commit history.
The Commit History Problem
Finding content in the current state of a repository is only part of what open GitLab access provides. Version control history is the more consequential exposure in many cases.
Repository history contains every version of every file since the first commit. A credential that was added to a configuration file, used for months, then replaced with an environment variable reference leaves a complete record: the original value in the commit that added it, the context in which it was used, and the commit message that removed it (often describing the service account or key material by name).
Several repositories in this assessment had exactly that pattern. Credentials that were no longer in the current file state were present in commits ranging from several months to more than two years prior. The credentials included:
- Service account keys for a cloud provider integration, present in a configuration file for approximately eight months before being moved to a secrets manager
- Database connection strings for an internal analytics platform, in a script that was refactored to accept credentials from environment variables but whose git history retained the original hardcoded values
- An API key for a third-party data provider, committed in an initialization script and later removed when the key was rotated following an unrelated incident
Whether these specific credentials remained valid at the time of the assessment was a question for the organization, not for external validation. The point was that the commit history provided the values, the context for what they accessed, and a timeline that informed remediation prioritization.
Why Internal Visibility Compounded the Risk
GitLab's three-tier visibility model — Public, Internal, Private — is designed for organizations where the authenticated user population is trusted. The model works correctly when authentication equals organizational membership.
The failure mode here was not a GitLab design flaw. The platform behaved exactly as configured. Internal repositories were accessible to authenticated users, and any external visitor could become an authenticated user. The combination of two correct behaviors — open registration and internal repository visibility — produced an incorrect outcome: publicly accessible proprietary content.
This pattern is common in enterprise deployments of platforms that distinguish between "authenticated" and "organizational member." The distinction matters, and infrastructure teams often do not realize the two are not equivalent until the platform is misconfigured in a way that separates them.
The repositories were not marked Internal through a deliberate decision to make them broadly accessible. They were Internal because that is the default visibility for many repository types in a GitLab deployment, and the team's mental model was that "internal" meant "only our people." In the absence of open registration, that mental model would have been correct.
Scope of Exposure
Calculating the exposure period required checking the GitLab instance's configuration history, which the organization's administrators could access. Open registration had been enabled since the instance was provisioned. The instance had been running for over three years.
During that period, the instance had been indexed by internet scanning services that catalog web-accessible services. The registration page had been reachable to anyone who encountered the hostname — through subdomain enumeration, through cached scan results, through DNS lookups on the organization's domain.
There was no way to determine from external observation whether anyone had previously registered an account and accessed the repositories. The GitLab audit log, accessible to administrators, could show account creation events, but the organization's log retention configuration determined how far back that history extended.
The exposure window was the interval between instance provisioning and the moment open registration was disabled during remediation. What had occurred during that window was an audit question, not a question the external assessment could answer.
Remediation
The immediate fix was straightforward. In GitLab's administrator panel, under Admin Area > Settings > General > Sign-up restrictions, the "Sign-up enabled" option was disabled. This immediately prevented new account registration without affecting existing accounts or repository access for current users.
The second step was auditing existing accounts. GitLab's user management view lists all registered accounts with registration dates and last activity. Accounts registered with domains outside the organization's known email domains, or accounts that had not been active in a defined period, warranted review. Several accounts in the instance had been registered with external email addresses and had never performed any activity — consistent with automated scanning or casual discovery that did not progress to active use. Whether any of those accounts had been used to exfiltrate data could not be determined from GitLab's activity logs, which did not capture repository clone events with sufficient granularity.
The third step was secrets remediation. Every repository that had been accessible during the open registration period required a commit history scan for credential patterns. For credentials found in history, the response depended on whether the values remained active: active credentials required immediate rotation, and the rotation should be treated as urgent regardless of the service's perceived importance. Credentials that had already been rotated for unrelated reasons required no further action, but documenting when the rotation had occurred relative to the exposure window provided assurance.
The recommended longer-term configuration added sign-up domain restrictions as a secondary control — even with open registration disabled, restricting future registration to the organization's email domains prevents accidental re-enablement from creating the same condition. The organization also moved selected high-sensitivity repositories from Internal to Private visibility, requiring explicit membership for access rather than relying on organizational control of the authenticated user population.
The Understated Risk of Version Control History
Open GitLab registration is frequently categorized as a configuration issue with straightforward remediation: disable the setting, done. That framing understates what the exposure actually provides.
A three-year commit history across dozens of repositories is not a snapshot of the current codebase. It is a detailed record of how the infrastructure was built, what approaches were tried, what credentials were used before rotation, what internal services existed before they were renamed or decommissioned, and what problems the engineering team considered important enough to solve programmatically.
This context is valuable for understanding an organization's attack surface in ways that current-state code inspection does not provide. An attacker with access to this history knows which services have historically handled authentication, which infrastructure patterns have been stable versus frequently changed, and which credential types the organization has relied on across years of operation. That knowledge informs where to focus active testing even after the initial exposure is remediated.
Secrets in commit history cannot be removed by disabling registration or changing file contents. The history exists in every clone that was made while the repository was accessible, in the git objects stored on the server, and in any backups taken during the exposure period. Rotation is the only effective remediation for credentials that appear in version control history — and rotation should be treated as certain rather than contingent on whether the credentials were definitely seen.
For the underlying access control principles that apply to this class of misconfiguration, see the broken access control knowledge article.