---
key: "s2c2f-consumer-adoption"
title: "Adopt the OpenSSF S2C2F as a consumer: the eight practices, the four maturity levels and a staged plan with evidence"
url: "https://softwaresecurity.com/domains/supply-chain/dependencies/s2c2f-adoption/"
api: "https://softwaresecurity.com/api/v1/articles/s2c2f-consumer-adoption.json"
domain: "supply-chain"
topic: "Software supply chain"
type: "Guide"
identifiers: []
tags: ["Dependency management","Supplier and open source risk"]
stages: ["build","govern"]
roles: ["platform-sre"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# Adopt the OpenSSF S2C2F as a consumer: the eight practices, the four maturity levels and a staged plan with evidence

Assess how your organization takes in open source against the Secure Supply Chain Consumption Framework's 25 requirements, then reach each maturity level in turn with evidence an assessor can check.

Canonical URL: https://softwaresecurity.com/domains/supply-chain/dependencies/s2c2f-adoption/

## In short

S2C2F, the OpenSSF's Secure Supply Chain Consumption Framework (version 1.1, kept as living Markdown), describes how an organization should take in open source: eight practices (Ingest, Scan, Inventory, Update, Audit, Enforce, Rebuild, and Fix and Upstream) broken into 25 requirements at four maturity levels. Level 1 is a package cache, vulnerability and license scanning, an automated inventory and manual updates. Level 2 adds end-of-life scanning, an OSS incident response plan, automated updates, vulnerabilities shown in pull requests, integrity checks and hardened package source configuration. Level 3, the level the spec ties to claiming compliance, adds a deny list, internal source mirrors, malware scanning, proactive security review, provenance checks and an enforced curated feed. Level 4 (rebuilding, signing, private fixes) is aspirational outside critical dependencies. Assess every team, take the lowest level as the organization's, and keep evidence per requirement.

- The requirement IDs are ING-1 to ING-4, SCA-1 to SCA-5, INV-1 and INV-2, UPD-1 to UPD-3, AUD-1 to AUD-4, ENF-1 and ENF-2, REB-1 to REB-4 and FIX-1: six at Level 1, seven at Level 2, six at Level 3 and six at Level 4.
- Levels are cumulative in practice: a team is at Level 2 only when every Level 1 and Level 2 requirement is met, and the organization is at the level of its weakest team.
- The spec says an organization can claim compliance with the requirements identified in Level 3, and calls Level 4 an aspirational north star best applied to your most critical dependencies.
- S2C2F covers how you consume open-source packages; its FAQ says it does not cover securing your build infrastructure, your own code or your containers, although the eight practices apply to containers.
- There is no certification and no released attestation format; the evidence is your own records, mapped to requirement IDs.
- Known-malicious package reports reach scanners through OSV `MAL-` entries, which helps SCA-4, but catching new malware needs behavioral analysis, a cooldown or a curated feed.

## Verify it

1. Run the level script on the assessment and confirm every team's level, and the organization's, matches what reviewers expect.
2. For each requirement marked met, open the evidence ID and confirm it is current, from the assessed team, and enforced rather than intended.
3. Point a build at the public registry directly, bypassing the repository manager, and confirm the build fails once ENF-2 is claimed.
4. Add a known-malicious package version to a test lockfile and confirm the scanner or the deny list blocks the pull request.
5. Pick one package from the inventory and trace who uses it in production within the time your OSS incident response plan promises.

## What you will learn

- Name the eight S2C2F practices and the 25 requirements, with the maturity level of each.
- Assess teams against the requirements and compute the level the organization actually supports.
- Plan each level's work with the evidence an assessor would ask for.
- Explain where S2C2F stops and producer-side baselines such as OSPS Baseline and SLSA start.

Audience: Security and platform engineers who own how their organization consumes open-source packages

Before you start:

- Know which package ecosystems your teams use and where their builds download packages from.
- Have a dependency scanner and an SBOM generator running, or be ready to add them.

Time: About 40 minutes: 18 to read and 22 for the exercise

## What is S2C2F, and which edition do you cite?

The Secure Supply Chain Consumption Framework started at Microsoft, where the spec says it has been used since 2019, and is now maintained by an OpenSSF project in the Supply Chain Integrity Working Group. Version 1.0 was released on 1 August 2022 and version 1.1 on 19 October 2022; v1.1 is the last tagged release in the ossf/s2c2f repository. The specification is now a living Markdown document: its maintainers make minor changes without a new version, and in 2024 they broadened SCA-5 from a security review to security analysis and widened UPD-3 from pull-request comments to the developer contribution flow. The requirement IDs and levels are the same in v1.1 and the current Markdown, so cite it as version 1.1 and note the revision date you read.

S2C2F has three goals: a strong OSS governance program, a lower mean time to remediate known vulnerabilities, and keeping compromised and malicious packages out. It rests on three ideas: control all artifact inputs, improve continuously, and work at scale without requiring a central internal registry or governance body. It applies to packages such as npm, PyPI, Maven and NuGet dependencies, and to source you copy in. Its FAQ is explicit about what it is not: guidance for securing your DevOps workflows, your build infrastructure, the code you write, or your containers, although the eight high-level practices still apply to containers.

## What do the eight practices ask for?

Each practice is stated as an outcome. The table quotes those outcomes and lists the requirements under each, with their levels.

| Practice | Outcome in the spec | Requirements (level) |
| --- | --- | --- |
| 1. Ingest it | I can ship any existing asset if external OSS sources are compromised or unavailable. | ING-1 trusted public package managers (L1); ING-2 an OSS binary repository manager (L1); ING-3 a deny list for known-malicious OSS (L3); ING-4 mirror all OSS source internally (L3) |
| 2. Scan it | I know if any OSS artifact in my pipeline has vulnerabilities or malware. | SCA-1 known vulnerabilities (L1); SCA-2 licenses (L1); SCA-3 end of life (L2); SCA-4 malware (L3); SCA-5 proactive security analysis (L3) |
| 3. Inventory it | I know where OSS artifacts are deployed in production. | INV-1 an automated inventory of all OSS used in development (L1); INV-2 an OSS incident response plan (L2) |
| 4. Update it | I can deploy updated external artifacts soon after an update becomes publicly available. | UPD-1 manual updates (L1); UPD-2 automated updates (L2); UPD-3 vulnerabilities shown in the contribution flow, with two-person review as a prerequisite (L2) |
| 5. Audit it | I can prove that every OSS artifact in production has a full chain-of-custody from the original artifact source and is consumed through the official supply chain. | AUD-1 verify provenance (L3); AUD-2 audit that developers use the approved ingestion method (L2); AUD-3 validate integrity (L2); AUD-4 validate SBOMs of consumed OSS (L4) |
| 6. Enforce it | I can rely on secure and trusted OSS consumption within my organization. | ENF-1 securely configured package source files (L2); ENF-2 enforce a curated OSS feed (L3) |
| 7. Rebuild it | I can rebuild from source code every OSS artifact I'm deploying. | REB-1 rebuild in a trusted build environment or validate a reproducible build (L4); REB-2 sign what you rebuild (L4); REB-3 SBOMs for what you rebuild (L4); REB-4 sign those SBOMs (L4) |
| 8. Fix it and upstream | I can privately patch, build, and deploy any external artifact within 3 days of harm notification and confidentially contribute the fix to the upstream maintainer. | FIX-1 private fix, rebuild and deploy, then contribute the fix upstream confidentially, for extreme cases only (L4) |

## What does each maturity level require?

The spec groups the requirements into four levels so an organization can improve in steps, and gives each level a theme. Different projects may sit at different levels; the spec suggests focusing on bringing all teams to one level by standardizing tools and process.

| Level | Theme in the spec | Requirements added at this level |
| --- | --- | --- |
| 1 | A package cache, an OSS inventory, and scanning and updating: the most common capabilities in the industry today. | ING-1, ING-2, SCA-1, SCA-2, INV-1, UPD-1 |
| 2 | Shift left: secure ingestion configuration, a faster mean time to patch, and incident response. The spec cites attackers exploiting the 2020 SaltStack vulnerability within 3 days and asks teams to patch faster than that. | SCA-3, INV-2, UPD-2, UPD-3, AUD-2, AUD-3, ENF-1 |
| 3 | Proactive security analysis of the most-used components, and less risk of consuming malicious packages: scan for malware before download and clone source internally. | ING-3, ING-4, SCA-4, SCA-5, AUD-1, ENF-2 |
| 4 | Aspirational: rebuild OSS on trusted infrastructure to defend against build-time attacks by the most sophisticated adversaries, and fix privately when needed. | AUD-4, REB-1, REB-2, REB-3, REB-4, FIX-1 |

## How do you assess where your organization is?

The spec says to assess at the organization level, because teams differ. Prepare by learning the framework, pick a diverse sample of teams, and interview them. Its 21 sample questions cover how teams choose dependencies, which ecosystems and registries they use, whether package source files list more than one feed (a dependency confusion risk), whether they use lockfiles, how they inventory and scan, how fast they update and whether updates are automated, whether they scan for malware, can block a known-bad package, clone source internally, review OSS before use, contribute fixes upstream, rebuild OSS, and have an incident playbook for a malicious component.

Record one row per team and requirement, with a status and an evidence ID, and keep the interview answers that support it. A status of met needs evidence that the control operates, such as a required CI check or a configuration file in every repository, not a policy that says it should. The script below reads that matrix, rejects unknown IDs, duplicate rows and claims without evidence, and reports each team's level, the gaps to its next level, and the organization's level as the lowest team's. For the matrix shown it prints `payments: Level 1; for Level 2: AUD-2 (partial)`, `search: Level 0; for Level 1: ING-2 (not met)` and `organization: Level 0 (the lowest team)`.

```python
"""Report the S2C2F maturity level each team's assessment supports, and the gaps to the next."""

import csv
import sys

# Requirement ID -> maturity level, from the S2C2F requirements table.
REQUIREMENTS = {
    "ING-1": 1, "ING-2": 1, "ING-3": 3, "ING-4": 3,
    "SCA-1": 1, "SCA-2": 1, "SCA-3": 2, "SCA-4": 3, "SCA-5": 3,
    "INV-1": 1, "INV-2": 2,
    "UPD-1": 1, "UPD-2": 2, "UPD-3": 2,
    "AUD-1": 3, "AUD-2": 2, "AUD-3": 2, "AUD-4": 4,
    "ENF-1": 2, "ENF-2": 3,
    "REB-1": 4, "REB-2": 4, "REB-3": 4, "REB-4": 4,
    "FIX-1": 4,
}  # fmt: skip
STATUSES = {"met", "partial", "not met", "not assessed"}


def read(rows):
    teams, problems = {}, []
    for n, row in enumerate(rows, start=2):  # line 1 is the header
        team, rid, status = row["team"], row["requirement"], row["status"]
        statuses = teams.setdefault(team, {})
        if rid not in REQUIREMENTS:
            problems.append(f"line {n}: {rid} is not an S2C2F requirement")
            continue
        if rid in statuses:
            problems.append(f"line {n}: {rid} assessed twice for {team}")
        if status not in STATUSES:
            problems.append(f"line {n}: unknown status {status!r}")
        if status in {"met", "partial"} and not row["evidence"]:
            problems.append(f"line {n}: {status} needs evidence")
        statuses[rid] = status
    return teams, problems


def level(statuses):
    """The highest level whose requirements, and those of every lower level, are all met."""
    reached = 0
    for lvl in (1, 2, 3, 4):
        if any(statuses.get(r) != "met" for r, l in REQUIREMENTS.items() if l == lvl):
            break
        reached = lvl
    return reached


if __name__ == "__main__":
    with open(sys.argv[1], newline="") as f:
        teams, problems = read(csv.DictReader(f))
    for problem in problems:
        print(problem)
    for team, statuses in sorted(teams.items()):
        reached = level(statuses)
        missing = [
            f"{r} ({statuses.get(r, 'not assessed')})"
            for r, l in REQUIREMENTS.items()
            if l == reached + 1 and statuses.get(r) != "met"
        ]
        gaps = f"; for Level {reached + 1}: {', '.join(missing)}" if missing else ""
        print(f"{team}: Level {reached}{gaps}")
    if teams:
        print(f"organization: Level {min(level(s) for s in teams.values())} (the lowest team)")
    sys.exit(1 if problems else 0)
```

## What does the assessment matrix look like?

One line per team and requirement, with the evidence ID the reviewer can open. Partial rows still need evidence of what exists. Leave rows out rather than guessing; the script reports a missing requirement as not assessed.

```csv
team,requirement,status,evidence
payments,ING-1,met,EV-01 npm and PyPI only; pip.conf and .npmrc in repo
payments,ING-2,met,EV-02 Artifactory remote repos proxy npmjs.org and pypi.org
payments,SCA-1,met,EV-03 osv-scanner job required on every pull request
payments,SCA-2,met,EV-03 osv-scanner --licenses allowlist
payments,INV-1,met,EV-04 CycloneDX SBOM per release in the artifact store
payments,UPD-1,met,EV-05 fix SLA in the dependency policy
payments,SCA-3,met,EV-06 end-of-life check in the weekly report
payments,INV-2,met,EV-07 OSS incident runbook tested 2026-08-19
payments,UPD-2,met,EV-08 Renovate with a 3-day cooldown
payments,UPD-3,met,EV-09 dependency review comments on pull requests
payments,AUD-2,partial,EV-10 binaries search run once; not scheduled
payments,AUD-3,met,EV-11 npm ci and pip --require-hashes in CI
payments,ENF-1,met,EV-12 single upstream feed; scope mapping in .npmrc
search,ING-1,met,EV-21 Maven Central only
search,ING-2,not met,
search,SCA-1,met,EV-22 Dependabot alerts on
search,SCA-2,met,EV-23 license report in the build
search,INV-1,met,EV-24 dependency graph export
search,UPD-1,met,EV-25 alerts triaged weekly
```

## A staged adoption plan, with the evidence for each level

Work level by level, and within a level start with the requirement that closes the biggest live risk. Each row lists what to put in place, what an assessor would accept as evidence, and the guide on this site that covers the work.

| Stage | Requirements | What to put in place | Evidence to keep | Guides here |
| --- | --- | --- | --- | --- |
| 1. Know and scan what you use | ING-1, ING-2, SCA-1, SCA-2, INV-1, UPD-1 | Install only from the ecosystems' public registries through a repository manager that proxies and caches them; scan every lockfile for known vulnerabilities and licenses in CI; generate an SBOM for every release; track vulnerable-dependency fixes to a deadline. | Registry and proxy configuration, the required scan job and its results, SBOMs stored per release, fix tickets with dates | /domains/supply-chain/dependencies/evidence-based-triage/, /domains/supply-chain/sbom/generate-release-inventory/, /domains/supply-chain/dependencies/open-source-license-compliance/ |
| 2. Patch fast and harden intake | SCA-3, INV-2, UPD-2, UPD-3, AUD-2, AUD-3, ENF-1 | Flag end-of-life components; write and rehearse an OSS incident plan that uses the inventory; let a bot open update pull requests; show new vulnerabilities in pull requests with two-person review; search for vendored binaries and direct downloads; install from lockfiles with hashes; bind every internal name to one registry. | End-of-life report, incident runbook and drill record, bot configuration, pull-request checks, the search results, `npm ci` or `--require-hashes` in CI, committed `.npmrc`, `pip.conf` or `nuget.config` | /domains/supply-chain/dependencies/dependency-confusion/, /domains/supply-chain/dependencies/package-attack-response/, /domains/supply-chain/dependencies/project-health-and-intake/ |
| 3. Keep malicious packages out | ING-3, ING-4, SCA-4, SCA-5, AUD-1, ENF-2 | A deny list you can update during an incident; internal mirrors of the source you depend on; malware checks before a package reaches a build; security review or fuzzing of your most-used components with findings reported upstream; provenance checks where the registry supports them; builds that can reach only the curated feed. | The deny list and its change log, mirror inventory, scanner results including `MAL-` findings, review reports and upstream advisories, provenance verification logs, network or registry policy that blocks direct downloads | /domains/supply-chain/provenance/read-and-verify-slsa/, /domains/supply-chain/provenance/artifact-signature-policy/, /domains/appsec/vulnerability-response/dependency-vulnerability-response/ |
| 4. Rebuild your critical dependencies | AUD-4, REB-1, REB-2, REB-3, REB-4, FIX-1 | For a short list of business-critical dependencies only: validate the SBOMs you receive, rebuild from source on trusted infrastructure or confirm a reproducible build, sign the result and its SBOM, and be able to ship a private fix while you disclose it upstream. | The critical-dependency list, rebuild and comparison logs, signatures, SBOMs, a FIX-1 drill or incident record | /domains/supply-chain/builds/reproducibility-and-hermeticity/, /domains/appsec/vulnerability-response/coordinated-disclosure/ |

## Which GitHub-native controls map to the requirements?

The project's reference implementation for GitHub shows how far native tooling goes toward Level 3: GitHub Packages for ING-2, the dependency graph for SCA-1 and INV-1, dependency review for SCA-2 and UPD-3, Dependabot for UPD-2, OpenSSF Scorecard's maintenance signals for SCA-3 and OpenSSF Package Analysis for SCA-4. The workflow below enforces part of it on every pull request. Dependency review fails when a new dependency has a high or critical advisory or a license outside the allowlist, and `deny-packages` is a deny list of package URLs (ING-3) you can extend during an incident. The second job runs osv-scanner v2.6.0 on the lockfile.

On a test lockfile containing `flatmap-stream` 0.1.1, the malicious package from the 2018 event-stream compromise, osv-scanner exited 1 and reported `MAL-2025-20690` from the OpenSSF malicious-packages feed alongside two GitHub advisories; with only `ms` 2.1.3 it exited 0; and `--licenses="Apache-2.0"` made it fail on the MIT-licensed `ms`. Known-malicious reports catch published compromises, not new ones, so pair them with a release-age cooldown and, at Level 3, a curated feed. The workflow passes actionlint 1.7.12.

```yaml
name: dependency-intake
on:
  pull_request:
    branches: [main]
permissions: {}
jobs:
  review:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
        with:
          fail-on-severity: high
          deny-packages: pkg:npm/flatmap-stream, pkg:npm/event-stream@3.3.6
          allow-licenses: Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MIT
          comment-summary-in-pr: on-failure
  osv:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      # osv-scanner v2.6.0; exits 1 on a known vulnerability or a known-malicious (MAL-) version
      - run: >-
          docker run --rm -v "$PWD:/src:ro"
          ghcr.io/google/osv-scanner@sha256:afd838850ac1a0fcc15ff4a041dc9ba11123c3f0d2666217a5f0fcf9222b55fa
          scan source -L /src/package-lock.json
```

## Where does S2C2F stop?

S2C2F is a consumer's framework. It assumes you can't make every upstream project secure, so it controls what enters your builds. Producers have their own baselines: maintainers can meet the OpenSSF OSPS Baseline (/domains/supply-chain/ci-cd/osps-baseline-for-maintainers/) and publish SLSA provenance, which is what makes AUD-1 checkable. The spec maps its requirements to NIST SP 800-161, SSDF (SP 800-218), the CIS Software Supply Chain Security Guide, OWASP SCVS, SLSA and the CNCF supply chain paper; for example ENF-1 and ENF-2 map to SSDF PO.5.2 and SCA-3 to PW.4.1.

There is no S2C2F certification. The spec expects conformance attestations in the future and mentions SCITT as a possible home, but no format has been published, and the project's attestation tool repository has had no changes since May 2024. Until then, the evidence is your matrix, the records behind each row, and the date you assessed them.

## Guided practice

Northwind (fictional) has three product teams. Use the interview notes to assign each team's statuses, find the organization's level, and plan the next quarter.

### Supplied material

```text
Team web (npm):
- Installs through the company Artifactory proxy; .npmrc maps @northwind to the internal registry.
- npm ci in CI; Dependabot security and version updates; dependency review required on pull requests with two approvals.
- osv-scanner runs on every pull request; SBOM per release; license report in the build.
- No end-of-life check; no written incident plan.
Team data (Python):
- pip --index-url points at the Artifactory PyPI proxy, plus --extra-index-url https://pypi.org/simple for packages the proxy lacks.
- Dependabot alerts on, updates done by hand monthly.
- SBOM per release; vulnerability and license scan nightly.
Team mobile (Gradle):
- Maven Central and Google's Maven repository directly, no proxy.
- Two vendored .aar binaries checked into the repository.
- Dependency graph and alerts on; updates by hand.
Organization:
- A deny list exists in Artifactory but only the web team's builds use Artifactory.
- No internal source mirrors; no malware scanning beyond OSV results.
```

### 1. Place each team

Assign each team's level from the notes, and name the requirement that holds it back.

Worked answer: web meets all of Level 1 and part of Level 2 (UPD-2, UPD-3 with its two-person review, AUD-3, ENF-1) but not SCA-3 (no end-of-life check), INV-2 (no incident plan) or AUD-2 (nobody looks for packages that bypass the proxy), so it is at Level 1. data is also at Level 1: the Artifactory proxy meets ING-2 and the nightly scans, SBOMs and manual updates cover the rest, but it fails ENF-1 at Level 2, because pip searches every index and installs the highest version it finds, so the public index can outrank an internal package. mobile is at Level 0: there is no binary repository manager (ING-2), and its vendored .aar files are what AUD-2 is meant to find.

### 2. Find the organization's level

State the organization's level and why.

Worked answer: Level 0, because mobile does not meet ING-2. The spec assesses at the organization level, and an organization is only as mature as its weakest team; web's Level 2 work counts for web, not for the company.

### 3. Plan the quarter

List the first five changes in order, with the evidence each produces.

Worked answer: 1. Route mobile's Gradle builds through the repository manager (ING-2), evidence: Gradle repository configuration and proxy logs. 2. Replace the vendored .aar files with packages from the proxy and schedule the binary search (AUD-2). 3. Fix data's ENF-1: one index that proxies PyPI, or uv with an explicit index for internal names, evidence: committed config. 4. Write and rehearse an OSS incident plan using the SBOM inventory (INV-2), evidence: runbook and drill record. 5. Add an end-of-life check (SCA-3). That brings every team to Level 1 first and web to Level 2; the deny list becomes useful for ING-3 only when every team's builds go through Artifactory.

Deliverable: Per-team statuses with evidence IDs, the organization's level, and an ordered plan of five changes with the evidence each produces.

Scope: Northwind and its teams are fictional. The levels follow the S2C2F requirements table; deciding whether a particular tool meets a requirement is a judgment the assessor records.

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

## Sources

- [OpenSSF S2C2F specification (living Markdown)](https://github.com/ossf/s2c2f/blob/main/specification/framework.md). Version 1.1; Markdown revised 27 August 2024.
- [ossf/s2c2f release v1.1](https://github.com/ossf/s2c2f/releases/tag/v1.1). 19 October 2022.
- [S2C2F FAQ (scope, SCA-3, SCA-5 and FIX-1, UPD-3, ENF-1)](https://github.com/ossf/s2c2f/blob/main/FAQ.md)
- [S2C2F reference implementation for GitHub](https://github.com/ossf/s2c2f/blob/main/Reference_implementation/GitHub.md)
- [S2C2F: maintaining the specification (versioning)](https://github.com/ossf/s2c2f/blob/main/specification/README.md)
- [ossf/S2C2F-attestation-schema-and-tool](https://github.com/ossf/S2C2F-attestation-schema-and-tool)
- [actions/dependency-review-action (inputs: fail-on-severity, deny-packages, allow-licenses)](https://github.com/actions/dependency-review-action). v5.0.0 (8 May 2026).
- [OSV-Scanner: scan source command and license scanning](https://google.github.io/osv-scanner/). v2.6.0 (14 September 2026).
- [OpenSSF malicious-packages: reports of malicious packages in OSV format](https://github.com/ossf/malicious-packages)
- [OSV entry MAL-2025-20690: malicious code in flatmap-stream (npm)](https://osv.dev/vulnerability/MAL-2025-20690)
- [OpenSSF Package Analysis](https://github.com/ossf/package-analysis)
- [GitHub Docs: About dependency review](https://docs.github.com/en/code-security/concepts/supply-chain-security/dependency-review)