The problem · secrets in source

A repository is a permanent, replicated, plaintext archive.

Deleting the file does not remove the secret. Rewriting history does not recall the copies. In Git, the ability to clone is the ability to read everything that was ever committed — and rotating the credential is the only remedy that actually finishes.

This page is about the structural problem, not a product tour. If you are mid-incident: rotate first, then read the rest.

Anatomy

Five steps, and only the last one works.

  1. 01

    It gets committed

    A config file, a fixture, a debug script. Client-side hooks refuse the patterns someone already described; a credential in a shape nobody has written a rule for goes straight through.

  2. 02

    It gets pushed

    The object is now on a server you do not solely control. Content-addressed storage is immutable by design — the history referencing it can be rewritten, but the object was already served.

  3. 03

    It gets cloned

    Read access in Git is all-or-nothing. Anyone who can clone can read every byte of every file in every revision, including files deleted years ago, and that read leaves no record.

  4. 04

    It gets replicated

    Forks, mirrors, CI caches, backup snapshots, container layers, and each developer's reflog hold independent copies. Nobody maintains that list, so nobody can finish cleaning it.

  5. 05

    You rotate under pressure

    Because the copies cannot be recalled, the only complete remedy is to invalidate the credential itself, in every system that trusts it, while production depends on it.


Where each control stops

Every one of these is worth having. None of them moves the read boundary.

The controls teams reach for are prevention and cleanup. They act on the commit, not on who can read the object afterwards.

ControlWhat it doesWhere it stops
.gitignoreKeeps a path out of future snapshots.Says nothing about what is already recorded, and a forced add walks around it.
Pre-commit hooks and scannersRefuse a recognized pattern before it is recorded.Advisory and client-side. They catch shapes someone has already described, on machines that opted in.
Server-side push protectionRejects a push carrying a known credential pattern.The same pattern boundary — and the secret already exists in the local history of whoever wrote it.
History rewritingRemoves the object from the rewritten history.Changes commit identity for everyone downstream, and does nothing about copies already taken.
A secrets managerKeeps live credentials out of the repository entirely. This is the correct control for a credential.It does not help with the source, config, keys, and data that must live in the repository and must not be readable by everyone holding clone access.

A different primitive

Move the boundary from the push to the object.

TOVIO encrypts protected paths at snapshot time — before the object is written, so plaintext never lands in a tracked tree and key material never enters the synced object store.

  • Cloning stops implying reading. Content keys are wrapped for explicitly named recipients. A clone that is not one of them holds the whole repository and reads ciphertext.
  • Refusal happens before decryption. An out-of-scope read is refused rather than recorded after the fact.
  • Protected reads through TOVIO are recorded. The per-actor audit chain is signed, which makes tampering with the record detectable.
  • It works with no server. Tier-0 Solo uses age/X25519 keys offline — no Key Authority to stand up before you can protect a single path.
tovio — protecting a pathSIMULATED EXAMPLE
$ tovio policy set "deploy/*.env" --read "team=ops"
✓ Policy set for deploy/*.env
  Files under deploy/*.env will be encrypted on the next commit.
 
$ tovio commit -m "protect the deploy env"
✓ committed  chg:4e1a08kq  blake3:700e7a8f56…
  1 file(s) · new change chg:yv4jgg9a · undo with `tovio undo`
 
# what a clone without team=ops holds for that path:
a3f1 9c02 7bd4 ee81 5a10 4f7c 2266 90ab
c18d 33e6 b0f2 4419 7ac5 d20e 8831 5fbb
 
$

What this does not do

  • It is not a secrets manager. A live production credential belongs in a managed store with short lifetimes and rotation — not in version control, encrypted or otherwise. A repository is the wrong lifecycle for a rotating secret.
  • It does not un-leak an existing Git repository. Anything already committed elsewhere still has to be rotated. An import brings the history with it.
  • It protects content, not shape. Which paths exist, when they changed, and who changed them remain part of the collaboration surface.

Read how the permission model actually works.

The documentation is the source of truth for implemented behavior, supported profiles, and specification-only surface.