Migrating repos with secrets in their history
Migrating repos with secrets in their history
fremforge runs a gitleaks pre-receive hook on every push, including the initial mirror-push that brings a customer’s history into a fremforge org. If the source repo has secrets in any historical commit (an old .env, a leaked API key, a private SSL key, etc.), the import push fails and the migration stalls.
This page documents the two supported options. Option A is the recommended path for most teams; Option B is a last-resort fallback.
Option A, rotate the secret + rewrite history (recommended)
Best when:
- The leaked secret is a real one (API key, SSH private key, certificate, password). It needs to be invalidated regardless of where the repo lives, leaving it in any host’s git history is an ongoing exposure.
- The team can spend ~1 hour on history rewrite + force-push to the source.
Procedure:
- Rotate the leaked secret first. Before touching git: change the API key, revoke the SSH key, rotate the certificate. The leaked secret value should be invalid before history is rewritten, that way even if the rewrite is incomplete, the value is harmless.
- Identify the commit(s). The fremforge import attempt’s gitleaks output names the rule + commit SHA. Confirm you understand which secret + which commits.
- Rewrite history with
git filter-repo. (upstream docs)# Install: brew install git-filter-repo (or pip install git-filter-repo) # Verify upstream is clean: git clone --mirror <source-url> repo.git cd repo.git # Replace the secret value across history (string-replace shape): echo 'literal:AKIAEXAMPLE12345===>REDACTED' > replace.txt git filter-repo --replace-text replace.txt # OR: drop the file containing the secret across history: git filter-repo --invert-paths --path path/to/leaky/file - Force-push the rewritten history to the source repo first.
- The source-side history rewrite is what makes the fix durable. If you push the rewritten history only to fremforge, an attacker who cloned the source before the rewrite still has the leaked value.
- GitHub:
git push --force-with-lease --mirror - GitLab + ADO: same. Both reject force-push to protected branches by default; relax temporarily, push, re-protect.
- Run the migration via the standard procedure, see github / gitlab / azure-devops.
- Verify after import that fremforge’s gitleaks log shows zero blocks for the migration push.
Caveats:
- Forks and clones still hold the original history. There’s no way to retrieve every clone, that’s why Step 1 (rotate the secret first) is the load-bearing step.
- Rewriting history breaks every open PR/MR/branch on the source. Coordinate with the team before pressing enter; some hosts retire PRs cleanly on rebase, others surface them as “merged in N+1 commits”.
- Tag and signature integrity break, any signed tag pointing at a rewritten commit is no longer valid. Re-tag after the rewrite.
Option B, start from an empty repo, push only the latest commit (last resort)
Best when:
- The source history is too tangled to rewrite cleanly.
- The team is willing to forgo all blame/log history; only the current state matters.
- Common for archived projects being mirrored “for safekeeping” but not active development.
Procedure:
- Clone the source repo’s latest tree (any branch) to a working directory.
rm -rf .git && git init && git add . && git commit -m "Initial import: <source-url>@<commit-sha>".- Push the new single-commit repo to fremforge via the standard procedure.
- Add a NOTES.md or CHANGELOG link pointing back to the source for any team member who needs the full history.
What this loses:
- All
git blame/git loghistory. - All historical PR / MR diffs (those stay on the source host until you delete it).
- All historical commit signatures.
Choosing between the two
| Question | Option A | Option B |
|---|---|---|
| Is the secret still valid? | rotate first | n/a |
| Need history preserved? | rewritten | discarded |
| One-time push? | yes | yes |
| New work continues on the same repo? | yes | yes |
| Forks/clones of leak still in the wild? | residual risk only if not rotated | n/a (history not in fremforge) |
| Operator action needed? | no | no |
Default to A unless there’s a strong reason against it.
Related
- GitHub migration · GitLab migration · Azure DevOps migration
- pre-migration-check.sh, paginates source repos, cross-references Marketplace-action compatibility, surfaces webhook + branch-protection + pipeline configurations. Does NOT perform a secrets-in-history scan (that’s gitleaks’ job, run locally per the page above)
- Security and supply chain, the gitleaks hook this page is about