Refolk
TeardownEngineering and open source

The Upstream Vulnerability Routing Teardown: SBOM Line to Acknowledged Report

Resolve who maintains the exact component version you ship, reach them on a verified private channel, and hold a logged acknowledgement or a coordinator case ID.

22 min readLast reviewed October 23, 2026Read as Markdown

Key takeaways

  • The CRA's upstream duty in Article 13(6) covers only the component version you integrate and only flaws in the component itself, not flaws created by your integration, and it applies from 11 December 2027.
  • Across 8,073 vulnerability-linked GitHub repositories, only 22% have an explicit security policy and 11% offer a private report button, so most contact lookups fail at the first rung.
  • GitHub private vulnerability reporting is off by default and 76.1% of projects leave it off, so a SECURITY.md that points to the advisory form can lead nowhere until you check the API.
  • 60.3% of security.txt files point to contacts on identifiable platforms, so a Contact domain that does not match the vendor's domain is probably a hosting abuse desk, not the maintainer.
  • CERT/CC recommends reporting to the maintainer first and bringing in a coordinator after about two weeks of silence, and it generally targets publication 45 days after its initial contact attempt.
  • In Refolk's index, 446 people in the US hold product-security or PSIRT titles against 22 in Germany, and only 1 German holds an OSPO title, so German industrial maintainers often have no named security owner.

This guide covers one job. You have found a vulnerability in a third-party component your product ships, and you need to get a private report to whoever actually maintains the version you integrate, with a record that proves you did. It is for the engineering managers, technical founders, developer-relations leads and technical sourcers who end up holding that report. It follows one transitive dependency from its SBOM line to an acknowledged report, wrong turns included, and finishes with an evidence log an auditor can read.

What does the upstream reporting duty require?

Article 13(6) of the EU Cyber Resilience Act (CRA) says that a manufacturer who finds a vulnerability in a component integrated in its product, open source included, must report it to the person or entity that manufactures or maintains that component. If the manufacturer wrote a fix, it must share that too. The Commission's guidance limits the duty to the exact version you integrate.

That guidance is C(2026) 5252, and paragraphs 222 to 229 deal with upstream reporting. It says outright that it is non-binding, but it is still the clearest public statement of what regulators expect. Six points in it decide how you route a report:

  • Version. Paragraph 223: "Manufacturers are required to report upstream only in respect of the version of the component that they integrate." The project's latest release is not the subject. Your release is.
  • Flaw type. The duty covers vulnerabilities in the integrated component itself, "and not vulnerabilities that exist as a result of the integration."
  • Duplicates. Check public vulnerability databases, project advisories and issue trackers first, so you do not file a duplicate.
  • Channel. Use the maintainer's own policy or channel if one exists, "particularly where premature disclosure of unpatched vulnerabilities could increase cybersecurity risks."
  • Fix. If you wrote a fix, share the code or documentation with the maintainer, in a machine-readable format where appropriate.
  • Exceptions. You do not need to report when the component no longer has a maintainer, or when you no longer rely on the original maintainer for new versions or security fixes. The second case describes a fork you maintain yourself.

Dates: Article 14 reporting applies from 11 September 2026. Article 13(6) and the other main obligations apply from 11 December 2027. The CRA does not prescribe a format for the record, so this guide gives you one.

If the CRA does not cover you, the procedure stays the same. It is ordinary coordinated vulnerability disclosure (CVD): you report privately to the party that can fix the flaw, and you agree together on when details go public. The CERT Guide to CVD recommends that a reporter "first try reporting the vulnerability directly to the vendor or maintainer of the software in question."

The case: one transitive parser, four candidate owners

The worked case is a composite. I have anonymised the component and describe each party by its role, because the lesson is in the sequence of decisions, not the package name. The day counts are the ones this case took. They are not benchmarks.

A product team found a memory-safety flaw in a small parsing library. The product does not depend on the library directly. It comes in transitively, two levels down, through a logging package. The SBOM line gave three facts: the package name, the version, and a source URL.

