Skip to content

lucatescari/gitveil

Repository files navigation

Gitveil logo

Gitveil

CI Release License Platform

Transparent file encryption in git -- a Rust implementation compatible with git-crypt.

Gitveil lets you store sensitive files (API keys, credentials, private configs) alongside public code in a git repository. Files you mark for encryption are automatically encrypted when committed and decrypted when checked out. Everyone else uses git normally -- they just can't read the encrypted files without the key.

How It Works

Gitveil hooks into git's clean/smudge filter mechanism:

  • On git add (clean filter): plaintext is encrypted before being stored in the repository
  • On git checkout (smudge filter): encrypted blobs are decrypted into your working copy
  • On git diff (textconv): encrypted files are decrypted on the fly for readable diffs

This is completely transparent -- after setup, you just use git as usual.

Compatibility

Gitveil is byte-compatible with git-crypt. Repositories encrypted with git-crypt can be unlocked with gitveil and vice versa. They use the same:

  • Key file format (FORMAT_VERSION 2, TLV-encoded)
  • Encrypted file format (\0GITCRYPT\0 header + HMAC-SHA1 nonce + AES-256-CTR ciphertext)
  • Git filter configuration (filter=git-crypt, diff=git-crypt)
  • .git-crypt/ directory structure for GPG-encrypted keys

Cryptography

  • AES-256-CTR for file encryption
  • HMAC-SHA1 to derive a deterministic nonce from the file contents (so identical plaintext produces identical ciphertext -- required for git to detect unchanged files)
  • 32-byte AES key + 64-byte HMAC key per key entry, generated from OS-level CSPRNG
  • Key material is zeroized on drop via the zeroize crate

Installation

Homebrew (macOS)

brew install lucatescari/gitveil/gitveil

From source

git clone https://github.com/lucatescari/gitveil.git
cd gitveil
cargo build --release
cp target/release/gitveil /usr/local/bin/

Quick Start

1. Initialize a repository

cd my-repo
gitveil init

This generates a symmetric key (stored in .git/git-crypt/keys/default) and configures git's clean/smudge/diff filters.

2. Specify files to encrypt

Create or edit .gitattributes in your repo root:

# Encrypt all .key files
*.key filter=git-crypt diff=git-crypt

# Encrypt a specific file
secrets.env filter=git-crypt diff=git-crypt

