Back to glossary

Maintainer

An open source maintainer is a person with authority to merge changes, cut releases, and publish a project's packages that every user then installs.

Also known as open source maintainer, package maintainer, project maintainer, package owner, co-maintainer

An open source maintainer, also called a package maintainer or project maintainer, is a person with authority over an open source project: they review and merge changes, cut releases, and hold the registry credentials that publish new versions. Every install inherits the maintainer's judgment and security, which makes maintainers the highest-leverage target in the software supply chain.

The word sounds like one role, but it names three separate permissions kept in three separate systems. Write access to the source repository lets someone merge code. A role on the package registry lets someone publish the artifact users install. The maintainers field in package metadata is text the publisher writes about itself. Nothing keeps the three in sync, and only the registry role reaches your machine.

That registry role is the one that matters. The registry checks the credential on a publish, not the code, so one maintainer account can ship a new version to every project whose version range accepts it. Most of that power sits with individuals. In a 2022 count by Josh Bressers, 16 million of 28 million npm package releases had exactly one maintainer, and Tidelift's 2024 survey of 437 maintainers found 60% are unpaid.

Our position: treat the set of people who can publish a package as part of the dependency. A change in who holds publish rights deserves the scrutiny of a code change, and every registry should expose that set as public, machine-readable data. A trusted name is still not evidence of safe code. The most damaging recent attacks shipped through real maintainer accounts, so the check that holds up is analyzing what each new version contains.

What is an open source maintainer?

#

An open source maintainer is the person who decides what goes into a project and ships it to users. The daily work is unglamorous: reviewing pull requests, triaging issues, keeping CI green, updating dependencies, writing release notes, and handling vulnerability reports. The authority is what defines the role. A maintainer can reject a change, and can publish a version that thousands of other projects pick up with no further review.

Maintainers also decide who else becomes a maintainer. That governance decision is as security-critical as any line of code, and in a solo project it is one person's call.

What is the difference between a contributor and a maintainer?

#

A contributor proposes changes, and a maintainer decides whether they land and when they ship. A contributor opens an issue, or a pull request from a fork, without write access to the repository or the package. The maintainer reviews the change, merges it, and later publishes a release that contains it.

GitHub's repository roles make the gradient explicit: Read, Triage, Write, Maintain, and Admin. Write lets someone push code. Maintain adds repository management, including pushing to protected branches, but excludes "sensitive or destructive actions." Admin adds collaborator management, deploy keys, and security advisories. No GitHub role grants permission to publish to npm or PyPI. Registry rights are granted separately, in a different system. Trusted publishing connects the two on purpose: once a registry trusts a CI workflow, anyone who can change or trigger that workflow can publish.

Most people become maintainers the same way: sustained contribution, then commit access, then release rights. That ladder is how projects survive burnout. It is also an attack path, as the xz Utils case below shows. The fsnotify maintainer dispute in May 2026 showed the reverse failure. Commit rights handed out years earlier for one-off fixes no longer matched who was stewarding the project. When those rights were revoked, downstream users, including Kubernetes maintainers, started asking whether a routine update was still safe, because from the outside a governance cleanup and a takeover look alike.

Three records of "maintainer" that disagree

#

Ask "who maintains this package?" and the answer depends on which record you read. For requests, the Python HTTP library, the core metadata of the latest release (2.34.2, uploaded May 14, 2026) and PyPI's role table disagree:

Bash
$ curl -s https://pypi.org/pypi/requests/json \
    | jq '{author_email: .info.author_email,
           maintainer_email: .info.maintainer_email,
           roles: .ownership.roles}'
{
  "author_email": "Kenneth Reitz <me@kennethreitz.org>",
  "maintainer_email": "Ian Stapleton Cordasco <graffatcolmingov@gmail.com>, Nate Prewitt <nate.prewitt@gmail.com>",
  "roles": [
    { "role": "Owner", "user": "Lukasa" },
    { "role": "Owner", "user": "graffatcolmingov" },
    { "role": "Owner", "user": "nateprewitt" }
  ]
}