Four parties behind that line had a plausible claim to be "the maintainer":

  1. The original author. Their name is on the licence header and on most of the early commits.
  2. The registry publisher. The accounts allowed to push releases of the package to its registry.
  3. The repository owner. The organisation that now holds the upstream repository: a German industrial manufacturer that took the project over.
  4. A fork. The SBOM's source URL pointed to it, and it was archived.

A fifth answer stayed possible the whole time: nobody. This split is the hard part of the job. The CERT Guide to CVD and the OpenSSF finder guide list places to look for a contact, but neither tells you which of these parties counts when they disagree.

The routing procedure, SBOM line to closed record

There are nine steps from vulnerable component to closed record: pin, confirm, check, resolve, pick, send, wait, escalate, close. Steps 4 and 5 are where cases go wrong, so each gets its own section below.

Routing one dependency vulnerability upstream

  1. Pin the exact version from the SBOM
    Record package name, version, SBOM source URL and the product release that ships it. Under an hour.
  2. Confirm it is a component flaw
    Reproduce outside your integration and write a reachability and root-cause note. Integration flaws are yours to fix, not upstream's.
  3. Check whether it is already known
    Search public vulnerability databases, the project's advisories and its issue tracker for the pinned version, and log the search. About an hour.
  4. Resolve the maintaining party
    Compare registry publisher, repo owner, fork status and recent release activity on your version line. End with a named party, a documented no-maintainer finding, or a documented we-forked finding.
  5. Pick and verify the private channel
    Walk the channel ladder and confirm the channel accepts reports before sending, for example by querying the PVR endpoint. Never put details in a public issue.
  6. Send privately and share any fix
    Send the full report on the verified channel with version, reproduction and any patch. Capture the delivery timestamp.
  7. Wait two weeks for acknowledgement
    Allow about two weeks of silence after delivery before bringing in a coordinator, following CERT/CC. Log every follow-up.
  8. Escalate to a coordinator if silent
    Hand the case to a coordinator such as CERT/CC or CISA, which may publish at 45 days. Record the coordinator case ID.
  9. Close the evidence record
    Log the acknowledgement, fix status and any exception relied on. Done when an outsider can reconstruct every decision.

How the case went through steps 1 to 3

  • Day 0, under an hour. The team pinned the package, version and product release, and copied the SBOM source URL exactly as written. Copying it exactly mattered later.
  • Day 0 to day 1. The team wrote the root-cause note. They reproduced the flaw with a minimal harness that called the parser directly, with none of the product's code involved. That made it a component flaw. If it had only triggered because the product's wrapper passed in unchecked input, it would have been an integration flaw: theirs to fix, and outside the upstream duty.
  • Day 1, about an hour. The team checked whether the flaw was already known. They searched public vulnerability databases, the project's advisories and its issue tracker for the pinned version. Nothing matched. The search terms, sources and date went into the log, because "not known" is a finding and needs evidence like any other.

Who maintains the exact version you ship?

The maintaining party is whoever currently controls releases for the version line you integrate and is still using that control. The author, the repository owner and the sponsor are evidence toward that answer. None of them is the answer by default. I found no published method for settling this in CERT/CC's guidance, GitHub's documentation or the CRA guidance, so the signals below are a working rubric, and each one comes with the way it can mislead you.

What each signal proves, and how it lies

  • Registry publish rights. Proves who can get a fix to your users. Misleads when an account keeps its rights long after its owner has left. Read the publisher metadata using the registry's own documentation. I have not verified specific registry commands, so check them there.
  • Repository ownership. Proves who controls the code and its settings. On GitHub, only the owner can turn on private vulnerability reporting. Misleads after a transfer, or when the owner is an organisation nobody watches.
  • Recent releases and commits on your version line. Proves someone is still exercising control. Misleads when the activity is all automated dependency bumps.
  • Corporate sponsor. Proves who employs or funds the work, and hints that a security team might exist. Misleads when the sponsor pays for the project but nobody there triages reports.
  • Original author. Proves history. As a current contact, it misleads more often than not.
  • Fork status. Proves whose code you actually build. If you build from a fork you maintain, or one that nobody maintains, a CRA exception may apply, but only if you write it down.

Who counts as the maintaining party

