Security advisories vs changelog: how to publish a security fix
You fixed a vulnerability. Now you have a writing problem: say too much on day one and you hand every attacker a recipe aimed at users who haven't upgraded yet; say nothing and you've shipped a silent patch that scanners, auditors, and burned users will eventually hold against you. The way out is not one carefully-worded paragraph — it's two documents on two different clocks: a changelog entry at ship time, and a security advisory when disclosure is responsible. This guide covers what each one is for, what goes in each, when a CVE is worth requesting, and the traps — starting with the most common one, which is publishing neither.
Two documents, two jobs
The changelog entry is an action signal. Its reader is a user deciding whether to upgrade, often skimming several releases at once. It needs to say: this release contains a security fix, here's roughly how bad, here's who's affected, upgrade now. It ships with the release, because a fix nobody hears about protects nobody.
The advisory is the full account. Its readers are security teams triaging exposure, scanners matching version ranges, and auditors reconstructing history. It carries what the changelog entry deliberately leaves out: the precise vulnerability class, exact affected-version ranges, severity scoring, workarounds for those who can't upgrade, discovery credit, and an identifier (CVE or similar) other systems can reference. It ships on the disclosure clock — which is usually later.
Trying to make one document do both jobs is how you get the two classic failures: the day-zero entry that doubles as an exploit tutorial, and the advisory published weeks later with no ship-day breadcrumb — so nobody upgraded in the window when it mattered.
The two clocks
A typical responsible-disclosure sequence looks like this:
- Report arrives privately. Acknowledge it, agree a disclosure window with the reporter. Fix in private — for open source that means not narrating the vulnerability in public commit messages and PR threads while users are exposed.
- Release day. The patched version ships with a changelog entry that names the severity and affected versions but not the mechanism. All supported branches get patched releases at the same time, not just the newest.
- Adoption window. Days to a few weeks, sized to how fast your users realistically upgrade. Hosted software: hours. A widely-pinned library: longer.
- Advisory day. Full advisory published, CVE public if you requested one — and the ship-day changelog entry is edited to link the advisory. Same URL, upgraded honesty. This is the changelog-side two-pass pattern: publish the safe version at ship time, complete it when disclosure is responsible.
The window is a judgment call with a real tension: every day it stays open, diligent users are patched and negligent attackers haven't been told where to look — but the diff is public, and a motivated attacker can often reconstruct the vulnerability from the patch itself. That's an argument for short windows, not for skipping the entry.
What the ship-day entry says
Four things, none of which help an attacker who hasn't already read your diff:
Security fix — upgrade recommended [tag: security] 2.4.8 fixes a high-severity vulnerability affecting all 2.x versions. If you run 2.x, upgrade now; 1.9 is not affected. No action needed beyond upgrading. We'll publish a full advisory with details and credit on <date>. This entry will be updated to link it.
That's the whole job: that it's a security fix, a severity word you can stand behind, who's affected (affected versions are safe to state — attackers can read version numbers; your users need them to know whether to act), the upgrade instruction, and a dated promise of details. What never goes in: the vulnerability class, the affected component's name if it narrows the search, reproduction hints, or the fix mechanism. “Fixes an issue where crafted zip filenames could overwrite files outside the extraction directory” is a working exploit description — save it for advisory day. More entry skeletons are in the release notes templates guide.
What the full advisory covers
- An identifier. A CVE ID, a GHSA ID, or at minimum a stable URL. Identifiers are what lets a scanner, a compliance sheet, and a Slack thread all point at the same fact.
- Exact affected versions. Ranges, not vibes: “>= 2.0.0, < 2.4.8” — including which backport releases fix which branches. This is the part machines consume, so precision matters most here.
- Severity, honestly. A CVSS score is the convention; treat it as a triage aid, not a verdict — a 9.8 in a component nobody enables matters less than a 6.5 in your default path. Say which.
- Impact in plain language. What an attacker could actually do: read other users' data, execute code, deny service. One sentence, no taxonomy required.
- Remediation and workarounds. The patched versions first; then mitigations for people who genuinely can't upgrade (disable the feature, filter the input) — clearly labeled as second-best.
- Credit and timeline. Name the reporter (with their permission) and the report-to-disclosure dates. Credit is the currency that keeps researchers reporting privately instead of tweeting.
Where it lives: for open source on GitHub, use
repository security
advisories — publishing one feeds the advisory database, which powers dependabot
alerts and OSV, meaning users you've never met get told by
their own tooling. For hosted products, a /security page on your own domain.
Either way, the changelog entry links the advisory and the advisory links the release
— each document is findable from the other.
Do you need a CVE?
A CVE is a globally-unique identifier for a publicly disclosed vulnerability, and the deciding question is: do strangers run old copies of your code?
For libraries, CLIs, and self-hosted software — yes, request one (GitHub's advisory flow can assign it, or your language ecosystem's CNA). Your announcement reaches whoever reads your changelog; the CVE reaches everyone else, because it's what vulnerability scanners and dependency-audit tools key on. For software people upgrade on purpose, the CVE is how the people who never read your notes find out they're exposed.
For pure SaaS — usually no. Nobody runs your old version; you patched everyone at deploy time. The security question for hosted products isn't disclosure of the vulnerability, it's whether user data was actually accessed — and if it was, you're in incident-notification territory: direct communication on legal timelines, a status-page and email matter, not an advisory. A changelog entry noting the fix is still right; a CVE for a bug only you could ever have run is ceremony.
Anti-patterns
- The silent patch. Fixing a vulnerability with no entry at all, ever. It rarely stays secret — patch-diffing is routine, and researchers publicize silently-fixed bugs precisely because vendors hid them. When it surfaces, every past release becomes suspect: what else was patched silently? One discovered silent patch costs more trust than ten honest advisories. If you fix it, log it — vaguely at first is fine; never is not.
- The day-zero recipe. Full technical detail in the ship-day notes, while your least-attentive users are weeks from upgrading. Precision is for advisory day.
- “Security improvements” forever. The vague entry is a loan against a dated promise. If no advisory or detail ever follows, readers learn your security entries mean nothing — and severity-blind vagueness means they can't tell “rotated an internal token” from “drop everything and upgrade.”
- Severity theater, both directions. Deflating a real vulnerability to “minor hardening” gets discovered exactly when it matters most — during someone's incident. Inflating routine fixes to “critical” burns the urgency you'll need for the real one.
- Advisory without a release. Publishing details before a patched version exists (or while only the newest branch is patched) turns your own advisory into a targeting package. Fix first — on every branch you've promised to support — then talk.
- Skipping the changelog because the advisory exists. Advisories live in a separate system most users never visit. The changelog is where your upgraders actually look — including inside dependabot PRs. Every security release gets an entry, even if it's one line linking the advisory.
Where Wakelog fits
The changelog half of this workflow is exactly what Wakelog does: entries are editable at stable permalinks, so the ship-day stub and the advisory-day update are the same URL — links shared in the panic window still point at the complete story. A security tag plus tag filtering gives auditors a one-click view of your security history. Scheduled publishing lets you write the advisory-day update the same afternoon you ship the fix and have it go live when the window closes. And RSS, JSON Feed, and Discord/Slack webhooks push the upgrade-now signal to subscribers within minutes of publish — which, on release day, is the entire point.
Start your free changelog Next: hotfix communication →
Related guides
- Hotfix communication: announcing emergency fixes without spreading panic
When something is on fire, users ask four questions. Answer them fast, in order, in one permanent place — and never fix it silently. - Firmware release notes: shipping updates to devices you don’t control
The reader of firmware notes is deciding whether to let a thing they depend on — a router, a thermostat, a lock — rewrite itself. No ctrl-Z, maybe no rollback. What to write for the homeowner and the fleet operator, and the trust rules that keep them pressing Update. - LTS and maintenance releases: release notes for versions you still support
The day you cut an LTS branch, your changelog becomes a matrix. Backport entries that name origin and siblings, the support table that answers “is my version still supported?”, security coverage across every branch at once, and version EOL announcements with dates that only ever move later.
Last updated 2026-07-27 · All guides