Definitely Typed, a project which manages npm packages inside the @types scope, is supported by Microsoft. However, it is not in the scope of the safe harbor for Microsoft’s bug bounty program.1
This article describes the vulnerabilities that were reported as potential vulnerabilities, using publicly available information. This was done without actually exploiting / demonstrating the vulnerabilities.
This article is not intended to encourage you to perform an unauthorized vulnerability assessment.
If you find any vulnerabilities in Definitely Typed related products, please report them to members of Definitely Typed.
There were vulnerabilities in the pull request management bot of Definitely Typed, which allowed an attacker to merge a malicious pull request into DefinitelyTyped/DefinitelyTyped.
This would result in tampering with arbitrary packages under the @types scope of npm.
Definitely Typed is a group that maintains a large number of TypeScript type definition packages to complement the type npm packages that do not support TypeScript. It is managed by many people, including the TypeScript maintainers.
Packages under the @types scope are downloaded about 3.7 billion times per month in total2 and are used in a large number of projects.
For example, VSCode, Angular and TypeScript itself is depending on Definitely Typed.
While I was discussing with @lambdasawa about systems that would have a significant impact if compromised, we came up with Definitely Typed.
After searching about Definitely Typed a bit, I found that it was used in a very large amount of projects, so I decided to investigate whether it could be compromised.
As @lambdasawa told me about a GitHub repository that is used to manage Definitely Typed files, I checked DefinitelyTyped/DefinitelyTyped.
Then, I noticed that normal GitHub users, who don’t have any permissions against DefinitelyTyped repository, are using @typescript-bot commands to merge pull requests.
Having had the experience of reporting the Homebrew vulnerability, I thought that this bot might have a similar vulnerability. So I decided to read the source code (DefinitelyTyped/dt-mergebot).
Before starting the investigation of dt-mergebot, I found the image below in the project overview.
It looks like Definitely Typed has the concept of a package maintainer, separate from the repository maintainer.
Package maintainers are users who have permission to manage only a single type definition package such as @types/node
, and are specified in each package.
If a user has package maintainer permissions for the target package, they can approve pull requests that contain only changes to the target package without using the Definitely Typed maintainer permissions.3
As described in the article of Homebrew vulnerability, it is difficult to properly parse pull requests, and it may induce mistakes.
Since it’s easy to obtain a package maintainer permission by adding a type definition, I decided to see if it’s possible to bypass package-based permissions management by using a specially crafted pull request.
After reading the source code of dt-mergebot, I found that it behaves as follows.
Ready to merge
, typescript-bot merges the pull requestWhile it looks safe as it fetches changed files from the API, I found the code shown below.
const { nodes, totalCount } = prInfo.files!;
if (nodes!.length < totalCount) console.warn(` *** Note: ${totalCount - nodes!.length} files were not seen by this query!`);
It looks like it checks the totalCount
parameter returned by the API and compares it with the total number of files that have actually been returned.
And if the number of returned files is less than totalCount
, it executes console.warn
.
After checking how dt-mergebot fetches the files, I found the following GraphQL query.
query PR($pr_number: Int!) {
repository(owner: "DefinitelyTyped", name: "DefinitelyTyped") {
id
pullRequest(number: $pr_number) {
[...]
files(first: 100) {
totalCount
nodes {
path
additions
deletions
}
}
[...]
}
}
}
You can see from this query, that dt-mergebot was only fetching the first 100 files when calculating the permissions required to merge.
As mentioned above, if the number of files obtained is less than totalCount
, it executes console.warn
and displays a warning on the console.
However, console.warn
does not stop subsequent processing. So if someone was to make a pull request that changes 101 or more files, the 101st and subsequent files would not be subject to permission checks. Hence the permissions check would be broken. As a result, it allows the bypassing of package-based permissions management.4
As mentioned at the beginning of this article, Definitely Typed doesn’t have a clear policy for vulnerability assessments.
So, I decided to not demonstrate it, and report it as a potential vulnerability.
I used the following assumptions as an attack procedure.
Ready to merge
as a commentI’ve sent an email that summarizes these steps to Definitely Typed members.
After that, this vulnerability was fixed in this commit.
After reporting/fixing a vulnerability described above, I asked the members of Definitely Typed if I could publish an article about the vulnerability.
As they allowed me to publish the article, I was reading the code to write the article.
While reading the code, I found that there was a regression.
From this commit, dt-mergebot started fetching all modified files for a pull request by paginating the GitHub API.
You would expect for this to cause no issues because all the files are fetched. But in fact, there is a pitfall in the GitHub API.
As documented, when retrieving a list of modified files with a pull request, GitHub API returns only a maximum of 3000 files.
This means that even if all files are correctly retrieved by using pagination, the client will only receive the first 3000 files.
So, if someone sent a pull request like the image above, GraphQL will return the response like this:
In addition, the totalCount
parameter returned by the GraphQL API has an upper limit of 3000 too.
Therefore, by sending a pull request that modified 3001 or more files to DefinitelyTyped/DefinitelyTyped, permission checks could be bypassed and arbitrary modifications could be made to DefinitelyTyped repository.
This has been fixed in this commit, and dt-mergebot now marks pull requests that contain 500 or more modified files as suspicious.
After writing the article above, I started checking other parts of Definitely Typed to see if there are any more vulnerabilities… And found another vulnerability.
There is a tool called types-publisher, which handles publishing of packages after dt-mergebot processes a pull request.
In this tool, the files in the target package are copied into the output directory, but the symbolic link was not taken into consideration during the copy process, so it was possible to publish the unintended files from the system.
However, since the following processing was performed before reaching the copy process, the files that could be published were limited to the files that could be parsed as TypeScript.
const src = createSourceFile(resolvedFilename, readFileAndThrowOnBOM(resolvedFilename, fs));
if (resolvedFilename.endsWith(".d.ts")) {
types.set(resolvedFilename, src);
} else {
tests.set(resolvedFilename, src);
}
I also reported this vulnerability via email, and the team fixed it by disallowing symbolic links in this commit.
The vulnerabilities described in this article had a significant impact on the TypeScript ecosystem.
There are many vulnerabilities in the supply chain that have a huge impact, such as this one.
This is why it’s so important that as many people as possible audit the supply chain.
If you have any questions/comments about this article, please send a message to @ryotkak on Twitter.
Date (JST) | Event |
---|---|
April 26, 2021 | Found first vulnerability |
April 27, 2021 | Reported first vulnerability |
April 28, 2021 | Fixed first vulnerability |
May 7, 2021 | Regression occurred |
June 4, 2021 | Noticed about the regression and reported it |
June 14, 2021 | Fixed the regression |
June 22, 2021 | Found a vulnerability in symbolic link handling |
July 7, 2021 | Fixed the symbolic link vulnerability |
August 8, 2021 | Published this article |