What one approval delegates (npm, measured Oct 25, 2026)
approve: express@5 → 5.3.0
approve: jest@30 → 30.5.2
An open source program office (OSPO) is the team that sets and enforces how an organization uses, contributes to, and releases open source software.
Also known as open source office, open source programs office
An open source program office (OSPO), also called an open source office or open source programs office, is the team owning an organization's open source policy: which packages engineers may use, which licenses are acceptable, how code is contributed or released, and who responds when a dependency is compromised. In 2025, 92% of OSPOs were involved in open source security.
The OSPO's core security mechanism is the intake decision. An engineer asks to use a package. The OSPO checks its license, its maintenance health, and its known vulnerabilities, then adds it to an approved list. That list holds package names. Package managers install versions, resolved fresh at install time across a tree of dependencies the reviewer never saw.
Our position: an OSPO that approves names once is governing the wrong unit. Attackers publish versions. Open source policy has to be evaluated against every name@version, at install time, on every install.
An OSPO is the team, sometimes a single person, that an organization makes accountable for how it uses and participates in open source. The TODO Group, the Linux Foundation working group for OSPO practitioners, defines it as "the center of gravity for an organization's open source operations and structure."
OSPOs are common in large companies and far from universal. In the Linux Foundation's 2025 State of OSPOs survey of 338 organizations, 27% had a formally structured OSPO, 15% had an informal one, and 34% had neither and no plans for one. In the 2026 edition, published October 6, 2026, 53% of large enterprises reported a formally structured OSPO.
The TODO Group's OSPO definition project splits the job into four roles: enabler, counselor, advocate, and "environmentalist." The last one covers the long-term health of the projects the organization depends on, through security, maintenance, and funding. Security lives in that role, and it is the subject of most of this entry.
An OSPO runs three flows: inbound, for what the organization consumes; outbound, for what it contributes and releases; and the compliance record that ties both to legal obligations. The Linux Foundation asked the 116 surveyed organizations with an OSPO to pick their OSPO's top three responsibilities:
| OSPO responsibility (2025, top-three pick, n=116) | Share |
|---|---|
| Establishing and improving open source policies and processes | 47% |
| Overseeing open source license compliance | 44% |
| Developing and executing open source strategy | 34% |
| Collaborating with open source organizations and communities | 33% |
| Advising on open source best practices | 30% |
Inbound work produces a usage policy, an approved-license list, an intake review, and an inventory of what shipped. The inventory is usually a software bill of materials. License compliance is the oldest OSPO function and the best standardized. OpenChain ISO/IEC 5230:2020 specifies what an open source license compliance program must do. Its sibling, ISO/IEC 18974:2023, does the same for open source security assurance. Both standards define what a program must have, not how to run it.
Outbound work covers contribution policy, release review, and the security contact and vulnerability process for projects the organization publishes.
Security has moved to the center of the role. In the same 2025 survey, of the organizations with an OSPO, 42% made open source security decisions and another 50% advised the team that did. Skills have lagged. An OpenSSF-commissioned study we covered in 2024 found OSPO teams tied with system operations for the highest unfamiliarity with secure software development, at 38%.
Approving a package once does not keep it safe because the approval covers a name, while each install resolves new versions across a dependency tree nobody reviewed.
Start with how resolution works. A package.json entry like "express": "^5.1.0" is a range, not a version. Without a lockfile, npm picks the newest version that matches at install time. It repeats that for every dependency of express, and every dependency of those, until the tree is complete. Each transitive dependency is chosen by a range written by someone the OSPO never evaluated. A lockfile freezes the result, which protects rebuilds and nothing else. As we wrote about lockfiles earlier this year: "Lockfiles make existing installs deterministic. They do not make new installs safe." New installs happen constantly: a dependency is added or updated, a bot opens an update pull request, CI runs npm install instead of npm ci, or someone runs a tool through npx.
We measured what one approval delegates. On October 25, 2026, we resolved two common requests with npm 10.9, express@5 and jest@30, without running install scripts:
mkdir req && cd req && npm init -y
npm install jest@30 --save-dev --package-lock-only --ignore-scripts
node fanout.mjs package-lock.jsonFor every package in the resulting lockfile, the script fetches the packument from the npm registry. It counts the accounts in maintainers, the npm accounts with publish rights, and the versions published under that name in the previous 365 days:
// usage: node fanout.mjs <path to package-lock.json>
import { readFileSync } from 'node:fs'
const lock = JSON.parse(readFileSync(process.argv[2], 'utf8'))
const names = new Set(Object.keys(lock.packages).filter(Boolean)
.map(p => p.slice(p.lastIndexOf('node_modules/') + 13)))
const yearAgo = Date.now() - 365 * 864e5
const accounts = new Set()
let releases = 0
for (const name of names) {
const doc = await (await fetch(`https://registry.npmjs.org/${name.replace('/', '%2f')}`)).json()
for (const m of doc.maintainers ?? []) accounts.add(m.name)
for (const [v, t] of Object.entries(doc.time ?? {}))
if (v !== 'created' && v !== 'modified' && Date.parse(t) > yearAgo) releases++
}
console.log({ packages: names.size, maintainerAccounts: accounts.size, releasesLast365d: releases })What one approval delegates (npm, measured Oct 25, 2026)
approve: express@5 → 5.3.0
approve: jest@30 → 30.5.2
| Approval request | Resolved to | Packages installed | npm maintainer accounts | Versions published, last 365 days |
|---|---|---|---|---|
| express@5 | 5.3.0 | 65 | 36 | 103 |
| jest@30 | 30.5.2 | 293 | 126 | 1,596 |
Approving jest is a standing grant of trust to 126 npm accounts. Any one of them can publish a version that the next fresh install resolves. Two caveats keep the numbers honest. The release count includes every version published under those names, not only versions inside the ranges in this tree. The maintainers field also lists npm accounts, not people.
Intake review still earns its place. License terms and project abandonment are properties of a project, and they change slowly, so reviewing them once per name is reasonable. Malware is a property of a version, and it arrives in minutes.
The axios compromise shows the gap in practice. axios is the kind of package every approved list contains, with about 100 million weekly npm downloads. The license, the project's maintenance record, and the name all stayed the same. The attacker published new versions that added one dependency, plain-crypto-js, whose install hook dropped malware.
The axios compromise, UTC
axios@1.14.0 published by the GitHub Actions trusted publisherplain-crypto-js@4.2.0 publishedplain-crypto-js@4.2.1 published with a postinstall dropperplain-crypto-js@4.2.1 as malwareaxios@1.14.1 published, adding plain-crypto-js as a dependencyaxios@0.30.4 published on the 0.x branchplain-crypto-js@0.0.1-security placeholderMalicious dependency flagged 16 minutes before the axios version that pulled it in existed.
The timeline, from npm registry timestamps and our report:
| Time (UTC) | Event |
|---|---|
| 2026-03-27 19:01:40 | axios@1.14.0 published by the GitHub Actions trusted publisher |
| 2026-03-30 05:57:32 | plain-crypto-js@4.2.0 published |
| 2026-03-30 23:59:12 | plain-crypto-js@4.2.1 published; its postinstall hook runs a dropper, setup.js |
| 2026-03-31 00:05:41 | Socket flags plain-crypto-js@4.2.1 as malware |
| 2026-03-31 00:21:58 | axios@1.14.1 published, adding plain-crypto-js as a dependency |
| 2026-03-31 01:00:57 | axios@0.30.4 published, poisoning the 0.x branch |
| 2026-03-31 03:25:11 | npm replaces plain-crypto-js with a 0.0.1-security placeholder |
The malicious dependency was flagged 16 minutes before the axios version that pulled it in existed. A control keyed to name@version had the answer before axios 1.14.1 was published. A control keyed to the name axios never would.
The registry still records the event. Removed versions keep their entry in the time map, but the manifest is gone:
$ npm view axios time --json | grep -E '"1\.14\.|"0\.30\.4'
"1.14.0": "2026-03-27T19:01:40.915Z",
"1.14.1": "2026-03-31T00:21:58.168Z",
"0.30.4": "2026-03-31T01:00:57.285Z",
$ npm view axios@1.14.1 version
npm error code E404
npm error 404 No match found for version 1.14.1
$ npm view axios@1.14.0 _npmUser.name _npmUser.trustedPublisher.id
_npmUser.name = 'GitHub Actions'
_npmUser.trustedPublisher.id = 'github'Walk the usual OSPO checks through that window. The license was unchanged. The repository's health signals, the inputs to tools like OpenSSF Scorecard, were unchanged, because they describe the repository, not the tarball on npm. A CVE scan would have come back clean, because malware does not get a CVE at publish time. Provenance did not help either. axios 1.14.0 was published through trusted publishing, but the attacker held the maintainer's browser sessions. In the maintainer's own account, Jason Saayman said: "Nothing in the publishing pipeline would have prevented a malicious release."
The damage came through ordinary automation. A GitHub Actions workflow in OpenAI's macOS app-signing pipeline ran axios 1.14.1 on March 31. OpenAI's disclosure traced the root cause to a misconfigured workflow: its action "used a floating tag, as opposed to a specific commit hash," and it had no minimumReleaseAge configured for new packages. OpenAI rotated its macOS code-signing certificate.
The pattern repeats at scale. The Mini Shai-Hulud worm, active since April 29, 2026, "self-propagates by republishing into packages reachable via stolen npm tokens." As of October 2026, Socket's campaign page counts 1,069 affected package artifacts across 507 unique packages. Each artifact carried the name of an existing, legitimate package. That is what maintainer account takeover looks like from the consumer side.
The EU Cyber Resilience Act (CRA) turns OSPO dependency diligence into a legal duty for manufacturers selling products with digital elements in the EU. The CRA is Regulation (EU) 2024/2847, and it entered into force in December 2024. Article 13(5) requires manufacturers to exercise due diligence when integrating third-party components, including free and open source ones, "so that those components do not compromise the cybersecurity of the product with digital elements." Article 13(6) adds a duty that runs upstream: a manufacturer that finds a vulnerability in a component must report it to whoever maintains that component.
The dates matter to OSPO planning. As our summary of ENISA's package manager advisory notes, reporting of actively exploited vulnerabilities started September 11, 2026, and the broader obligations apply from December 2027. Penalties reach €15 million or 2.5% of global annual turnover.
The CRA also creates a role that lands on OSPOs whose companies publish open source: the open-source software steward. The OpenSSF's CRA steward summary defines a steward as a legal person, other than a manufacturer, that provides sustained support for free and open source products intended for commercial activities. Under Article 24, stewards must document a cybersecurity policy, cooperate with market surveillance authorities, and report actively exploited vulnerabilities in code they help develop, plus severe incidents affecting their development systems. An OSPO now has to decide, project by project, whether its organization is a steward.
The CRA does not let an OSPO outsource diligence to volunteers. In July 2025, a Fortune 500 company sent curl maintainer Daniel Stenberg nine questions about libcurl 7.87.0 with a two-week deadline. As we reported in our coverage of CRA pressure on maintainers, he declined: "I am not their vendor without having a more formal relationship established." He is right. A questionnaire sent to a maintainer measures the maintainer's patience. Diligence has to be computed from the artifact the organization actually ships.
Each control below evaluates a different unit. The useful question for an OSPO is which unit each one checks, and what passes through it.
| Control | Unit evaluated | Catches | Misses | Cost |
|---|---|---|---|---|
| Intake review, approved list | Package name, once | Unacceptable licenses, abandoned projects, obvious typosquats | Malicious versions of approved packages; most transitive dependencies | Reviewer time per request |
| Lockfile plus npm ci | Exact versions already resolved | Drift in rebuilds | New installs, updates, npx, global installs | Lockfile maintenance |
| Cooldown (minimum release age) | Version publish age | Malicious versions caught and removed within the window | Attacks found after the window | Delays urgent fixes |
| Install scripts off by default | Lifecycle hooks | preinstall and postinstall droppers | Payloads that run on import | Native modules need explicit approval |
| Install-time package firewall | Every name@version, at any depth | Known-malicious versions, before download | Installs that bypass the proxy; malware not yet analyzed | A proxy in the install path |
| SBOM | Inventory after build | Where a component shipped, after disclosure | Prevention of any kind | Generation and storage |
Two of these became defaults recently. pnpm 10.0.0 blocked lifecycle scripts by default on January 7, 2025, and npm v12 followed when it shipped on July 8, 2026. Default-on install scripts were a design mistake, and both changes correct it. Installs on npm 11 and earlier still run them. pnpm 11 set a dependency cooldown default of 1,440 minutes. npm replaced axios 1.14.1's malicious dependency with a placeholder about three hours after the axios version appeared, well inside a one-day cooldown.
The firewall row is where Socket works. Socket Firewall is an HTTP/HTTPS proxy between the package manager and the registry. For each download it extracts the package name, version, and registry, checks them against the organization's policy, and blocks the request before any code reaches disk, including for transitive dependencies. In the axios window, plain-crypto-js@4.2.1 was already flagged when axios 1.14.1 was published. A version-level package firewall does not cover malware nobody has analyzed yet, and it does nothing for installs that route around it.
Intake itself can be automated rather than abandoned. Anthropic wired Socket's API into its internal package request tool. Packages that meet its thresholds are approved automatically, and the rest go to a human. Before that, its engineers spent about 15 minutes per dependency on 5 to 10 reviews a day, according to Socket's Anthropic case study. The case study reports a 95% reduction in hands-on review.
Verdict: use intake review for license and project-health decisions, which change on the scale of months. Use install-time, version-level controls for malware, which arrives in minutes. An OSPO with only the first has a license program, not a security program.
OSPO staff, and the maintainers they depend on, are now attacked directly. In April 2026, an attacker posing as a well-known Linux Foundation community leader messaged members of the TODO Group's Slack, the community where OSPO practitioners meet. The lure ended with a request to install a "Google certificate" that was a malicious root certificate, as detailed in our report on the TODO Group Slack impersonation attack. The axios maintainer was compromised through a staged Slack workspace as well.
The deeper problem is a mismatch of units. On npm, trust is granted to an account. A typical OSPO intake reviews a package name. Attackers ship a version. Registries are adding controls at the account layer, such as trusted publishing and staged publishes, but an approved name will keep meaning "trust whatever these accounts publish next" until consumers can express policy about publishers and pipelines. Until then, the OSPO's security job is to close that gap at install time, for every version, on every install.
Manifest confusion is a mismatch between an npm package's registry manifest and the package.json in its tarball, two copies that tools trust inconsistently.
A software bill of materials (SBOM) is a machine-readable inventory of the components, versions, and dependency relationships inside a piece of software.
Software composition analysis (SCA) inventories the third-party code in an application and checks each component for vulnerabilities, licenses, and malware.