Skip to content
Français
Français
Sécurité medium

OpenSSF Scorecard

2 min read

Read this page in French

OpenSSF Scorecard logo

In March 2024, XZ Utils, a compression utility present on nearly every Linux server, was compromised by a backdoor hidden over two years. The attacker had earned the maintainer's trust, obtained commit rights, then injected malicious code allowing remote code execution through SSH. That attack captures the software supply chain dilemma perfectly: how do you trust dependencies maintained by strangers?

Supply chain attacks are exploding. According to Sonatype, they rose by 245% in three years. SolarWinds, Event-Stream, Polyfill.io, tj-actions: every incident shows that the popularity of a project does not guarantee its security. An npm package with millions of downloads can be taken over by an attacker. A GitHub action with thousands of stars can exfiltrate your secrets.

The fundamental problem: we trust blindly. We install hundreds of dependencies without checking who maintains them, how they are built, whether the releases are signed. We use GitHub actions with mutable tags (@v1) that can change at any moment. We load scripts from third-party CDNs with no control.

OpenSSF Scorecard gives an objective answer to that question of trust. This tool, built by the Open Source Security Foundation, analyses a GitHub repository automatically and gives it a score from 0 to 10 based on concrete security criteria: branch protection, code reviews, signatures, known vulnerabilities, dangerous patterns in the workflows, and more.

What you will learn

  • Understand what Scorecard evaluates and how to read the results
  • Install and use the CLI against a public repository
  • Wire Scorecard into a CI/CD pipeline, on GitHub and on GitLab
  • Read the 18 checks that run by default, their weight, and the remediation of each
  • Raise your score through a progressive action plan

What is OpenSSF Scorecard?

Scorecard is an automated tool measuring the security posture of an open source project. It runs a series of checks (heuristic controls) and produces a score for each of them.

The idea is simple: rather than trusting a dependency blindly, you objectively verify whether the project follows security good practices.

Why it is useful

A Scorecard score does not replace a code review: it answers a different question, and a far quicker one to ask. Does the project behind this dependency work cleanly? Four uses follow from that, from the most common to the most automated.

  1. Assessing a dependency before adoption: a score below 5 should raise a flag
  2. Auditing your own projects: identifying the weaknesses to fix
  3. Proving your security maturity: showing a score badge in your README
  4. Automating the monitoring: wiring Scorecard into the CI/CD

What Scorecard evaluates

Scorecard analyses varied aspects of a project's security:

CategoryExample checks
Code protectionBranch Protection, Code Review, Signed Releases
Build qualityPinned Dependencies, CI Tests, Fuzzing
MaintenanceActive maintainers, response to vulnerabilities
Operational securityDangerous Workflows, Token Permissions

Every check returns a score from 0 to 10 with a reason explaining the result.

Installation

Scorecard can be installed in several ways depending on the environment.

