No Commits in Two Years, and the Repository Still Ships Risk
No commits in two years is a fact about a git log: the code stopped being changed. It is silent on whether a pipeline still builds it, whether anything imports it, whether the credentials inside it still authenticate, or whether an instance is running right now. Activity is the cheapest field in the estate to read, which is how it ends up carrying decisions it cannot support.
The field that sorts fastest
Every source control system puts the last commit date on the repository page and exposes it through the API. It sorts several thousand rows in a second and needs nobody's cooperation to produce. When the list is too long to read one row at a time, the field that sorts fastest becomes the field decisions get made with.
What that field records is the last time a person changed the code. As a measure of engineering attention it is fair, and as an input to sequencing it is reasonable. Read as a statement about exposure, it reports on the wrong system. The commit history describes the source. The exposure sits in what the source produced and where that output ended up.
A repository can sit at the bottom of that sort while several other things about it stay live.
- A pipeline defined in an external CI system builds it every night and the build succeeds, because the schedule lives in that system and runs on its own clock
- A dozen internal services import the library it publishes, and each of those services builds this week
- The artifact it published sits in your registry and installs on request, since the registry serves what was published to it whatever happened to the repository
- An access token committed to it three years ago still authenticates against a system nobody rotated
- A container built from it runs in production on infrastructure whose renewal somebody approves each year without reading
Why recency collapsed as a criterion
Last-commit recency was one of the fields CWS tried as a sort key on an estate of roughly 6,300 repositories, and it failed as soon as it had to carry that many rows. The reason it failed is structural, which is why the same failure is available on a much smaller estate.
A library that reached the state its authors intended and a repository whose owning team was reorganized away produce the same commit graph. Both go flat and stay flat. One is finished and the other is orphaned, that difference is the entire question, and it has to come from a system other than the git log.
The direction of the error is worth sitting with. The repositories that sort to the bottom on recency include the shared libraries everything imports, because a dependency that behaves correctly stops attracting commits. When a vulnerability lands in something that library depends on, the reach is wide and the response is slow, because responding starts with finding a team willing to claim the repository.
Stable code and abandoned code produce the same commit history.
Four records worth more than the commit date
Each of the four can be read from a system that already holds the answer, without asking the owning team to characterize its own work. That property matters more here than anywhere else, because on a dormant repository the owning team may have gone.
Build history comes first. A pipeline that ran last month against code that last changed two years ago is being triggered by something: a nightly schedule, a base image rebuild, a dependency update bot. All of those leave a run record. Recent successful builds with no recent commits means an automated system is using this repository and nobody has looked at it.
Consumers come second, and the answer lives in dependency manifests and lockfiles across the rest of the estate. Resolve the internal packages other services import back to the repositories that publish them, then add the download records from your internal registry. A fan-in of zero is worth writing carefully. It means no consumer was found across the tenants you enumerated, and the tenants you failed to enumerate are exactly where a dormant system would sit.
Deployment comes third, and it depends on a link somebody had to choose to record. The question is which running workload was built from this repository, and answering it needs a trail from a deployed artifact back to its source. Where the deploy path recorded no such link, the honest entry is unknown, and an unknown belongs on the gap list.
Credentials come fourth. Scan the full commit history, because the interesting material was committed and reverted years ago and sits below the current head of the branch. Then test each finding for whether it authenticates today. Rotation happens when somebody notices, and dormancy removes the occasions on which anyone would. Access belongs alongside it: a repository going quiet leaves every team membership and deploy key where it was on the day of the last commit.
- CI run history for the trailing year, including scheduled runs and runs triggered by dependency updates
- Download records from the internal package registry for anything this repository publishes
- Dependency manifests and lockfiles across every enumerated tenant, resolved back to the repository behind each internal package
- Deployment records and image provenance, answering which running workloads were built here
- Secret scanning across the full history, with every finding tested against the system it was issued for
- The access list, since membership persists on its own schedule while the repository goes quiet
The credentials in a repository do not expire because the code stopped moving.
The archive flag records a decision
Where the platform offers the flag, archiving makes a repository read-only and marks it in the interface. On GitHub the read-only state covers the whole surface together, issues, pull requests, code, labels, milestones, projects, wiki, releases, commits, tags, branches and permissions, and the repository carries an archive label and a banner. It also keeps appearing in code search, where the documented way to leave archived repositories out of a query is to exclude them by hand. That is the whole of the action and it does that job well. Where a platform has no such flag, the decision has to be recorded somewhere outside the repository, which is a smaller version of the same problem.
The weight the flag carries inside a security program is a separate matter. The flag is machine-readable and a scope query is easy to write against it, which is how it ends up deciding what stays in the denominator. The flag reaches the repository. The published artifact stays installable, a pipeline defined in another system keeps its schedule, and the workload keeps running. Removing the row on the strength of the flag takes the repository out of the denominator and leaves everything it produced where it was.
The word does double duty, and both meanings turn up in the same meeting. The NIST Secure Software Development Framework asks organizations to archive and protect each software release, meaning preserve the release and its provenance so it can be examined later. That is retention. Archiving a repository to take it out of scope is a removal. The two pull in opposite directions and share a name.
CIS Control 2 asks organizations to maintain a software inventory and to address software the vendor no longer supports. The control is written about products you bought. Internally built code whose maintainers have gone is the analogue on your own side of that line, and it arrives at the same question: something is running and no team has agreed to fix it. Six things turn an archive decision into one you can show somebody.
- The build record showing no run inside a stated window, with the window itself written down
- The consumer check stated with its boundary: no importer found across these tenants, on this date
- The deployment answer: either no running workload was built here, or the name of the one that has to be retired first
- Credentials from the full history, each one rotated or confirmed dead, each one recorded
- A named person who accepts the decision, and the date they accepted it
- The route back, since the repository may be the only surviving description of a system that is still up
An archive flag is a decision about the repository. The running system needs a decision of its own.
Retiring the system is separate work
Decommissioning removes the running thing, and it is spread across systems belonging to different people. The workload sits with whoever runs the platform, the DNS record with networking, the service account with identity, the cloud resources with whoever holds that budget. The sequence crosses four teams, and it stops after the first step because that is where the first team's authority ends.
Ownership is the constraint, and there is an incentive underneath it. Switching something off carries a small chance of breaking a thing nobody knew about. Leaving it up registers as zero on every measure anyone reports. The people making that call are responding sensibly to how the work is counted, and that is the part a security program can change.
CWS argues for holding the archive decision open until a decommission item closes against it, with an owner and a date. That is a practice recommendation. Without it, an estate accumulates repositories marked done alongside systems still answering on port 443, and the two records never meet.
Dormancy also ends. A repository with no commits in two years can take one next week, and whoever picks it up should find the reasoning already attached. Keep the repository and its history. The retirement record carries six entries.
- The workload, the environment it runs in, and the date it is scheduled to stop
- The DNS record and any load balancer rule that still resolves to it
- The published artifact, either withdrawn from the registry or marked with its status, since installs continue while it is available
- Service accounts, deploy keys and machine identities created for it, each revoked with a date against it
- Storage buckets, databases and cloud resources that outlive the workload, which is where the data sits after everyone stops thinking about the system
- The archive decision itself, dated and attributed, so a repository that wakes up in month six reopens reasoning somebody already did