---
title: 'Responsible Disclosure'
source: 'https://academia.sh/en/courses/penetration-testing/responsible-disclosure'
course: 'Security Tooling and Penetration Testing'
language: en
updated: '2026-09-26T03:02:55+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measures the process from notification to publication in terms of stages and party count'
    - 'Compares the number of unfixed findings published under three publication policies'
    - "Gathers the candidate, evidence, and trace counts produced across the course's eighteen lessons into a single table"
---

# Responsible Disclosure

The time from notification to publication is measured across six stages: single-party findings close in an average of 12.7 days, four-party findings in 96.0 days, and the longest stage is remediation. Comparing three publication policies, the fixed 90-day window publishes 13 findings unfixed, the fix-linked window publishes none unfixed but pushes the latest publication to day 226, and the extendable window stays at 3. The lesson closes the course and gathers the candidate, evidence, and trace counts of eighteen lessons into a single table.

The previous lesson measured the report in terms of reproducibility, and it rested on a single assumption: where the report is going is known, a remediation team is there, and that team is expecting the finding. In a contracted assessment, this assumption holds. It falls away when the finding comes from outside — from an independent research effort, from an item spotted in a third-party component.

**Responsible disclosure** is the process that determines, in that case, who to notify, through which channel, within how much time, and when the finding can be published. This lesson measures it in terms of two numbers: **how many days** and **how many parties**. The measurement is a model, and all durations are fictional; no real organization, real component, or real vulnerability identifier is written.

- **TP31.** The finding set is the previous lesson's 61 records. Each finding falls into one of four notification classes, and the class determines the number of parties that enter the process.
- **TP32.** The process has six stages: finding the notification channel, acknowledgment, technical verification, remediation, deployment, and a verification pass. Durations are in days.
- **TP33.** Remediation time deviates from the class baseline by a factor of 0.6 to 1.6; for a portion of findings, remediation **gets stuck** and the duration quadruples.
- **TP34.** The **ready day** is the day the fix is deployed and verified. A fix being written and a fix being applied are two separate dates.
- **TP35.** Three publication policies are compared: a fixed publication 90 days after notification; publication 14 days after the fix is ready; and 90 days, with a one-time 30-day extension if remediation is still underway.
- **TP36.** If a finding is not ready on its publication day, it counts as **published unfixed**, and the number of days in between is the period it stays open after publication.

## From Notification to Publication

```js
// disclosure.mjs — responsible disclosure process: days and parties from notification to publication.
// THIS IS A MODEL; the finding set, durations, and parties are fictional.
export const SEED = 20260714, N = 61;
const makeRng = (t) => { let s = t >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
const r = makeRng(SEED);

// Stages: from finding the notification channel to publication. Durations are in days
// and vary by class; each class involves a different number of parties.
export const STAGE = ["channel discovery", "acknowledgment", "technical verification",
  "remediation", "deployment", "verification pass"];
const SHORT = ["channel", "ack", "review", "remediation", "deploy", "pass"];
export const CLASS = [
  { name: "org configuration", share: 0.34, parties: ["org"], days: [1, 1, 2, 3, 2, 2] },
  { name: "org software", share: 0.28, parties: ["org", "dev team"],
    days: [1, 2, 4, 14, 7, 3] },
  { name: "third-party component", share: 0.23,
    parties: ["org", "dev team", "component vendor"], days: [5, 7, 10, 45, 21, 5] },
  { name: "shared infrastructure", share: 0.15,
    parties: ["org", "dev team", "infrastructure operator", "regulator"],
    days: [3, 5, 8, 30, 30, 7] },
];
const pickClass = (u) => { let t = 0;
  for (const c of CLASS) { t += c.share; if (u < t) return c; } return CLASS.at(-1); };

// Each finding falls into a class; remediation time deviates from the class baseline,
// and gets stuck for a portion of findings.
export const FINDING = Array.from({ length: N }, (_, i) => {
  const c = pickClass(r()), multiplier = 0.6 + r(), stuck = r() < 0.12;
  const days = c.days.map((d, j) => (j === 3 ? Math.round(d * multiplier * (stuck ? 4 : 1)) : d));
  return { no: i + 1, class: c.name, parties: c.parties, days, stuck,
    ready: days.reduce((a, d) => a + d, 0) };
});

if (import.meta.url === `file://${process.argv[1]}`) {
  const gen = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
    : String(v).padEnd(B[0]))).join("");
  const yz = gen([22, 9, 9, 6, 8, 13, 9, 7, 10, 9]);
  console.log(`model (seed ${SEED}): ${N} findings, ${CLASS.length} notification classes`);
  console.log("\n" + yz(["finding class", "finding", ...SHORT, "avg days", "parties"]));
  for (const c of CLASS) {
    const l = FINDING.filter((f) => f.class === c.name);
    const avg = l.reduce((a, f) => a + f.ready, 0) / l.length;
    console.log(yz([c.name, l.length, ...c.days, avg.toFixed(1), c.parties.length]));
  }
  const parties = new Set(CLASS.flatMap((c) => c.parties));
  const longest = STAGE.map((a, j) => [a, CLASS.reduce((x, c) => x + c.days[j], 0)])
    .sort((a, b) => b[1] - a[1])[0];
  console.log(`\nthe four classes involve ${parties.size} distinct parties in total; ` +
    `the longest stage is "${longest[0]}" (${longest[1]} days total across the four classes)`);
  const deploy = CLASS.reduce((a, c) => a + c.days[4], 0);
  console.log(`once remediation is written, deployment alone takes ${deploy} days: ` +
    `a fix existing and a fix being applied are two separate dates`);
}
```

```
model (seed 20260714): 61 findings, 4 notification classes

