Private Repos, even on a hosted forge

On every hosted forge, 'private' repos aren't very private. But maybe this proof-of-concept shows they could be ?

June 15, 2026 - 5 minute read -
Privacy

Every hosted forge — GitHub, GitLab, Bitbucket, etc. — allows you to choose whether your new repo is “public” or “private”. But “private” also means the service operator can read it.

And so can anyone that the service operator gets:

  • compelled by,
  • breached by,
  • acquired by,
  • … you get the idea.

After a couple of decades of this, we’ve collectively stopped noticing how strange that is. We’re somehow ok with “private” meaning “private, trust me bro”. We’re ok with the service operator snooping into our private code. We’re ok with the service operator training their models on it! Not only do they know about our code, but they also benefit from it!

Well, that doesn’t sit well with me. As someone who believes that privacy is a fundamental human right, and a precondition for democracy, I care about “private” really meaning “private”.

So, when I started working on radicle.garden, a few months ago - see my previous post on why - I was very intrigued about how we could bring Radicle’s truly private repos into this hosted service. Radicle Garden has just recently been shipped, so no better time to make that happen!

Now, private repos are built into Radicle as first-class citizens. They work straight out of the box! Privacy even has its own section in the Radicle User Guide! (a good reminder why I love working on this project)

But Radicle’s private repos require that every node added to the allow-list is trusted. In the context of radicle.garden - the hosted service - adding a Garden node to the allow-list, brings us back to the same “trust me bro” situation, where the service operator — i.e. me and my colleagues — could technically read your repo contents. And if we’re going to be the better alternative I keep talking about, we should do better:

We shouldn’t be able to read your code, even if we wanted to.

With that goal, the answer shapes itself quite clearly: “End-to-End-Encryption” (E2EE).

But I’m far from a cryptography expert… Yet the idea was so inviting I just had to give it a go. If you read on, or even try this out, please help me find flaws in this first design (Hint: there are probably some)! Help me learn where it breaks!

With that big, fat, disclaimer out of the way, let me share what I’ve done so far:

What I built

In a nutshell, my approach is inspired by git-remote-gcrypt and based around a Git remote helper that wraps the standard Radicle one (git-remote-rad). It not only encrypts the contents of a repository (which other remote helpers, like git-remote-gcrypt already do), but also allows encrypting Radicle Issues and Patches (the Pull Request equivalent), so all collaboration artifacts also stay private.

You use it like this:

rad seal init       # one time setup (behind the scenes, it uses `git config remote.rad.vcs seal`) 
git push rad        # how you regularly push, but encryption happens before push to rad://
git pull rad        # how you regularly pull, but fetches rad:// and then decrypts

Under the hood, every push ensures your data is protected before the bytes leave your machine. File contents, file names, directory structure, commit messages, issue or patch comments, as well as author identity are all encrypted (by a symmetric key) and anonymised on the way out, then restored on the way in, when cloning or pulling. The symmetric key is age-encrypted to the public keys of the project’s delegates, so they can access it and use it to decrypt. A seeding Garden node only ever sees ciphertext.

No changes to the Radicle protocol are required. (Hat tip to Lorenz Leutgeb for helping make that happen!). The helper sits on top of Radicle’s normal Git transport. The encrypted commits are still valid Git objects, so Radicle’s peer-to-peer replication, signed refs and fetch protocol all work normally. The seeder just can’t read the content.

What the seeder can and cannot see

Some things are not encrypted however, so I’m choosing to avoid referring to this as an “E2EE” solution.

Here’s the threat model in one glance:

Data Visible to the seeder?
Source code No (encrypted)
File and directory names No (HMAC-hashed)
Directory structure No (flattened)
Commit messages No (encrypted)
Author name and email No (anonymised)
Commit timestamps No (quantised)
Radicle Issues and Patches Yes (use sealed)
Sealed Issues / Patches No (encrypted)
Branch names Yes
Commit graph topology Yes
Number of files and commits Yes


Two of those leaks are worth naming explicitly:

  • Branch names stay cleartext because Radicle’s gossip protocol routes on refs. Encrypting them would mean some changes to the protocol – undesirable.
  • Radicle Issues and Patches are also in cleartext, because the rad CLI writes don’t go through the remote helper. But Sealed Issues and Patches solve that.
  • For more details, and a detailed overview of how this works, the motivation, considered alternatives and more, there is a detailed design doc.

Built for radicle.garden

This isn’t a feature announcement. git-remote-seal is open-source, lives in its own repository, and is separate from Radicle or Radicle Garden. In fact, it might even work with other git hosting providers – though it won’t protect any of the content in their Issues / PRs / MRs and this makes that considerably less interesting than when used with Radicle.

However, it was built specifically with radicle.garden in mind, to help fill a gap. It helps combine the convenience of a hosted service, with the privacy built into self-hosted Radicle. With git-remote-seal, radicle.garden hopefully becomes more attractive for seeding even your private, encrypted repositories, without having to trust the service operators!

Where it is

Remember: current status is an early alpha prototype.

  • Browse the README: install instructions and a quick start guide.
  • Browse the design document for threat model and more detailed docs.
  • Clone, anonymously:
    • on Radicle: rad clone rad:z2XAxzriW78r9uge45P9fSk9E7BCZ
    • git clone https://yorgos.radicle.garden/z2XAxzriW78r9uge45P9fSk9E7BCZ.git

If you want to poke at it, file issues, or argue with the design choices, the project lives on Radicle. There is an open topic on the Radicle Zulip, which is the best place to chat about any of this. Please share your experiences if you try this out!

🌱