---
key: "dependency-project-health"
title: "Review dependency health without treating a score as a guarantee"
url: "https://softwaresecurity.com/domains/supply-chain/dependencies/project-health-and-intake/"
api: "https://softwaresecurity.com/api/v1/articles/dependency-project-health.json"
domain: "supply-chain"
topic: "Software supply chain"
type: "Guide"
identifiers: ["CWE-1104","CWE-1357","A03:2025","V15.1.2","V15.1.4"]
tags: ["Dependency management","Build integrity and provenance","Supplier and open source risk"]
stages: ["build","release","govern"]
roles: ["platform-sre"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# Review dependency health without treating a score as a guarantee

Combine security practices, maintenance, licensing and product exposure into a recorded adoption or replacement decision.

Canonical URL: https://softwaresecurity.com/domains/supply-chain/dependencies/project-health-and-intake/

## In short

Decide intake from evidence about the role the package will play, not from a score. Check that the exact version you will ship links to a reviewed source commit, ideally through verified provenance; who can publish it; how the project handles security reports; and the Scorecard checks that matter for your use, such as Code-Review, Maintained, Dangerous-Workflow and Signed-Releases. Pin the approved version, contain the remaining risk, name an update owner and set reopening triggers.

- A Scorecard total is a risk-weighted average of unrelated checks; read the individual checks, the scan date and every `-1` (inconclusive) result.
- Scorecard rates Dangerous-Workflow and Webhooks as Critical risks and Code-Review, Maintained, Signed-Releases and Token-Permissions as High.
- A package's repository link is often unverified metadata; only provenance, such as npm's SLSA attestation, binds a version to a commit.
- Whoever can publish controls what you install: the 2025 Shai-Hulud worm republished packages with stolen maintainer tokens.
- Turn residual risk into enforceable conditions: a pinned version and digest, an isolated worker, size limits and a named update owner.

## Verify it

1. Run `./intake-evidence.sh <package> <version>` and copy the provenance, repository link type and Scorecard scan date into the intake record.
2. Confirm that the version you will pin has provenance with `verified: true`, or record the missing link as a blocking or accepted gap.
3. List publishers with `npm view <package> maintainers` and reopen the intake when that list changes.
4. Re-run the evidence script for every new major version and after any maintainer or repository change.
5. Test the pilot limits: a 101,000,000-byte expansion must stop at the 100,000,000-byte cap and clean up, while a 99,000,000-byte archive completes.

## What you will learn

- Interpret project-health findings in a concrete execution context.
- Apply hard intake gates separately from aggregate scores.
- Convert residual risk into testable pilot conditions and ownership.

Audience: Engineers and reviewers who approve new open source dependencies and own their updates

Before you start:

- Read dependency metadata and distinguish adoption, maintenance activity and security response evidence.

Time: About 28 minutes: 6 to read and 22 for the exercise

## Assess the dependency in the role it will have

Project health becomes a security decision only when joined to how you use the dependency. A parser for anonymous uploads, a development formatter and a privileged deployment agent have different exposure and replacement costs. Start with required functionality, execution context, data handled, update mechanism, available alternatives and the team's ability to maintain a fork. Popularity or a high aggregate score cannot answer those product-specific questions.

The fictional team needs an archive parser for untrusted customer uploads. Candidate A has a higher total security score but a single active maintainer and an unexplained release workflow gap. Candidate B has a lower score because its badge and packaging checks are incomplete, yet its recent fixes have clear review and release records. The task is not to pick the prettier dashboard. It is to decide which evidence addresses the consequence of parsing attacker-controlled bytes and whether the remaining uncertainty can be bounded operationally.

## Read checks and observations behind the aggregate

OpenSSF Scorecard runs about 20 automated checks against a source repository and rates each from 0 to 10, or `-1` when it could not reach a result. Each check carries a risk level: Dangerous-Workflow and Webhooks are Critical; Binary-Artifacts, Branch-Protection, Code-Review, Dependency-Update-Tool, Maintained, Signed-Releases, Token-Permissions and Vulnerabilities are High. A failed check can signal a real weakness, unsupported tooling or an observation limitation, and a passed check can cover a narrow behavior rather than the entire project. Inspect raw findings, the scan date and the repository identity. Do not assume that a repository's settings describe every package published under its name, or that its score is a probability of compromise.

For archive parsing, relevant evidence includes response to input-validation defects, release provenance, reviewed changes, test cases for malformed input and a way to report vulnerabilities. A quiet repository may be stable or abandoned; commit frequency alone does not distinguish those states. A recently added maintainer can improve continuity or introduce takeover risk; examine the release authorization path. Where an automated finding conflicts with a supplied release record, preserve both and investigate the specific claim rather than averaging away the disagreement.

| Scorecard check | Risk | What it measures | Why it matters for an upload parser |
| --- | --- | --- | --- |
| Dangerous-Workflow | Critical | Workflow patterns such as untrusted checkouts in privileged triggers | A compromised CI can publish malicious releases. |
| Code-Review | High | Whether recent changes were reviewed before merge | Unreviewed parser changes reach releases directly. |
| Maintained | High | Commit and issue activity in the last 90 days | Parser fixes need a responsive maintainer. |
| Signed-Releases | High | Signatures or provenance on release assets | Links what you download to what was built. |
| Vulnerabilities | High | Open OSV advisories against the project | Known unfixed issues before you adopt it. |
| Fuzzing | Medium | Integration with OSS-Fuzz or other fuzzers | Malformed archives are the main input risk. |

## Collect the evidence with deps.dev, Scorecard and npm

The script below gathers intake evidence for one exact npm version from public APIs, without a token: deps.dev reports licenses, advisories, verified provenance and how it linked the version to a repository; the Scorecard API returns the latest published result for that repository, usually from OpenSSF's weekly scan; and npm lists who can publish. For `sigstore` 5.0.0, deps.dev showed SLSA provenance with `verified: true` naming the source commit, and a repository link backed by `SLSA_ATTESTATION`. For the widely used `tar` 7.5.22 it showed no provenance and only an `UNVERIFIED_METADATA` repository link, taken from `package.json`, with one publisher; Scorecard's 2026-10-05 scan of that repository scored 5.6, with Code-Review 0 and Signed-Releases and Branch-Protection inconclusive. That says nothing about the quality of the code. It means the claim that the registry tarball came from the reviewed repository rests on trust in the publisher, which an intake gate like the one in this guide must record.

```bash
#!/usr/bin/env bash
set -euo pipefail
PKG="$1" VER="$2"   # an npm package and exact version; URL-encode scoped names (%40scope%2Fname)
DEPS="https://api.deps.dev/v3/systems/npm/packages/$PKG/versions/$VER"

# 1. License, advisories, provenance and how deps.dev linked the version to a repository
curl -s "$DEPS" | jq '{publishedAt, licenses, advisoryKeys,
  provenance: [.slsaProvenances[] | {sourceRepository, commit, verified}],
  repository: [.relatedProjects[] | select(.relationType == "SOURCE_REPO")
    | {id: .projectKey.id, relationProvenance}]}'

# 2. OpenSSF Scorecard checks for that repository (latest published result; -1 means inconclusive)
REPO=$(curl -s "$DEPS" | jq -r '[.relatedProjects[] | select(.relationType == "SOURCE_REPO")][0].projectKey.id')
curl -s "https://api.scorecard.dev/projects/$REPO" \
  | jq '{date, score, checks: [.checks[] | {name, score}]}'

# 3. Who can publish this package, and whether npm holds provenance for this version
npm view "$PKG@$VER" maintainers dist.attestations.provenance --json
```

## Record conditions that can actually be enforced

A dependency intake record needs the exact package/repository relationship, required capability, criticality, exposure, assessed evidence, unresolved questions, chosen version/digest, approving role and maintenance plan. Conditions should be measurable: pin an approved archive digest, keep parsing in a restricted worker, cap input/output size, and retain regression tests. A vague promise to monitor the project leaves no event that changes the decision. Record the license as part of intake; /domains/supply-chain/dependencies/open-source-license-compliance/ covers the check.

The fixture's local rule forbids production intake for this parser unless the distributed package can be linked to an authorized release and the team has an owned update path. Candidate A lacks that link despite its higher score, so it remains pending. B has the needed release evidence and a documented response route, but its single active release owner creates continuity risk. A conditional pilot can limit exposure and establish fallback support; it is not proof that B is safer in every dimension. Stronger isolation also increases latency and operational work, which the decision should acknowledge.

## Use change events and expiry instead of a permanent badge

A health assessment expires when relevant facts change: maintainer ownership, release signing identity, registry namespace, security response, major architecture, support status or your own use of the package. Reassess high-consequence dependencies on a scheduled cadence as well as on those events. An old intake approval should not authorize a new major version that introduces native code or network access without review. Watch the publishing path too: in September 2025 the Shai-Hulud worm stole npm tokens from developer machines and CI runners and used them to publish malicious versions of other packages those tokens could publish. A new version that loses provenance its predecessors had, adds an install script or comes from a new publisher deserves review before it is installed; pnpm `trustPolicy: no-downgrade` fails an install when a package's trust evidence is weaker than in earlier releases. Ownership changes were the signal that mattered in /domains/supply-chain/dependencies/case-study-event-stream/ and /domains/supply-chain/dependencies/case-study-polyfill-io/: the handover of an npm package and the sale of a CDN domain.

NIST supply-chain risk guidance and SSDF provide broader risk and lifecycle context, but the concrete policy here is fictional. For an organization-wide program, the OpenSSF S2C2F puts intake checks like these into a maturity model (/domains/supply-chain/dependencies/s2c2f-adoption/), and a maintainer's dated OSPS Baseline claim (/domains/supply-chain/ci-cd/osps-baseline-for-maintainers/) is one more piece of evidence to read, not a score. Its purpose is to show how evidence becomes an engineering condition. Preserve the rejected or deferred option and why; otherwise the next team may repeat the same superficial score comparison. The exercise includes all relevant observations and decision thresholds. It deliberately leaves code quality unproven so the reader cannot turn project-health evidence into an unsupported statement that the parser is vulnerability-free.

## Guided practice

Select a bounded pilot candidate for an archive parser used on anonymous uploads. The local intake gate requires authorized-release linkage and an owned update path.

### Supplied material

```text
Candidate A: pkg:generic/zipfast@8.0; Scorecard total 8.8/10; package digest HA; release tag exists but no evidence connects registry archive HA to reviewed commit; one active maintainer; last security response 200 days ago, resolution unknown.
Candidate B: pkg:generic/archivecore@3.2; total 6.4/10; package digest HB; E1 authenticated provenance links HB to reviewed C32; E2 security contact responded in 2 days in supplied incident; release owner is a single person; missing badge/fuzzing check evidence.
Product: anonymous uploads, maximum compressed size 10 MB; expanding archives can exhaust disk. Worker isolation is available at cost of 30 ms/job.
Local policy: unknown release origin blocks; assign internal update owner; pilot must enforce 10 MB input and 100 MB expanded-output caps, no network, scratch-only writes; reassess in 30 days.
Fictional internal owner Jo can maintain B updates; no owner assigned for A.
```

### 1. Choose relevant evidence

Explain why total score alone cannot satisfy the intake gate.

Worked answer: A scores higher but its shipped HA lacks authorized-release linkage and an internal update owner. Those are mandatory local conditions. B supplies the release link and Jo can own updates, despite lower automated totals.

### 2. Make the scoped decision

Decide A and B without claiming comprehensive safety.

Worked answer: Defer A pending release-origin evidence and ownership. Permit a conditional B pilot at HB/C32 only, subject to required isolation and size controls. B is not declared vulnerability-free or universally superior.

### 3. Translate a condition into a test

Specify exact output-cap behavior for 99 MB and 101 MB expansion.

Worked answer: With the exercise cap defined in decimal MB, 99,000,000 expanded bytes may complete; an attempted 101,000,000 bytes must abort before exceeding 100,000,000 and clean scratch output. Compressed input under 10 MB does not waive expansion limits.

### 4. Record residual risk

Name two uncertainties and the operational cost.

Worked answer: Single-person release continuity and absent fuzzing evidence remain open. Isolation adds 30 ms/job and maintenance burden. Jo owns updates; product owner accepts only the limited pilot, not indefinite unrestricted use.

### 5. Set reopening triggers

Write a 30-day review and two immediate triggers.

Worked answer: Review pilot and supplier evidence after 30 days. Immediately reopen on maintainer/release-identity change or use outside the isolated capped worker. A new version needs its own digest/release-link verification.

Deliverable: A dependency intake decision with hard gates, pilot conditions, residual risks and exact re-evaluation events.

Scope: Scores, packages and incidents are fictional. The guide models risk reasoning, not a recommendation to use a real dependency.

Articles and teaching material: CC BY 4.0. Original executable code: MIT. Attribution: Software Security.

## Sources

- [OpenSSF Scorecard project and checks](https://github.com/ossf/scorecard)
- [NIST SP 800-161 Rev. 1, updated: supply-chain risk management](https://csrc.nist.gov/pubs/sp/800/161/r1/upd1/final). May 2022, updated 1 November 2024.
- [NIST SP 800-218: SSDF 1.1, final (1.2 initial public draft published 2025-12-17)](https://csrc.nist.gov/pubs/sp/800/218/final). Version 1.1 (February 2022).
- [OpenSSF Scorecard: checks and risk levels](https://github.com/ossf/scorecard/blob/main/docs/checks.md)
- [OpenSSF Scorecard API](https://api.scorecard.dev/)
- [deps.dev API v3](https://docs.deps.dev/api/v3/)
- [OpenSSF: Concise Guide for Evaluating Open Source Software](https://best.openssf.org/Concise-Guide-for-Evaluating-Open-Source-Software)
- [CISA: widespread supply chain compromise impacting the npm ecosystem (2025-09-23)](https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem)
- [pnpm settings: dependency resolution (trustPolicy)](https://pnpm.io/settings/dependency-resolution)