The metadata names one author and two maintainers. PyPI's ownership key lists three accounts with the Owner role. Two match the declared maintainers by username or email address. The third, Lukasa, holds full upload and administration rights and appears nowhere in the metadata. Kenneth Reitz, the declared author, holds no role on the project.

Person or accountNamed in package metadataRole on PyPICan publish?
Kenneth ReitzAuthorNoneNo
Ian Stapleton Cordasco (graffatcolmingov)MaintainerOwnerYes
Nate Prewitt (nateprewitt)MaintainerOwnerYes
LukasaNot listedOwnerYes

None of this is a mistake by the project. The core metadata specification defines the Maintainer field as a name and contact details, "intended for use when a project is being maintained by someone other than the original author." In metadata, "maintainer" means the person to contact. On the registry, it means the person who can ship code to you.

Figure 1 shows the three records side by side. The source repository holds code-level roles (Read, Triage, Write, Maintain, Admin) that let someone merge. The registry holds publish roles (owner, maintainer, team) that let someone upload an installable version. Package metadata holds the author and maintainer fields, written by the publisher and checked by no one. A user's install pulls only from the registry, so only the registry roles decide who can put code on the user's machine. The repository reaches users only when someone with a registry role, or a trusted publishing workflow bound to the registry, publishes from it.

The npm registry has the same split, plus a history. package.json carries author and contributors fields that the publisher writes. The registry separately records who can publish (npm owner ls), and stamps every version with the publishing account (_npmUser) and a snapshot of the maintainer list at publish time. That snapshot turns the maintainer set into versioned data:

Bash
$ curl -s https://registry.npmjs.org/chalk | jq -r '. as $p
    | ["5.6.0","5.6.1","5.6.2"][] as $v
    | "\($v)\t\($p.time[$v])\t\($p.versions[$v]._npmUser.name // "(unpublished)")\t\([$p.versions[$v].maintainers[]?.name] | join(","))"'
5.6.0   2025-08-17T07:27:47.572Z   sindresorhus    sindresorhus,qix
5.6.1   2025-09-08T13:13:05.239Z   (unpublished)
5.6.2   2025-09-08T14:47:54.486Z   sindresorhus    sindresorhus

Chalk had two maintainers when 5.6.0 shipped. Version 5.6.1 was published at 13:13 UTC on September 8, 2025, during the Qix compromise, in which co-maintainer Qix's phished account pushed a crypto-wallet drainer into packages with 2 to 3 billion weekly downloads. Version 5.6.1 is gone from the registry, which kept only its timestamp. Version 5.6.2, published about 95 minutes later, lists one maintainer. As of November 2, 2026, npm access list collaborators chalk still returns only sindresorhus: read-write.

The rule of thumb: only the registry's role table answers "who can ship code to my machine?" Package metadata answers "who would like to hear from you?"

How do package registries define maintainer permissions?

#

Package registries define maintainer permissions differently, and the difference sets how much one compromised maintainer can do. The question that matters is whether "publish a version" and "change who can publish" are separate rights.

RegistryRolesWho can publishWho can add or remove publishers
npm, package owned by usersOne level ("there is only one level of access")Every ownerEvery owner
npm, package owned by an organizationOwner, Admin, MemberAll three rolesOwners and admins, who manage team package access
PyPIOwner, MaintainerBoth rolesOwner only
RubyGems.orgOwner, Maintainer (added November 2024)Both rolesOwner only
crates.ioNamed user owner, GitHub team ownerBoth kindsNamed owners only

Sources: npm owner docs, npm organization roles, PyPI's role descriptions, RubyGems.org's new maintainer role, and the Cargo Book.

On PyPI, a Maintainer "can upload releases" but "cannot invite collaborators" and cannot delete files, releases, or the project. RubyGems added the same split for the reason Ruby Central engineer Colby Swandale gave: "As long as every user has owner permissions, gaining access any of those accounts is enough to fully take over a gem." On crates.io, a team owner can publish and yank but cannot change owners. The Cargo Book is blunt about the other kind: "Needless to say, you shouldn't make people you don't fully trust into a named owner."

You can read the list directly on three of these registries:

Bash
$ npm owner ls debug
qix <npm@josh.junon.me>
tootallnate <nathan@tootallnate.net>

