Repository ownership changes: audit access before handoff
Repository ownership changes require more than moving a Git project. Audit people, tokens, deploy keys, workflows, apps, and agent actions before access lingers.

Repository ownership changes are security events, even when the move looks like ordinary housekeeping. The repository may move to a new team in an afternoon, but its surrounding permissions can remain attached to departed maintainers, retired automation, old cloud accounts, and credentials that nobody has opened in months.
I have seen teams call a handoff complete after changing the repository owner and removing two people from the collaborator page. Then a forgotten deployment job keeps publishing with a personal token, an orphaned SSH key still reads private source, or an old Git hosting app retains authority to alter pull requests. The repository page looked clean. The actual control plane was not.
The practical rule is simple: transfer ownership only after you can name every identity and service that can cause a meaningful effect. "Meaningful" includes reading nonpublic code, pushing commits, opening or approving pull requests, changing build definitions, accessing secrets, publishing packages, deploying software, and changing access itself.
A repository transfer does not transfer accountability
A repository owner is an administrative label. Accountability means that a current person can explain what the repository connects to, why each connection exists, who maintains it, and how to revoke it during an incident.
Those are different things. Git hosting permissions describe access inside one service. Modern projects also depend on CI runners, package registries, cloud roles, code scanning apps, release bots, documentation publishers, chat notifications, DNS providers, and artifact stores. Each system has its own identities and its own memory of the project.
The distinction matters most when a project moves because of a reorganization, an acquisition, an internal platform migration, or staff turnover. The receiving team tends to focus on keeping the build green. The departing team tends to focus on removing their names. Neither task proves that control moved safely.
Write down the ownership boundary before anyone edits access. It should identify:
- the receiving technical owner who accepts operational responsibility
- the business owner who decides who may access the code and releases
- the incident contact who can authorize urgent revocation
- the source of record for repository membership and service identities
- the date when the former team loses authority
Do not use a group mailbox or a vague team name as the final owner. A group may receive mail, but it cannot make a decision at 2 a.m. Identify named people and review that record when the team changes.
There is another uncomfortable point: a project with no current owner should not retain broad production access merely because it still builds. If no one can safely answer for deployment credentials, pause releases or reduce permissions until somebody can. Availability is not an excuse to preserve unknown authority.
Build an inventory from effects, not from the repository page
A useful access inventory starts with actions that can happen, then works backwards to the identities that can cause them. Starting from the collaborator list is faster, and it misses too much.
Ask what can happen to this project without a developer sitting at the Git hosting website. A commit can arrive from a bot. A CI workflow can obtain a cloud token. A package can publish after a tag appears. A webhook receiver can trigger a production deployment. A code review app can write comments or change checks. Each effect points to a permission that needs an owner.
Use a working table with five columns: capability, identity, credential location, current owner, and revoke procedure. The revoke procedure deserves its own column because "delete the token" is often not the real procedure. You may need to uninstall an app, remove a cloud trust rule, delete a deploy key, invalidate a registry token, or change a webhook secret at both ends.
A first pass should cover these categories:
- Human identities, including direct collaborators, organization teams, outside collaborators, and organization administrators.
- Nonhuman identities, including machine users, service accounts, CI runner identities, and application installations.
- Credentials, including SSH deploy keys, personal access tokens, app private keys, webhook secrets, registry tokens, and cloud credentials.
- Execution paths, including workflow files, reusable workflow references, runner groups, deployment environments, release scripts, and scheduled jobs.
- Data exits, including webhooks, package publishing, documentation publication, backups, mirrors, issue integrations, and notification services.
The inventory should name the credential's purpose in plain language. "CI token" is not enough. "Read source and publish the internal command line package when a signed release tag is pushed" tells the next maintainer what to test and what risk follows a failure.
You will find entries nobody recognizes. Do not let a plausible name keep them alive. Ask for evidence: where it is configured, which job used it recently, what it can do, and who will own it after the transfer. If the answer remains vague, schedule removal. Unknown credentials tend to become known only after an incident.
Personal credentials make a handoff fragile
Any production or release path that depends on one maintainer's personal token is already overdue for replacement. A project move simply exposes the weakness.
Personal credentials create two failure modes. The first is obvious: the former maintainer may retain access after leaving the project. The second is more common: their account gets disabled or its token expires, and a release path breaks when the receiving team needs it most. Teams often respond by asking the former maintainer to create another token. That repairs the immediate failure and hardens the dependency.
Replace personal credentials with a service identity only when the service actually needs a durable identity. Give it the smallest permission set that completes its documented job. A release publisher may need permission to publish one package. It does not need broad organization administration or access to every repository.
Do not confuse a machine user with a well managed service identity. A machine user is simply an account used by automation. It can still have an unknown password, a personal recovery email, broad membership, and no owner. Treat it as a normal identity with a lifecycle: provision it deliberately, document its owner, review its memberships, and remove it when its job ends.
This is where the popular advice to "just use one shared automation account" fails. It is popular because it gets automation running quickly and avoids the effort of understanding each integration. It also concentrates unrelated permissions in one identity. When one project moves teams, nobody can revoke its access without risking every other project that depends on it.
Separate identities by operational purpose. A build reader, a release publisher, and a production deployer often need different privileges and different owners. That separation makes revocation less risky and incident investigation less ambiguous.
Check user-owned tokens outside the Git host too. A deployment script may read a token from a CI secret, but that token can belong to a departed person's cloud account. The secret's location does not tell you its authority. Follow it to the issuer and inspect the permissions there.
Deploy keys and application installations need separate reviews
Deploy keys, Git hosting apps, and OAuth integrations all provide repository access, but they fail differently. Treating them as one list causes sloppy revocation.
A deploy key is usually an SSH public key attached to a repository. It may grant read access or write access, depending on how someone configured it. Its strength is narrow attachment. Its weakness is weak identity: the key says little about the system holding the private half. If a comment says "build server" and that server changed hands twice, the repository record will not rescue you.
For every deploy key, verify four facts: where the private key lives, which process uses it, whether it needs write permission, and who owns the host or secret store that holds it. Remove write access from keys that only fetch source. Remove any key you cannot connect to an active system and named owner.
An application installation presents the reverse problem. It usually has a better identity, permissions, and event history, but it may be installed across many repositories. Removing it from one project might not stop a related service elsewhere. Inspect the application's requested permissions, installation scope, private key rotation process, callback URLs, and the organization account that can alter the installation.
GitHub's documentation separates deploy keys from GitHub Apps for good reason. Deploy keys attach to repositories, while an app receives permissions through an installation and uses its own credentials. Do not assume that deleting a deploy key affects app access, or that uninstalling an app invalidates an SSH key. They are unrelated authority paths.
OAuth integrations deserve the same suspicion. They may act on behalf of a user rather than a dedicated application identity. During a transfer, determine whether the integration's authorization depends on a former maintainer. If it does, move it to a supported service identity or remove it. Waiting for a person to leave the company is not a revocation plan.
Workflow files can grant more authority than their names suggest
A workflow that appears to run tests can still obtain credentials, invoke reusable workflows, write to the repository, or trigger deployment systems. Read the file before you decide its access is harmless.
Inspect all executable repository configuration, not only the workflow that performs releases. That includes CI definitions, scripts called by those definitions, dependency update configuration, deployment manifests, infrastructure code, package publication settings, and scripts triggered by comments or pull requests.
Pay attention to places where a job crosses a trust boundary. Common examples include a workflow that exchanges an identity token for a cloud role, a job that runs code from a pull request with repository write permission, or a reusable workflow pulled from another repository. The workflow name might say "lint". Its permissions tell the truth.
GitHub's Actions documentation warns that pull_request_target runs in the context of the base repository and can access privileges that an ordinary pull request workflow does not have. That event is not automatically wrong. It is wrong to pair it with untrusted pull request code or scripts that an outsider can influence. During a handoff, find these workflows and make the receiving team explicitly accept them.
A simple repository search catches many obvious references. Run it locally after fetching the full repository history you need to inspect:
git grep -nE '(AWS_|AZURE_|GCP_|TOKEN|SECRET|DEPLOY|PUBLISH|ssh |curl |webhook)' -- \
'.github' '.gitlab-ci.yml' 'scripts' 'infra' 'package.json' 2>/dev/null
Expect output shaped like this:
.github/workflows/release.yml:42: id-token: write
scripts/publish.sh:18: curl -H "Authorization: Bearer $REGISTRY_TOKEN"
infra/deploy.sh:9: ssh -i "$DEPLOY_KEY" "$DEPLOY_HOST"
The command does not prove a secret exists or that a job is dangerous. It gives you a review queue. Trace every hit to the credential issuer, permission scope, and failure path. Also search for workflow references that point outside the repository, because code can inherit authority through a reusable workflow that this repository does not control.
Do not grant broad defaults to make an inherited workflow pass. Fix the job's declared permissions and test the one action it must perform. A transfer is a good time to remove permissions that survived only because nobody wanted to disturb an old pipeline.
Revoke in an order that preserves evidence and avoids outages
Revocation needs sequencing. If you delete everything first, you may lose the evidence needed to identify an active dependency. If you wait for perfect documentation, old access may remain indefinitely.
Freeze discretionary changes during the review. The receiving owner should know whether a new app installation, new deploy key, or new organization administrator appears before the baseline is complete. This does not require stopping ordinary development, but it does require change visibility.
Take an export or recorded snapshot of memberships, external collaborator lists, deploy keys, app installations, webhooks, CI secrets metadata, cloud trust relationships, and recent audit events. Do not store secret values in the record. Store identifiers, scopes, owners, creation context if available, and the time you inspected them.
Then apply this order:
- Remove direct access for former maintainers and reduce former organization administrators where the transfer requires it.
- Disable or uninstall unknown integrations and revoke deploy keys with no active owner.
- Replace known personal credentials with service identities, then test the exact build, publish, or deploy path.
- Rotate shared secrets such as webhook secrets, application private keys, and registry credentials after the replacement works.
- Review audit events and failed jobs for a period that fits the project's release cadence, then remove temporary exceptions.
The order separates unknown authority from known dependencies. An unknown deploy key provides no supported function, so remove it early. A known release credential needs a replacement first, otherwise you turn a security correction into an avoidable outage.
Use a deliberate break-glass path for a failed replacement. It should name who can restore service, how long the exception lasts, and how the team records it. Do not restore a departed maintainer's broad token because the release is late. Create a temporary narrowly scoped credential under current ownership, record the exception, and remove it after the repair.
Agent access must follow the same ownership boundary
Autonomous coding agents can edit code, call APIs, use SSH, publish artifacts, and affect infrastructure through the tools attached to them. A repository transfer that ignores agent access leaves a large gap in the review.
Do not ask only which people can run an agent. Ask which agent processes can act for the repository, which tools they can call, which credentials those tools use, and whether a reviewer can reconstruct a specific action later. An agent running from a developer's shell, a CI job, or a remote runner may have different authority even when it uses the same model.
Keep long lived credentials outside the agent's prompt and working files. Passing a secret through environment variables or tool output makes it available to logs, subprocesses, accidental commits, and the agent's own context. Masking a value in one log viewer does not make the process boundary safe.
For teams using Sallyport, the Mac app can keep HTTP and SSH credentials in its encrypted vault while an MCP-capable agent requests an action through its local shim, rather than receiving the credential itself. Its session and activity journals give the receiving owner separate records of agent runs and individual calls, which is more useful than a transcript when a handoff needs review.
During the transition, revoke agent sessions tied to old workstations or processes and make the new owner approve fresh runs. Then inspect per credential use: a read-only source fetch and a production deployment should not inherit the same approval expectation. The point is not to make every command painful. The point is to make powerful calls visible to someone who owns the consequences.
A written agent inventory should include the repository scope, execution location, tool channels, credential owner, action owner, and emergency revoke action. If you cannot fill in those fields, the agent has access that the project cannot responsibly transfer.
Audit records must answer who changed control
A log that says "token used" is insufficient after an ownership change. You need to answer whose token it was, what process used it, what it did, which repository it targeted, and whether the action was allowed under the new ownership.
Keep repository audit events, CI execution records, cloud audit records, package registry events, and agent action records together in the transfer file or incident system. They will not share one format, and that is fine. What matters is a common time reference and enough identifiers to follow an event across systems.
Test the records before you declare the transfer done. Make a harmless change through each supported route: a normal developer push, a pull request automation event, a scheduled job if one exists, a package publish to a nonproduction destination if available, and an agent action that needs approval. Confirm that the receiving team can find the evidence without calling the former maintainers.
Tamper evidence is useful, but it does not replace retention and access control. An append-only log tells you whether somebody altered a record. It does not help if the event source never recorded the call, the log retention expired, or no current person can read the record during an incident.
Set a review date after the move. The first review catches automation that runs only weekly or monthly, and the second review catches people who request an exception because something stopped working. Those exceptions are diagnostic material. Each one reveals a dependency that the initial inventory missed.
The receiving team should be able to remove every access path
A handoff is complete when the receiving team can revoke every meaningful access path without needing the old team to explain a secret, locate a machine, or approve a change. That standard is stricter than a repository transfer dialog, and it prevents the failure that keeps repeating: code ownership moves on paper while control remains scattered elsewhere.
Start with the least glamorous artifact, the access inventory. Put an owner and revoke procedure beside every connected service. Then remove the entries that have neither. The work feels tedious until the first urgent credential rotation. At that point, the inventory is the difference between a contained repair and a week of guessing.
FAQ
What should happen when a software repository changes owners?
Treat the transfer as an access review, not an administrative rename. Confirm who controls the repository, then inventory every identity and connected service that can read, write, deploy, publish, or administer it.
Does removing old maintainers revoke all repository access?
No. Removing a user from a repository often leaves deploy keys, machine users, app installations, package tokens, and cloud identities intact. Each of those has its own revocation path.
Is it safe to transfer a repository without an access audit?
Only if the new team has reviewed every capability first. A quiet transfer can preserve unknown automation, inherited organization privileges, and credentials owned by people who no longer have operational responsibility.
Which connected services should I review after a repository transfer?
Start with direct collaborators, teams, organization roles, deploy keys, access tokens, Git hosting apps, SSH keys, CI identities, cloud roles, package registries, and webhooks. Then add any system that receives source changes or can publish a build.
Are deploy keys still safe after a project moves teams?
It can be, if the key belongs to a documented service account with a narrow job and an active owner. An SSH key whose owner, scope, or purpose is unknown should be removed rather than preserved for convenience.
Should every developer on the new team be a repository admin?
No. A repository administrator can usually change branch protections, alter workflows, add credentials, or grant others access. Reserve administration for people who can answer for those changes.
What is the best order for rotating repository credentials?
List every credential, service identity, and webhook destination before you delete anything. Revoke unknown or personal credentials first, rotate shared secrets next, then test the documented automation with its replacement identity.
Do repository audit logs replace CI and deployment logs?
You need both. Git hosting audit events can explain permission and configuration changes, while CI logs explain what ran after a change. Keep them long enough to reconstruct an incident that begins before the handoff.
How do I control AI coding agents during an ownership change?
Every sensitive action should tie back to a human owner and a specific agent run. A gateway that keeps credentials outside the agent can record the call without placing long lived secrets in the agent context.
How can I prove the old team no longer controls a repository?
First, identify the services that can still act on the repository after the previous team has left. Ownership must be visible in access records, credential records, workflow configuration, and the incident contact list.