finding class           finding  channel   ack  review  remediation   deploy   pass  avg days  parties
org configuration            26        1     1       2            3        2      2      12.7        1
org software                 15        1     2       4           14        7      3      40.3        2
third-party component        11        5     7      10           45       21      5     113.2        3
shared infrastructure         9        3     5       8           30       30      7      96.0        4

the four classes involve 5 distinct parties in total; the longest stage is "remediation" (92 days total across the four classes)
once remediation is written, deployment alone takes 60 days: a fix existing and a fix being applied are two separate dates
```

Read together, the last two columns do not move in perfect lockstep, but the ends are clear: the average duration grows from 12.7 days to 113.2 days — nearly ninefold — and only the four-party class, at 96.0 days, falls below the three-party class. A single-party configuration finding closes in 12.7 days; a three-party component finding closes in 113.2 days. The source is not remediation alone: channel discovery grows from 1 day to 5, acknowledgment from 1 day to 7, because each additional party adds its own notification channel, acknowledgment, and technical review.

The longest stage is remediation, taking 92 days across the four classes. But looking at remediation alone is misleading: **deployment alone takes 60 days.** A fix being written and a fix being applied are two separate dates, and the gap between them is still an open exposure. In the shared infrastructure class, deployment takes as long as remediation itself, because the change has to be applied not in one place but across every operated item.

The channel discovery stage is small, but it says something: whoever is making the notification first has to search for **who to notify**. An organization without a published notification address adds days to its own remediation time from the very start.

## Publication Window and Party Count

```js
// publication.mjs — publication policy: fixed window, fix-linked window, extendable window.
import { FINDING, CLASS } from "./disclosure.mjs";

const N = FINDING.length, WINDOW = 90, EXTENSION = 30, WAIT = 14;
// Each policy returns a publication day for a finding.
const POLICY = [
  ["fixed window", () => WINDOW],
  ["fix-linked", (f) => f.ready + WAIT],
  ["extendable window", (f) => (f.ready <= WINDOW ? WINDOW : WINDOW + EXTENSION)],
];
const gen = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");
const yz = gen([20, 11, 11, 13, 16, 20]);
console.log(`${N} findings; window ${WINDOW} days, extension ${EXTENSION} days, ` +
  `post-fix wait ${WAIT} days (fictional)`);
console.log(yz(["policy", "avg publish", "latest", "fixed", "unfixed",
  "open after publish"]));
for (const [name, f] of POLICY) {
  const days = FINDING.map(f), fixedBefore = FINDING.filter((k) => k.ready <= f(k)).length;
  console.log(yz([name, (days.reduce((a, d) => a + d, 0) / N).toFixed(1), Math.max(...days),
    fixedBefore, N - fixedBefore,
    (FINDING.reduce((a, k) => a + Math.max(0, k.ready - f(k)), 0) / N).toFixed(1)]));
}