Terminal window
# Get the latest available version
VERSION=$(curl -s https://api.github.com/repos/ossf/scorecard/releases/latest | grep tag_name | cut -d '"' -f 4)
# Download the release
wget "https://github.com/ossf/scorecard/releases/download/${VERSION}/scorecard_${VERSION#v}_linux_amd64.tar.gz"
# Extract and install
tar xzf scorecard_*_linux_amd64.tar.gz
sudo install scorecard /usr/local/bin/scorecard
# Check the installation
scorecard version

Basic usage

Configuring GitHub authentication

A GitHub token is required

Without a GitHub token, Scorecard hits the API limits very quickly (60 requests per hour). In practice, a token is indispensable to analyse a project.

Token type and permissions:

Token typeRequired scopeLimitation
Classic tokenpublic_repo (or repo for private repositories)Full access to every check
Fine-grained tokenContents: read, Metadata: readCannot read the classic branch protection rules

Recommendation: prefer a ruleset and the problem disappears

The token constraint only applies to classic branch protection. Scorecard then has to read settings that are not public, which requires a classic token with the public_repo scope, or a fine-grained token carrying Administration: Read. Without either, Branch-Protection returns -1.

With a ruleset, it needs none of those rights: rulesets are public, and the GITHUB_TOKEN handed to every workflow by default is enough. That is written in Scorecard's own code, and it is what its error message suggests. Moving from classic protection to a ruleset therefore removes a secret to create, store and rotate.

See the official documentation.

Terminal window
# Create a classic token at https://github.com/settings/tokens
# Minimal scope: public_repo (or repo for private repositories)
# Export the token
export GITHUB_AUTH_TOKEN=ghp_xxxxxxxxxxxx

Analysing a GitHub project

Once the token is configured, the basic command analyses a repository:

Terminal window
# Analyse a public project
scorecard --repo=github.com/kubernetes/kubernetes

Reading the output

Scorecard prints a summary per check. Here is a real example on the Kubernetes project:

Aggregate score: 6.8 / 10
Check scores:
|---------|------------------------|-----------------------------------------------------------------------|
| SCORE | NAME | REASON |
|---------|------------------------|-----------------------------------------------------------------------|
| 10 / 10 | Binary-Artifacts | no binaries found in the repo |
| 10 / 10 | CI-Tests | 13 out of 13 merged PRs checked by a CI test |
| 5 / 10 | CII-Best-Practices | badge detected: Passing |
| 8 / 10 | Code-Review | Found 13/16 approved changesets |
| 10 / 10 | Contributors | project has 37 contributing companies or organizations |
| 0 / 10 | Dependency-Update-Tool | no update tool detected |
| 10 / 10 | Fuzzing | project is fuzzed |
| 10 / 10 | License | license file detected |
| 10 / 10 | Maintained | 30 commit(s) and 4 issue activity found in the last 90 days |
| 0 / 10 | Pinned-Dependencies | dependency not pinned by hash detected |
| 0 / 10 | SAST | SAST tool is not run on all commits |
| 10 / 10 | Security-Policy | security policy file detected |
| 8 / 10 | Vulnerabilities | 2 existing vulnerabilities detected |
|---------|------------------------|-----------------------------------------------------------------------|

Checks showing "?"

Some checks can show ? instead of a score. That means the check could not run (insufficient data, token permissions, or a feature the project does not use).

Output formats

Scorecard supports several formats for integration with other tools:

Terminal window
# JSON format for automated processing
scorecard --repo=github.com/org/project --format=json > scorecard.json
# SARIF format for GitHub Security
scorecard --repo=github.com/org/project --format=sarif > scorecard.sarif

The checks in detail

Scorecard carries about twenty checks, 18 of which run by default. Each check evaluates one security aspect and gives a score from 0 to 10. Here is the full list with the evaluation criteria and the actions that raise each score.

The overall score is a weighted average

The score you read is not the plain average of the checks: each one carries a weight from 1 to 10. Without that table you cannot tell where to start, and fixing a check worth 1 costs the same reading time as one worth 10 for a tenth of the effect.

WeightChecks
10Binary-Artifacts, Branch-Protection, Code-Review, Dangerous-Workflow, Maintained, Signed-Releases, Token-Permissions, Vulnerabilities
7.5Dependency-Update-Tool, Packaging, Pinned-Dependencies, SAST
5CI-Tests, Contributors, Fuzzing, License
1CII-Best-Practices, Security-Policy, Webhooks

Two weights come as a surprise, and they change the order of priorities. Security-Policy weighs 1, although it naturally sits next to License which weighs 5. And Signed-Releases weighs 10, as much as Branch-Protection: signing your releases counts as much as protecting your main branch.

A check that was not evaluated is not a zero

The API returns -1 when a check simply could not be evaluated, for lack of a release to inspect for Signed-Releases, or for lack of the rights needed to read a classic branch protection. That -1 is excluded from the denominator: it does not penalise the overall score.

The consequence is counter-intuitive and worth remembering: making a setting readable but wrong scores worse than leaving it unreadable. As long as a setting is not exposed, it stays out of the calculation. Once exposed and badly set, it enters the denominator and can break a tier. A half-configured ruleset can therefore score worse than no ruleset at all.

On ossf/scorecard, sigstore/cosign and kubernetes/kubernetes, Branch-Protection does return -1 with the message "some github tokens can't read classic branch protection rules". Those three projects are not poorly protected: they are simply not measurable with a public token.

Critical checks (high risk)

Binary-Artifacts

Risk: unauditable code in the repository.

Checks the absence of executable binary files (.exe, .class, .pyc, minified JS, and so on) in the source code. Binaries cannot be reviewed and may carry malicious or obsolete code.

Score 10/10: no binary found in the repository.

Remediation: remove the binaries and generate them from the source code at build time.

Branch-Protection

Risk: malicious code injected with no control.

Checks that the main branches (main, master) are protected through the GitHub protection rules.

The tiered scale:

PointsRequirements
3/10Force push and branch deletion forbidden
6/10+ Pull request mandatory, at least 1 reviewer required, branch up to date before merge, last push approved
8/10+ At least 1 required CI status check
9/10+ At least 2 reviewers, review by code owners with a CODEOWNERS file present
10/10+ Approvals dismissed after a new commit, admins included

Those tiers are sequential, and that is the subtlety costing the most time. Scorecard builds the score tier by tier and stops at the first incomplete tier: everything earned above it counts for nothing at all. Reading the table as a partial sum, "I ticked two rows out of five, so I have part of the points", leads to a wrong conclusion.

Measured on a repository with a rich ruleset: leaving require_last_push_approval at false is enough to block the 6/10 tier. The score drops to 5/10, and two far more demanding settings already enabled, code owner review and dismissal of stale approvals, earn nothing as long as that single boolean is missing.

Remediation: configure a ruleset under Settings > Rules > Rulesets, rather than the classic rules under Settings > Branches. The reason is given further down, in the section about the authentication token: a ruleset is readable without administration rights.

Code-Review

Risk: vulnerabilities or malicious code going undetected.

Measures whether changes pass through human review before integration. It analyses the last 30 commits to check they were approved.

The scale:

  • -7 points if a human change is not reviewed
  • -3 additional points if several changes are not reviewed
  • -3 points if bot changes are not reviewed

AI reviews

Reviews by bots (including AI/ML ones) do not count as code review, because they do not guarantee that a second person understood the change.

Remediation: make reviews mandatory through the branch protection rules.

Dangerous-Workflow

Risk: repository compromise (critical).

Detects dangerous patterns in GitHub Actions workflows:

  • Untrusted checkout: pull_request_target or workflow_run with an explicit checkout of the pull request, which lets the pull request author run arbitrary code with the repository secrets
  • Script injection: unsanitised GitHub context variables in inline scripts (for example ${{ github.event.issue.title }})

Score 10/10: no dangerous pattern detected.

Remediation: read the GitHub Security Lab guide and the Securing pull_request_target guide.

Signed-Releases

Risk: installing malicious releases.

Checks that releases are signed cryptographically. It analyses the last 5 releases for the presence of signature files.

Extensions looked for: .minisig, .asc (PGP), .sig, .sign, .sigstore, .sigstore.json, .intoto.jsonl (SLSA).

The scale:

  • 8/10: a signature present for every release
  • 10/10: a SLSA attestation (.intoto.jsonl) present

Remediation: sign your releases and publish provenance attestations; see GitHub Actions attestations.

Token-Permissions

Risk: tokens holding excessive permissions.

Checks that GitHub Actions workflows follow the principle of least privilege for the GITHUB_TOKEN.

The optimal score: permissions set to read-only at the global level, and write permissions declared only on the jobs that need them.

# ✅ Good practice
permissions:
contents: read # Read-only by default
jobs:
deploy:
permissions:
contents: write # Write for this job only

Remediation: use StepSecurity to work out the minimal permissions needed, and see GITHUB_TOKEN permissions.

Vulnerabilities

Risk: known, exploitable vulnerabilities.

Checks through OSV (Open Source Vulnerabilities) whether the project or its dependencies carry open, unfixed vulnerabilities.

The score: depends on the number and the severity of the vulnerabilities detected.

Remediation: fix the vulnerabilities in the code or update the dependencies to non-vulnerable versions.

Build quality checks

CI-Tests

Risk: bugs and vulnerabilities going undetected.

Checks whether the project runs automated tests before merging pull requests. It analyses the last 30 commits for the presence of CI checks.

Systems detected: GitHub Actions, Travis CI, CircleCI, Jenkins, AppVeyor, BuildKite, Woodpecker, and any system whose name contains "test" or "e2e".

Score 10/10: every recent commit went through CI tests.

Remediation: add tests with GitHub Actions or another CI system.

Dependency-Update-Tool

Risk: outdated dependencies carrying known vulnerabilities.

Checks whether the project uses an automatic dependency update tool.

Tools detected:

Score 10/10: a configuration file for one of those tools is detected.

Remediation: enable Dependabot in the GitHub settings or configure Renovate at the root of the project.

Fuzzing

Risk: undiscovered vulnerabilities in the code.

Checks whether the project uses fuzzing (testing with random data) to find bugs.

Methods detected:

  • Inclusion in OSS-Fuzz
  • ClusterFuzzLite in the repository
  • Native Go fuzz tests (func FuzzXxx)
  • Property-based testing libraries (QuickCheck, fast-check, FsCheck, and so on)

Remediation: add the project to OSS-Fuzz or use the language's native fuzzing.

Pinned-Dependencies

Risk: compromised dependencies (a supply chain attack).

Checks that dependencies are pinned to hashes rather than to mutable versions or tags.

What is analysed:

  • GitHub actions (they must use a SHA, not @v1)
  • Docker images (they must use a digest)
  • Dependencies in shell scripts and Dockerfiles
# ❌ A mutable tag: it can change without notice
- uses: actions/checkout@v4
# ✅ A pinned SHA: immutable, with the version in a comment
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

Remediation: use StepSecurity to pin the actions automatically, and Renovate with pinDigests: true for the Docker images. The procedure is detailed in Pinning actions by SHA.

SAST

Risk: bugs and vulnerabilities in the source code.

Checks whether the project runs static application security testing (SAST) on pull requests.

Tools detected:

  • CodeQL (github-code-scanning)
  • SonarCloud
  • The github/codeql-action action in the workflows

Remediation: enable CodeQL under Settings > Security > Code scanning or add the github/codeql-action action to a workflow.

Governance checks

CII-Best-Practices

Risk: security good practices not followed.

Checks whether the project earned an OpenSSF Best Practices badge.

The scale:

  • 2/10: badge in progress
  • 5/10: "Passing" badge
  • 7/10: "Silver" badge
  • 10/10: "Gold" badge

Remediation: register on bestpractices.dev and fill in the questionnaire. That questionnaire holds 67 criteria at the passing level: the shortcuts are detailed in Earning the OpenSSF Best Practices badge without losing a day.

Contributors

Risk: a project depending on a single organisation.

Checks whether the project has contributors from several different organisations (based on the "Company" field of the GitHub profiles).

Score 10/10: at least 3 different organisations among the last 30 commits, with at least 5 commits each.

Remediation: encourage contributors to fill in their organisation in their GitHub profile.

License

Risk: an obstacle to security auditing, and a legal risk.

Checks for the presence of a licence in the repository.

The scale:

  • 6/10: a LICENSE, COPYING or COPYRIGHT file detected
  • 9/10: + the file at the root of the project
  • 10/10: + a licence recognised by the FSF or OSI (an SPDX identifier)

Remediation: add a LICENSE file at the root with an SPDX licence.

Maintained

Risk: unfixed vulnerabilities in an abandoned project.

Evaluates the recent activity of the project over the last 90 days.

Score 10/10: at least 1 commit per week over the last 90 days.

Partial score: issue activity from collaborators or maintainers.

Score 0: an archived project.

Careful

An inactive project can be the target of a maintainer takeover, an attacker regaining control to inject malicious code (see XZ Utils).

Security-Policy

Risk: insecure reporting of vulnerabilities.

Checks for the presence of a SECURITY.md file explaining how to report vulnerabilities responsibly.

The scale:

  • 6/10: a contact email or URL present
  • 9/10: + explanatory text beyond the bare links
  • 10/10: + disclosure timelines mentioned (for example "90 days")

Remediation: create a SECURITY.md file at the root with the reporting instructions. On GitHub, use Settings > Security > Security advisories.

Other checks

Packaging

Checks whether the project publishes packages through GitHub Packages or publication actions towards npm, PyPI and others.

SBOM (experimental)

Checks for the presence of an SBOM in the source code or in the release artefacts.

This check does not run by default: it appears neither in the CLI output nor in the securityscorecards.dev API response. A reader looking for their SBOM score will not find it, and that is not a configuration mistake.

Webhooks (experimental)

Checks that the configured webhooks use a secret to authenticate the requests.

This one does not run by default either, and its weight is 1 when it does. These are the two checks explaining the gap between the twenty described here and the 18 checks the API returns.

CI/CD integration

GitHub Actions

The native integration runs Scorecard automatically and publishes the results in the GitHub Security tab:

.github/workflows/scorecard.yml
name: Scorecard Analysis
on:
push:
branches: [ "main" ]
schedule:
# Weekly analysis on Sunday at midnight
- cron: '0 0 * * 0'
permissions:
# Needed to publish the results
security-events: write
id-token: write
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
sarif_file: results.sarif

Publishing the results

The publish_results: true option shows the score on securityscorecards.dev and gives you a badge for the README.

GitLab CI

On GitLab, you use the CLI directly:

.gitlab-ci.yml
scorecard:
stage: security
image: gcr.io/openssf/scorecard:v5.5.0@sha256:b9a535801cb5fc8e7b2bea4cf45dd2db9a342cab8cc32bb5be2ac1355a95356f
variables:
GITHUB_AUTH_TOKEN: $GITLAB_TOKEN
script:
- scorecard --repo=gitlab.com/$CI_PROJECT_PATH --format=json > scorecard.json
artifacts:
reports:
codequality: scorecard.json
paths:
- scorecard.json
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Showing the score badge

Once the results are published, you can add a badge to the README to show the hardening effort:

![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/my-org/my-project/badge)

The badge updates automatically after each analysis.

Raising your score

Start by reading your report, not this list. The optimal order depends on where you start from, and several of these steps may earn nothing at all because the check already sits at its maximum. Measured on five real repositories, steps 2, 3 and 5 were already at 10/10 while the overall score was stuck at 6.9. So cross your report with the weight table given above: a check worth 10 stuck at 3 matters ten times more than a check worth 1 stuck at 0.

Here is the action plan, in generally decreasing order of gain:

  1. Enable branch protection

    In the GitHub settings, configure:

    • Mandatory review by at least 1 person
    • Required CI statuses before merge
    • Force push forbidden
  2. Pin the dependencies

    Replace every mutable tag with a SHA in the GitHub Actions workflows and the Dockerfiles.

  3. Sign the releases

    Sign the artefacts and publish provenance attestations.

  4. Enable Dependabot or Renovate

    Configure automatic dependency updates to receive the security fixes.

  5. Audit the workflows

    Check there is no dangerous pattern and minimise the token permissions.

Earning the OpenSSF Best Practices badge without losing a day

CII-Best-Practices is the one check that no repository setting can satisfy. It reads a record hosted on bestpractices.dev, and that record is filled in by hand: 67 criteria at the passing level, 43 of them mandatory, as counted in the project's criteria/criteria.yml. This is the check whose remediation fits in one sentence and takes half a day. Two official mechanisms cut that cost, and neither appears on the service's landing page. The BadgeApp documentation is nonetheless explicit about the order of preference:

We do support POST for changing project data, but we generally recommend using automation proposals URLs or the .bestpractices.json file instead.

Pre-filling with .bestpractices.json

Placed at the repository root, or as .project.d/bestpractices.json, this file is read by the service to pre-fill the answers. Each criterion takes two keys, <criterion>_status and <criterion>_justification. The status accepts ?, N/A, Unmet or Met, and a ? means "I do not know": it is ignored entirely, so a partial file breaks nothing and can be completed over time.

.bestpractices.json
{
"release_notes_status": "Met",
"release_notes_justification": "Every version publishes its notes on the repository Releases page",
"floss_license_osi_status": "Unmet",
"floss_license_osi_justification": "The content is under CC BY-SA 4.0, a free licence but not OSI-approved",
"warnings_status": "?"
}

Pre-filling through a proposal URL

An edit URL carries the answers in its parameters. The service shows them highlighted in the form, a human reviews, then saves:

https://www.bestpractices.dev/en/projects/<ID>/passing/edit?release_notes_status=Met&release_notes_justification=...

Three rules decide the outcome, and none of them is obvious:

RuleWhat happens otherwise
The record must already existAn edit URL aimed at an unregistered repository answers 404. Looking a project up by URL creates nothing.
Criterion names use underscores, never dots or bracketsThe key is dropped silently, with no error message at all.
Without the overrides parameter, only the still unknown fields are filledOn a field that already holds a value, the proposal flags a disagreement and changes nothing.

Everything travels in the query string, whose length the server bounds: keep the proposal URL for a handful of criteria, and use the file for the full questionnaire.

Two traps in their own documentation

Each of these costs half an hour to whoever meets it alone. From docs/api.md, the project links to a file called basepractices-json.md that does not exist: the real name is bestpractices-json.md. And the sample file docs/self.json still carries description_sufficient and contribution_criteria, two identifiers removed from the criteria set. Since an outdated key is ignored without warning, a file copied from that sample half works, and nothing says so.

What caps the badge, and what does not

Two facts save a long search for a mistake that never happened. The release_notes criterion is mandatory: without a published release, the passing badge stays out of reach whatever the other answers say. On the other hand floss_license_osi, which a Creative Commons licence cannot satisfy since it is not OSI-approved, is only suggested: answering Unmet with a justification is enough, and the badge remains reachable. That nuance matters for a content repository or a lab catalogue. One last thing to plan for, the questionnaire and its justifications are written in English, and other people will read them.

Three checks out of reach for a lone maintainer

A well-kept personal project structurally tops out around 7/10, and knowing why saves a long search for a mistake you did not make. Three checks, all of high weight, depend on the size of the team rather than on the maintainer's rigour.

CheckWhy it resists
Code-ReviewIt counts approved changesets. Overriding your own protection as an administrator creates no approval, and nobody else reviews.
MaintainedIt penalises any repository younger than 90 days, whatever its activity. Time is the only remedy.
ContributorsIt counts distinct organisations among the contributors, not people. A single-author repository cannot climb.

Those three are absent from the plan above, and deliberately so: no setting unlocks them.

Scorecard in a DevSecOps strategy

Scorecard fits naturally into a wider supply chain security approach. It checks the development practices upstream, while the scanners detect the vulnerabilities in the artefacts produced. The combination gives you defence in depth: a repository can hold no known CVE and still be one stolen maintainer account away from compromise, which is exactly what Scorecard measures and a scanner does not.

Key points

  • Scorecard gives an objective answer to the question of trusting an open source project, in seconds.
  • Analyse your critical dependencies before adoption: a score below 5 should raise a flag.
  • Wire Scorecard into your CI/CD to watch your own projects over time, with a weekly schedule.
  • The three checks that move the needle fastest are Branch-Protection, Pinned-Dependencies and Token-Permissions.
  • Show the badge to make the security effort visible, and combine Scorecard with artefact scanners for defence in depth.
  • A good Scorecard score does not guarantee the absence of vulnerabilities, but it significantly cuts the risk of a maintainer takeover and of supply chain attacks like the XZ Utils one.

Next steps

Resources

Is this site useful to you?

Fewer than 1% of readers support this site.

I maintain this site for free, with no ads, no ad profiling and no account to create. Any support, even a symbolic one, helps cover hosting and keeps these resources free. Thank you for the help.

The form does not show? Open Ko-fi in a new tab.

Subscribe and follow my DevSecOps work on LinkedIn