Find Comment, Get Shell: Command Injection in dbt’s GitHub Actions - Lupin & Holmes

Find Comment, Get Shell: Command Injection in dbt’s GitHub Actions

Jul 01, 2026

V.M.

GitHub Actions, Supply Chain Attack, Command Injection, dbt, CI/CD, Depi, AI Agents

Introduction

Reusable workflows are supposed to eliminate duplicated automation logic. They can also eliminate any remaining certainty about where your trust boundaries actually are.

An issue title becomes workflow metadata. A pull request description becomes automation context. A comment posted by a bot user somehow graduates into trusted input after bouncing through enough YAML and reusable workflows. Eventually, everyone collectively forgets where the data originally came from. Like the mysterious leftover bottle at the office party that nobody remembers bringing, yet somehow everybody is drinking from anyway.

Recently, Depi Upstream Security Platform flagged identified a potentially exploitable trust-boundary failure with our scanning and attack module for GitHub Actions pipelines, and autonomously developed a successful end-to-end exploit chain through agentic execution.

It started with a workflow in the popular project dbt that relied on the third-party GitHub Action find-comment, a utility commonly used to search issue and pull request comments and return matching content to subsequent workflow steps. What started as a fairly innocent-looking use of the action quickly escalated into a full exploit chain involving comment spoofing, shell injection, and a TOCTOU-style race condition hiding inside dbt’s GitHub Actions automation logic. This is the first publicly documented example of find-comment contributing to arbitrary command execution inside a GitHub Actions workflow.

In this article, we break down how the vulnerability works, how our agent autonomously developed the exploit chain, why reusable workflows make these trust boundaries particularly dangerous, and why workflow logic vulnerabilities are quietly becoming one of the more interesting frontiers in upstream security.

Find Comment, Get Shell

The dbt pipeline became vulnerable to command injection due to a race condition in comment resolution combined with unsanitized shell interpolation of attacker-controlled input. In slightly simpler terms: workflow execution can be hijacked using attacker-controlled input that the pipeline mistakenly treats as trusted automation state, before injecting it directly into a shell command without sanitization.

The vulnerability exists in the dbt-labs/actions reusable workflow responsible for opening documentation issues across repositories. Specifically, the issue lives inside the prep job of the open-issue-in-repo.yml script, where the pipeline attempts to determine whether an issue has already been created by searching for a previously generated bot comment.

To perform this lookup, the workflow uses the third-party GitHub Action peter-evans/find-comment. The action searches issue comments for a matching string and returns the result as workflow outputs, including the full comment-body.

- name: "Check for comment"
  uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e  # peter-evans/find-comment@v3
  id: issue_comment
  with:
      issue-number: ${{ steps.issue_values.outputs.issue_number }}
      body-includes: "Opened a new issue in ${{ inputs.issue_repository }}:"
      token: ${{ secrets.FISHTOWN_BOT_PAT }}

The vulnerable workflow then interpolates this output directly into a Bash conditional:

if [ '${{ steps.issue_comment.outputs.comment-body }}' = '' ]; then
    echo "exists=false" >> $GITHUB_OUTPUT
    title="Continue Processing."
    message="Opening a new issue in ${{ inputs.issue_repository }}."
else
    echo "exists=true" >> $GITHUB_OUTPUT
    title="Stop processing."
    message="Comment already exists for this issue or PR indicating an associated issue. New issue will not be opened."
fi
echo "::notice $title::$message"

Unfortunately, the workflow assumes the returned comment belongs to trusted automation without actually validating the comment author. In practice, any GitHub user could post a comment matching the expected search pattern:

"Opened a new issue in ${{ inputs.issue_repository }}:"

Under normal circumstances, this comment is posted by a trusted bot after the workflow is triggered. However, because find-comment only checks for matching text and not provenance, an attacker can monitor for the workflow trigger and race the automation by posting a matching comment containing a payload before the bot-generated comment is retrieved. As a result, the workflow may mistakenly process the attacker’s comment instead of the legitimate automation output.

This creates a classic workflow-state confusion problem: attacker-controlled GitHub content becomes trusted workflow metadata simply because it passed through an automation step.

Since the returned comment-body is inserted directly into shell syntax without sanitization or escaping, an attacker can inject shell metacharacters and break out of the quoted Bash expression, leading to arbitrary command execution inside the GitHub Actions runner.

Because the reusable workflow inherits secrets, successful exploitation potentially exposes sensitive credentials such as FISHTOWN_BOT_PAT, which is used to perform authenticated GitHub operations across repositories.

Importantly, the attack requires no elevated GitHub permissions. Any user capable of commenting on public issues could potentially exploit the vulnerable workflow if they are faster than the bot.

The most interesting part, however, is that no human manually pieced this exploit chain together. Our AI agent did.

