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.ymland a JSON Schema. Version 2.0.0 (1 January 2025) rebuilt it aroundheader,projectandrepositorywith 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.ymlorsecurity-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 explicitdependencies.sbomlist. - Validation is one command.
cue vet -d '#SecurityInsights' schema.cue security-insights.ymlprints nothing when the file is valid. Pin the schema to the release tag you target, becausemainmay run ahead of the latest release. - Consumers have real tooling. The
si-toolingGo 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.
| Date | Release | What changed |
|---|---|---|
| 2 October 2023 | Specification v1.0.0 | First 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 2025 | Specification 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 2025 | si-tooling v2.0.0 | Go library to read version 2 files published on GitHub |
| 9 May 2025 | Specification v2.1.0 | Lands SIEP-106 changes, adds project.documentation.support-policy, tightens the CUE schema, adds adoption guidance and example files |
| 31 January 2026 | Specification v2.2.0 | Adds project.documentation.design "to support OSPS Baseline: OSPS-SA-01"; renames the vulnerability reporting policy field to policy |
| 1 February 2026 | si-tooling v2.2.0 | Tracks specification v2.2.0 |
| August to October 2026 | Specification main (unreleased) | Web editor "wizard", acceptance of both .yaml and .yml, an AI drafting prompt for maintainers |
| 8 October 2026 | si-tooling v2.3.0 | si.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.
| Topic | Version 1.0.0 | Version 2.x |
|---|---|---|
| File name | SECURITY-INSIGHTS.yml | security-insights.yml or security-insights.yaml |
| Schema language | JSON Schema Draft 7 (security-insights-schema.yaml) | CUE (spec/schema.cue, definition #SecurityInsights) |
| Top level sections | header, project-lifecycle, contribution-policy, documentation, distribution-points, security-artifacts, security-testing, security-assessments, security-contacts, vulnerability-reporting, dependencies | header, project, repository |
| Expiry | header.expiration-date required, at most one year after last review | No expiry field; last-updated and last-reviewed dates |
| SBOM location | dependencies.sbom list with sbom-file, sbom-format, sbom-url | repository.release.attestations entries with an in-toto predicate URI |
| Multi repository projects | One file per repository | Parent file with project, child files pointing to it via header.project-si-source |
| URL rule | URLs had to begin with https:// | The #URL pattern accepts http:// or https:// |
| Validator in si-tooling | Python script (v1/si-validator.py), version 1 only | Go 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
| Field | Required | Type and rule | What it is for |
|---|---|---|---|
schema-version | Yes | Pattern X.Y.Z, no leading v | Which schema version to validate against |
last-updated | Yes | Date YYYY-MM-DD | When the content last changed |
last-reviewed | Yes | Date YYYY-MM-DD | When a maintainer last confirmed it was accurate |
url | Yes | URL | The 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" |
comment | No | String | Free text |
project-si-source | No | URL | Raw 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
| Field | Required | What it holds |
|---|---|---|
name | Yes | Project name |
administrators | Yes, at least one | People with administrative access to project resources, as contacts |
repositories | Yes, at least one | Every repository in the project, each with name, url and a comment, including the one holding this file |
vulnerability-reporting | Yes | How to report a vulnerability (see below) |
homepage | No | Landing page |
roadmap | No | Roadmap or schedule |
funding | No | Funding or sponsorship information |
steward | No | A 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" |
documentation | No | Links: 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:
| Field | Required | Rule |
|---|---|---|
name | Yes | String; may be a team description |
primary | Yes | Boolean; the schema says only one entry should be primary |
affiliation | No | String |
email | No | Must match an email pattern |
social | No | String, usually a profile URL |
Because email is optional, a schema-valid contact can give a consumer no way to reach anyone.
Vulnerability reporting
| Field | Required | What it holds |
|---|---|---|
reports-accepted | Yes | Whether the project currently accepts vulnerability reports |
bug-bounty-available | Yes | Whether a bug bounty is offered |
bug-bounty-program | No | URL of the bounty page |
contact | No | One contact, which can be a person or a mail group |
policy | No | URL of the disclosure rules (renamed from security-policy in 2.2.0) |
pgp-key | No | PGP public key for encrypted reports |
in-scope and out-of-scope | No | Lists of strings |
comment | No | Extra 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
| Field | Required | What it holds |
|---|---|---|
url | Yes | The repository URL |
status | Yes | One of active, abandoned, concept, inactive, moved, suspended, unsupported, WIP, taken from the repostatus.org vocabulary |
accepts-change-request | Yes | Whether any change requests are accepted |
accepts-automated-change-request | Yes | Whether bot or machine generated change requests are accepted |
core-team | Yes, at least one | Maintainers or approvers |
license | Yes | url and an SPDX expression |
security | Yes | Assessments, champions, tools |
bug-fixes-only | No | Whether only bug fixes are accepted |
no-third-party-packages | No | Whether the repository avoids outside package dependencies entirely |
documentation | No | contributing-guide, dependency-management-policy, governance, review-policy, security-policy |
release | No | Release 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
| Field | Required (if release present) | What it holds |
|---|---|---|
automated-pipeline | Yes | Whether releases come from an automated pipeline |
distribution-points | Yes, at least one | Links 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 |
changelog | No | URL, may include a {version} placeholder |
license | No | Release licence if it differs from the repository licence |
attestations | No | List 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.selfis required and holds acomment, plus optionalname,evidenceURL anddate. 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-partyis an optional list of the same shape, for external audits.championsis an optional list of contacts who advocate for security practice in the core team.toolsis an optional list. Each tool has aname, atypefrom the fixed setfuzzing,container,secret,SCA,SASTorother,rulesets(usedefaultif not customised), anintegrationobject with three booleans (adhoc,ci,release) and aresultsobject that can point to an attestation for each of those three run types.versionandcommentare 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-toolingdiscovery code lists the root and.github/and checks the two current names plus legacy spellingsSECURITY-INSIGHTS.yml,SECURITY_INSIGHTS.ymlandsecurity_insights.yml. - CLOMonitor's checks documentation inspects
security-insights.yml,.github/security-insights.ymlandSECURITY-INSIGHTS.ymlin 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.
| Question | OSPS Baseline | Scorecard | security.txt | Security Insights |
|---|---|---|---|---|
| What is it | Control catalogue | Automated checker | Web contact file | Project metadata file |
| Who writes it | OpenSSF SIG | Tool computes results | Site owner | Project maintainers |
| Where it lives | baseline.openssf.org | Results from a run or API | /.well-known/security.txt on a web origin | Repository root or .github/ |
| Security contact | Control OSPS-VM-02 requires it | Scores SECURITY.md content | Contact required | vulnerability-reporting.contact optional |
| Expiry or freshness | Versioned catalogue | Each run is fresh | Expires required | last-updated, last-reviewed |
| Signature | Not applicable | Not applicable | OpenPGP recommended | None in the format |
| Best use for a consumer | Defining what good looks like | Observing practices | Reporting a website vulnerability | Finding 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:
- Is it alive? Read
repository.status.activeis what you hope for;movedshould send you to the new location;abandoned,unsupportedandinactivemean fixes may not come. - Can I report a problem? Read
project.vulnerability-reporting.reports-accepted, then look forcontact,policyandrepository.documentation.security-policy. - Is support defined? Look for
project.documentation.support-policyand follow it to release support dates. - Are the packages I use official? Compare the package URL you install with
repository.release.distribution-points. - Can I verify releases? Look for
project.documentation.signature-verificationand release attestations with provenance or verification summary predicates. - Has anyone outside looked at it? Read
repository.security.assessments.third-party. - How fresh is this? Compare
header.last-reviewedwith 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/v2parses files into a Gosi.SecurityInsightsstructure. Version 2.3.0 addedsi.Discover(finds the file using "at most two GitHub API calls"),si.Fetch(raw bytes),si.Load(parse bytes), and a commandsi fetchthat runs discovery and parsing over many targets and prints a JSON array. Each result has a status ofok,not-found,invalidorerror, 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 readsGITHUB_TOKENwhen set, because unauthenticated GitHub API requests are limited to 60 an hour. When we triedsi fetchwithout a token while writing this post, the GitHub API rate limit had already been reached, both targets came back with statuserror, 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
mainin 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_insightscheck 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-policyin 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-uriis 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-verificationexists, 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 Insights | security.txt (RFC 9116) | SECURITY.md | CycloneDX or SPDX SBOM | |
|---|---|---|---|---|
| Purpose | Self-reported project security facts: contacts, status, policies, releases, tools, attestation locations | How to report vulnerabilities in a web origin's services | Human readable security policy and reporting instructions | Inventory of components, versions, identifiers, relationships |
| Format | YAML, CUE schema | Line based text fields, optional OpenPGP signature | Free form Markdown | JSON, XML and other serialisations defined by each standard |
| Location | Repository root or .github/ (.gitlab/ and similar) | /.well-known/security.txt on each web origin | Repository root, .github/ or docs/ | Release assets, registries, attestations, or a link from Security Insights |
| Who writes it | Project maintainers | Site owner | Project maintainers | Build tooling, then the producer publishes it |
| Freshness signal | last-updated, last-reviewed | Expires (required) | None | Document timestamp |
| Machine readable | Yes | Yes | No | Yes |
| Who reads it | Baseline evaluators, project scorers, intake pipelines | Security researchers, scanners of websites | People, plus checkers that score its content | Vulnerability management, licence and procurement tooling |
| Scope | One open source project | One web origin | One repository | One 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 says | What to do |
|---|---|
| No file found | Fall back to SECURITY.md and repository signals; record "no declaration" |
| File invalid against the declared schema version | Record as a finding; do not use its values |
status is moved | Follow the new location before adopting or updating |
status is abandoned, unsupported or inactive | Treat as end of maintenance; plan an upgrade, a fork or a replacement |
reports-accepted: false on an active repository | Human review before adoption |
reports-accepted: true with no contact, policy or security-policy link | Ask upstream to add one; record the gap |
last-reviewed older than your threshold | Treat declared facts as unreviewed |
| SBOM attestation listed | Fetch for the version you use and verify it; keep a copy |
| No SBOM attestation | Generate your own SBOM for that component |
| Third party assessment listed | Read 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 situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull 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
- OpenSSF: Security Insights project page
- Security Insights specification repository (ossf/security-insights, formerly security-insights-spec)
- Security Insights specification site
- Security Insights: Get Started guide
- Security Insights: schema reference
- Security Insights: CUE schema, spec/schema.cue
- Security Insights: FAQ
- Security Insights: governance
- Security Insights: versioning policy
- Security Insights: releases (v1.0.0, v2.0.0, v2.1.0, v2.2.0)
- Security Insights: minimum example
- Security Insights: full example
- Security Insights: multi repository child example
- Security Insights: v1.0.0 specification text
- Security Insights: AI drafting prompt
- Security Insights web editor
- si-tooling repository
- si-tooling: changelog
- si-tooling: v2 module README
- si-tooling: v1 README
- Go module proxy: si-tooling v2 versions
- OSPS Baseline
- OSPS Baseline v2026.08.28
- OSPS Baseline: implementation guidance for maintainers
- OpenSSF Scorecard repository
- OpenSSF Scorecard: checks documentation
- OpenSSF Scorecard: security-insights.yml
- RFC 9116: A File Format to Aid in Security Vulnerability Disclosure
- CLOMonitor: checks documentation
- in-toto attestation framework specification
- in-toto predicate: CycloneDX
- in-toto predicate: SPDX 2
- in-toto predicate: SPDX 3
- SLSA: verification summary attestation
- repostatus.org
- CUE: installation
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.