The Extraction Gauntlet: Separating the Product From Its First Tenant
How we split the product's own repository out of the private instance it grew up inside — three adversarial audit rounds, an object-database check that 'fresh git init' alone doesn't pass, and a denylist that is not allowed to name what it protects.
Published August 25, 2026
Field note · draft. This describes a real repository-separation process. The first tenant — the private instance the product grew up inside — is kept anonymous per our content rules. What is described here is our own security method, which we are happy to show.
Any product that is built by dogfooding it inside a real company has a delicate problem the day it wants outside developers: the product’s code and the company’s operating history are tangled together in one repository, and git remembers everything. You cannot scrub your way to safety, because every old version of every file is still recoverable. So you do not scrub. You extract.
The verdict: extract, never clean
The private instance stays private forever — tenant number one, the company’s operating record and the founder’s working notes. The product code, which was brand-agnostic by design, gets copied into a brand-new repository with fresh history by an allowlist: only the files that are genuinely product come across, and every remaining identifier is transformed at the copy step. Two independent AI audits, run the same day, converged on that verdict before a line of it was executed.
Three rounds, three kinds of reader
The thing we learned is that no single reviewer can clear a repository like this, because each kind of reader is blind to a different class of leak. So the method became a triangle:
- The builder lane runs the extraction and knows the mechanics. It catches the obvious: live credentials, brand names, seed data. (There were no credentials — that part had been verified clean earlier — but the builder still found trapped instance-data files and named leaks in fixtures.)
- The insider probe is a reviewer who knows the domain vocabulary. It catches things that read as innocent to a stranger: an on-brand adjective that is also a product family, a test regex whose literals happen to spell the real product line.
- The cold reader arrives with fresh context and one job: can I re-identify anyone from what is left? This round found the hardest class — derivative fingerprints. Initials. A datestamp baked into an account name. “Synthetic” financial fixtures that were actually small perturbations of real books, still carrying a real shareholder’s name and real customers with real overdue balances. A test fixture that pointed at a real, uninvolved third-party merchant.
Each round found a class the others structurally could not, and the severity fell by a class each time — from one-search-identifiable, to fingerprint-by-combination, to tooling-version drift. That progression is the point: the gauntlet is designed so the scariest findings come first and the last round is arguing about edge cases.
”Fresh git init” is necessary, not sufficient
A finding worth its own paragraph: an early round left recoverable pre-scrub commits inside the
shipped repository. The history looked fresh, but amended commits had left orphaned objects that
a git fsck could still surface. The fix was to regenerate the whole repository from scratch —
never amend — and to add object-database checks to the verification: zero unreachable objects,
a single clean commit, even the commit-message scratch file scrubbed. Fresh history is a claim
you have to prove against the object store, not a checkbox you tick at init.
A denylist that cannot name what it protects
The most counterintuitive rule came from the cold read. The natural way to keep leaks out of the copy is a denylist of forbidden strings, checked in the build. But a plaintext denylist of the very names you are hiding is a reversible concordance — hand someone the list of what you scrubbed and you have handed them a map to the original. So the shipped repository carries no list at all. The pattern set lives only in the private extraction script; the public build decodes it from a secret at check time and fails loudly if the secret is missing. The guard is not allowed to name what it guards.
What actually held
The iron rule that made it work: never hand-edit the copy. Every fix landed in the source or in the extraction script, and the copy was deleted and regenerated. A fix that would not survive a re-run of the extraction is not a fix. When it was done, the product repository stood on its own: fresh single-commit history, zero recoverable pre-scrub objects, secret-scanner clean, and the full test suite green with no live credentials anywhere in the environment.
None of this is exotic. It is the boring, adversarial discipline that a security reviewer actually wants to see, written down honestly — including the parts we got wrong on the first pass and fixed on the second.
Questions founders ask
- Why not just clean the original repository and share that?
- Because git history keeps every version of every file. No in-place cleanup can make a repository that grew up around one company's operating history safe to hand to an outside developer — the old versions are still recoverable. The only sound move is extraction: copy the brand-agnostic product code into a repository with fresh history, and leave the private instance private forever as tenant number one.
- What is a derivative fingerprint, and why can't a denylist catch it?
- A denylist greps for known strings — a brand name, a domain, an account ID. A derivative fingerprint is identifying information that no single string names: a set of initials, a datestamp embedded in an account label, cent-precision figures that read as real because real numbers are rarely round, a career artifact quoted in a comment. A regex gate structurally cannot enumerate these. The defenses that work are an accreting private gate, fixing at the source and regenerating rather than hand-editing the copy, and periodic cold reads by someone with fresh eyes.