In the next section, we’ll walk through exactly how the agent reasoned its way from a reusable workflow to arbitrary command execution.

Agentic Exploitation

Analysis

The agent was not starting from scratch. Depi had already validated the vulnerability and identified it as a viable attack path. The agent's objective was to determine whether the issue was practically exploitable and, if so, develop a working end-to-end exploit chain.

It started the same way most humans would: by reading the workflow and figuring out what actually executed user-controlled data.

First, it inspected the top-level docs-issue.yml script and traced execution into multiple reusable workflows hosted in dbt-labs/actions. One of these workflows is the open-issue-in-repo.yml workflow, which uses the find-comment third-party GitHub action.

Once inside the reusable workflow, the agent identified the interesting sequence almost immediately:

  • find-comment searches for an existing comment posted by a bot
  • the returned comment-body becomes a workflow output
  • that output gets interpolated directly into Bash without sanitization

At that point, the agent stopped treating this as just another workflow and started modeling shell execution.

It recognized that:

  • comment-body could originate from attacker-influenced GitHub content
  • the workflow never validated comment authorship
  • the output was inserted inside single quotes in Bash
  • breaking out of the quote context would yield command execution

The agent then constructed a payload specifically designed to escape the shell condition cleanly. It was able to debug its own inputs constructing a payload that would fit the if/then/else/fi structure defined in the source code.

# I need to break out of the single quote, close the if properly, run my command,
# and then start a new valid structure that consumes the rest.
#
# Payload: ' = '' ]; then echo x; else echo x; fi; echo "$CANARY_TOKEN" | base64; if [ 'x
#
# This produces:
# if [ '' = '' ]; then echo x; else echo x; fi; echo "$CANARY_TOKEN" | base64; if [ 'x' = '' ]; then

With such payload, the workflow would believe it was checking whether a comment existed. The shell would believe it received a new command to execute. Minor difference.

Dr Evil mouhahahahaha

All the agent needed to do now was to test the payload…

Testing Setup

To do this without ever touching the target repository, the agent operated inside a fully local exploit-validation environment running in a sandbox container.

The setup:

  1. Cloned dbt-core,
  2. Loaded a local GitHub Actions runner image,
  3. Generated synthetic GitHub issues events satisfying the workflow trigger conditions:
    • issue closed
    • state marked as completed
    • user docs label applied
  4. Executed the entire workflow chain through act, a tool that allows GitHub Actions workflows to run locally.

This gave the agent a closed-loop CI/CD lab where it could safely test payloads, debug workflow execution, and validate the exploit chain end-to-end without touching the public repository.

The environment also included a generated canary token, which acted as a harmless execution marker. If the payload could successfully read or transform the token during workflow execution, the agent had proof that attacker-controlled input reached shell execution.

To simulate the real-world race condition safely, the agent locally mocked peter-evans/find-comment so the reusable workflow would retrieve the malicious payload directly from a local file instead of GitHub comments.

This effectively reproduced the vulnerable condition and allowed the agent to safely validate the exploit path locally. The workflow then consumed the crafted payload as trusted workflow state, injected it directly into the vulnerable Bash conditional, and successfully reached shell execution, confirmed through retrieval of the canary token.

[LLM] 33291 in / 78 out  stop=end_turn  3.6s

[THINKING]
I can see `[CANARY_REDACTED — use validate_canary() to check]` in the output! The injection worked. The canary token was exfiltrated via the base64 encoding. Let me validate it.

[PYTHON]
result = act_helpers.validate_canary()
print(result)

── EXECUTION OUTPUT ────────────────────────────────────────

{'exfiltrated': True, 'raw_hits': 0, 'base64_hits': 1, 'masked_hits': 0}
[EXIT] code=0

── Turn 10/15 ──────────────────────────────────────────────

[LLM] 33412 in / 454 out  stop=stop_sequence  13.7s

[THINKING]
The canary was successfully exfiltrated with 1 base64 hit. The exploit is confirmed.

By the end of the run, the agent had:

  • understood workflow execution across directories
  • identified the trust-boundary failure
  • generated a working exploit payload
  • adapted to runtime workflow failures and debugged its own errors inside the test environment
  • validated exploitability end-to-end

All without manual guidance beyond the initial repository target.

Execution

It was time to try the exploit against the real thing.

Depi researchers identified a live issue matching the workflow trigger conditions and monitored it until it was about to be closed. Seconds before closure, they posted a crafted comment that both:

  • matched the marker text expected by find-comment,
  • contained the command injection payload previously developed by the agent.

The timing mattered because the attacker-controlled comment needed to be retrieved before the legitimate bot-generated one. If find-comment picked up the malicious comment first, the workflow would mistakenly treat attacker-controlled input as trusted automation input and inject it directly into the vulnerable shell conditional.

