Safeguard
Supply Chain Security•

OpenSSF Security Insights Explained: security-insights.yml Schema

Security Insights is an OpenSSF YAML file where projects self-report security contacts, policies, releases and tools. Here is the v2 schema and its uses.

Safeguard Security Team
42 min read

OpenSSF Security Insights is a single YAML file, security-insights.yml, in which an open source project reports its own security facts in a fixed, machine-readable shape: who runs it, how to report a vulnerability, where releases are published, which security tools run, and where to find attestations such as an SBOM. The current release is version 2.2.0, published on 31 January 2026, and the file is validated against a CUE schema maintained in the OpenSSF ossf/security-insights repository.

Consumers use it to answer due diligence questions without scraping prose: is this project maintained, who is the security contact, and where are the release SBOMs? Linux Foundation tools already read it to score projects against the OpenSSF Open Source Project Security (OSPS) Baseline. Every value is self-reported, so treat it as claims to check, not proof.

Key takeaways

  • It fills the gap between SECURITY.md and an SBOM. SECURITY.md is free text for humans and an SBOM lists components; Security Insights holds structured facts "that only maintainers can report".
  • Version 2 is a different file from version 1. Version 1.0.0 (2 October 2023) used SECURITY-INSIGHTS.yml and a JSON Schema. Version 2.0.0 (1 January 2025) rebuilt it around header, project and repository with a CUE schema; 2.1.0 (9 May 2025) and 2.2.0 (31 January 2026) added fields.
  • Look for it in two places with two spellings. The README tells consumers to accept security-insights.yml or security-insights.yaml, at the repository root or in the forge directory such as .github/, and to report an error if both spellings sit in the same place.
  • There is no dedicated SBOM field in version 2. SBOMs, VEX documents and provenance are listed as repository.release.attestations, each with a name, a location and an in-toto predicate URI. Version 1 had an explicit dependencies.sbom list.
  • Validation is one command. cue vet -d '#SecurityInsights' schema.cue security-insights.yml prints nothing when the file is valid. Pin the schema to the release tag you target, because main may run ahead of the latest release.
  • Consumers have real tooling. The si-tooling Go module (v2.3.0, 8 October 2026) discovers, fetches and parses files from public GitHub repositories. CLOMonitor and LFX Insights read the file for project scoring.
  • Trust is the weak point. The file is self-asserted, unsigned and, in version 2, has no expiry. Back it with signed releases, real attestations and independent checks.

This post goes one level deeper than our earlier posts on project security metadata. For the control catalogue that Security Insights helps evaluate, see what the OSPS Baseline is and how consumers can use it. For the website side of vulnerability contact data, see how to write a security.txt file and how to check whether a software vendor has a security.txt file.

What is the Security Insights specification?

The Security Insights specification "provides a mechanism for projects to report information about their security in a machine-processable way", in the words of the OpenSSF project page. The same page says the data "is intended to fill the gaps between simplified solutions such as SECURITY.md and comprehensive automated solutions such as SBOMs", and that in that gap sit facts that "must be self-reported by projects".

Some facts cannot be inferred from a repository. The specification's FAQ gives a concrete example: tools "frequently fail to detect whether a project runs automated tests". Who the security contact is, whether a bug bounty exists, whether the project only accepts bug fixes now, which third party audits were done and where the release SBOM lives are all facts the maintainers know and a crawler has to guess. Security Insights gives those facts a fixed place and a fixed shape.

Who maintains it

The specification lives in the OpenSSF GitHub organisation. Its repository was originally ossf/security-insights-spec and now redirects to ossf/security-insights; the rename is reflected in a January 2026 documentation change. The OpenSSF project page shows the project at the Sandbox stage and links it to the ORBIT working group (ossf/wg-orbit). The version 1.0.0 text, by contrast, said maintenance was led by the OpenSSF Identifying Security Threats Working Group, so if you read older material you will see that name.

Governance is light and written down in the repository's GOVERNANCE.md:

  • There is no steering committee. Decisions are "guided by the consensus of community members who have achieved maintainer status".
  • Three guiding principles frame every change: favour simplicity, ensure stability, and make cautious incremental improvements. Breaking changes "should be extremely rare".
  • Schema changes go through a Security Insights Enhancement Proposal (SIEP), filed as a GitHub issue with a template. A SIEP from a non-maintainer needs a maintainer sponsor, and discussion runs for no more than 30 days.
  • A favourable vote on a specification change must include maintainers from at least two organisations.

The versioning policy uses semantic versioning, with "ideally" at most one major and four minor releases a year, each matched by a new si-tooling release.

What versions exist

The release dates below come from the GitHub releases page of the specification repository and the git tags of si-tooling.