// The relationship between party count and exceeding the window.
const y2 = gen([22, 8, 10, 14, 16, 14]);
console.log("\n" + y2(["finding class", "parties", "findings", "over 90 days", "over 120 days",
  "stuck"]));
for (const c of CLASS) {
  const l = FINDING.filter((k) => k.class === c.name);
  console.log(y2([c.name, c.parties.length, l.length, l.filter((k) => k.ready > WINDOW).length,
    l.filter((k) => k.ready > WINDOW + EXTENSION).length, l.filter((k) => k.stuck).length]));
}
const over = FINDING.filter((k) => k.ready > WINDOW);
const manyParties = FINDING.filter((k) => k.parties.length >= 3);
console.log(`\n${over.length} of ${N} findings exceed the ${WINDOW}-day window; ` +
  `${over.filter((k) => k.parties.length >= 3).length} of them come from classes ` +
  `involving three or more parties`);
console.log(`${(100 * manyParties.filter((k) => k.ready > WINDOW).length / manyParties.length)
  .toFixed(1)}% of the ${manyParties.length} findings with three or more parties exceed the ` +
  `window; ${FINDING.filter((k) => k.parties.length === 1 && k.ready > WINDOW).length} of the ` +
  `${FINDING.filter((k) => k.parties.length === 1).length} single-party findings exceed it`);
const stuck = FINDING.filter((k) => k.stuck);
console.log(`the ${stuck.length} findings whose remediation gets stuck wait an average of ` +
  `${(stuck.reduce((a, k) => a + k.ready + WAIT, 0) / stuck.length).toFixed(1)} days unpublished ` +
  `under the fix-linked policy; under the fixed window they publish on day ${WINDOW}`);
```

```
61 findings; window 90 days, extension 30 days, post-fix wait 14 days (fictional)
policy              avg publish     latest        fixed         unfixed  open after publish
fixed window               90.0         90           48              13                 5.9
fix-linked                 63.9        226           61               0                 0.0
extendable window          96.4        120           58               3                 3.1

finding class          parties  findings  over 90 days   over 120 days         stuck
org configuration            1        26             0               0             3
org software                 2        15             0               0             3
third-party component        3        11             9               2             2
shared infrastructure        4         9             4               1             1

