tl;dr Using TruffleHog Public Monitoring, we found a GitLab token exposed in a public GitLab CI artifact. The token had the
apiscope and Owner access to 55 projects, including GnuTLS, Nettle, libtasn1, libidn2, and ocserv, giving it a potential path to compromise their software supply chains.GnuTLS ships in the base image of nearly every major Linux distribution and is used by software including cURL, Wget, CUPS, and rsyslog. We disclosed the token to its owner, who revoked it within a few hours.
Why GnuTLS matters
GnuTLS is an open-source library that implements the SSL, TLS, and DTLS protocols. Its primary use is securing connections across the internet. It ships in the base image of nearly every major Linux distribution, CUPS printing systems, and networking tools such as Wget and cURL.
What the token could access
The Truffle Security research team identified the token in a public GitLab CI artifact produced by the openconnect/ocserv project. We enumerated the projects associated with the token using GitLab's Projects API.
GET https://gitlab.com/api/v4/projects?membership=true
This endpoint returns the projects where the token's user has membership, together with the user's access level for each project.
GitLab has a set of predefined roles that can be assigned to a project. In the response, the access_level field maps to the role held on that project:

Of the 75 projects returned, the token had Owner access on 55. The projects most relevant to the potential supply chain impact were:
GnuTLS (
gnutls/gnutls)Nettle (
gnutls/nettle)libtasn1 (
gnutls/libtasn1)libidn2 (
libidn/libidn2)ocserv (
openconnect/ocserv)
What the token itself was allowed to do
GitLab personal access tokens have per-project roles and a separate set of global scopes. We enumerated the token's scopes using:
GET https://gitlab.com/api/v4/personal_access_tokens/self
The response contained:
scopes: ["api"]
According to GitLab's documentation, the api scope:
Grants complete read and write access to the API, including all groups and projects, the container registry, the dependency proxy, and the package registry. Also grants complete read and write access to the registry and repository using Git-over-HTTP.
The combination of the api scope and Owner access to 55 projects made the token highly privileged. We prepared a disclosure report and sent it to the project maintainers, then examined what a potential attack using the token could have looked like.
A potential path to a supply chain compromise
Among the projects the token could access, GnuTLS had the broadest downstream reach. It is a hard dependency for software including cURL, Wget, CUPS, and rsyslog, and is packaged by most Linux distributions.
We enumerated the rules and permissions for every branch in the GnuTLS project. Most protected branches restricted direct pushes. The maintained gnutls_3_7_x release branch had a broader rule that allowed direct pushes from Maintainers.
The default branch, master, restricted direct pushes to a single named user. Changes had to arrive through a merge request with at least one approval and a passing pipeline. The gnutls_3_7_x release branch was different because the token already satisfied its Maintainer requirement.
On this branch, “protected” did not mean “requires review.” It meant “restricts who may push,” and the token satisfied that restriction. No merge request or approval was required before a commit could be pushed directly to the release line. Force pushes were blocked, so the token could not rewrite history, but it could append a new commit.
The default branch was more restrictive, but an attacker holding an Owner token could still open a merge request. Because the Owner role could edit approval rules, protected-branch settings, and project membership, the controls governing that merge request were also within the token's reach.
How the token entered a public artifact
The leak resulted from three parts of the CI configuration in the public openconnect/ocserv project that combined to write the token into a downloadable artifact.
The token was stored as a GitLab CI/CD variable, the standard mechanism for holding a secret. During a pipeline, GitLab masks secret values in job logs by replacing them with [MASKED]. That protection applies to log output, not to files published as artifacts. In this case, the token was written to a file and uploaded in plaintext.
The exposure occurred as follows:
1. A test dumped the environment to a file. The helper script tests/connect-ios-script ran env > apple-ios.$REASON.tmp, writing every variable visible to the runner into an untracked temporary file.
The dump included every project-wide CI variable available to the job, including the GitLab token referenced as
API_TOKEN, even though the test job did not use it.
3. The Fedora job published the file as a public artifact. Its artifacts configuration used untracked: true and when: always, so every run uploaded the temporary environment dump. The artifact expired after one day, but until then it could be downloaded from the public project without authentication.
Why the token was present
The API_TOKEN variable was used by gitlab-triage, a tool that automatically manages issues and merge requests according to a set of policies. It can add or remove labels, post comments, close or reopen items, and perform similar project housekeeping. Because it makes these changes through the GitLab API, gitlab-triage requires a token with the api scope.
Exposure window
We used the project’s Git history and scheduled-job activity to reconstruct when the conditions required for the leak were present.
The account owner confirmed that the token became active on January 11, 2026, based on the first successful run of the scheduled job tied to it. Before then, the job used a per-project token that did not have sufficient permissions, so it was not exposing a usable credential.
The fix
Within a few hours of our disclosure, the OpenConnect and GnuTLS maintainers shipped a fix. Commit f3f74de3, tests: do not store environment information, removed the env > ...tmp commands from both test helpers. The runner's environment, including secrets injected into it, is no longer written to a file that can be uploaded as an artifact.
Review for malicious activity
The token has since been revoked. The account owner reviewed activity across the projects associated with the credential and found no commits attributed to the account in GnuTLS, libtasn1, or libidn.
The account did not have commit access to the openconnect/openconnect client project, and the review of ocserv found no new members, tokens, or webhooks created after the token became active. Based on the activity reviewed, the maintainer found no indication that the token had been misused.
Key takeaways
Audit CI artifacts as well as build logs. The token was masked in the job log but written into a public artifact in plaintext, so the pipeline logs did not reveal the exposure.
A leaked credential belonging to a trusted maintainer can satisfy access controls designed around that maintainer's role. In this case, the token already held the project roles and scope needed to reach sensitive repositories and settings.
Review Owner and Maintainer access regularly. Roles that outlive someone's involvement in a project increase the impact of a leaked credential, so stale access should be removed.


