diff --git a/proposals/github-private-vulnerability-reporting.md b/proposals/github-private-vulnerability-reporting.md new file mode 100644 index 00000000..4692b9c2 --- /dev/null +++ b/proposals/github-private-vulnerability-reporting.md @@ -0,0 +1,98 @@ +# Proposal: Switch Security Reporting to GitHub Private Vulnerability Reporting + +Author: [Vadim Bauer](https://github.com/Vad1mo) + +Discussion: [Harbor community meeting 8 July 2026](https://hackmd.io/CyQk5FdVQwWObMLVNqxW1w?both#July-8-2026) + +## Abstract + +Replace Harbor's email-based security intake (the CNCF groups.io list `cncf-harbor-security@lists.cncf.io`) with GitHub's private vulnerability reporting and repository security advisories, consolidating intake, triage, embargoed fix development, CVE assignment, and disclosure into one advisory workflow. Distributor coordination and disclosure timing are unchanged. + +## Background + +Today a reporter emails the security team with name, affiliation, repro, and PoC. The team replies within three business days, scores CVSS by hand, may file a manual MITRE CVE request, notifies `cncf-harbor-distributors-announce` under embargo, negotiates a disclosure date (~14 business days for critical issues), and finally publishes a GitHub advisory plus patches. Maintainer pain points: + +- **Manual, fragmented workflow.** Triage, scoring, CVE tracking (a separate out-of-band MITRE request), and disclosure are stitched across email, MITRE, and GitHub, with the report re-transcribed into an advisory only at the end. +- **Patches leak in the public repo.** The list has no code-hosting surface, so fixes get developed in the public repo (or ad-hoc private branches with no access barrier) where commits and diffs expose the bug before embargo lifts. +- **Email is a hurdle for reporters and maintainers.** + +## Proposal + +Harbor adopts **GitHub private vulnerability reporting** as the sole intake channel and **repository security advisories** as the triage/fix/CVE/disclosure workflow across the org's public repositories, replacing the mailing list. + +Flow: a reporter opens the repo **Security** tab and clicks **Report a vulnerability**; a maintainer accepts it as a draft advisory; the fix is co-developed with the reporter in a temporary private fork; a CVE is requested from GitHub in-band; the maintainer publishes the advisory. + +### Advantages over the mailing list + +- **In-repo intake, no address to find.** With private reporting enabled, a reporter files from the repo **Security** tab (only title and description mandatory; GitHub account required) ([report privately](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/report-privately)). +- **Structured form, not a blank email.** The form prompts for affected products (ecosystem, versions, vulnerable functions), CWE, description, and a severity dropdown with a built-in CVSS calculator. Caveat: fields are optional and there is no PoC field, so omissions are still possible ([report privately](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/report-privately)). +- **Report converts into the advisory, no re-transcription.** A submitted report creates a PROPOSED advisory; **Accept and open as draft** turns that same object into the draft worked on in-repo, accruing CVE, CVSS, and version data. Intake and final advisory are one artifact ([manage vulnerability reports](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/fix-reported-vulnerabilities/manage-vulnerability-reports)). +- **Reporter auto-added as a scoped collaborator.** Discussion, fix, and credit live on one advisory; comments are visible only to the reporter and collaborators, giving them a private thread to follow. Caveat: docs do not confirm per-state-change notifications ([report privately](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/report-privately)). +- **Patch built in a temporary private fork CI cannot see.** From the draft, a maintainer creates a fork (`repo-ghsa-xxxx-xxxx-xxxx`) where collaborators commit the fix and review PRs; integrations and CI cannot access it, so the patch stays private until disclosure. Caveat: fork creation, merge, and publish are distinct manual steps, not one "merge = ship" event ([temporary private fork](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/collaborating-in-a-temporary-private-fork-to-resolve-a-repository-security-vulnerability)). +- **Per-advisory access with auto-cleanup.** An advisory admin adds named users/teams to a single advisory only (no main-repo or cross-advisory access); collaborators are auto-removed when they leave the org. Caveat: write-only, no read-only tier ([adding a collaborator](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/adding-a-collaborator-to-a-repository-security-advisory)). +- **In-band CVE via GitHub as CNA.** A maintainer requests a CVE inside the advisory instead of filing a MITRE form; GitHub reviews (usually within 72h) and reserves a CVE bound to the record, published to MITRE only when the advisory publishes. Caveat: eligibility-gated, not one-click (GitHub cannot assign if another CNA covers the component) ([about repository security advisories](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/about-repository-security-advisories)). +- **Built-in CVSS + single publish gate.** The calculator (v3.1/v4.0) stores a structured severity field; the advisory stays a private draft until published, and publishing feeds the GitHub Advisory Database and Dependabot via structured version metadata ([create repository advisory](https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/fix-reported-vulnerabilities/create-repository-advisory)). Caveat: Advisory Database inclusion is reviewed (up to 72h) and Dependabot reaches only consumers of affected Harbor **Go modules**, not container/OCI distribution. +- **Formal credit + queryable audit trail.** Structured Credits (nine roles) with reporter accept/decline, carried into the published GHSA; the org audit log records `repository_advisory`/`advisory_credit` actions, timestamped, actor-attributed, exportable ([audit log events](https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/audit-log-events-for-your-organization)). +- **Org-level rollout.** Org owners/security managers can enable private reporting across all public repos and default it for new ones ([configuring PVR for an organization](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/configuring-private-vulnerability-reporting-for-an-organization)). + +### High-level process changes + +- Enable private reporting org-wide via a security configuration, defaulted for new repos. +- Rewrite `SECURITY.md` to point at the **Security** tab / **Report a vulnerability** form; remove or redirect the mailing-list instructions. +- Define triage: who accepts a PROPOSED report into a draft, first-response SLA, advisory labels, and a close/decline policy for spam and invalid reports. +- Map the security team onto GitHub roles: private reports are visible to repo admins and security managers, so the team roster becomes a GitHub team with the **security manager** role, replacing list membership. +- Standardize CVSS scoring with the built-in calculator. +- Make the temporary private fork the mandatory embargoed-fix workflow; assign fork/merge admins. +- Scope collaborators per advisory (reporter + assigned maintainers only). +- Request CVEs in-band via GitHub as CNA; document handling when another CNA already covers a component. +- Keep distributor coordination: notify `cncf-harbor-distributors-announce` under embargo from the draft advisory before publish. +- Publish the advisory after the fork merges and the negotiated date, keeping the ~14-business-day critical target. +- Decommission and redirect the mailing list; drain in-flight reports through the new workflow. + +## Non-Goals + +- The distributor embargo list (`cncf-harbor-distributors-announce`) stays as the pre-disclosure channel. +- Disclosure timing is unchanged; this changes tooling, not the timeline. +- Non-security bugs continue through normal GitHub issues. +- Maintainers still decide CVSS/CWE values; only the recording moves onto structured fields. +- GitHub is used as CNA only where eligible; no switch away from a component already covered by another CNA. + +## Rationale + +GitHub-centric wins because intake, embargoed discussion, fix, CVE, score, and published advisory become one artifact instead of manual handoffs across inbox, MITRE, and GitHub. + +Nothing forces Harbor to stay on the list. CNCF treats a `cncf.io` security list as optional infrastructure ("CNCF could help create a mailing address ... should projects need one") and leaves the reporting medium to the project — "Email, Web form etc." — naming GitHub's coordinated disclosure as an acceptable alternative ([CNCF security guidelines](https://contribute.cncf.io/maintainers/security/security-guidelines/)). CNCF projects already run intake through GitHub's **Report a vulnerability** form instead of a list — **Argo CD** ([advisories/new](https://github.com/argoproj/argo-cd/security/advisories/new)), **in-toto** ([advisories/new](https://github.com/in-toto/in-toto/security/advisories/new)), and **Backstage** — so the switch breaks no CNCF obligation and follows established precedent. + +Trade-offs: + +- **GitHub account required** to file (reporter becomes a collaborator); a documented fallback contact may be warranted. +- **GitHub dependency** on availability and CNA review turnaround (~72h). +- **Open intake invites noise.** Anyone with a GitHub account can file, so expect spam and low-quality reports the moderated list filtered by friction; triage needs an explicit close/decline policy. +- **No CI on the private fork cuts both ways:** the patch cannot leak, but it also cannot be tested by automation before merge; fixes need manual verification. +- **No read-only collaborator tier**, and **Dependabot does not cover OCI/container consumers**, so downstream notification of most Harbor users still relies on the distributor list and release notes. + +## Compatibility + +- **SECURITY.md** is updated to the new intake; documentation change only. +- **Distributor list**: the draft advisory becomes the source feeding the embargo notification. +- **In-flight reports** at cutover are handled to completion through the new workflow; the list stays read-only until they close. +- **Historical CVEs and existing advisories** remain valid; only new-report handling changes. + +## Implementation + +1. **Governance sign-off** (maintainers / CNCF liaison): agree to the switch and to decommissioning the list; name the org owners / security managers who own the config. +2. **Enable private reporting** (org owners / security managers): org-level security configuration across public repos, defaulted for new repos. +3. **Define the workflow** (security team): triage ownership, SLA, labels, per-advisory scoping, CVSS usage, private-fork process, in-band CVE request. +4. **Update docs** (maintainers): rewrite `SECURITY.md`; note the GitHub-account requirement and any fallback. +5. **Pilot** (security team): run one or two reports end-to-end (report → draft → fork → CVE → publish) before cutover. +6. **Cutover** (security team / list admins): announce the new channel, set the list read-only with a redirect, route all new reports through GitHub. +7. **Drain and decommission** (list admins): close in-flight list reports, then archive `cncf-harbor-security@lists.cncf.io`. + +## Open issues + +- **Fallback for reporters without a GitHub account** — keep a minimal contact, and if so what. +- **CNA eligibility overlap** — who files the CVE when another CNA already covers a component. +- **OCI/container downstream notification gap** — how consumers of Harbor images (not Go modules) are notified beyond the distributor list and release notes. +- **Cross-repo routing** — Harbor spans multiple repos (core, helm chart, operator, satellite); how reports filed against the wrong repo are transferred, and which repos get private reporting monitored. +- **Mailing-list archive retention** — whether and how long to keep the groups.io archive. +- **Reporter state-change visibility** — whether maintainers must manually keep reporters informed at triage/draft/publish.