---
key: "coordinated-disclosure"
title: "Run coordinated vulnerability disclosure from report to advisory"
url: "https://softwaresecurity.com/domains/appsec/vulnerability-response/coordinated-disclosure/"
api: "https://softwaresecurity.com/api/v1/articles/coordinated-disclosure.json"
domain: "appsec"
topic: "Application security"
type: "Guide"
identifiers: []
tags: ["Vulnerability management","Vulnerability disclosure"]
stages: ["respond"]
roles: ["developer","appsec-engineer","security-leader"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# Run coordinated vulnerability disclosure from report to advisory

Make private reporting discoverable, verify reports, coordinate a tested remedy, and give affected users enough information to act.

Canonical URL: https://softwaresecurity.com/domains/appsec/vulnerability-response/coordinated-disclosure/

## In short

Publish a monitored private reporting route, such as a `security.txt` file, a disclosure policy and GitHub private vulnerability reporting, then acknowledge quickly and reproduce with synthetic data. Fix every supported branch and hosted deployment, get a CVE through a CNA, and publish an advisory with exact affected and fixed versions once those fixes exist. Close when they are deployed and verified. In the EU, an actively exploited vulnerability also starts Cyber Resilience Act reporting clocks.

- A reporting route is real only if someone owns it and answers on a stated cadence.
- Reproduce with synthetic accounts; never ask the reporter for more real customer data.
- Separate the vulnerability fix from the incident question of whether it was exploited.
- Publish affected and fixed versions only after the fixed artifacts exist and pass the regression.
- From 11 September 2026, EU manufacturers report actively exploited vulnerabilities within 24 hours (early warning) and 72 hours (notification).

## Recommended defaults

| Setting | Value | Note | Source |
| --- | --- | --- | --- |
| `security.txt` location and fields | `/.well-known/security.txt` over HTTPS with at least one `Contact` and exactly one `Expires` | Keep `Expires` less than a year in the future. | RFC 9116 |
| EU CRA early warning | Within 24 hours of becoming aware of an actively exploited vulnerability or a severe incident | — | Regulation (EU) 2024/2847, Article 14; applies from 11 September 2026 |
| EU CRA notification | Within 72 hours of becoming aware | — | Regulation (EU) 2024/2847, Article 14 |
| EU CRA final report | Within 14 days after a corrective measure is available; one month after notification for a severe incident | — | Regulation (EU) 2024/2847, Article 14 |
| Common researcher deadline | 90 days to fix, plus 30 days for patch adoption when fixed early | Google Project Zero policy; other programs differ, so agree dates per case. | Google Project Zero vulnerability disclosure policy |

## Verify it

1. Fetch `/.well-known/security.txt` over HTTPS and confirm each `Contact` works and `Expires` is less than a year away.
2. Send a test report through each published channel and time the acknowledgement against your stated cadence.
3. Run the reporter's case and the neighboring paths against the fixed build of every supported branch.
4. Compare the advisory version ranges with the published packages and the hosted deployment digest.
5. For products sold in the EU, walk through a 24-hour early warning to the ENISA single reporting platform with the people who would send it.

## What you will learn

- Translate a report into an owned reproduction and fix plan.
- Agree a disclosure date with reporters and downstream vendors.
- Publish identifiers, branch statuses and advisories that match the verified fixes.
- Decide when regulatory reporting such as the EU CRA applies.

Audience: Maintainers, product security teams and engineering leads who receive vulnerability reports and publish advisories.

Before you start:

- Identify a product owner, a security-report intake route and a supported release inventory.

Time: About 32 minutes: 10 to read and 22 for the exercise

## Who receives and owns a vulnerability report?

A disclosure process is a route from a private report to a verified fix and useful public guidance. A security mailbox that nobody monitors does not provide that route. Publish the supported products, reporting channel, information needed to reproduce, expected acknowledgement cadence and how the team handles updates. State the scope and behavior the organization authorizes in its policy; do not imply that a disclosure page grants unlimited permission to test customer systems.

Use a fictional report: an authenticated red tenant member can download a blue tenant invoice by changing an export id. The researcher supplies two synthetic accounts, one request and redacted response evidence. The first responder's task is to preserve the report, acknowledge receipt, assign an owner and determine whether the exposure is still active. They should not request additional real customer records to make the report more persuasive. Give the researcher a way to share sensitive details privately and keep access limited to people working the issue.

Make the route machine-discoverable as well. RFC 9116 defines `/.well-known/security.txt`, served over HTTPS as plain text: at least one `Contact`, exactly one `Expires`, which it recommends keeping less than a year ahead, and optional fields such as `Encryption`, `Policy`, `Canonical`, `Preferred-Languages` and `Acknowledgments`; CSAF 2.0 registers a further `CSAF` field that points to the advisory provider metadata. The file below passes those checks in a small validator. For open-source projects on GitHub, also enable private vulnerability reporting, which lets a researcher open a draft security advisory that only maintainers can see. ISO/IEC 29147 describes the disclosure side of this process and ISO/IEC 30111 the internal handling side.

```text
# Served as text/plain at https://example.test/.well-known/security.txt
Contact: mailto:security@example.test
Contact: https://example.test/security/report
Expires: 2027-04-30T00:00:00Z
Encryption: https://example.test/.well-known/security.asc
Preferred-Languages: en
Canonical: https://example.test/.well-known/security.txt
Policy: https://example.test/security/disclosure-policy
Acknowledgments: https://example.test/security/thanks
CSAF: https://example.test/.well-known/csaf/provider-metadata.json

```

## How do you reproduce a report without expanding exposure?

Create or use controlled tenant fixtures with the supplied ownership relationships. Compare expected permission with the observed response and examine whether the same missing check appears in download, preview and bulk export paths. Reproduction establishes the behavior; severity analysis considers what records are exposed, prerequisites, scope and whether public deployment differs from the report. A missing reproducer does not automatically mean false positive: ask for the smallest missing evidence while continuing code inspection.

Track receipt time, reproduction status, affected release range, owner, containment, fix plan and next communication date. Separate a vulnerability assessment from an incident assessment. The flaw may exist without known exploitation, while logs or researcher evidence may indicate actual exposure. If active exploitation is suspected, incident handling and containment can proceed alongside the coordinated disclosure plan. Do not postpone a protective fix merely to preserve an originally proposed publication date.

## When is a fix ready to announce?

The correction should enforce tenant and object permission before file release. Test the researcher's case and neighboring paths, with a normal allowed download to show the feature still works. Identify supported release branches, hosted deployments, cached files and signed URLs that may outlive the code change. A patch to the API cannot revoke a previously issued bearer URL unless the storage or token mechanism supports an appropriate revocation or expiry path.

Prepare the advisory from verified facts: affected and fixed versions, exploit prerequisites, consequence, mitigation and upgrade instructions. Avoid claims such as no data was accessed unless the investigation can support them. Coordinate credit and publication with the reporter and other affected maintainers, using explicit dates and reasons for any change. The purpose is to give users enough information to act while avoiding premature release of sensitive exploit details or private evidence. A vulnerability identifier helps cross-reference the issue but is not itself evidence of a correct fix.

## How do you assign an identifier and publish the advisory?

Request a CVE from a CVE Numbering Authority whose scope covers the product: your own organization if it is a CNA, GitHub for repositories it hosts, or a vendor or national CNA. The identifier helps people match scanners, advisories and patches; it does not establish severity or prove a fix. Publish the advisory where consumers' tools read it: a GitHub security advisory for GitHub-hosted packages, OSV-format data for open-source ecosystems, and CSAF 2.0 documents, listed through the `CSAF` field and the `/.well-known/csaf/provider-metadata.json` provider file, for enterprise customers.

Each advisory needs the affected and fixed version ranges per branch, the preconditions, the consequence, mitigations with their limits, upgrade instructions and credit as agreed with the reporter. Machine-readable ranges let scanners tell customers whether they are affected; a prose-only advisory forces every consumer to interpret it by hand. The details of CSAF, VEX statuses, CVE records and OSV, with validated examples, are in /domains/appsec/vulnerability-response/security-advisories-csaf-vex/; running intake, triage and response targets as a team is covered in /domains/appsec/vulnerability-response/psirt-operating-model/.

## What should the case record hold?

Keep the whole case in one record from the first report: the reporter and agreed credit, each branch's status and fixed version, the CWE and CVSS vector, what is known about exploitation and the period the logs cover, every promise made to the reporter with its date, and the agreed disclosure date. The advisory, the CVE record and the CSAF document are then generated from it, and a script can check the promises. The example is fictional.

```yaml
# Disclosure case record (fictional). Dates are ISO; "promised" entries are commitments made to the reporter.
id: CVD-2026-031
summary: Tenant member can download another tenant's invoice export by changing the export id
reporter: {name: R. Okafor, credit: "R. Okafor", contact: via VDP form}
received: 2026-10-01
acknowledged: 2026-10-02
supported_branches: ["3.2", "3.3"]
affected:
  - {branch: "3.1", status: affected, fix: none, note: end of life since 2026-06-30}
  - {branch: "3.2", status: affected, fixed_in: "3.2.4"}
  - {branch: "3.3", status: affected, fixed_in: "3.3.1"}
cwe: CWE-639
cvss: "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N"
exploitation: {evidence: none found, log_window_days: 2}
promised:
  - {what: status update, due: 2026-10-04, done: 2026-10-04}
  - {what: status update, due: 2026-10-08, done: null}
  - {what: fix for supported branches, due: 2026-10-15, done: 2026-10-05}
disclosure: {agreed_date: 2026-10-12, cve: reserved, cve_disclosed: null}
```

## How do you keep promises to the reporter on schedule?

Reporters judge a disclosure program mostly by whether it does what it said it would. Run a daily check over open cases. On 10 October 2026 the script reports that the status update promised for 8 October is two days late, that the advisory must say end-of-life branch 3.1 is affected with no fix, and that the agreed disclosure date is two days away, when the advisory and CVE record should be published together. Once the CVE ID is disclosed, it also prints the 72-hour limit for publishing the record under the CNA rules. Tested with Python 3.14 and PyYAML 6.0.3.

```python
"""Check a disclosure case: overdue promises to the reporter, open branches and the CVE record deadline.

Usage: python disclosure_timeline.py case.yaml 2026-10-10
"""
import sys
from datetime import date, timedelta

import yaml


def check(case, today):
    for p in case["promised"]:
        if not p["done"] and p["due"] < today:
            yield f"OVERDUE promise: {p['what']} due {p['due']} ({(today - p['due']).days} days late)"
    for a in case["affected"]:
        if a["branch"] in case["supported_branches"] and not a.get("fixed_in"):
            yield f"OPEN supported branch {a['branch']} has no fixed version"
        if a["branch"] not in case["supported_branches"]:
            yield f"ADVISORY must state branch {a['branch']} is affected with no fix ({a.get('note', 'unsupported')})"
    disclosure = case["disclosure"]
    days_left = (disclosure["agreed_date"] - today).days
    yield f"Disclosure agreed for {disclosure['agreed_date']} ({days_left} days); publish advisory and CVE record together"
    if disclosure.get("cve_disclosed"):
        deadline = disclosure["cve_disclosed"] + timedelta(hours=72)
        yield f"CVE record must be published by {deadline} (CNA rules 4.2.0, 72 hours)"
    if case["exploitation"]["evidence"] != "none found":
        yield "EXPLOITATION evidence: open an incident and check CRA Article 14 reporting today"


if __name__ == "__main__":
    case = yaml.safe_load(open(sys.argv[1], encoding="utf-8"))
    for line in check(case, date.fromisoformat(sys.argv[2])):
        print(line)
```

## What does a short advisory look like?

A human-readable advisory can be short if it is precise. The skeleton gives the identifiers and vector at the top, a version table per branch including the unsupported one, what to do, what is known about exploitation and over which period, credit and a revision history. Generate it from the case record with the CSAF and CVE data so the three never disagree.

```markdown
# KSA-2026-031: Cross-tenant invoice export download

Published: <YYYY-MM-DD>   Updated: <YYYY-MM-DD>   CVE: <CVE ID>   CWE: CWE-639
Severity: CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

## Summary
An authenticated member of one tenant could download another tenant's invoice exports by changing the export ID.

## Affected and fixed versions
| Branch | Affected | Fixed in | Notes |
| --- | --- | --- | --- |
| 3.3 | 3.3.0 | 3.3.1 | Hosted service updated on <date> |
| 3.2 | 3.2.0 to 3.2.3 | 3.2.4 | |
| 3.1 | all | no fix | End of life since 2026-06-30; upgrade to 3.2.4 or 3.3.1 |

## What to do
Upgrade to a fixed version. If you cannot upgrade, <workaround and its limits>.

## Exploitation
<What is known, and the period your logs cover. Do not claim "no data was accessed" unless the logs prove it.>

## Credit
<Reporter name, as agreed.>

## Revision history
| Date | Change |
| --- | --- |
| <YYYY-MM-DD> | Initial publication |
```

## When can you close the case?

Closure has several independent conditions: supported fixes are available, hosted changes are deployed, the regression passes, advisory details match the releases and the reporter has received the promised update. If an unsupported branch remains affected, say so. If log retention prevents ruling out earlier access, preserve that limitation. A fixed main branch alone does not close a report affecting customers who run an older supported branch.

Afterward, review the process failures that mattered. An unread mailbox suggests ownership and alerting gaps; repeated object-authorization reports suggest a design or regression-suite problem. Measure time to acknowledgement, reproduction, containment and verified remediation separately so a fast automated reply does not conceal a slow response. Retain a minimal private evidence record under an appropriate retention policy, and remove unnecessary personal data from public issues, screenshots and reproducer files.

- Fixed versions exist for every supported branch and pass the reporter's case and the neighboring paths.
- Hosted deployments run the fixed build, and long-lived tokens or signed URLs issued before the fix have expired or been revoked.
- The advisory and CVE record are published with the same version ranges, and unsupported affected branches are named.
- The reporter has received the promised updates and the agreed credit.
- Exploitation questions are answered as far as the logs allow, with the log window stated.

## Which regulatory clocks run alongside disclosure?

Coordinated disclosure timing is negotiated; some reporting duties are not. Under Article 14 of the EU Cyber Resilience Act, from 11 September 2026 a manufacturer of a product with digital elements must report an actively exploited vulnerability, and any severe incident affecting the product's security, through ENISA's single reporting platform to the CSIRT of its main establishment. The early warning is due within 24 hours of becoming aware, the notification within 72 hours, and the final report within 14 days after a corrective measure is available, or one month after the notification for a severe incident. The clock starts at awareness of exploitation, not at a fix, so the case record must capture when the team first had credible evidence.

Other regimes, such as sector rules or contracts, can add their own deadlines. Record each applicable clock in the case with an owner, so the people who track product-security obligations and the engineers fixing the issue work from the same timeline. If exploitation is confirmed, run the case as an incident too, as described in /domains/appsec/vulnerability-response/product-incident-response/.

## What do you do when the reporter disagrees?

A researcher and maintainer may disagree about severity, affected scope or publication timing. Reduce the disagreement to specific claims. If the researcher says all tenants are exposed but the supplied case proves only one legacy export path, reproduce that path and inspect related endpoints before broadening the claim. If the maintainer believes authentication makes the issue low risk, identify what an ordinary tenant member can actually obtain. Evidence about prerequisites and consequence is more useful than arguing from a vulnerability label.

Keep communication dates separate from promises of a fix. A team can commit to a status update on Day 3 even when validation is incomplete. The update should describe what was reproduced, what remains uncertain, the next action and the next date. Silent delays leave the reporter unable to distinguish active work from neglect. Conversely, publishing a fixed version number before that artifact is available and tested gives users an action they cannot reliably take.

Before publication, compare the advisory against release artifacts and supported branches one final time. Check that an upgrade from 3.2.3 to 3.2.4 actually removes both download and preview exposure, and that any mitigation describes its limitations. If the hosted system is fixed but self-hosted customers need an update, say so explicitly. The advisory is an engineering instruction to affected users; accuracy about which action changes their risk matters more than a polished account of the team's response.

## Guided practice

Coordinate disclosure of a fictional cross-tenant export flaw: decide the disclosure date, the identifiers and statuses, the reporter update and whether regulatory reporting applies. The report, releases and dates are supplied; no messages are sent.

### Supplied material

```text
Day 0 (2026-10-01): a researcher reports that a member of tenant red can download tenant blue's invoice export with GET /exports/blue-9; synthetic response shows blue total 90.
Branches: 3.3 and 3.2 supported; 3.1 reached end of life on 2026-06-30 and some customers still run it. Hosted service runs 3.3.
Day 4: fixes 3.2.4 and 3.3.1 pass the reporter's case and the preview and bulk paths; hosted service updated to 3.3.1 the same day.
Day 5: a partner that embeds 3.2 in its own appliance asks for 10 days to ship an update. The researcher plans a blog post on Day 7.
Your company is a CNA. The product is sold in the EU. Logs cover only the last 48 hours; no exploitation found in them.
Promises so far: updates on Day 3 (sent) and Day 7.
```

### 1. State the finding precisely

Write one sentence with actor, action, prerequisite and impact.

Worked answer: An authenticated member of one tenant can request another tenant's export ID and receive its invoice data, because the export download path does not check the tenant (CWE-639).

### 2. Agree the disclosure date

Balance the researcher's Day 7 plan and the partner's request.

Worked answer: Ask the researcher to move to about Day 15 so the partner can ship, explaining that fixes are already available for direct customers; offer to notify the partner's customers in the advisory. If the researcher declines, publish on Day 7 with the fixes and tell the partner now; do not let the date slip silently.

### 3. Set identifiers and statuses

Give the CVE, CWE, CVSS and the status of each branch for the advisory and CSAF.

Worked answer: Assign a CVE from your own CNA with CWE-639 and a CVSS 4.0 vector. 3.3.0 and 3.2.0 to 3.2.3 are `known_affected` with `vendor_fix` to 3.3.1 or 3.2.4; 3.3.1 and 3.2.4 are `fixed`; 3.1 is `known_affected` with a `no_fix_planned` remediation that tells customers to upgrade.

### 4. Write the Day 7 update

What can the update to the researcher say?

Worked answer: Fixed versions and the hosted fix date, the partner's request and the proposed date, the CVE ID once disclosed, and the credit wording to confirm. State that logs cover only 48 hours, so the team cannot say whether earlier exploitation happened.

### 5. Decide on CRA reporting

Does the EU CRA require a report now?

Worked answer: Not on current evidence: Article 14 reporting is triggered by an actively exploited vulnerability or a severe incident, and none has been found. Record the limited log window and a trigger: any evidence of exploitation starts the 24-hour early-warning clock.

Deliverable: A case record with branch statuses and identifiers, an agreed or fallback disclosure date with reasons, the Day 7 update text and the CRA decision.

Scope: Paper exercise: the report, releases, partner and dates are fictional. Real disclosure dates are negotiated per case, and legal decides whether regulatory reporting applies.

Articles and teaching material: CC BY 4.0. Original executable code: MIT. Attribution: Software Security.

## Sources

- [OWASP Vulnerability Disclosure](https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html)
- [GitHub: coordinated disclosure of vulnerabilities](https://docs.github.com/en/code-security/concepts/vulnerability-reporting-and-management/coordinated-disclosure)
- [RFC 9116: a file format to aid in security vulnerability disclosure](https://www.rfc-editor.org/rfc/rfc9116.html). April 2022.
- [GitHub: configuring private vulnerability reporting for a repository](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/configure-vulnerability-reporting/configure-for-a-repository)
- [CVE Program: CVE Numbering Authorities](https://www.cve.org/ProgramOrganization/CNAs)
- [OASIS CSAF Version 2.0](https://docs.oasis-open.org/csaf/csaf/v2.0/csaf-v2.0.html)
- [European Commission: CRA reporting obligations](https://digital-strategy.ec.europa.eu/en/policies/cra-reporting)
- [Google Project Zero: vulnerability disclosure policy](https://projectzero.google/vulnerability-disclosure-policy.html)