$ curl -s https://crates.io/api/v1/crates/serde/owners | jq -r '.users[] | "\(.kind)\t\(.login)"'
user    dtolnay
team    github:serde-rs:publish

$ curl -s https://pypi.org/pypi/requests/json | jq -c '.ownership.roles'
[{"role":"Owner","user":"Lukasa"},{"role":"Owner","user":"graffatcolmingov"},{"role":"Owner","user":"nateprewitt"}]

Our verdict: split publish rights from ownership rights wherever the registry allows it. On PyPI and RubyGems, give co-maintainers the Maintainer role. On crates.io, publish through a team owner, as serde does, and keep named owners to one or two people. On npm, tiers exist only through organizations, so a package owned by individual accounts gives every co-maintainer the power to add or remove every other one. Know what the split does not do. A Maintainer role still publishes, so it does not stop the first malicious release. It stops an attacker from locking out the real owners or adding accounts for persistence, and on PyPI from deleting releases.

Why are maintainers the main target of supply chain attacks?

#

Maintainers are the main target of supply chain attacks because one maintainer credential converts into code execution on every downstream machine, including through every transitive dependency path. That credential usually belongs to one unpaid person. Using ecosyste.ms data, Bressers estimated about 1.4 million unique people hold publish rights, and Andrew Nesbitt estimated roughly 10,000 maintain the packages behind 80% of usage, according to our coverage of the Tidelift survey. The same survey found 61% of unpaid maintainers work alone.

Attackers borrow a maintainer's authority in three ways:

  • Take over the account. Phishing, stolen tokens, or malware on the maintainer's laptop. Maintainer account takeover covers these routes in depth. In March 2026, a coordinated campaign hunting high-impact Node.js maintainers used cloned companies, staged Slack workspaces, and fake meeting software to plant remote access trojans. One of those intrusions led to the axios compromise on March 31, 2026. Malware riding a logged-in session makes 2FA irrelevant.
  • Inherit a dormant role. Publish rights outlive interest. When the node-ipc compromise shipped three malicious versions on May 14, 2026, the package had twelve listed maintainers, and the publishing account had been dormant on the package for years. As of November 2, 2026, the registry lists one.
  • Be given the role. Offer to help, do good work, or claim to be someone who already belongs, and receive publish rights. No login looks unusual. In the is package hijack in July 2025, an email that appeared to come from a removed former owner asked co-maintainer Jordan Harband to re-add the account. "Everything seemed normal, so I obliged," Harband said. Malware was published the next morning. The event-stream incident in 2018 followed the same pattern. Per npm's incident report, the attacker, "posing as a maintainer, took over maintainership" of the module. On September 9, 2018, the new maintainer added flatmap-stream as a dependency in event-stream@3.3.6, and flatmap-stream@0.1.1 carried a payload that harvested private keys from large wallets in the Copay bitcoin app. npm's security team was notified on November 26, 2018, about 11 weeks later.

The third route is the hardest to defend, because every control along the way sees a legitimate maintainer doing legitimate maintainer things. It also produces no CVE, so vulnerability scanners have nothing to match.

The promotion ladder is an attack path

#

The xz Utils case is the clearest example of an attacker earning the maintainer role instead of stealing it. The xz Utils backdoor (CVE-2024-3094) came from "Jia Tan," a contributor who climbed every rung of the normal ladder over more than two years. Each step was a routine open source event. Russ Cox's timeline, built from the mailing list and commit history, dates each one.

DateEventAuthority gained
2021-10-29Jia Tan sends a first, harmless patch to the xz-devel mailing listNone (contributor)
2022-04-22 to 2022-06-21Personas "Jigar Kumar" and "Dennis Ens" press maintainer Lasse Collin to hand over the projectNone
2022-06-29Collin writes that "Jia Tan may have a bigger role in the project in the future"None (social standing)
2022-10-28Added to the Tukaani GitHub organizationOrganization membership
2022-11-30README lists Jia Tan as a project maintainerPublic maintainer title
2022-12-30Merges commits directly into the repositoryCommit access
2023-03-18Tags and builds v5.4.2, the first release by Jia TanRelease rights
2024-02-24Releases v5.6.0 with a backdoor hidden in the release tarballUsed
2024-03-29Andres Freund discloses the backdoor on oss-securityDiscovered

