Git history moves between platforms with one command, but almost everything that protects the code does not: branch protection, required reviews, code owners, deploy keys, app installations, webhooks and access groups live in the platform, not in the repository, and have to be rebuilt deliberately. Organisations migrate source control platforms when a self-hosted server reaches end of support, when they move to a hosted service or back on premises, after mergers, and when consolidating several platforms into one. This playbook covers the migration itself: what is in Git and what is not, how to move history completely and prove it, how to deal with secrets already in history, how to rebuild the controls and non-human identities around the code, and how to retire the old platform. It is vendor-neutral and based on the Git documentation, NIST SP 800-218 (SSDF), NIST SP 800-204D, the CISA and NSA guidance on defending CI/CD environments and the OpenSSF Scorecard checks. Ongoing repository hardening is in the Git Repository Security Guide.
Key takeaways
- Git is the easy part. A mirror clone moves all refs; the controls, identities and integrations around the repository are platform metadata that must be rebuilt.
- Secrets in history migrate too. Every credential ever committed arrives on the new platform; scan before the move and rotate what you find, because history rewriting does not make a leaked secret safe.
- Rebuild protections before opening write access. The window between import and protection is when the new platform has code without review rules.
- Machine access is the long tail. Deploy keys, app installations, CI tokens, webhooks and automation accounts outnumber people; inventory and reissue them, do not copy them.
- One writable home per repository. During the overlap, make the old repository read-only as soon as the new one accepts pushes, or the two will diverge.
What is in Git and what is not
| Object | Where it lives | Migration approach | Watch for |
|---|---|---|---|
| Commits, branches, tags, notes | Git | Mirror clone and push all refs | Platform-specific hidden refs (for example review refs) that a mirror may or may not include |
| Large file storage objects | Separate object store referenced by pointers in Git | Fetch all objects for all refs and push them to the target's store | Pointers arrive but the files do not; builds fail later |
| Commit and tag signatures | Git objects | Preserved by the mirror; re-register signing keys on the target for verification display | Signatures reported as unverified because keys or identities were not mapped |
| Pull or merge requests, reviews and comments | Platform | Migration tooling or export; at minimum archive | Review evidence needed for audit lost with the old platform |
| Branch protection, required reviews, code owners rules | Platform (code owners file is in Git; enforcement is not) | Recreate as configuration and test that it blocks | Rules that existed per repository with no central record |
| Users, teams and permissions | Platform and identity provider | Map groups from the identity provider; rebuild by intent | Personal accounts, outside collaborators and administrator rights copied across unchanged |
| Deploy keys, access tokens, app installations | Platform | Inventory and reissue on the target | Old keys and tokens still valid on the old platform after cutover |
| Webhooks and integrations | Platform | Recreate with a shared secret; remove from the old platform | Webhook payloads still sent to retired endpoints |
| CI configuration and stored CI secrets | Git (definitions) and platform (secrets) | See the CI/CD server migration playbook | Pipelines that run on import with no protections yet |
| Audit logs | Platform | Export and archive for the retention period | Evidence of past access lost |
Moving history completely and proving it
The Git documentation describes git clone --mirror as setting up a mirror that maps all refs, including remote-tracking branches and notes, not just local branches. For repositories using large file storage, the Git LFS documentation describes git lfs fetch --all as downloading all objects referenced by any commit reachable from the given refs, and says this is primarily for backup and migration purposes. Use both.
- Mirror clone each repository from the source to a controlled migration host.
- Fetch all large file objects for every ref if the repository uses large file storage.
- Check integrity: run
git fsck, which verifies the connectivity and validity of the objects in the database, on the mirror before pushing. - Push all refs and large file objects to the target.
- Prove equality: compare the list of refs and the commit each points to in source and target. Matching commit IDs mean matching history, because Git identifies commits by the hash of their content.
- Check signatures: spot-check signed commits and tags with
git verify-commitandgit verify-tagon a clone of the target.
Keep the mirror clones as an archive, protected like the source, until the old platform is decommissioned. NIST SP 800-218 task PS.1.1 asks that all forms of code be stored on the principle of least privilege; the migration host and its clones are in scope.
Secrets already in history
A migration copies every secret ever committed, in every branch and tag. In the Uber 2016 breach, an AWS key in a private repository opened the way to data on 57 million people; private does not mean safe.
- Scan full history before migrating: NIST SP 800-204D recommends scanning repositories for secrets even before they are populated, since exposed credentials may already be leaked. Scan all refs of each mirror, not only the default branch. See the Git Secret Scanning Guide.
- Rotate first: revoke and replace every valid secret found at the system it grants access to. That is the fix; anything else is clean-up.
- Rewrite history only by decision: rewriting changes every later commit ID, breaks signatures, open reviews and references in other systems. Do it when policy requires removing the data, after rotation, and before the push to the target, so the target never holds it.
- Turn on push protection in the target before developers start pushing. NIST SP 800-204D lists push protection that scans commits for keys and tokens as a requirement (COMMIT-REQ-1).
Secrets found in Git are a common breach path; see the Internet Archive breach, where an exposed source control token was part of the chain.
Rebuilding controls around the code
The OpenSSF Scorecard checks give a useful list of platform settings that do not travel with Git: Branch-Protection, Code-Review, Token-Permissions, Dangerous-Workflow and Webhooks (whether webhooks authenticate request origins with a secret). Recreate each, then confirm with the same checks on the target.
| Control | Target setting | Source guidance |
|---|---|---|
| Protected default and release branches | No direct pushes, no force pushes, no deletion; status checks required | OpenSSF Scorecard Branch-Protection |
| Two-person review | At least one approving review from someone other than the author; approvals reset on new commits | CISA and NSA two-person rule; NIST SP 800-204D |
| Code owners for sensitive paths | Required review from owners of pipeline definitions, infrastructure code and security configuration | NIST SP 800-204D treats SCM write access as high risk |
| Signed commits | Required on protected branches where the organisation signs | CISA and NSA recommend signing code and CI/CD configuration and verifying signatures |
| Fork and outside contributor builds | Approval required before CI runs for outside contributors | NIST SP 800-204D (strictest setting) |
| Default job token permissions | Read-only by default | OpenSSF Scorecard Token-Permissions |
| Webhooks | Shared secret configured on every webhook | OpenSSF Scorecard Webhooks |
| Single sign-on and MFA | All users through the identity provider; phishing-resistant MFA for administrators | CISA and NSA recommend MFA and federation for humans |
Apply these at organisation level where the target allows, so new repositories inherit them. The GitHub Organisation Security Checklist shows the settings on one common platform.
Where identity fits
A source control platform holds more machine identities than most teams realise, and they are the ones that get copied without review.
| Identity | Target pattern | Before cutover |
|---|---|---|
| Deploy keys (per repository SSH keys) | New key pair per consumer, read-only unless write is justified | List from the source; drop unused ones. See How to find unused GitHub deploy keys and SSH keys |
| Personal access tokens used by automation | App or service identity with scoped, expiring tokens | Find automation running as people |
| Installed apps and OAuth integrations | Reinstall only reviewed apps, scoped to named repositories | See How to audit GitHub Apps and OAuth apps in an organisation |
| Machine users and bot accounts | Replace with app identities where possible; owner and expiry for each | See How to audit GitHub outside collaborators and machine users |
| Migration identity | Read on source, create and write on target, per wave | Time-boxed; removed at sign-off |
The platform's tokens are a prime target: the Gitloker campaign used malicious OAuth apps and stolen credentials to wipe repositories. A migration is the moment to cut that integration surface down. MITRE ATT&CK tracks collection from code repositories as T1213.003.
The phased playbook
- Inventory: repositories with owners and activity, large file usage, users and groups, outside collaborators, deploy keys, tokens, apps, webhooks, CI integrations, and every external system that clones from the old URLs (build servers, package builds, documentation sites, scripts).
- Design the target: organisation structure, groups mapped from the identity provider, organisation-wide protection rules, push protection, default token permissions and the app allow list.
- Scan and rotate: scan the full history of each mirror for secrets and rotate valid findings before the repository is pushed.
- Pilot: migrate one active repository with large files, signed commits and CI; confirm protections block a direct push and an unreviewed merge.
- Migrate in waves: archive-only and inactive repositories first, then active ones by team. For each repository: freeze the source branch for writes, mirror, verify, push, apply protections, then open the target for writes.
- Switch consumers: update remotes in CI, deployment and automation; reissue deploy keys and tokens; recreate webhooks with secrets.
- Make the source read-only: archive each migrated repository on the old platform and alert on any push or new token use.
- Decommission: export audit logs and review history, revoke every remaining key, token and app installation on the old platform, delete its accounts in the identity provider, then sanitise storage and backups following NIST SP 800-88.
Rollback: until the source is decommissioned, rollback for a repository means re-opening the source for writes after pushing back any commits made on the target (a mirror push in the other direction). Because both sides hold the same commit IDs, this is safe as long as only one side has been writable at a time. Small organisations can often migrate everything in one weekend with a write freeze; large estates need waves per team and a published mapping of old to new URLs.
Detection and measurement
| Metric or signal | Why it matters | Target (our recommendation) |
|---|---|---|
| Repositories with matching refs and commit IDs in source and target | Completeness of history | 100% of migrated repositories |
| Active repositories meeting the protection baseline | Controls were rebuilt | 100% before open for writes |
| Valid secrets found in history and not yet rotated | Exposure carried across | Zero at wave sign-off |
| Pushes, clones or token use on the old platform after read-only | Missed consumers or misuse | Zero unexplained; alert on pushes |
| Machine identities on the target versus source | Integration surface reduced, not copied | Fewer, each with owner and expiry |
| Large file pointer errors in builds | Objects not migrated | Zero |
Send the audit logs of both platforms to the same place throughout. See the GitHub Audit Log Detection Guide for the kinds of events to watch on one platform.
Common failure patterns
- Protections applied after import: repositories are open for writes for days before branch protection and review rules are set.
- Secrets carried in history: credentials committed years ago arrive on a new platform, sometimes with wider visibility than before.
- Large file objects left behind: pointer files migrate, the content does not, and the old platform cannot be retired.
- Dual writes: both repositories accept pushes, histories diverge and a fix lands only on the old one.
- Copied machine access: every deploy key, bot account and app is recreated without review, so the target inherits the old platform's sprawl.
- Old platform kept alive: left running, unpatched and still holding valid tokens, "for reference".
Framework mapping
| Activity | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | Other |
|---|---|---|---|
| Protecting code on both platforms | AC-6, CM-5 | PR.AA-05 | SSDF PS.1.1 |
| Rebuilding branch protection and review | SA-10, CM-3 | PR.PS-06 | OpenSSF Scorecard Branch-Protection, Code-Review |
| History integrity and signed commits | SI-7, CM-14 | PR.DS-01 | SSDF PS.2 |
| Secrets in history and machine identities | IA-5 | PR.AA-05 | ATT&CK T1552.001 |
| Inventory of repositories and integrations | CM-8 | ID.AM-02 | |
| Audit logs and archives | AU-2, AU-11, CP-9 | DE.CM-09, PR.DS-11 | |
| Threats the migration must not reopen | AC-6 | PR.AA-05 | ATT&CK T1213.003, T1195.002 |
Practitioner checklist
- Inventory repositories, identities, integrations and external consumers of old URLs.
- Mirror clone, fetch all large file objects and run
git fsckbefore pushing. - Compare refs and commit IDs between source and target for every repository.
- Scan full history for secrets and rotate valid findings before the push.
- Apply protection rules and push protection before opening the target for writes.
- Reissue deploy keys, tokens and app installations; never copy them.
- Recreate webhooks with shared secrets and delete them from the old platform.
- Keep exactly one writable home per repository during the overlap.
- Archive review history and audit logs for the retention period.
- Revoke everything on the old platform, then decommission and sanitise.
Standards and references
- Git: git-clone documentation (--mirror)
- Git LFS: git-lfs-fetch documentation
- NIST: SP 800-218, Secure Software Development Framework (SSDF) Version 1.1
- NIST: SP 800-204D, Strategies for the Integration of Software Supply Chain Security in DevSecOps CI/CD Pipelines
- CISA and NSA: Defending Continuous Integration/Continuous Delivery (CI/CD) Environments
- OpenSSF: Scorecard checks
- MITRE ATT&CK: Data from Information Repositories: Code Repositories
- NIST: SP 800-53 Rev. 5, Security and Privacy Controls
Related NHI Mgmt Group resources: Git Repository Security Guide · Git Secret Scanning Guide · GitHub Organisation Compromise Playbook · CI/CD server migration playbook · Artefact and container registry migration playbook