# Encrypt everything in a directory
config/private/** filter=git-crypt diff=git-crypt

Important: Add .gitattributes before adding the files you want encrypted. If a file was already committed in plaintext, gitveil cannot retroactively encrypt its history.

3. Use git normally

echo "API_KEY=sk-secret-123" > secrets.env
git add secrets.env
git commit -m "add secrets"

The file is encrypted in the repository but appears as plaintext in your working copy.

4. Share access

Option A: Symmetric key (simpler, you handle secure transport)

gitveil export-key ~/gitveil-key
# Send the key file securely to your collaborator

Your collaborator then runs:

gitveil unlock ~/gitveil-key

Option B: GPG (key is encrypted to each user's GPG public key)

gitveil add-gpg-user collaborator@example.com

Your collaborator then runs:

gitveil unlock

GPG decrypts the symmetric key automatically using their private key.

5. Lock when done

gitveil lock

This removes the key from your machine and re-encrypts files in your working copy.

Commands

gitveil init

Generate a key and prepare the repository for encryption.

gitveil init [-k <key-name>]
Option Description
-k, --key-name Use a named key instead of default

gitveil lock

Remove the key and re-encrypt files in the working copy.

gitveil lock [-k <key-name>] [-a] [-f]
Option Description
-k, --key-name Lock a specific named key
-a, --all Lock all keys
-f, --force Force lock even with uncommitted changes

gitveil unlock

Decrypt the repository.

gitveil unlock [<key-file>...]

Without arguments, attempts GPG-based unlock using keys in .git-crypt/. With key file arguments, uses symmetric key files.

If your GPG private key is passphrase-protected, gitveil will let gpg-agent invoke pinentry to prompt you (terminal or GUI), just like git-crypt. Only collaborator files matching a secret key in your local keyring are tried, so pinentry fires at most once.

gitveil add-gpg-user

Add a GPG user as a collaborator.

gitveil add-gpg-user [-k <key-name>] [-n] [--trusted] [--from <source>] [<GPG_USER_ID>]
Option Description
-k, --key-name Use a specific named key
-n, --no-commit Don't auto-commit the GPG-encrypted key
--trusted Skip GPG Web of Trust verification
--from <source> Import GPG key(s) from a file, directory, or git URL

When called with no arguments and no --from, gitveil checks for a globally configured keyring directory (see gitveil config set-keyring). If configured, it scans the directory and shows an interactive picker.

Import keys from a shared keyring

If your team stores GPG public keys in a shared repository, you can import them directly:

# Import a single key file
gitveil add-gpg-user --from /path/to/keys/alice.asc

# Browse a directory and pick interactively
gitveil add-gpg-user --from /path/to/keys/

# Clone a git repo and pick from it
gitveil add-gpg-user --from git@github.com:company/gpg-keys.git

When pointing at a directory (or git URL), gitveil scans for .asc, .gpg, .pub, and .key files, shows a list of found keys (name, email, fingerprint), and lets you select one or more to add as collaborators.

gitveil config

Manage global gitveil configuration.

# Set a global GPG keyring directory
gitveil config set-keyring /path/to/team-keys

# Show current configuration
gitveil config show

# Remove the keyring setting
gitveil config unset-keyring

When a keyring directory is configured, gitveil add-gpg-user (with no arguments and no --from) will automatically scan the keyring directory and present an interactive picker to select GPG keys. This is useful when your team stores GPG public keys in a shared folder or git repository.

The keyring directory has no special format -- it's just a folder containing GPG public key files (exported with gpg --export or gpg --armor --export). Files are matched by extension (.asc, .gpg, .pub, .key) and can be organized in subdirectories. Non-key files and symlinks are ignored.

team-keys/
├── engineering/
│   ├── alice.asc
│   └── bob.pub
├── design/
│   └── carol.gpg
└── README.md           # ignored (not a key extension)

The keyring path is stored in ~/.config/gitveil/config (respects $XDG_CONFIG_HOME). The config file is created with 0600 permissions and the config directory with 0700 permissions.

gitveil rm-gpg-user

Remove a GPG user's access.

gitveil rm-gpg-user [-k <key-name>] [-n] <GPG_USER_ID>
Option Description
-k, --key-name Remove from a specific named key
-n, --no-commit Don't auto-commit the removal

Note: this only prevents future unlocks. For full revocation, rotate the key.

gitveil ls-gpg-users

List GPG users who have access.

gitveil ls-gpg-users [-k <key-name>]

Shows each user's name, email, and fingerprint. Without -k, lists users for all keys.

gitveil completions

Generate shell completions.

gitveil completions bash >> ~/.bashrc
gitveil completions zsh > ~/.zfunc/_gitveil
gitveil completions fish > ~/.config/fish/completions/gitveil.fish

gitveil export-key

Export the symmetric key to a file.

gitveil export-key [-k <key-name>] [<output-file>]

Omit the output file to write to stdout.

gitveil status

Show the encryption status of files in the repository.

gitveil status [-e] [-u] [-a | --all] [-f | --fix]
Option Description
(none) List files marked for encryption (the actionable set), tracked + untracked
-e Show only files whose committed blob is encrypted
-u Show only files marked for encryption whose blob is plaintext (the set needing re-encryption — pair with -f to fix)
-a, --all Include files without the git-crypt filter too (verbose git-crypt-style listing)
-f, --fix Re-stage tracked files whose committed blob is plaintext but should be encrypted (skips files deleted from the working tree; never auto-adds untracked files)

By default, status is focused on files governed by a git-crypt filter — for a large repo this is the actionable subset. Tracked and untracked filter-matched files are shown. Use -a/--all for the full git-crypt-style listing that also includes non-filter files.

When a filter-marked file's committed blob is plaintext (typically because it was staged before .gitattributes took effect), *** WARNING *** is appended to its line and a summary at the end suggests gitveil status -f. Untracked filter-marked files appear with an (untracked) suffix to make it clear that the file on disk is still plaintext — it'll be encrypted on staging.

Works without gitveil init -- the command is informational and can be used to audit filter coverage before initializing. If filter-marked files were committed without init, status surfaces them as WARNINGs.

Performance: at most one git ls-files per category, one batched git check-attr, and one batched git cat-file regardless of repo size. On a Unity project with ~4,000 files it completes in ~130 ms vs ~65 seconds for git-crypt -- roughly 500x faster.

Named Keys

Gitveil supports multiple named keys, allowing you to share different files with different groups of people:

# Initialize a named key
gitveil init -k team-backend

# Use it in .gitattributes
# db-credentials.env filter=git-crypt-team-backend diff=git-crypt-team-backend

# Share with specific people
gitveil add-gpg-user -k team-backend backend-dev@company.com

# Export the named key
gitveil export-key -k team-backend ~/backend-key

Limitations

  • File contents only. Gitveil cannot encrypt filenames, commit messages, branch names, or other git metadata.
  • No history rewriting. If a file was committed in plaintext before adding the filter=git-crypt attribute, the plaintext remains in git history.
  • Encrypted files are opaque blobs. Git cannot compute deltas on encrypted content, so storage efficiency is reduced for encrypted files.
  • No key rotation or revocation. Removing a collaborator's GPG key does not re-encrypt with a new symmetric key. They still have the old key.

Security Considerations

No ciphertext integrity verification

AES-256-CTR provides confidentiality but not integrity. An attacker with push access to the repository can flip bits in the ciphertext, which flips the corresponding bits in the plaintext. The HMAC-SHA1 is used only for deterministic nonce derivation, not for authentication. There is no tamper detection on decryption. This is inherited from git-crypt's design -- adding MAC verification would break compatibility.

Trust model for gpg.program

Gitveil respects the gpg.program git config setting, which means the GPG binary is determined by local repository config. An attacker who can modify .git/config (e.g., via a malicious clone) could point this to an arbitrary program. This is the same trust model as git itself -- local config is trusted. Be cautious when running gitveil in repositories you did not create.

Large file memory usage

The clean filter must read the entire file into memory to compute the HMAC-SHA1 nonce before encryption can begin. Very large files (multi-GiB) may cause high memory usage.

Project Structure

src/
  main.rs              # Entry point + CLI dispatch
  cli.rs               # clap CLI definitions
  config.rs            # Global configuration (XDG keyring path)
  constants.rs         # Magic bytes, sizes, field IDs
  error.rs             # Error types
  crypto/
    aes_ctr.rs          # AES-256-CTR encryption
    hmac.rs             # HMAC-SHA1 nonce derivation
    random.rs           # Secure random generation
  key/
    format.rs           # TLV field serialization
    entry.rs            # Key entry (version + AES key + HMAC key)
    key_file.rs         # Multi-version key file container
  filter/
    clean.rs            # Encrypt on git add
    smudge.rs           # Decrypt on git checkout
    diff.rs             # Decrypt for git diff
  commands/
    init.rs             # Initialize repository encryption
    lock.rs             # Secure repository, remove keys
    unlock.rs           # Decrypt repository
    status.rs           # Show encryption status
    export_key.rs       # Export symmetric key
    add_gpg_user.rs     # Add GPG collaborator
    config.rs           # Global config management
    rm_gpg_user.rs      # Remove GPG collaborator
    ls_gpg_users.rs     # List GPG collaborators
  git/
    repo.rs             # Repository inspection
    config.rs           # Git filter configuration
    checkout.rs         # Force checkout for lock/unlock
  gpg/
    operations.rs       # GPG encrypt/decrypt via subprocess
    import.rs           # Import GPG keys from files/URLs
benchmark/               # Performance benchmarks (gitveil vs git-crypt)

License

GPL-3.0-only

About

A fast, cross-platform Rust implementation of git-crypt for transparent file encryption in Git repositories. Byte-compatible drop-in replacement.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors