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).

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.
Applies to

Coordinating reported vulnerabilities. Disclosure timing and notification obligations require case-specific decisions.

Learning goals and prerequisitesAbout 32 minutes: 10 to read and 22 for the exercise

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.

Before you start

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

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

Refresh first: Respond to an exploited or malicious dependency

Go to the guided practice

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.

Configuration/.well-known/security.txtPlain 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.

cases/CVD-2026-031.yamlYAML
# 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.

disclosure_timeline.pyPython
"""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.

advisories/KSA-2026-031.mdMarkdown
# 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 scenario material
Plain 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. 1. State the finding precisely

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

    Expected result and reasoning

    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. 2. Agree the disclosure date

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

    Expected result and reasoning

    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. 3. Set identifiers and statuses

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

    Expected result and reasoning

    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. 4. Write the Day 7 update

    What can the update to the researcher say?

    Expected result and reasoning

    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. 5. Decide on CRA reporting

    Does the EU CRA require a report now?

    Expected result and reasoning

    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.

Your 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.

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

Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.

No draft saved on this device yet.

Sources & further reading

The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.

  1. OWASP Vulnerability Disclosure
  2. GitHub: coordinated disclosure of vulnerabilities
  3. RFC 9116: a file format to aid in security vulnerability disclosure. April 2022.
  4. GitHub: configuring private vulnerability reporting for a repository
  5. CVE Program: CVE Numbering Authorities
  6. OASIS CSAF Version 2.0
  7. European Commission: CRA reporting obligations
  8. Google Project Zero: vulnerability disclosure policy

Reference information

SDLC phase
Maintenance

Download this guide as Markdown