Two details matter. First, the pressure campaign targeted the maintainer's capacity, not his credentials. "Progress will not happen until there is new maintainer," one persona wrote on June 7, 2022. Second, the attacker was patient at both ends. Nearly 17 months passed between the first patch and the first release, and another 11 months between the first release and the backdoor. By the time the backdoor shipped, Jia Tan had been the project's releaser for nearly a year, so any check that looks for a new or unfamiliar publisher would have stayed silent.

The pattern did not stop with xz. In April 2024, the OpenJS Foundation reported a "credible takeover attempt" in which emails from several addresses asked that the sender be "designated as a new maintainer" of a popular JavaScript project. Maintainers grew more guarded: in Tidelift's 2024 survey, 66% said they now trust pull requests from non-maintainers less, and 37% vet their own co-maintainers more carefully. That caution is rational, and it makes recruiting the next maintainer harder for projects that already have only one.

Watching maintainer changes from the consumer side

#

Consumers can observe maintainer changes in registry data, and the signal is useful within clear limits. On npm, three fields carry it: the current owner list, each version's _npmUser, and each version's maintainers snapshot. An account that has never published this package before, or a maintainer list that grows right before a release, is worth a look.

Socket turns those fields into alerts. New author fires when an npm collaborator publishes a version of a package for the first time. Unstable ownership fires when a new collaborator has begun publishing versions. New author is npm-only and rated low severity, because most new collaborators are legitimate. Unstable ownership is rated high severity.

Ownership signals have three blind spots:

  • Same-identity takeovers. An attacker who publishes as the existing maintainer, as in the Qix and axios compromises, changes nothing in the ownership record.
  • Patient insiders. An attacker who earns the role ages past any recency window, as Jia Tan did.
  • Noisy governance. Legitimate handovers, cleanups, and disputes trip the same signals. In the fsnotify thread, a user noted that automated scanners call a project unmaintained after 12 months without a release, which pressured a mature library into shipping. A quiet project is not the same as one with no one left to fix it.

So ownership changes are a reason to look harder, not a verdict. The check that works whether the publisher is new, old, or hijacked is the content check: did this version add an install script, a network call, obfuscated code, or a new dependency it does not need?

What maintainers can control, and what each control misses

#

Maintainers can shrink their own blast radius, and every measure leaves a gap:

  • Split roles. Give co-maintainers publish-only roles where the registry supports them. A publish-only account can still ship one malicious version.
  • Use phishing-resistant 2FA. A security key defeats relay phishing. It does nothing against malware already running in the maintainer's browser session.
  • Publish through CI with trusted publishing. Removing long-lived tokens closes the stolen-token route, and npm revoked all classic tokens on December 9, 2025. Provenance proves which workflow built a version, not that a maintainer intended it. An attacker who controls the repository or the workflow publishes with valid provenance.
  • Prune dormant publishers. Every account that can publish is attack surface. Pruning does nothing against compromise of an active maintainer.
  • Write down how maintainers are added. A GOVERNANCE.md that requires a second maintainer's sign-off and a public announcement turns a quiet handover into a visible event. It does not stop an attacker patient enough to satisfy the process, as xz showed.

The open problem: the role is social, the permission is a credential

#

Registries model a maintainer as an account. Projects experience a maintainer as a person with a job, a limited supply of attention, and a strong incentive to accept help. Attacks now target both sides of that gap: the account through phishing and malware, the person through flattery, pressure, and patience.

Registries have hardened the account side: role tiers on PyPI, RubyGems, and crates.io, and the revocation of classic tokens on npm. The person side has almost no tooling. Adding a stranger as an owner of an npm package with millions of weekly installs takes one command, and downstream users see it only as an edit to the packument's maintainers array. Removals can be just as quiet: in the is hijack, Harband was irritated that npm "would remove an owner without notifying the other owners." We think a change to a popular package's publisher set should be a first-class public event that downstream tools can subscribe to, with a waiting period before a new publisher's first release is installable. Until registries build that, the defense that covers all three routes ignores who published and reads what was published.