DateReleaseWhat changed
2 October 2023Specification v1.0.0First formal release, described in its notes as "the culmination of more than two years of discussion". JSON Schema Draft 7 file security-insights-schema.yaml; file name SECURITY-INSIGHTS.yml
1 January 2025Specification v2.0.0"Completely overhauls the specification based on feedback gained throughout 2024." New header, project, repository structure; CUE schema; PDF of the specification attached to the release
1 January 2025si-tooling v2.0.0Go library to read version 2 files published on GitHub
9 May 2025Specification v2.1.0Lands SIEP-106 changes, adds project.documentation.support-policy, tightens the CUE schema, adds adoption guidance and example files
31 January 2026Specification v2.2.0Adds project.documentation.design "to support OSPS Baseline: OSPS-SA-01"; renames the vulnerability reporting policy field to policy
1 February 2026si-tooling v2.2.0Tracks specification v2.2.0
August to October 2026Specification main (unreleased)Web editor "wizard", acceptance of both .yaml and .yml, an AI drafting prompt for maintainers
8 October 2026si-tooling v2.3.0si.Discover, si.Fetch, the si fetch command, GITHUB_TOKEN support, both file name spellings

Two details catch people out. The 2.2.0 rename of vulnerability-reporting.security-policy to policy broke strict semantic versioning in a minor release; the release notes cite "reported confusion around the field's purpose" and no sign that adopters used the old field. And the README warns that "the main branch may be slightly ahead of the latest release", so a schema pulled from main is a preview.

Version 1 and version 2 side by side

If you consume files from many projects you will still meet version 1 files. CLOMonitor's documentation, for example, reads both. The structures are not compatible.

