In the Kotlin team, security is always a priority. Being an open-source programming language inherently provides Kotlin with significant security guarantees and protection against potentially malicious code being added to our codebase. However, just being open source does not ensure that we are completely invulnerable to determined attackers who want to inject their malware.
There are many ways open-source projects can be and are being exploited. One approach is to infiltrate build servers that are used to produce a project’s binaries and to inject malicious code during the build process. This way, the injected code can’t be found by examining the original project’s source code or by examining its dependencies.
One of the measures we are taking to protect Kotlin builds from such an attack is to make them reproducible. With reproducible builds, anyone in the world can download Kotlin’s source code and its dependencies for a specific version, build it, and verify that it produces exactly the same binaries bit by bit.
We’ve reached an important milestone with this project, and we’re ready to share our initial progress. You can now build the Kotlin compiler binaries yourself and get exactly the same output that we publish.
It’s now possible to build the Kotlin project locally and get the same set of artifacts that we publish to GitHub and Maven Central after each Kotlin release. Kotlin/Native compiler artifacts are not covered right now.
Why is this important?
With reproducible builds, you can easily check that any artifacts or dependencies that you want to use haven’t been tampered with.
While it was always possible to build Kotlin locally, officially published artifacts can now easily be verified against ones obtained on other machines. This greatly reduces the possibility of attacks targeting build agents.
This additional level of transparency also benefits our partners. Here’s what Jeffrey van Gogh, Director of Engineering on Android Developer Experience at Google, had to say:
We worked closely with JetBrains to get the Kotlin compiler build to be reproducible. Having reproducible builds for the compiler helps build a more reliable supply chain of build tools. We leverage this work at Google internally by verifying the reproducibility of every bootstrap build of the compiler to build the next version of the compiler.
How to build and verify artifacts
To build Kotlin from source, we provide a docker container with several open JDKs, instructions, and scripts.
For example, there are instructions and utility scripts for the latest stable Kotlin release 1.8.0.
Once executed, the build downloads all the needed dependencies inside a docker container, compiles and packages. You can find intermediate and final artifacts in the directory with sources.
How did we achieve this?
The Kotlin compiler for JVM and JS targets was already producing stable artifacts when we first started working on reproducible builds.
But making other Kotlin artifacts reproducible was technically challenging. It required not only updating our build infrastructure, but also fixing issues that we found along the way, both in the tooling and in Kotlin itself. Here are some of the most noteworthy changes:
We’ve fixed the problem preventing Dokka from producing stable documentation for the list of deprecated methods.The Kotlin compiler uses absolute paths in klib libraries, but now it’s possible to use an experimental flag to overcome this.Some build internals have been changed:Removed instability in setting artifacts attributes. Fixated the encoding used during the build in the Gradle daemon.Updated maven plugins to versions supporting reproducible builds.
This is our first step in our effort to make the Kotlin build process more transparent.
This reproducibility check is now a mandatory step for our release process, which means that we will update our instructions and script for every Kotlin release.
We are also planning to cover even more artifacts in the future. Stay tuned!