GitLab's incoming email feature — the one that lets users reply to issue notifications by email, create issues by sending mail to a project alias, or create merge requests from email — has a design flaw with serious supply chain implications. Every user on a GitLab instance is automatically assigned a unique incoming email address, and that address embeds what is functionally a highly privileged access token directly in the local part of the address (e.g., incoming+j.doe-abc123xyzTOKEN@gitlab.example.com).
According to the reporting from Dark Reading, these tokens are not scoped-down, single-purpose credentials. An attacker who obtains a user's incoming email address can leverage the embedded token to authenticate as that user — and depending on the user's role, that means pushing code to protected repositories, modifying CI/CD pipelines, exfiltrating private source code, or injecting malicious commits into software that downstream consumers trust. That is the textbook definition of a supply chain attack vector, and it's delivered through one of the leakiest channels in any organization: email.
I've seen enough credential-leakage incidents to know why this one deserves immediate attention. Email addresses get forwarded, CC'd, auto-responded to by out-of-office agents, scraped into ticketing systems, indexed by search engines when posted to public issue trackers, and retained forever in mail archives. A credential that rides inside an email address inherits every one of those exposure paths — and most security teams have zero detection coverage for it.
Technical Analysis
What Is Affected
- GitLab self-managed instances (CE/EE) with the incoming email feature enabled (
gitlab-mailroomservice configured with an IMAP/Microsoft Graph mailbox). This is the primary exposure surface because self-managed admins control whether the feature is on and how mail flows. - GitLab.com users — incoming email is enabled by default on GitLab.com, meaning every user with issue-reply-by-email or "email issue to this project" usage has one of these addresses active.
- Any user who has ever copied their incoming email address, had it assigned automatically, or used the "new issue by email" / "new merge request by email" workflow.
How the Weakness Works (Defender's View of the Attack Chain)
- Token generation: GitLab generates a per-user incoming email token and embeds it in the email alias. There is no separate, revocable credential the user consciously creates — it's assigned automatically.
- Exposure: The address leaks through normal email behavior — forwards, reply-all chains, auto-responders, external ticketing integrations, mailing list archives, or a compromised mailbox of any third party who ever received it.
- Weaponization: The attacker extracts the token from the address. Because the token is tied to the user's identity, it can be used to interact with GitLab as that user — historically, the concern researchers raised is that these tokens grant far broader access than users expect, effectively functioning as a bearer credential for the account in the context of email-driven actions (issue creation, comments, merge request operations).
- Supply chain impact: With contributor- or maintainer-level identity, the attacker files issues containing malicious links, comments with poisoned code snippets, or drives merge request workflows — all under a legitimate identity that bypasses the suspicion an external account would trigger.
Exploitation Status
This issue was disclosed publicly by security researchers and covered by Dark Reading in late 2025. No CVE identifier has been published in the source reporting at the time of writing, and I won't speculate one into existence. What matters operationally: the technique requires no exploit code — just possession of a leaked email address. GitLab shipped fixes in its November 2025 security releases that change how these tokens are generated and scoped, and administrators on older versions remain exposed. Treat every incoming email token issued before your upgrade as potentially compromised if it ever touched an external system.
Detection & Response
This is a credential-exposure threat, so detection centers on two questions: (1) where have these addresses leaked, and (2) is anyone using them anomalously?
Sigma Rules
The first rule hunts for GitLab incoming email addresses appearing in outbound web traffic to domains that are not your GitLab instance — a strong indicator that a token-bearing address was pasted to a paste site, webhook, translator, AI assistant, or third-party tool. The second rule targets abuse on self-managed instances: a single source IP driving the mailroom endpoint with submissions under many different incoming email addresses, which is characteristic of an attacker replaying harvested tokens. The third catches mass token rotation or enumeration activity in GitLab logs via Sysmon/process telemetry on the GitLab host itself.
---
title: GitLab Incoming Email Token Observed in Outbound Web Traffic to External Domains
id: 3f8a2c14-9b6d-4e71-a5c3-2d7e9f1b8a42
status: experimental
description: Detects GitLab incoming email addresses (which embed privileged access tokens) appearing in HTTP requests destined for domains other than the organization's GitLab instance, indicating potential token leakage to paste sites, webhooks, AI tools, or third-party services.
references:
- https://www.darkreading.com/application-security/gitlab-email-addresses-supply-chain-attacks
- https://attack.mitre.org/techniques/T1552/
author: Security Arsenal
date: 2026/01/15
tags:
- attack.credential_access
- attack.t1552
logsource:
category: proxy
detection:
selection_pattern:
c-uri|re: '(?i)incoming%2B|incoming\+[a-zA-Z0-9._%+-]*-'
filter_known_gitlab:
c-uri|contains:
- 'gitlab.example.com'
- 'gitlab.internal'
condition: selection_pattern and not filter_known_gitlab
falsepositives:
- Legitimate integrations where users email issues into SaaS ticketing systems (validate and allowlist specific destinations)
level: high
---
title: GitLab Mailroom Submissions from Single Source Across Multiple Incoming Addresses
id: 7c1e4b92-3a8f-4d56-b9e2-5f3a1c7d8e60
status: experimental
description: Detects a single source IP delivering a high volume of messages to distinct GitLab incoming email addresses on a self-managed instance, consistent with an attacker replaying harvested token-bearing addresses to inject issues, comments, or merge requests under multiple identities.
references:
- https://www.darkreading.com/application-security/gitlab-email-addresses-supply-chain-attacks
- https://attack.mitre.org/techniques/T1078/
author: Security Arsenal
date: 2026/01/15
tags:
- attack.initial_access
- attack.t1078
logsource:
category: mail-gateway
detection:
selection:
Recipient|re: '^incoming\+[^@]+@'
condition: selection
falsepositives:
- Bulk legitimate issue-by-email imports during migrations (time-bound and sourced from internal relays)
level: medium
---
title: GitLab Rails Console Token Enumeration or Mass Rotation on Self-Managed Host
id: 9d4f6a18-2c5b-4e87-a1d9-6b8c3f5e2a71
status: experimental
description: Detects execution of the GitLab Rails console with commands referencing incoming email tokens on a self-managed GitLab host. May indicate an attacker with host access harvesting tokens, or an administrator performing emergency rotation — investigate context immediately.
references:
- https://www.darkreading.com/application-security/gitlab-email-addresses-supply-chain-attacks
- https://attack.mitre.org/techniques/T1552.001/
author: Security Arsenal
date: 2026/01/15
tags:
- attack.credential_access
- attack.t1552.001
logsource:
category: process_creation
product: linux
detection:
selection_console:
CommandLine|contains:
- 'gitlab-rails console'
- 'gitlab-rails runner'
selection_token:
CommandLine|contains:
- 'incoming_email_token'
- 'reset_incoming_email_token'
condition: selection_console and selection_token
falsepositives:
- Administrator performing legitimate token rotation during incident response (confirm against change tickets)
level: high
The mail-gateway rule above is intentionally threshold-free at the Sigma layer — apply a count aggregation (count(Recipient) by SourceIP > 5 over 1h) in your SIEM to suppress noise from legitimate imports.
KQL — Microsoft Sentinel / Defender
This first query hunts outbound email (via Defender for Office 365 telemetry) for messages addressed to GitLab incoming email aliases — then flags any case where such a message was auto-forwarded or replied to an external recipient, which is exactly how these tokens escape your control boundary. The second query is for self-managed instances shipping GitLab mailroom/application logs into Sentinel via Syslog/CEF: it looks for incoming email activity where the authenticated identity churns across many users from one source.
// Hunt 1: GitLab incoming email addresses leaking via outbound mail or auto-forward
let Lookback = 14d;
EmailEvents
| where Timestamp > ago(Lookback)
| extend Recipients = extract_all(@"([\w.+-]+@[\w-]+\.[\w.]+)", RecipientEmailAddress)
| mv-expand Recipients
| where Recipients matches regex @"(?i)^incoming\+[^@]+-[^@]+@"
| where Recipients !endswith "gitlab.example.com" // your instance domain
| project Timestamp, SenderFromAddress, Recipients, Subject, DeliveryAction, EmailDirection
| summarize LeakCount = count(), Subjects = make_set(Subject, 5) by SenderFromAddress, tostring(Recipients), EmailDirection
| order by LeakCount desc;
// Hunt 2: Self-managed GitLab — mailroom processing messages for many distinct users from one source
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where DeviceVendor has "GitLab" or DeviceProduct has "gitlab"
| where Message has "incoming_email" or Message has "mail_room" or Message has "mailroom"
| extend RecipientToken = extract(@"incoming\+([^@\s]+)@", 1, Message)
| where isnotempty(RecipientToken)
| summarize DistinctIdentities = dcount(RecipientToken), Messages = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
by SourceIP, bin(TimeGenerated, 1h)
| where DistinctIdentities > 3
| order by DistinctIdentities desc;
Velociraptor VQL
For endpoint forensics, the highest-value hunt is locating cached or saved copies of incoming email addresses on user workstations — Outlook/Thunderbird caches, browser form history, downloaded .eml files, and notes. Finding them tells you exactly whose tokens need rotation and how the address propagated.
-- Hunt endpoints for saved GitLab incoming email addresses (token-bearing aliases)
-- in mail caches, exported .eml/.msg text, and browser autofill/form data.
LET patterns = 'incoming\\+[A-Za-z0-9._%+-]+-[A-Za-z0-9_-]{8,}@'
LET mail_files = SELECT FullPath, Size, Mtime
FROM glob(globs=[
'C:/Users/*/AppData/Local/Microsoft/Outlook/*.ost',
'C:/Users/*/**/*.eml',
'/home/*/.thunderbird/**/Mail/**/*',
'/home/*/**/*.eml'
])
WHERE Size < 52428800 -- skip huge OSTs in the string scan; triage separately
SELECT FullPath, Mtime,
upload(file=FullPath) AS EvidenceFile,
count(item=hits) AS TokenHits,
hits[0] AS SampleMatch
FROM foreach(row=mail_files,
query={
SELECT FullPath, Mtime,
extract_all(pattern=patterns,
buffer=read_file(filename=FullPath, length=10485760)) AS hits
FROM scope()
WHERE len(list=extract_all(pattern=patterns,
buffer=read_file(filename=FullPath, length=10485760))) > 0
})
On live systems, pair that with a netstat() check on the GitLab host to baseline which IPs deliver mail to the mailroom listener — any SMTP delivery from outside your expected relay chain warrants investigation.
Remediation Script (Self-Managed GitLab)
This Bash script verifies your GitLab version against the patched releases, enumerates which users have active incoming email tokens, and rotates every token in bulk — the correct posture after any suspected leak. Run it on the GitLab host (Omnibus) with root privileges.
#!/usr/bin/env bash
# GitLab incoming-email token audit and mass rotation
# Run on Omnibus GitLab host as root. Review output before and after rotation.
set -euo pipefail
echo "=== GitLab version ==="
gitlab-rake gitlab:env:info 2>/dev/null | grep -E 'GitLab.*information|version' || \
cat /opt/gitlab/version-manifest.txt | head -5
echo ""
echo "=== Incoming email configuration status ==="
grep -Ei 'incoming_email|mail_room' /etc/gitlab/gitlab.rb | grep -v '^#' || \
echo "(no explicit incoming_email config found — feature may be disabled)"
echo ""
echo "=== Users with active incoming email tokens (count only) ==="
gitlab-rails runner '
count = User.where.not(incoming_email_token: nil).count
puts "Users with active tokens: #{count}"
' 2>/dev/null
read -rp "Rotate ALL incoming email tokens now? This invalidates every existing alias. [y/N] " confirm
if [[ "${confirm}" =~ ^[Yy]$ ]]; then
gitlab-rails runner '
rotated = 0
User.where.not(incoming_email_token: nil).find_each do |u|
u.reset_incoming_email_token!
rotated += 1
end
puts "Rotated tokens for #{rotated} users. Notify users to re-copy their new incoming email addresses from Preferences."
' 2>/dev/null
fi
echo ""
echo "=== Optional: disable incoming email entirely if not business-required ==="
echo "Add to /etc/gitlab/gitlab.rb then run: gitlab-ctl reconfigure"
cat <<'EOF'
gitlab_rails['incoming_email_enabled'] = false
gitlab_rails['incoming_email_address'] = ""
EOF
Remediation
- Upgrade GitLab immediately. Apply the November 2025 security release or later for your branch — GitLab's patch changes how incoming email tokens are generated and limits the blast radius of the addresses. Confirm your exact target version against the official GitLab security release notes at https://about.gitlab.com/releases/categories/releases/ and the advisory linked from the Dark Reading report (https://www.darkreading.com/application-security/gitlab-email-addresses-supply-chain-attacks).
- Rotate every incoming email token after upgrading — the script above does this in bulk. A patched instance still honors pre-patch tokens until they're rotated.
- Disable the feature if you don't need it. If your developers don't actively use reply-by-email or issue-by-email workflows, set
gitlab_rails['incoming_email_enabled'] = falseand reconfigure. A disabled feature has no attack surface. - Audit where these addresses have traveled. Search your email gateway, ticketing system, and any SaaS integrations for the
incoming+pattern over the last 12 months. Every external system that stored one is a compromise path for the corresponding user identity. - Hunt for misuse retroactively. Review GitLab audit logs for issue/comment/MR activity attributed to the email channel, especially content from users who deny sending it, or activity from accounts while they were on leave.
- Enforce layered identity controls. Require 2FA on all GitLab accounts, restrict who can merge to protected branches regardless of contribution channel, and require signed commits for release branches — so a hijacked contributor identity alone can't ship code.
- Educate users. The incoming email address visible in GitLab's UI under Preferences and in notification footers is a credential, not a convenience. It must never be shared, forwarded, or pasted into third-party tools — including AI assistants.
The broader lesson: any system that embeds bearer credentials in routable, human-visible identifiers — email addresses, URLs, QR codes — will eventually leak them through channels you don't control. When you inventory secrets in your environment, make sure "credentials hiding in plain sight" is a line item.
Related Resources
Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.