TopicVersion 1.0.0Version 2.x
File nameSECURITY-INSIGHTS.ymlsecurity-insights.yml or security-insights.yaml
Schema languageJSON Schema Draft 7 (security-insights-schema.yaml)CUE (spec/schema.cue, definition #SecurityInsights)
Top level sectionsheader, project-lifecycle, contribution-policy, documentation, distribution-points, security-artifacts, security-testing, security-assessments, security-contacts, vulnerability-reporting, dependenciesheader, project, repository
Expiryheader.expiration-date required, at most one year after last reviewNo expiry field; last-updated and last-reviewed dates
SBOM locationdependencies.sbom list with sbom-file, sbom-format, sbom-urlrepository.release.attestations entries with an in-toto predicate URI
Multi repository projectsOne file per repositoryParent file with project, child files pointing to it via header.project-si-source
URL ruleURLs had to begin with https://The #URL pattern accepts http:// or https://
Validator in si-toolingPython script (v1/si-validator.py), version 1 onlyGo module; CUE schema is the recommended validator

Dropping expiration-date matters: version 1 forced a trust cut-off date, as RFC 9116 does for security.txt, while version 2 leaves the staleness decision to the consumer.

What a security-insights.yml file contains

The version 2.2.0 schema has three top level keys. header is always required. project is required in a single repository file and in the parent file of a multi repository project, and omitted in child files. repository is required in single repository files and child files, and optional in a parent file. The tables below list each field with its requirement status as written in spec/schema.cue. Fields marked optional are optional in the schema, not unimportant.

The header

FieldRequiredType and ruleWhat it is for
schema-versionYesPattern X.Y.Z, no leading vWhich schema version to validate against
last-updatedYesDate YYYY-MM-DDWhen the content last changed
last-reviewedYesDate YYYY-MM-DDWhen a maintainer last confirmed it was accurate
urlYesURLThe canonical place this file is published, so someone holding an old copy can find the current one. The schema comment stresses this "is not the project URL"
commentNoStringFree text
project-si-sourceNoURLRaw URL of a parent file whose project section this file inherits

header.url is easy to get wrong: it is the file's own canonical raw location, so a reader holding a copy from a release archive can fetch the current version.

The project section

FieldRequiredWhat it holds
nameYesProject name
administratorsYes, at least onePeople with administrative access to project resources, as contacts
repositoriesYes, at least oneEvery repository in the project, each with name, url and a comment, including the one holding this file
vulnerability-reportingYesHow to report a vulnerability (see below)
homepageNoLanding page
roadmapNoRoadmap or schedule
fundingNoFunding or sponsorship information
stewardNoA link and comment naming an organisation that provides sustained support, described in the schema as a steward "as defined by the European Union Cyber Resilience Act, Article 3"
documentationNoLinks: design, detailed-guide, code-of-conduct, quickstart-guide, release-process, support-policy, signature-verification

For consumers, support-policy (how releases are supported) and signature-verification (how to verify release signatures) are the two documentation links most worth having before you adopt a dependency.

Contacts

Contacts appear in several places (administrators, the vulnerability contact, the repository core team, security champions) and share one shape:

FieldRequiredRule
nameYesString; may be a team description
primaryYesBoolean; the schema says only one entry should be primary
affiliationNoString
emailNoMust match an email pattern
socialNoString, usually a profile URL

Because email is optional, a schema-valid contact can give a consumer no way to reach anyone.

Vulnerability reporting

FieldRequiredWhat it holds
reports-acceptedYesWhether the project currently accepts vulnerability reports
bug-bounty-availableYesWhether a bug bounty is offered
bug-bounty-programNoURL of the bounty page
contactNoOne contact, which can be a person or a mail group
policyNoURL of the disclosure rules (renamed from security-policy in 2.2.0)
pgp-keyNoPGP public key for encrypted reports
in-scope and out-of-scopeNoLists of strings
commentNoExtra instructions

Note that a contact is not required, so a file can say reports-accepted: true and give no route for reports (version 1 made a contact conditionally required). A consumer check should look for reports-accepted: true and at least one of contact, policy or the repository's documentation.security-policy.

The repository section

FieldRequiredWhat it holds
urlYesThe repository URL
statusYesOne of active, abandoned, concept, inactive, moved, suspended, unsupported, WIP, taken from the repostatus.org vocabulary
accepts-change-requestYesWhether any change requests are accepted
accepts-automated-change-requestYesWhether bot or machine generated change requests are accepted
core-teamYes, at least oneMaintainers or approvers
licenseYesurl and an SPDX expression
securityYesAssessments, champions, tools
bug-fixes-onlyNoWhether only bug fixes are accepted
no-third-party-packagesNoWhether the repository avoids outside package dependencies entirely
documentationNocontributing-guide, dependency-management-policy, governance, review-policy, security-policy
releaseNoRelease details (see below)

status is the most useful single field for a consumer: moved, abandoned or unsupported is an explicit signal to look elsewhere for fixes.

Release and distribution details

FieldRequired (if release present)What it holds
automated-pipelineYesWhether releases come from an automated pipeline
distribution-pointsYes, at least oneLinks with a uri and a comment; the URI is "not restricted to http/s", so a package URL such as pkg:npm/foobar is allowed
changelogNoURL, may include a {version} placeholder
licenseNoRelease licence if it differs from the repository licence
attestationsNoList of attestations, each with name, location, predicate-uri and an optional comment

distribution-points is how a consumer learns which package names are official.

SBOM and VEX locations: the attestations list

Version 2 has no field called sbom. Instead, the schema defines an attestation as an "in-toto attestation", with a predicate-uri described in the current schema text as "a URI identifying the attestation's predicate type", with the example https://slsa.dev/provenance/v1. The full example file lists four release attestations: a release VEX, a release SBOM, a maintainer identity verification summary attestation with predicate https://slsa.dev/verification_summary/v1, and SCA scan results.

For SBOMs, the in-toto attestation framework's predicate list defines real type URIs for both major formats: https://spdx.dev/Document (with versioned forms such as https://spdx.dev/Document/v2.3 and https://spdx.dev/Document/v3) and https://cyclonedx.org/bom. The example file in the specification repository uses placeholder strings such as https://intoto.SPDX and https://intoto.VEX. Those are illustrations, not registered predicate types, so do not copy them into a real file.

A consumer looking for an SBOM therefore needs a small amount of logic: walk repository.release.attestations, match on predicate-uri prefixes for SPDX or CycloneDX (and fall back to matching on name), substitute the release version into any {version} placeholder in location, then fetch and verify the document.

Security assessments and tools

The repository.security block has three parts.

  • assessments.self is required and holds a comment, plus optional name, evidence URL and date. A project with no self assessment still has to say so in the comment, which the minimum example does with "Self assessment has not yet been completed."
  • assessments.third-party is an optional list of the same shape, for external audits.
  • champions is an optional list of contacts who advocate for security practice in the core team.
  • tools is an optional list. Each tool has a name, a type from the fixed set fuzzing, container, secret, SCA, SAST or other, rulesets (use default if not customised), an integration object with three booleans (adhoc, ci, release) and a results object that can point to an attestation for each of those three run types. version and comment are optional.

The tools list says which categories of tool the maintainers run and when. It does not say what those tools found or whether findings were fixed, unless the results attestations exist and you read them.

Where the file lives and how it is named

The Get Started guide gives two recommended locations "in priority order": the repository root, or the forge directory such as .github/ or .gitlab/. Either .yml or .yaml works; keep only one. Consumers in the wild are more lenient than the guide:

  • The si-tooling discovery code lists the root and .github/ and checks the two current names plus legacy spellings SECURITY-INSIGHTS.yml, SECURITY_INSIGHTS.yml and security_insights.yml.
  • CLOMonitor's checks documentation inspects security-insights.yml, .github/security-insights.yml and SECURITY-INSIGHTS.yml in that order, prefers the first version 2 file it finds and falls back to the first version 1 file.

Multi repository projects

Projects with several repositories can avoid repeating project data. One repository holds a parent file with the project section. Every other repository holds a child file that sets header.project-si-source to the parent's raw URL, omits project, and has its own repository section. The Get Started guide is explicit that the parent must be served as raw content: "Consumers fetch the URL with a plain GET and expect a text/plain or application/yaml response." On GitHub that means a raw.githubusercontent.com URL, not a github.com/.../blob/... page, which "returns HTML and will fail".

Project level data, including the vulnerability reporting policy and the repository list, goes in the parent; repository URL, status, licence, core team, release process and tooling go in each child.

How Security Insights relates to OSPS Baseline, Scorecard and security.txt

These four things are often mentioned together and overlap in places, but they answer different questions.

OSPS Baseline is a set of controls. The OSPS Baseline site describes it as "a minimum definition of requirements for a project relative to its maturity level", maintained by the OpenSSF Security Baseline SIG. The current version is v2026.08.28. Baseline says what a project should do; it does not define a file format. Security Insights is one of the ways a project can make Baseline evidence machine-readable. The Baseline implementation guidance for maintainers says many Baseline evaluation tools "will use this to evaluate controls that are not easily machine-auditable via platform APIs", and tells maintainers to create a security-insights.yml following the full example. The link is visible in the schema history too: version 2.2.0 added project.documentation.design for control OSPS-SA-01 (publish design descriptions of system actors and actions), and in August 2026 the specification repository added a detailed-guide link to its own file "for OSPS-DO-01" (user guides for basic functionality).

OpenSSF Scorecard is an automated checker that infers practices from a repository. Its checks documentation lists checks such as Security-Policy, Signed-Releases, SBOM, Maintained, Fuzzing, SAST and Dependency-Update-Tool. The Security-Policy check works by "looking for a file named SECURITY.md (case-insensitive) in a few well-known directories" and scores its content. The checks documentation does not describe reading Security Insights files. The relationship is complementary: Scorecard measures what it can see, and Security Insights declares what cannot be seen. Where both speak to the same topic (a fuzzing tool declared in security.tools and Scorecard's Fuzzing check, for example) a consumer can compare a claim with an observation. The Scorecard project publishes its own Security Insights file at its repository root, with schema version 2.2.0.

RFC 9116 security.txt is a different layer. RFC 9116, published in April 2022, defines a text file at /.well-known/security.txt on a web origin, for reporting vulnerabilities in that organisation's sites and services. Its Contact and Expires fields "MUST always be present", the RFC recommends that Expires be "less than a year into the future", and it recommends signing the file with an OpenPGP cleartext signature. Security Insights is about a source code project, lives in a repository, and has no required expiry or signature. Version 1 of Security Insights even listed security.txt as one acceptable target for its security-policy link.

QuestionOSPS BaselineScorecardsecurity.txtSecurity Insights
What is itControl catalogueAutomated checkerWeb contact fileProject metadata file
Who writes itOpenSSF SIGTool computes resultsSite ownerProject maintainers
Where it livesbaseline.openssf.orgResults from a run or API/.well-known/security.txt on a web originRepository root or .github/
Security contactControl OSPS-VM-02 requires itScores SECURITY.md contentContact requiredvulnerability-reporting.contact optional
Expiry or freshnessVersioned catalogueEach run is freshExpires requiredlast-updated, last-reviewed
SignatureNot applicableNot applicableOpenPGP recommendedNone in the format
Best use for a consumerDefining what good looks likeObserving practicesReporting a website vulnerabilityFinding declared facts and artefact locations

The overlap to watch is the security contact. If SECURITY.md, the Security Insights contact and security.txt disagree, reports go to the wrong place, so pick one source of truth and point the others at it.

How consumers use Security Insights

Automated due diligence

The most common consumer is a pipeline that decides whether a dependency meets a minimum bar. A Security Insights file gives it fast answers:

  1. Is it alive? Read repository.status. active is what you hope for; moved should send you to the new location; abandoned, unsupported and inactive mean fixes may not come.
  2. Can I report a problem? Read project.vulnerability-reporting.reports-accepted, then look for contact, policy and repository.documentation.security-policy.
  3. Is support defined? Look for project.documentation.support-policy and follow it to release support dates.
  4. Are the packages I use official? Compare the package URL you install with repository.release.distribution-points.
  5. Can I verify releases? Look for project.documentation.signature-verification and release attestations with provenance or verification summary predicates.
  6. Has anyone outside looked at it? Read repository.security.assessments.third-party.
  7. How fresh is this? Compare header.last-reviewed with today.

Finding SBOM and VEX locations

When a vulnerability hits a component you ship, you want upstream's VEX statement and its SBOM for your release. A consumer walks the attestations list as described above, resolves {version}, fetches the document, and verifies it with whatever signature mechanism the project documents. If the list is empty, you have learnt something too: you will need to generate your own SBOM for that component.

Checking for a vulnerability reporting policy

Intake reviews ask whether a project has a disclosure process. A file with reports-accepted: true, a contact with an email or a policy URL, and a recent last-reviewed date is a reasonable machine answer, as long as you also check that the URL resolves. A file with reports-accepted: false on an active repository is worth a human look before adoption.

Tooling that exists today

  • si-tooling version 2 (Go). The module at github.com/ossf/si-tooling/v2 parses files into a Go si.SecurityInsights structure. Version 2.3.0 added si.Discover (finds the file using "at most two GitHub API calls"), si.Fetch (raw bytes), si.Load (parse bytes), and a command si fetch that runs discovery and parsing over many targets and prints a JSON array. Each result has a status of ok, not-found, invalid or error, and the command exits with code 2 when any target hit a transport error, "so an incomplete batch is never mistaken for a clean one". It reads GITHUB_TOKEN when set, because unauthenticated GitHub API requests are limited to 60 an hour. When we tried si fetch without a token while writing this post, the GitHub API rate limit had already been reached, both targets came back with status error, and the command exited non-zero, which is the behaviour the README promises.
  • si-tooling version 1 (Python). A Docker-wrapped generator and validator for version 1 files only. Its README warns it "does not have any feature support for v2" and recommends the CUE schema instead.
  • The CUE schema. The recommended validator, attached to each release and kept at spec/schema.cue.
  • A web editor and an AI drafting prompt. Added to main in August and October 2026. The prompt page calls its output "an experimental starting point for maintainer review, not a verified declaration".
  • CLOMonitor. The Linux Foundation tool has a security_insights check that passes when "a valid OpenSSF Security Insights v1 or v2 manifest file is found", and a dependencies policy check that reads the dependency management policy URL from the file (repository.documentation.dependency-management-policy in version 2).
  • LFX Insights and the OSPS Baseline Scanner. The specification README says LFX Insights "reads a project's Security Insights file to evaluate security hygiene against the OSPS Baseline", and points to a GitHub Action that runs the same scanner on individual repositories. The Baseline maintainer guidance names the scanner as a Privateer plugin for GitHub repositories.

Trust limits: what a Security Insights file cannot tell you

The specification is candid about this. Its FAQ says: "The file contains a project's own claims, so treat it as self-reported. It may be out of date or even wrong." The README adds: "Treat the contents as a snapshot. It describes the commit or release it ships with, not necessarily the project's current state." Here is what that means in practice.

Self-asserted values

Every field is written by the maintainers. A tools entry saying a fuzzer runs in CI is a statement, not evidence. A third-party assessment entry is a link, and the link may point to a summary rather than a report. The FAQ suggests tools "may choose to show results with and without unverified claims", which is a good pattern: report what the project declares and, separately, what you could confirm.

Stale files

Version 2 has no expiry. A file reviewed in 2024 is still schema-valid in 2026. The Get Started guide recommends a review "every 1 to 6 months", and the FAQ says every 3 or 6 months, but nothing enforces it. A consumer has to set its own threshold, for example treating anything with last-reviewed older than 12 months as unreviewed. Staleness also hides in links: an SBOM location with a {version} placeholder may resolve for old releases and 404 for new ones if the project stopped publishing.

No signature in the format

Neither the file nor the schema has a signature field. Integrity comes from wherever you fetch it. If you read the file from the tagged commit of a release, the file is as trustworthy as that commit; if you read it from the default branch, it describes the branch today, not the release you installed. The header.url field helps a reader find the current version, but nothing proves the current version is the authentic one beyond the hosting platform's own controls.

Loose URL rules

The version 2 #URL pattern accepts http:// as well as https://, so a consumer should refuse plain HTTP itself.

How to back the file up

  • Read it at the release tag. For a dependency you have pinned, read the Security Insights file at the commit or tag of that release, which is what the README means by a snapshot.
  • Prefer verifiable attestations. An attestation entry whose predicate-uri is a real in-toto predicate type (SLSA provenance, an SLSA verification summary, an SPDX or CycloneDX document) can be fetched and checked. A free text comment cannot.
  • Check signatures where the project documents them. If project.documentation.signature-verification exists, follow it for the release you use.
  • Cross-check with observation. Compare declared tools with what an automated checker observes, and declared status with commit and release activity.

Comparison: Security Insights, security.txt, SECURITY.md and SBOMs

Security Insightssecurity.txt (RFC 9116)SECURITY.mdCycloneDX or SPDX SBOM
PurposeSelf-reported project security facts: contacts, status, policies, releases, tools, attestation locationsHow to report vulnerabilities in a web origin's servicesHuman readable security policy and reporting instructionsInventory of components, versions, identifiers, relationships
FormatYAML, CUE schemaLine based text fields, optional OpenPGP signatureFree form MarkdownJSON, XML and other serialisations defined by each standard
LocationRepository root or .github/ (.gitlab/ and similar)/.well-known/security.txt on each web originRepository root, .github/ or docs/Release assets, registries, attestations, or a link from Security Insights
Who writes itProject maintainersSite ownerProject maintainersBuild tooling, then the producer publishes it
Freshness signallast-updated, last-reviewedExpires (required)NoneDocument timestamp
Machine readableYesYesNoYes
Who reads itBaseline evaluators, project scorers, intake pipelinesSecurity researchers, scanners of websitesPeople, plus checkers that score its contentVulnerability management, licence and procurement tooling
ScopeOne open source projectOne web originOne repositoryOne build or release

For an open source project the practical answer is all of them where they apply: SECURITY.md for people, a Security Insights file that points to it, per-release SBOMs listed as attestations, and security.txt on the project website.

Step by step: write, validate and read a version 2 file

Step 1: write a minimal version 2 file

Start from the specification's minimum example and replace every value. This is a complete single repository file for a hypothetical project; the domain names are reserved example domains.

header:
  schema-version: 2.2.0
  last-updated: '2026-10-24'
  last-reviewed: '2026-10-24'
  url: https://raw.githubusercontent.com/example-org/widget/main/security-insights.yml

project:
  name: Widget
  administrators:
    - name: Priya Example
      email: priya@example.org
      primary: true
  repositories:
    - name: widget
      url: https://github.com/example-org/widget
      comment: The only repository for Widget; this file lives here.
  vulnerability-reporting:
    reports-accepted: true
    bug-bounty-available: false
    contact:
      name: Widget security team
      email: security@example.org
      primary: true
    policy: https://github.com/example-org/widget/security/policy

repository:
  url: https://github.com/example-org/widget
  status: active
  accepts-change-request: true
  accepts-automated-change-request: true
  core-team:
    - name: Priya Example
      primary: true
  license:
    url: https://github.com/example-org/widget/blob/main/LICENSE
    expression: Apache-2.0
  security:
    assessments:
      self:
        comment: No self assessment has been published yet.

Note the choices: no v in schema-version, quoted dates, header.url set to the file's raw URL, and a team address for the vulnerability contact.

Step 2: add release data once you publish releases

When the project ships releases with SBOMs, add a release block under repository. Use real predicate type URIs.

  release:
    automated-pipeline: true
    changelog: https://github.com/example-org/widget/releases/tag/v{version}
    distribution-points:
      - uri: https://github.com/example-org/widget/releases
        comment: GitHub release page
      - uri: pkg:npm/widget
        comment: Official npm package
    attestations:
      - name: Release SBOM (CycloneDX)
        predicate-uri: https://cyclonedx.org/bom
        location: https://github.com/example-org/widget/releases/download/v{version}/widget.cdx.json
        comment: Replace {version} with the release version.
      - name: Build provenance
        predicate-uri: https://slsa.dev/provenance/v1
        location: https://github.com/example-org/widget/releases/download/v{version}/widget.intoto.jsonl
        comment: Replace {version} with the release version.

Only list attestations you actually publish for every release. A link that 404s for the latest version is worse than no link, because it looks like evidence.

Step 3: validate against the schema

Install CUE or run it through Go, and pin the schema to the release tag you declared.

curl -sSLO https://raw.githubusercontent.com/ossf/security-insights/v2.2.0/spec/schema.cue
cue vet -d '#SecurityInsights' schema.cue security-insights.yml
echo "exit: $?"

If you have Go but not a CUE binary, the same check runs with:

go run cuelang.org/go/cmd/cue@v0.17.1 vet -d '#SecurityInsights' schema.cue security-insights.yml

We ran exactly this against the file in step 1 while writing this post. It printed nothing and exited 0, as the Get Started guide says a valid file should. Then we changed status: active to status: retired, which is not in the allowed list, and CUE reported repository.status: 8 errors in empty disjunction, one line per allowed value, and exited 1. Writing schema-version: v2.2.0 with a v produced an out of bound error against the version pattern, and a malformed email address failed the email pattern in the same way.

Step 4: validate in CI on every change

Add a workflow so a later edit cannot break the file silently, which the Get Started guide recommends. This GitHub Actions job installs CUE through Go and validates on pull requests that touch the file. Pin third party actions to full commit SHAs in your own repository; tags are shown here for readability.

name: security-insights
on:
  pull_request:
    paths:
      - 'security-insights.yml'
      - '.github/security-insights.yml'
permissions:
  contents: read
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false
      - uses: actions/setup-go@v5
        with:
          go-version: 'stable'
      - name: Validate against Security Insights v2.2.0
        run: |
          curl -sSLO https://raw.githubusercontent.com/ossf/security-insights/v2.2.0/spec/schema.cue
          FILE=security-insights.yml
          [ -f "$FILE" ] || FILE=.github/security-insights.yml
          go run cuelang.org/go/cmd/cue@v0.17.1 vet -d '#SecurityInsights' schema.cue "$FILE"
      - name: Fail if last-reviewed is older than 180 days
        run: |
          FILE=security-insights.yml
          [ -f "$FILE" ] || FILE=.github/security-insights.yml
          REVIEWED=$(grep -E '^\s*last-reviewed:' "$FILE" | head -1 | tr -d "'\" " | cut -d: -f2)
          AGE=$(( ( $(date +%s) - $(date -d "$REVIEWED" +%s) ) / 86400 ))
          echo "last-reviewed $REVIEWED is $AGE days old"
          [ "$AGE" -le 180 ]

The age check is a house rule, not part of the specification; it makes "review every 1 to 6 months" enforceable.

Step 5: read another project's file in a CI job

Now the consumer side. This job reads the Security Insights files of the upstream projects you depend on and fails if any of them is missing a reporting route or marked as not active. It uses si fetch from si-tooling v2.3.0 and jq, and passes the job's token so the GitHub API rate limit is not hit.

name: upstream-security-insights
on:
  schedule:
    - cron: '17 6 * * 1'
  workflow_dispatch:
permissions:
  contents: read
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-go@v5
        with:
          go-version: 'stable'
      - name: Fetch upstream Security Insights files
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          go run github.com/ossf/si-tooling/v2/cmd/si@v2.3.0 fetch \
            example-org/widget \
            example-org/parser \
            > si.json
      - name: Apply intake rules
        run: |
          jq -r '.[] | [.target, .status, (."schema-version" // "-")] | @tsv' si.json
          # Rule 1: a transport error means the snapshot is incomplete.
          jq -e 'all(.[]; .status != "error")' si.json
          # Rule 2: report targets with no file or an invalid file, without failing.
          jq -r '.[] | select(.status == "not-found" or .status == "invalid") | "WARN \(.target): \(.status)"' si.json
          # Rule 3: every parsed file must accept reports and give a route.
          jq -e 'all(.[] | select(.status == "ok") | .insights;
                  .project."vulnerability-reporting"."reports-accepted" == true and
                  ((.project."vulnerability-reporting".contact != null) or
                   (.project."vulnerability-reporting".policy != null) or
                   (.repository.documentation."security-policy" != null)))' si.json
          # Rule 4: every parsed repository must be active.
          jq -e 'all(.[] | select(.status == "ok") | .insights; .repository.status == "active")' si.json
          # List declared SBOM locations for the record.
          jq -r '.[] | select(.status == "ok") | .target as $t
                 | (.insights.repository.release.attestations // [])[]
                 | select((."predicate-uri" | test("spdx.dev|cyclonedx.org")) or (.name | test("(?i)sbom")))
                 | "\($t)\t\(.location)"' si.json

The target list accepts owner/repo, owner/repo/path, a github.com blob URL or a raw URL, according to the si-tooling README. Two cautions apply. The JSON keys in insights come from the module's generated Go types, which use the same hyphenated names as the YAML (vulnerability-reporting, reports-accepted, predicate-uri), but look at a real si.json from your own targets before you rely on exact paths, because optional blocks are simply absent when a project leaves them out. And si fetch reads files from the default branch, so this job checks the project's current declaration, not the snapshot for the version you have pinned. For a pinned dependency, fetch the file at the release tag directly:

curl -sSfL "https://raw.githubusercontent.com/example-org/widget/v1.4.2/security-insights.yml" -o widget-si.yml \
  || curl -sSfL "https://raw.githubusercontent.com/example-org/widget/v1.4.2/.github/security-insights.yml" -o widget-si.yml
cue vet -d '#SecurityInsights' schema.cue widget-si.yml

Validate fetched files before you act on them. An invalid file is a finding in itself.

What a consumer policy can look like

A starting point for an intake decision table, not a standard:

What the file saysWhat to do
No file foundFall back to SECURITY.md and repository signals; record "no declaration"
File invalid against the declared schema versionRecord as a finding; do not use its values
status is movedFollow the new location before adopting or updating
status is abandoned, unsupported or inactiveTreat as end of maintenance; plan an upgrade, a fork or a replacement
reports-accepted: false on an active repositoryHuman review before adoption
reports-accepted: true with no contact, policy or security-policy linkAsk upstream to add one; record the gap
last-reviewed older than your thresholdTreat declared facts as unreviewed
SBOM attestation listedFetch for the version you use and verify it; keep a copy
No SBOM attestationGenerate your own SBOM for that component
Third party assessment listedRead the evidence and its date before giving it weight

Common mistakes

Listing contacts that bounce. A personal address of a maintainer who has moved on, or a mail group nobody reads, is worse than no contact because it looks like a working route. Use a team address or a reporting form, send a test message after every change of maintainers, and record the date you last confirmed it worked. The schema checks that an email looks like an email; it cannot check that anyone answers.

Linking SBOMs that are not updated per release. A {version} placeholder in an attestation location promises an SBOM for every release. If the release pipeline stopped producing one, the link quietly breaks for new versions while old ones still resolve. Generate the SBOM in the same automated pipeline that cuts the release, and add a post-release check that the declared URL for the new version returns 200.

Forgetting to update the file on repository moves. When a repository moves to a new organisation or forge, repository.url, project.repositories, header.url and any project-si-source URLs in child files all go stale at once. Set status: moved in the old location if it stays readable, update every URL in the new one, and re-validate. Child files pointing at a parent that has moved will fail for consumers that fetch the parent.

Pointing header.url at the repository. The field is for the file's own canonical raw location. The schema comment says explicitly that it is not the project URL.

Keeping both .yml and .yaml. The README tells consumers to report an error rather than choose between them.

Copying placeholder predicate URIs. https://intoto.SPDX in the example file is an illustration. Use a registered predicate type such as https://spdx.dev/Document or https://cyclonedx.org/bom.

Declaring tools you do not run. If a scanner was removed from CI, remove it from the file too.

Validating against main. The main branch can contain unreleased schema changes. Validate against the tag that matches your schema-version.

Never reviewing. Put a review reminder in your release checklist.

FAQ

What is security-insights.yml?

A YAML file, defined by the OpenSSF Security Insights specification, in which an open source project self-reports its security contacts, status, policies, release locations, attestations and tools. Put it at the repository root or in .github/.

Who maintains the Security Insights specification?

The Open Source Security Foundation. The specification is developed in the ossf/security-insights GitHub repository by maintainers under a written governance model, and the OpenSSF project page lists it at the Sandbox stage under the ORBIT working group.

What is the latest version of Security Insights?

Version 2.2.0, released on 31 January 2026. Earlier releases are 1.0.0 (2 October 2023), 2.0.0 (1 January 2025) and 2.1.0 (9 May 2025). The main branch has unreleased changes since 2.2.0.

How do I validate a security-insights.yml file?

Download spec/schema.cue for the release you target and run cue vet -d '#SecurityInsights' schema.cue security-insights.yml. No output means the file is valid.

Is SECURITY-INSIGHTS.yml the same as security-insights.yml?

The upper case name belongs to version 1.0.0. New files should use the version 2 lower case names, though some consumers still look for the old one.

Does Security Insights replace SECURITY.md?

No. SECURITY.md is the human readable policy and is what many checkers look for. Security Insights adds structured facts and can link to SECURITY.md through vulnerability-reporting.policy or repository.documentation.security-policy.

Does Security Insights replace an SBOM?

No. It can tell you where a project publishes its SBOMs, through release attestations, but it does not list components itself.

Can I trust what a Security Insights file says?

Treat it as the project's own claims. The specification's FAQ says it "may be out of date or even wrong". Check freshness, verify any attestations it links to, and compare declared practices with what you can observe.

Does OpenSSF Scorecard read Security Insights files?

Scorecard's checks documentation describes its Security-Policy check as looking for SECURITY.md, and does not describe reading Security Insights. CLOMonitor and LFX Insights are the tools the specification names as readers.

Where Safeguard fits

Safeguard's fit here is indirect, and it is worth being plain about it. Nothing in this post describes a Safeguard feature that reads or writes Security Insights files; the validation and consumer jobs above are plain open source tooling you can run anywhere. What Safeguard does cover is the next step after the file has told you about a project: scanning the repositories, container images and SBOM files you build with (safeguard scan --dir ., --image, --sbom), matching components against vulnerability databases, showing which of your projects contain a given component and version, ranking findings with reachability analysis, and opening fix pull requests through autonomous remediation, which ships switched off. If an upstream file lists an SBOM attestation, that SBOM can be one of the inputs you scan.

A Security Insights file tells you who to contact upstream and where its SBOMs live, but it does not ship a fix for you; when a dependency you rely on has a known vulnerability, the clock still runs until the fixed version is in your build. The section below describes how Safeguard closes that gap for the open source dependencies and container images you build and ship, including fork and patch, which is in early access.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

The first demo sets up the local runner on your own machine, approves it with its device code, then scans from the terminal. That scan produces the component inventory for your own code, which is the list of upstream projects whose Security Insights files you would want to read in step 5.

Click through it at your own pace, or open it full screen.

The second demo is a real Safeguard CLI session: asked to fix a lodash CVE, the assistant reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. It then shows /permissions, /model and /scan .. The same approval gated flow applies when an upstream project's file says a release is fixed and you need to move to it.

Click through it at your own pace, or open it full screen.

The third demo connects Claude to the Safeguard MCP server with OAuth and chooses which tools Claude can use. With your projects in Safeguard, that connection lets you ask which of them contain a given component and version, the question that follows once an upstream file has told you a project is abandoned or has published an advisory.

Click through it at your own pace, or open it full screen.

To try the CLI, download Safeguard and follow the CLI documentation.

Quick check

A project's security-insights.yml says reports-accepted: true but has no contact. Is the file valid?

Yes. In version 2.2.0 the contact field under vulnerability-reporting is optional, so the file validates. A consumer should still treat it as a gap and look for a policy URL or repository.documentation.security-policy.

Where does a version 2 file say where the release SBOM is?

In repository.release.attestations. Each entry has a name, a location and an in-toto predicate URI, such as https://spdx.dev/Document or https://cyclonedx.org/bom. Version 2 has no separate sbom field.

Why should you validate against the v2.2.0 tag rather than main?

Because the specification README says main "may be slightly ahead of the latest release" and should be treated as a preview. Pinning to the tag matches the schema-version you declared.

Sources

Last reviewed: 24 October 2026. Security Insights v2.2.0 is the latest release; the specification's main branch carries unreleased changes, so check the releases page before pinning a schema.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.