# Removing someone from git-crypt, SOPS or Ansible Vault

> None of the three can take back access someone already had. What each tool does when a person leaves, in what order, and why old commits stay readable.

URL: https://crunu.com/blog/remove-someone-from-git-crypt-sops-ansible-vault
Language: en
Updated: 2026-10-30

You can't fully remove someone from git-crypt, SOPS or Ansible Vault. git-crypt has no command to take a user out at all. SOPS and Ansible Vault let you change the keys, but only for the next version of each file: the person's old key still opens every earlier commit in the repository. In all three tools, the fix is the same two steps. Re-encrypt with a key the person never had, then reissue every secret they could read at the provider that issued it.

This post walks through what each tool actually does when someone leaves, the order that avoids a gap, and the part that no file-encryption tool can do for you. The wider process, from accounts to provider keys, is in the guide to [what to rotate when a developer leaves](https://crunu.com/guides/developer-offboarding-secret-rotation).

## Why removal is hard when secrets live in Git

All three tools encrypt files that sit in a repository. That design has real strengths: no server to run, secrets versioned next to the code that uses them, and reviews in the same pull request. It also means the repository is the distribution channel. Every clone, fork, CI cache and laptop backup holds a full copy of the encrypted history.

Removing a person therefore has two halves. The first is cryptographic: future versions of the file must be encrypted so that the person's key no longer works. The second is about what they already have. Anyone who could decrypt a file at commit 120 can still decrypt commit 120 after you rotate at commit 121, because Git keeps the old ciphertext and the old key still matches it.

*Figure: Diagram of where a secret goes once it is in a Git repository. Git host: the repository, pull requests and forks. Commit history: old commits keep the value after the file is deleted; it stays after you delete it. CI caches and artifacts: copies of the checked-out files. Every clone: laptops and build machines that pulled it; it stays after you delete it. Everyone with read access: today and anyone added later. Deleting the copy doesn’t undo the leak, so the value has to be reissued at its provider.*

Keep that in mind through the rest of the post. Rotating keys protects what you write next. It never protects what was already readable.

## git-crypt: there is no remove command

The git-crypt README is direct about it: "git-crypt does not support revoking access to an encrypted repository which was previously granted." It explains that in GPG mode "there's no del-gpg-user command to complement add-gpg-user", and in symmetric mode "there's no support for rotating the key." The reason given is that it is "an inherently complex problem in the context of historical data" ([git-crypt README](https://github.com/AGWA/git-crypt)).

The feature request for removal has been open since 2015. In it, the maintainer explains that rotation means you "have to rotate git-crypt's internal symmetric key and re-encrypt all files", and that old key versions would still be needed to read old commits ([git-crypt issue #47](https://github.com/AGWA/git-crypt/issues/47)).

The workaround is to start over with a new key. Users in that issue have posted several scripts; they share the same outline:

1. Decrypt the working copy with the current key and copy the protected files somewhere safe outside the repository.
2. Remove the protected files and the `.git-crypt` directory, and commit.
3. Run `git-crypt init` to create a new key, then `git-crypt add-gpg-user` for each person who should keep access.
4. Put the files back, commit, and check with `git-crypt status` that they are encrypted before you push.
5. Ask everyone who stays to pull and decrypt again with the new key.

Three caveats come with this. Branches that still carry the old key will conflict when merged, so do it when few branches are open. If you used a symmetric key exported with `git-crypt export-key`, every remaining person needs the new key file, delivered the same careful way as the first. And the history is untouched: one participant in the thread points out that the scripts change the key only for future commits.

git-crypt also leaves some things readable by design. The README notes that it "does not encrypt file names, commit messages, symlink targets, gitlinks, or other metadata." Removing a person changes nothing there: those parts were never encrypted, so treat them as readable by anyone with a clone.

## SOPS: update the keys, then rotate the data key

SOPS encrypts each file with a data key, and wraps that data key for every recipient: an age or PGP key, or a key in a cloud KMS. Removing a recipient is supported, but there is a trap in it.

`sops updatekeys` reads your `.sops.yaml` and adds or removes recipients on the file. On its own, it does not change the data key. The SOPS documentation says to use `updatekeys` "if you want to add a key without rotating the data key", and recommends rotating when a key is removed, because the person may have obtained the data key already ([SOPS key management](https://getsops.io/docs/usage/key-management/)).

For a compromised or departing recipient, the documented order is: remove the key from the key group in `.sops.yaml`, then run both commands for each encrypted file, then commit and push.

```sh
sops updatekeys secret.sops.yaml
sops rotate --in-place secret.sops.yaml
```

`sops rotate` "generates a new data encryption key and reencrypt all values with the new key." The documentation also warns that doing this in the wrong order can leave the old key with access in some cases.

Then comes the step that is easy to skip. SOPS is explicit that only after this is done "should you start rotating the actual passwords, API keys and other secrets". If you put new provider keys into the file before the data key changes, the old recipient can read the new values too.

If your recipients are cloud KMS keys rather than personal age or PGP keys, also check who can call decrypt on those KMS keys. Removing someone from the cloud account's IAM may be what actually ends their access to future versions.

## Ansible Vault: rekey, file by file

Ansible Vault uses a password, or several passwords with vault IDs, rather than per-person keys. There is no list of people in the file. Anyone who knows the password can decrypt, so removing a person means changing the password and getting the new one to everyone who stays.

For whole encrypted files this is one command. The Ansible documentation calls password rotation for files "straightforward with the rekey command":

```sh
ansible-vault rekey --vault-id prod@old-password-file --new-vault-id prod@prompt group_vars/prod/vault.yml
```

Encrypted variables are harder. If you used `ansible-vault encrypt_string` to put single values inline in a YAML file, the same page says: "You cannot rekey encrypted variables" ([Ansible: encrypting content](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault_encrypting_content.html)). Each one has to be decrypted with the old password and encrypted again with `ansible-vault encrypt_string` under the new one. On a large inventory, that is the slow part of the job.

Vault IDs help limit the damage in the future. If development and production use different vault passwords, a developer who only ever had the development password does not force a production rekey. The [comparison of SOPS and Ansible Vault](https://crunu.com/compare/sops-vs-ansible-vault) covers how the two tools differ beyond offboarding.

## What changes, and what doesn't

|                            | git-crypt                                | SOPS                                    | Ansible Vault                                               |
| -------------------------- | ---------------------------------------- | --------------------------------------- | ----------------------------------------------------------- |
| Remove a person            | Not supported; start over with a new key | `updatekeys` after editing `.sops.yaml` | Change the shared password                                  |
| Re-encrypt future versions | Re-initialize and re-add everyone else   | `sops rotate` on every file             | `ansible-vault rekey` per file; inline variables one by one |
| Old commits                | Readable with the old key                | Readable with the old key or data key   | Readable with the old password                              |
| Secrets inside the files   | Still valid until reissued               | Still valid until reissued              | Still valid until reissued                                  |

You can rewrite Git history to drop old ciphertext, but it doesn't help here. Copies made before the rewrite still exist in clones, forks and backups, and the person you are removing may hold one. Treat everything in the old history as seen.

## The step all three leave to you: reissue the values

Re-encryption only stops the person from reading what you write next. The database password, the Stripe key and the cloud access key inside those files are the same values they could read last week. To make their copy worthless, you have to create new values at each provider and switch your services over.

*Figure: Timeline of removing Tom Halvorsen, an example member of Kiteline, a fictional company. In Crunu: 1, the member is removed and the server stops serving them at once. 2, the service tokens, share links, invites and enrollment codes they could use are revoked. 3, the keys of their 6 environments are rotated in the admin’s browser and sealed to everyone who stays; the server stores only the new sealed keys. 4, an exposure report shows which versions of which environments they received, when and from which address. By hand: 5, 34 values are reissued at their providers, because what has been seen can’t be revoked.*

Do it in an order that avoids outages. For each value, issue a new one while the old one still works, deploy it, confirm nothing uses the old one, then revoke the old one. The [key rotation guides by service](https://crunu.com/rotate) cover the provider side for AWS, Stripe and others.

Then decide what to reissue. Here, file-based tools give you nothing to go on: none of the three records who decrypted which file, or when. The honest answer is usually "everything the person's key or password could open", which for a shared vault password means every value in every file that uses it.

## A short checklist for file-encrypted repositories

- [ ] List every file and branch encrypted to the person's key or to a password they knew
- [ ] For SOPS: edit `.sops.yaml`, then `updatekeys` and `rotate --in-place` on each file, and commit
- [ ] For Ansible Vault: rekey each file; re-create each inline encrypted variable under the new password
- [ ] For git-crypt: re-initialize with a new key and re-add everyone who stays
- [ ] Check cloud KMS permissions if SOPS uses KMS keys
- [ ] Reissue every value the person could read at its provider, highest impact first
- [ ] Revoke the old values once services run on the new ones
- [ ] Record the date, the files and the values you changed

The [offboarding checklist tool](https://crunu.com/tools/offboarding-checklist) builds a list like this for your own stack.

## How it looks with a key per environment

Part of the difficulty above comes from the model: one key or one password protects many files, and nothing records reads. A vault built around per-environment keys changes some of this and leaves some of it in place.

*Figure: Each environment of payments-api at Kiteline, a fictional company, has its own key, sealed only to those on its access list. Jonas Lindqvist is an editor on development and staging through the Developers access profile, so those two keys are sealed to his key. The production list has Amara Okafor as a reader and the machine api-prod-1, but not Jonas, so nothing he holds opens production.*

In Crunu, each environment, such as `payments-api / production`, has its own key, sealed separately to each person's and machine's public key. When an owner or admin removes someone through **Member removal**, the server stops serving that person anything in the organization immediately, and the keys of every environment where they had a key are rotated in the admin's browser. A developer who only had development never held the production key, so production does not need re-encrypting on their account.

The wizard also shows an **Exposure report**: which versions of which environments the person received, when and from which addresses, built from the server's record of fetches. Next to it is a **Reissue checklist** of values they saw that are still valid, listed by file path and variable name, never by value.

The limit is the same as in the table above. Crunu does not reissue keys at AWS or Stripe, and rotation does not undo what the person already saw. It narrows the list to what they actually received instead of everything they could have opened. If you are weighing a move, see the pages for [git-crypt](https://crunu.com/alternatives/git-crypt), [SOPS](https://crunu.com/alternatives/sops) and [Ansible Vault](https://crunu.com/alternatives/ansible-vault).

## Sources

- [git-crypt README](https://github.com/AGWA/git-crypt), checked October 30, 2026.
- [git-crypt issue #47: deleting a GPG user](https://github.com/AGWA/git-crypt/issues/47), checked October 30, 2026.
- [Key management, SOPS documentation](https://getsops.io/docs/usage/key-management/), checked October 30, 2026.
- [Encrypting content with Ansible Vault, Ansible documentation](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault_encrypting_content.html), checked October 30, 2026.
