fixdossier / Docs / Coverage and limits

What a fixdossier security record covers, and what it does not

The record is built from what the GitHub API returns for one repository. It is a starting point for review, not an inventory of every security fix you ever made. This page lists what goes in, what is left out, and how the record tells you when something is missing.

Quick start

Fetch more history and fail the job if anything was cut short:

yaml
name: security-record
on:
  schedule:
    - cron: '0 6 * * 1'
  workflow_dispatch:
permissions:
  contents: read
  pull-requests: read
jobs:
  record:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - id: fd
        uses: itulo/fixdossier@v0
        with:
          max_pages: 30          # up to 3,000 items per source
          fail_on_gaps: 'true'   # exit 2 if any source was cut short or unreadable
      - run: echo "fixes=${{ steps.fd.outputs.fix_count }} gaps=${{ steps.fd.outputs.gap_count }}"
        if: always()

Advisory rows vs heuristic rows

Rows mapped by advisory patched_versions rest on a version the maintainer wrote into a published advisory. Rows labelled tag commit match rest on a release tag pointing at the exact commit. Rows labelled date heuristic (…) rest on dates alone. The split in the examples (generated 29 Oct 2026 with max_pages=2):

RepoAdvisory rowsCommit rowsFix mappingBot PR mappingGaps
axios/axios51151 advisory patched_versions, 1 date heuristic (commit)36 date heuristic, 11 unreleased2 (pull requests, commits)
fastify/fastify16016 advisory patched_versions4 date heuristic4 (all sources except advisories)
vitejs/vite23022 advisory patched_versions, 1 date heuristic (advisory)11 date heuristic4 (all sources except advisories)

None of the examples has a tag commit match row. An advisory patched_versions row can still point at a later release than the one that introduced the fix when history is cut short; check that every range in patched_versions has an entry in matched_releases.

The labels and their failure modes are explained in How fixdossier maps a GitHub security advisory to a release.

Unmatched and unreleased fixes

  • A commit or bot PR with no release on or after its date is shown with _unreleased_ and mapping unreleased. The summary line “Fixes not yet in a release” counts security-fix rows with no release.
  • An advisory where no patched range matched and no release came later gets mapping none.
  • Merged bot PRs since the last release are listed with _unreleased_. In the axios example, 11 Dependabot rows, the newest merged 2026-10-27, were unreleased at generation time.

Gaps in this record

Each source is read separately. If a source fails or is truncated, the other sources are still used, a workflow warning is printed, and a line is added under “Gaps in this record”. The possible messages:

MessageCause
<source>: stopped after N pages (raise max_pages for full history)There was another page and max_pages was reached.
<source>: HTTP 401/403 (missing permission or rate limit) — section skippedToken lacks access, the permission was not granted in the workflow, or the API rate limit was hit.
<source>: HTTP 404 (not found or not visible to this token) — section skippedEndpoint not visible to the token (for example a private repo without access).
<source>: HTTP <other> (API error) — section skippedAny other non-2xx response.
<source>: network error (…)The request failed before a response.

One more message can appear: tags: could not peel annotated tag <name> (HTTP <status>) (or the network error text instead of the HTTP status), when the extra request that resolves an annotated tag to its commit fails (see tag commit match). From the fastify example:

markdown
## Gaps in this record

- releases: stopped after 2 pages (raise max_pages for full history)
- tags: stopped after 2 pages (raise max_pages for full history)
- pull requests: stopped after 2 pages (raise max_pages for full history)
- commits: stopped after 2 pages (raise max_pages for full history)

The step still succeeds unless fail_on_gaps: 'true', in which case it exits with code 2 after writing the files. The gap_count output gives the number of messages.

Permissions

Releases, tags and commits need contents: read; pull requests need pull-requests: read. Published advisories on a public repository are readable with the default token. On private repositories, if the token cannot list advisories, you get a gap for “security advisories”. See GitHub's notes on GITHUB_TOKEN permissions.

max_pages

  • Each source is read in pages of 100 items. max_pages (default 10 in the Action) caps pages per source, so the default reads up to 1,000 releases, 1,000 tags, 1,000 closed PRs, 1,000 advisories and 1,000 commits.
  • Closed PRs are sorted by last update, newest first; commits are newest first. Truncation drops the oldest history.
  • Truncated releases also hurt mapping: an advisory whose patched release is older than the oldest fetched release is matched to a later release in the same range, or falls back to the date heuristic if no fetched release satisfies any range.
  • A run makes up to 5 × max_pages list requests, plus one request per annotated tag that the tags endpoint returns as a tag object rather than a commit. GITHUB_TOKEN has its own rate limit; anonymous local runs are limited to 60 requests per hour.

Not covered

  • Silent fixes. Commits without an ID or keyword in the message.
  • Other branches. Only default-branch commits are scanned.
  • Unpublished advisories. Drafts, triage and closed advisories are not read.
  • Advisories about your dependencies. Global advisories and Dependabot alerts are not read. Dependency fixes appear only if a bot PR was merged.
  • Code scanning and secret scanning alerts. Not read.
  • Releases outside GitHub. A package published to a registry without a GitHub Release is invisible to the mapping.
  • Correctness of the fix. The record says a change happened and when; it does not say the vulnerability is gone.

False positives

Keyword matching works in both directions. In axios, the commit “chore(deps): bump form-data floor to ^4.0.6 to patch CRLF injection” has no CVE or GHSA ID and was found only by the word “injection”; it is a real fix. A commit titled “refactor dependency injection container” would match the same way and is not. Review commit rows before you rely on them.

Docs pages

Not legal advice. fixdossier is an engineering tool for evidence collection and review support. It is not legal advice and not a compliance certificate. It does not make a product compliant with the PLD, the CRA or anything else, and its output can be incomplete or wrong. Review it, and ask a lawyer about your situation.