13 of 61 findings exceed the 90-day window; 13 of them come from classes involving three or more parties
65.0% of the 20 findings with three or more parties exceed the window; 0 of the 26 single-party findings exceed it
the 9 findings whose remediation gets stuck wait an average of 106.9 days unpublished under the fix-linked policy; under the fixed window they publish on day 90
```

Three policies protect three different things, and none subsumes the other. The fixed window gives a firm date and holds the organization to a deadline, but it publishes 13 findings before they are fixed, and those stay both open and public for an average of 5.9 days. The fix-linked window never publishes a finding before it is fixed; its average publish day, 63.9, is the earliest, because most findings close quickly. Its cost is not in the last column, but in the "latest" column: **day 226.** When remediation gets stuck, publication can be deferred indefinitely, and the decision belongs to the party that received the notification.

The extendable window sits between the two: it brings the number published before being fixed down from 13 to 3, and the post-publication open period down from 5.9 days to 3.1 days, while keeping the latest publication at 120 days. What it gains is a firm upper bound; what it adds is the decision of when to grant the extension.

The second table shows who exceeds the window, and the result collapses onto a single axis: **all 13 findings that exceed 90 days come from classes involving three or more parties.** None of the 26 single-party findings exceed the window; 65.0 percent of the 20 findings with three or more parties do. What determines the duration is not the technical weight of the exposure, it is how many organizations have to line up on the same calendar. The 9 findings whose remediation gets stuck are spread across this table, and under the fix-linked policy they wait an average of 106.9 days before publication.

A practical rule follows from this: the window cannot be written as a single number. The party count is known on the first day of notification, and the window length can be tied to that count. For a single-party finding, 90 days is more than enough; for a four-party finding, the same 90 days means publishing before coordination is complete.

## Summary

- The time from notification to the ready day depends on the party count: single-party findings take an average of 12.7 days, two-party 40.3 days, three-party 113.2 days, four-party 96.0 days.
- The increase does not come from remediation alone; channel discovery grows from 1 day to 5, acknowledgment from 1 day to 7, because each party adds its own channel, acknowledgment, and review.
- Deployment takes a total of 60 days across the four classes: a fix being written and a fix being applied are two separate dates, and the gap between them is still an open exposure.
- The fixed 90-day window publishes 13 findings before they are fixed and leaves them open for an average of 5.9 days; the fix-linked window publishes none of them unfixed but pushes the latest publication to day 226.
- The extendable window brings the number published unfixed down to 3 and the remaining open period down to 3.1 days, while keeping the upper bound at 120 days.
- All 13 findings that exceed 90 days come from classes involving three or more parties; none of the 26 single-party findings exceed it. Window length should be tied to party count, not technical weight.

## Course Wrap-Up

| Lesson | Candidate | Turned into evidence | Trace left on defense |
|---|---|---|---|
| Open-Source Intelligence | 29 items, 20 candidates | 9 evidenced findings, 6 false positives | 12 access log entries, 0 alerts |
| Domain Name and Registration Lookup | 20 candidates, 4 criteria | 8 evidenced findings, 0 false positives | 26 log entries, 0 alerts |
| Subdomain and Asset Discovery | 24 zone records | 21 found, 7 dangling | 8.57 records per name, 5 alerts |
| Port Scanning | 96 points, 21 ambiguous | 18 evidenced findings, 3 false positives | 72–90 log entries, 2 alerts |
| Service and Version Detection | 21 services, 4 levels | 16 confirmed, 0 wrong version | 168 log entries, 6 alerts |
| Packet Capture and Analysis | 85 claim candidates | 50 evidenced findings, 28 inferences | 0 log entries, 1 configuration confirmation |
| Protocol Analyzers | 91 candidates, 30 fields | 43 evidenced findings, 48 inferences | 0 log entries, 1 alert per flow |
| Request Manipulation | 18 candidates, 40 requests | 8 evidenced findings, 6 eliminated | 35 log entries, 16 alerts |
| Sandbox Analysis | 14 items, 27 behaviors | 12 resolved, 1 wrong | 0 log entries, 5 external connections |
| File and Link Reputation Services | 24 items, 8 submittable | 86% precision, 50% recall at threshold 7 | 0 log entries, 14 service queries |
| Exploitation Frameworks | 16 candidates, 9 real | 3 evidenced findings, 3 evidence of absence | 31 log entries, 17 alerts |
| Test Distributions and Labs | 18 skill items | 13 covered, 5 open | 0 log entries, 0 real risk |
| Rules of Engagement | 64 targets, 11 borderline | 20 confirmed in scope | 0 log entries, 6 parties, 3 stop-work |
| Test Types | 78 / 63 / 60 candidates | 6 / 20 / 31 evidenced findings | 468 / 221 / 90 log entries |
| Vulnerability Validation | 240 raw candidates | 61 evidenced findings, 9 pending | 461 log entries, 313 chained |
| Impact Assessment | 61 findings, 2 contexts | 0 common rank in the top ten | 0 log entries, 40-person-day queue |
| Reporting | 61 records, 8 fields | 8 reproducible, 3 closed | 0 log entries, 9.4 hours writing |
| Responsible Disclosure | 61 findings, 4 classes | 48 fixed-before-publish | 0 log entries, 5 parties, 13 over window |

The table's second and third columns are never equal in any row, and this course's rule follows from that: **the output of a penetration test is not a count of findings, it is evidenced, reproducible findings; when an unevidenced candidate makes it into the report, it wastes the remediation budget.** Nine rows read "0 log entries" in the fourth column, and the two lessons that produce the most evidenced findings are among them: the step that leaves the most trace is not the step that produces the most evidence.

Across the eighteen lessons, the candidate-to-evidence gap ran in the same direction every time. Reconnaissance produced candidates in abundance and evidence sparingly; the analysis tools separated evidence from inference; validation cut the list to a quarter and wrote down its own error rate as well; the process lessons showed that evidence alone is not enough.

One column stayed one-sided throughout this entire measurement. How many log entries and how many alerts each step left on the defense side was counted every time: from 12 access log entries to 468, from zero alerts to 17. But **who** reads those log entries, **in how much time**, and **at what threshold** was never asked. Does an alert actually reach anyone, how quickly is it looked at, which log entries below which threshold are never seen at all, and is it written down who does what in a real incident? The **M24/K08 Security Operations and Incident Response** course begins with exactly this question.