Active on your version lineInactive on your version line
Active contributor or fork owner
Ask for the maintainer's channel; send no details
Maintaining party
Report here on a verified private channel
History or abandoned
Log it; test for a no-maintainer or fork exception
Dormant owner
Try their channel; escalate after two weeks of silence
No release controlControls releases
The maintaining party controls releases for your version line and is still using that control; every other combination is evidence or a documented exception.

Wrong turn 1: emailing the author (day 1)

The first instinct was to email the author, using an address taken from old commit metadata. The email went out the same day with full details. Two days passed with no reply. The check that should have come first is the date of the named person's last release and last commit on this version line. Both were years old. A Refolk search on the author's name showed them at a different employer with no visible link to the project. That took a minute, where reading old commit emails would have taken an afternoon. The team classified the author as history.

The email still went into the log as a contact attempt. It also left full details in the inbox of someone with no role in the project, so the mistake cost more than time.

Wrong turn 2: the archived fork (day 2)

The SBOM source URL pointed at the fork, so a teammate sent the report to the fork owner's listed address. The fork owner replied that they had stopped tracking the project. The check that catches this is to compare the SBOM source URL with what the build actually resolves. Here the lockfile resolved to the registry package published from upstream, and the source URL was stale metadata.

If the build really had pulled the archived fork, the answer would change. The team would no longer rely on the original maintainer, the CRA exception would apply, and the job would become patching the fork and documenting the exception.

The resolution (days 2 to 3)

  • Repository owner: the manufacturer's organisation, after a transfer.
  • Registry publish rights: two accounts. One had published releases on the integrated version line within the past few months, and its commits came from an address on the manufacturer's domain. The other was dormant.
  • Verdict: the maintaining party is the manufacturer, reached through the active publisher. The author is history, the fork is irrelevant, and "nobody" is ruled out.

This step took three days, the top of the 1 to 3 day budget I use. No published benchmark backs that budget.

Who holds the security role at the maintainer?