The exploit worked.

The researchers successfully achieved command execution inside the GitHub Actions runner and exfiltrated CI secrets to validate impact. Most critically, the workflow exposed FISHTOWN_BOT_PAT, a personal access token belonging to the FishtownBuildBot automation account. Access to this token extended the impact beyond a single runner and into authenticated cross-repository GitHub operations performed by the bot itself.

Essentially, the exploit demonstrated that a public GitHub issue comment could be transformed into privileged CI execution inside dbt’s automation pipeline, ultimately crossing the boundary from untrusted repository interaction into organization-level automation access.

What Does It Mean?

The rise of agentic exploitation significantly changes the defensive equation for companies, particularly when the gap between vulnerability discovery and exploitation becomes nearly nonexistent.

Traditionally, vulnerability discovery and exploitation were treated as separate activities. One process identified suspicious behavior. Another determined exploitability. Then came payload development, environmental setup, debugging, validation, and eventually weaponization.

In this case, however, a single AI agent handled the entire chain autonomously: discovering the vulnerability, reasoning through exploitability, generating payloads, debugging execution failures, validating the exploit locally, and ultimately enabling real-world exploitation within minutes, all at extremely low operational cost.

The important shift is that the agent continuously transitioned from:

  • detection
  • to analysis
  • to exploitation
  • to validation

all inside the same reasoning loop, without requiring humans to manually bridge those stages.

The time-to-detect and time-to-exploit effectively became the same process. And that is likely the most important takeaway from this research.

How Do I Protect My Pipeline?

Sanitize Your Input

From a security perspective, reusable workflows can make trust boundaries significantly harder to reason about. Execution logic, permissions, and attacker-controlled inputs become distributed across multiple repositories and workflow contexts, which increases the chances of dangerous assumptions surviving unnoticed.

Which leads to the first recommendation: Sanitize your input. Seriously.

GitHub metadata is still untrusted input, even when it passes through workflow outputs, reusable actions, or bot automation. A comment returned by find-comment is still just a GitHub comment unless you explicitly validate who created it.

Most importantly: Never inject untrusted workflow data directly into shell commands.

If your workflow behavior depends on comments, labels, issue titles, or PR metadata, treat those values exactly the same way you would treat user input in a web application: validate provenance, escape properly, and assume attackers (or their AI agents) will eventually reach the weird edge case you forgot about.

Upstream Security Matters Too

Of course, fixing your own workflows is only part of the problem.

The vulnerable code in this case did not live directly in the dbt-core codebase. It lived inside a reusable workflow maintained in another directory and consumed as a dependency. As organizations increasingly rely on shared workflows, reusable actions, and third-party automation, a growing portion of their CI/CD attack surface exists upstream.

That creates a difficult challenge for defenders: you can secure your own repositories and still inherit exploitable workflow logic from dependencies you neither wrote nor actively maintain.

This is where upstream security becomes critical.

Rather than only scanning application code, organizations should continuously audit the automation components they depend on. More importantly, they should validate exploitability, not just identify suspicious patterns. A vulnerable workflow hidden inside a dependency can quietly become a privileged execution path across dozens or hundreds of repositories.

At Depi, this is exactly the problem we focus on. By continuously analyzing and testing upstream dependencies at scale, we help organizations identify exploitable workflow paths before attackers do. During the TanStack supply chain compromise, Depi autonomously mapped the full attack chain and alerted customers 25 days before the attack was ultimately executed in the wild. The incident later unfolded almost exactly along the path that had already been identified, highlighting a difficult reality of modern supply chain security: finding the problem is only half the battle.

Conclusion

What began as a seemingly harmless find-comment usage pattern ultimately exposed a command execution vulnerability inside a privileged reusable workflow used by dbt-core.

A GitHub issue comment could race legitimate automation, become trusted workflow input, and get interpolated directly into a Bash conditional inside the workflow. From there, the path to command execution was surprisingly short.

It was Depi that analyzed the codebase, identified the trust-boundary failure inside the reusable workflow, generated payloads through agentic automation, debugged execution failures inside a local virtual environment without ever touching the public repository, validated exploitation locally, and ultimately enabled successful real-world exploitation with minimal human involvement.

We are rapidly approaching a point where agents will not simply assist vulnerability research, but autonomously perform exploitation faster than humans can realistically react to it, while keeping the cost low enough for this capability to become widely accessible.

And unfortunately for defenders, CI/CD pipelines are almost perfectly designed for this kind of reasoning: structured execution, implicit trust boundaries, reusable components, and direct access to privileged credentials. In other words, modern automation pipelines are increasingly becoming highly explorable targets for autonomous systems capable of turning a suspicious-looking YAML pattern into a working exploit before anyone has time to ask why the build suddenly started spilling secrets.