Finding the maintaining party is not the same as finding a person who will triage your report. When the maintainer is a company, look for a named product-security or PSIRT owner (a product security incident response team handles vulnerability reports for a vendor's products). At German manufacturers, expect to come up short.

Segment (Refolk's index)CountTop employers in sample
US product security / PSIRT titles446Apple, HP, Meta
Germany, same titles22SAP, Dräger, Rolls-Royce Power Systems
Germany, OSPO titles1OASIS
20.3x
US product-security and PSIRT titles versus Germany, in Refolk's index
446 against 22, and title matching is narrow, so treat both as floors rather than totals.

The US employer list comes from a 25-person sample: Apple 4, HP 4, Meta 3. In Germany's 22 the leaders are SAP with 5, Dräger with 4 and Rolls-Royce Power Systems with 2. An OSPO (open source program office) is the team that manages a company's open-source use and contributions. Germany's single OSPO title belongs to someone at OASIS, a standards body, not a manufacturer. In Germany, product-security titles outnumber OSPO titles 22 to 1. For routing, this means an upstream maintainer inside a German industrial company will often have no named security owner, and you may have to find the person before you can find the channel.

In the case, the team used Refolk to find the manufacturer's product-security people. They used those names for one purpose only: asking for the preferred private channel.

Which private channel do you use, and in what order?

Start with the maintainer's own published security channel, once you have confirmed it works. Next comes GitHub private vulnerability reporting, if it is enabled. Then a security.txt contact whose domain matches the vendor's. Then a public request for a security contact that contains no details. After about two weeks of silence, go to a coordinator. No single public source ranks all of these together, so this order is my assembly from GitHub's documentation and the CERT Guide to CVD.

Private vulnerability reporting (PVR) is a GitHub repository feature that lets a reporter open a private advisory draft that only the maintainers can see. GitHub's order is to check the repository's security policy first. Without a policy or PVR, you follow the instructions in the security policy or open an issue asking the maintainers for a preferred security contact. The sources disagree in one place: GitHub also says PVR works without following SECURITY.md. My rule is that if SECURITY.md names a channel other than PVR, you use that channel, because it is the maintainer's stated preference and matches the CRA guidance's "maintainer's own policy or channel."

The private channel ladder

  1. Maintainer's own policy
    SECURITY.md or a documented security address, verified to accept reports
  2. GitHub PVR
    Use only if the API reports enabled: true
  3. security.txt
    Use only if the Contact domain matches the vendor and Expires is in the future
  4. Detail-free issue
    Public request for a preferred security contact, reviewed by a second person
  5. Coordinator
    CERT/CC or CISA after about two weeks of silence
Move down a rung only after the rung above is confirmed missing or dead, and never put details in a public channel.

How often each rung exists

Most of the time the top rung is missing. The table shows that how often it exists depends heavily on which population you sample.

PopulationHas security policyPrivate channel / PVR
8,073 vuln-linked GitHub repos22%11% PVR; 32% any private contact
1,982 projects from GitHub advisories52.7%23.1% PVR
707 projects from huntr bounty reports76.5%29.0% PVR
302 PyPI policies100% (sample)94.37% name a private channel

Private contact availability in 8,073 vulnerability-linked repos

  1. Vulnerability-linked repos studied
    8,073
  2. Any private contact information
    2,584

    32%

  3. Private report button
    905

    11%

Fewer than a third of vulnerability-linked GitHub repositories offer any private contact, and about one in nine has a private report button.

GitHub ships PVR disabled by default, and 76.1% of projects leave it that way. You can read a repository's PVR state with gh api repos/OWNER/REPO/private-vulnerability-reporting.

security.txt: useful, rare, and often someone else's

security.txt is a file in the /.well-known/ folder of a domain that names the domain's security contacts. BSI TR-03183-3 lists it among intake mechanisms, alongside CVD and CSAF (I am citing this secondhand). Adoption varies too much across populations to rely on, and the rows below come from different samples, so do not average them.

Populationsecurity.txt adoption
All scanned domains (241M)0.238%
Top 1M domains1.25%
Large Spanish companies9.6%
112 large EU manufacturers31.3%
Swedish orgs and municipalities44.7%

Even when the file exists, check it before trusting it. Of the files found on the top 1M domains, only 44% meet RFC standards. 13% have passed their expiry date, and 45% have no expiry at all. 60.3% of files point to contacts on identifiable platforms. A file whose Contact domain matches the vendor's domain is a strong signal. A file that points at a platform usually reaches a hosting abuse desk. Among large Spanish companies, none of the 28 in banking and insurance published one.

How the case went through step 5 (days 3 to 4)

  • The upstream repository had no SECURITY.md.
  • The PVR endpoint returned enabled: false.
  • The manufacturer's domain had a security.txt, but its Contact sat on a hosting platform's domain. The team treated it as a weak signal and sent no details there.
  • Wrong turn 3. A developer drafted a public issue titled "Security contact?" and pasted in the stack trace "to help them triage." A second reviewer caught it. The issue was rewritten from the template below and posted.
  • At the same time, the team sent a short contact request with no details to the active publisher account and to the named product-security lead.
  • Day 4. The active publisher replied, and the owner turned PVR on. The team filed the full report through PVR with the version, the reproduction harness and their patch, and captured the delivery timestamp. Only the title and description are mandatory in the PVR form. Fill in the rest anyway.
Detail-free request for a security contact
Hello maintainers,

I have a potential security issue to report in this project and would like to send it privately.

Could you tell me your preferred private channel, or enable private vulnerability reporting on this repository?

I will not post any details here.

Thanks,
<name>, <organisation>
<contact address>

Post only after a second person confirms nothing in it hints at the component path, version or failure.

What do you do when the maintainer does not respond?

Allow about two weeks of silence after confirmed delivery, then bring in a coordinator such as CERT/CC or CISA. This is the trigger in the CERT Guide to CVD. Before you escalate, confirm that the silence came from the maintaining party and not from a departed author or a stale fork.

Know the timelines before you hand a case over:

  • CERT/CC generally targets publication of the details 45 days after its initial contact attempt with the vendor.
  • CISA may also disclose at 45 days.
  • Non-responsive vendor. When no embargo was set, CERT/CC treats a vendor as non-responsive once at least six weeks have passed since the vendor's last response.
  • Ninety days is often called a de facto norm, but only secondary sources say so. Do not quote it as policy.
  • CVE "CNA of last resort". I have not verified how this procedure works. Check the CVE program's own documentation before you count on it.

In the case, the manufacturer acknowledged on day 9 and confirmed a fix was planned for the version line the product ships. Without that reply, the escalation date would have been day 18, two weeks after the day-4 delivery, and the team would have logged a coordinator case ID.

If the resolution step ended with "nobody", the escalation path is different. A component with no maintainer falls under the CRA exception. You document the finding, patch your own copy, and decide whether a coordinator should still hear about it so other users find out.

How does this go wrong?

Most failures are routing mistakes, not missing channels: a channel that is listed but dead, a contact that belongs to the hosting platform, a report sent to the wrong party, or details posted in public. Reporter behaviour causes more leaks than missing policies do. 285 of 302 PyPI security policies name a private channel, yet 787 security-related issues were filed publicly as GitHub issues across 58 repositories.

  1. The "security contact?" issue leaks. GitHub allows the issue, but it is public immediately and should contain nothing about the bug. Looks like: a helpful stack trace or a version-specific hint. Check: a second person reviews the text before it is posted.
  2. SECURITY.md points to a disabled PVR form. Looks like: a clean policy that sends you to a form that will not accept anything. Check: query the PVR endpoint before sending.
  3. security.txt reaches the host, not the vendor. 60.3% of files point to platform contacts. Looks like: a responsive address that turns out to be an abuse desk. Check: does the Contact domain match the vendor's domain?
  4. Expired security.txt. 13% are past their expiry date. Check: read the Expires field before using the contact.
  5. Reporting on a fork you no longer track, or reporting an integration bug. Neither falls under paragraph 223. Looks like: case 2's teammate mailing the fork owner. Check: compare against the SBOM source URL, what the lockfile resolves, and your root-cause note.
  6. Duplicate report. The maintainer may already know. Check: search databases, advisories and the issue tracker first, and log the search.
  7. The departed author or archived repo trap. No public method exists to detect this reliably. Looks like: an inbox that accepts mail and never answers. Check: the date of the named person's last release and last commit on your line. Treat silence past the two-week CERT/CC threshold as the trigger to escalate.
A listed channel is a claim, not a delivery. Verify it accepts reports before anything sensitive leaves your hands.

What goes in the evidence record?

No authority mandates a field list. Keep one log entry per report covering the version, the root-cause finding, the awareness search, every contact attempt with its channel and timestamp, the acknowledgement, and any exception you relied on. Practitioner sources recommend an "upstream-report log" with a field like "Upstream maintainer informed: <yes, date, channel / not needed: maintainer aware>."

The exceptions are where records matter most. The CRA removes the duty when there is no maintainer or when you have forked, but only a record makes that visible: without it, the absence of an upstream report looks like an omission. The duty rewards documentation, not effort.

Upstream-report log entry
Report ID:
Product and release:
Component, version, source URL (as in SBOM):
Build resolves to (lockfile source):
Root-cause note: component flaw / integration flaw, link
Awareness search: sources, terms, date, result (already known: link / not known)
Maintaining party: named party / no maintainer / we forked
Evidence for maintaining party: publisher, repo owner, last release on our line, sponsor
Channel verification: PVR API enabled true/false; security.txt Contact domain and Expires
Contact attempts: date, time, channel, recipient, details included yes/no, outcome
Delivered: timestamp, channel, fix or patch shared yes/no
Upstream maintainer informed: yes, date, channel / not needed: maintainer aware
Acknowledged: date, by whom, fix status
Escalation: coordinator, date, case ID
Exception relied on: none / no maintainer / no longer rely on original maintainer, evidence
Closed: date, by

One entry per vulnerability per integrated version; append contact attempts, never overwrite them.

Keeping the routing current

Channels decay, so check them again for every report instead of reusing last quarter's answer. A repository's PVR setting can change, a security.txt can expire, publish rights move between accounts, and the person who answered last time may now work somewhere else.

Three habits keep this cheap. First, store the evidence behind each routing decision, not just the decision, so the next report starts from facts you can recheck. Second, when a component's sponsor or active publisher changes, re-run the resolution step before the next report rather than after a bounce. Third, for corporate maintainers without a named security owner, which in Refolk's index describes much of German industry, keep the names of whoever gave you a private channel, and confirm they still hold the role before you use them again.

Before you close the case

  • The SBOM entry, integrated version and product release are recorded together.
  • A written root-cause note shows the flaw reproduces in the component outside your integration.
  • The awareness search is logged with sources, terms, date and result.
  • The maintaining party is named with release-control and activity evidence, or a no-maintainer or fork finding is documented.
  • The channel was verified working before any details were sent.
  • No public issue, commit message or forum post contains details of the flaw.
  • The delivery timestamp and channel are logged, and any patch you wrote was shared.
  • An acknowledgement is logged, or a coordinator case ID exists after two weeks of silence.
  • Every dead end appears in the log as a contact attempt, marked with whether details were included.

Questions practitioners ask

Who do I report a vulnerability to if the project has no SECURITY.md?

Check whether GitHub private vulnerability reporting is enabled with `gh api repos/OWNER/REPO/private-vulnerability-reporting`, then look for a security.txt on the maintaining organisation's domain and confirm its Contact domain matches theirs. If neither works, open a public issue asking only for a preferred security contact, with no bug details. If nothing answers in about two weeks, CERT/CC's guidance is to bring in a coordinator.

What should I do when the maintainer does not respond to my vulnerability report?

Wait about two weeks of silence after confirmed delivery, then engage a coordinator such as CERT/CC or CISA, both of which may publish details around 45 days after their initial contact attempt. Before escalating, confirm you reached the maintaining party and not a departed author or an archived fork, because silence from the wrong party is not maintainer silence. Log the coordinator case ID.

Do I have to report upstream if we maintain our own fork of the component?

Under the Commission's CRA guidance, no report is needed when you no longer rely on the original maintainer for new versions or security fixes, which is what maintaining your own fork means. The exception only protects you if you document it: record the fork, when you stopped tracking upstream, and evidence that your build pulls from the fork. The guidance is non-binding, so keep the record either way.

Is it safe to open a GitHub issue asking for a security contact?

Yes, as long as the issue says nothing about the bug. GitHub's documentation allows an issue asking maintainers for a preferred security contact, but that issue is public the moment you post it. Ask for a private channel and nothing else: no component path, no stack trace, no version hint. Have a second person review the text first, because reporters leak details this way all the time.

How long should it take to find who maintains a dependency?

Budget 1 to 3 days to resolve the maintaining party, and know that no published benchmark backs that range. Most of the time goes on comparing the registry publisher, repository owner, fork status and recent release activity on the version line you ship. If three days pass without a named party, decide explicitly between a documented no-maintainer finding and a coordinator, rather than letting the case drift.

Does the CRA require a specific format for the upstream report record?

No. The CRA does not publicly prescribe a record format, and no authority mandates a field list. Practitioner sources recommend an upstream-report log with a field such as 'Upstream maintainer informed: yes, date, channel / not needed: maintainer aware', plus documentation of any no-maintainer or fork exception. Keep enough that an auditor can see who you contacted, when, through which channel, and why.

Try it on the search you came here for

Stop building boolean strings. Just describe the person.

Type one sentence. I plan the search, read GitHub, public LinkedIn and Crunchbase records, and the open web as it is right now, and hand back a ranked list with the reason next to every name.

  1. 01Describe them

    One plain sentence. Role, city, stack, stage, whatever matters to you.

  2. 02I read the web live

    GitHub, public LinkedIn and Crunchbase records, the open web. Not a database that went stale last quarter.

  3. 03You read the shortlist

    Ranked, with the reasoning under every name. Open a profile, ask a follow-up, narrow it down.

  • No boolean, no filters, no seat to buy. One box.
  • Read at search time, so a profile updated yesterday counts today.
  • Every step visible as it runs, every name with its reason.

500 free credits on sign-up. No card, no demo call. See real searches.

Read next