Skip to content

patch: add type and resolves DSL for annotating patches#22466

Open
andrew wants to merge 4 commits into
Homebrew:mainfrom
andrew:patch-fixes-cve
Open

patch: add type and resolves DSL for annotating patches#22466
andrew wants to merge 4 commits into
Homebrew:mainfrom
andrew:patch-fixes-cve

Conversation

@andrew

@andrew andrew commented May 30, 2026

Copy link
Copy Markdown
Contributor

Part 2 of https://github.com/orgs/Homebrew/discussions/6869, following on from #22459 which exposed patches in Formula#to_hash.

This adds two optional stanzas to the patch do block so formulae can declare what kind of patch is being applied and what it resolves:

patch do
  url "https://deb.debian.org/.../libquicktime_1.2.4-12.debian.tar.xz"
  sha256 "..."
  type :backport
  resolves "CVE-2016-2399", "CVE-2017-9122"
  apply "patches/CVE-2016-2399.patch", "patches/CVE-2017-9122_et_al.patch"
end

Both stanzas and the JSON output use CycloneDX pedigree.patches terminology so a future CycloneDX SBOM emitter can consume the data directly:

  • type is one of :unofficial, :monkey, :backport, :cherry_pick
  • resolves takes one or more CVE identifiers, GHSA identifiers, or issue URLs

Serialised in Formula#to_hash / the JSON API as:

"patches": [
  {
    "strip": "p1",
    "url": "https://...",
    "sha256": "...",
    "type": "backport",
    "resolves": [
      {"type": "security", "id": "CVE-2016-2399"},
      {"type": "security", "id": "CVE-2017-9122"}
    ]
  }
]

CVE/GHSA identifiers are emitted as "type": "security"; URLs are emitted as "type": "defect", so non-security build-fix patches can also be annotated:

patch do
  url "..."
  type :unofficial
  resolves "https://github.com/foo/bar/issues/123"
end

CVE identifiers are also inferred automatically from the patch url, apply paths and local file path, so formulae like glibc, unzip, zip and libquicktime that already apply Debian-style CVE-YYYY-NNNN.patch files get resolves data with no formula changes. With current homebrew-core that yields 22 CVEs for glibc/unzip alone. Explicit resolves declarations are merged with anything inferred.

The FormulaAudit/Patches cop validates that resolves arguments are CVE/GHSA identifiers or URLs (autocorrecting CVE case/hyphen variants) and that type is one of the permitted symbols.

The immediate consumer is brew vulns, which can use resolves to suppress false positives where the upstream version is affected but Homebrew's bottle is patched. Related: #22467.


  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same change?
  • Have you added an explanation of what your changes do and why you'd like us to include them? Performance claims (e.g. "this is faster") must include Hyperfine benchmarks.
  • Have you written new tests (excluding integration tests) for your changes? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) with your changes locally?

  • AI was used to generate or assist with generating this PR.

Written with Claude Code; I've reviewed the diff and run brew lgtm locally, and verified the inference output against glibc and unzip with HOMEBREW_NO_INSTALL_FROM_API=1 brew info --json=v2.


Copilot AI review requested due to automatic review settings May 30, 2026 08:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for tracking CVEs that patches “fix” across Homebrew’s patch model, serialization, and audit tooling.

Changes:

  • Introduces CVE extraction/normalization from patch URLs and paths, and merges these with explicitly declared fixes.
  • Serializes fixes into formula patch metadata for both external and local patches.
  • Adds RuboCop audit + autocorrect for patch do ... fixes ... end CVE identifiers, with new test coverage.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Library/Homebrew/rubocops/patches.rb Audits fixes args for canonical CVE formatting and non-string inputs; adds autocorrect.
Library/Homebrew/resource.rb Adds fixes DSL storage to patch resources.
Library/Homebrew/patch.rb Adds CVE extraction helper and wires fixes into local patch creation.
Library/Homebrew/local_patch.rb Stores explicit fixes and infers additional CVEs from local patch file path.
Library/Homebrew/external_patch.rb Merges explicit fixes with CVEs inferred from URL/apply paths.
Library/Homebrew/formula.rb Serializes inferred/explicit fixes into to_hash["patches"].
Library/Homebrew/test/rubocops/patches_spec.rb Adds RuboCop tests for valid/invalid/canonicalized fixes CVEs.
Library/Homebrew/test/patch_spec.rb Adds unit tests for CVE extraction and fixes merge/inference behavior.
Library/Homebrew/test/formula_spec.rb Adds serialization tests for explicit and inferred fixes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Library/Homebrew/formula.rb Outdated
Comment thread Library/Homebrew/local_patch.rb Outdated
@andrew andrew marked this pull request as draft May 30, 2026 09:16
@andrew andrew changed the title patch: add fixes DSL for declaring CVEs addressed by patches patch: add type and fixes DSL for annotating patches with CVEs May 30, 2026
@andrew andrew force-pushed the patch-fixes-cve branch from 2318b1b to 11015b3 Compare May 30, 2026 09:30
@andrew andrew changed the title patch: add type and fixes DSL for annotating patches with CVEs patch: add type and resolves DSL for annotating patches May 30, 2026
@andrew andrew requested a review from Copilot May 30, 2026 09:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Comment thread Library/Homebrew/rubocops/patches.rb Outdated
Comment thread Library/Homebrew/resource.rb Outdated
Comment thread Library/Homebrew/test/rubocops/patches_spec.rb Outdated
Comment thread Library/Homebrew/test/rubocops/patches_spec.rb Outdated
Comment thread Library/Homebrew/test/rubocops/patches_spec.rb Outdated
Comment thread Library/Homebrew/test/rubocops/patches_spec.rb Outdated
Comment thread Library/Homebrew/test/rubocops/patches_spec.rb Outdated
Comment thread Library/Homebrew/test/rubocops/patches_spec.rb Outdated
@andrew andrew force-pushed the patch-fixes-cve branch 2 times, most recently from 9fbf795 to 1530545 Compare May 30, 2026 10:06
@andrew andrew marked this pull request as ready for review May 30, 2026 10:53

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks good so far! Main omission I think is user-facing docs. Might also be nice to make a homebrew/core PR (which would be 🔴 until this is merged) so we can review what this might look like in action?

Comment thread Library/Homebrew/patch.rb Outdated
CVE_PATTERN = /CVE-?(\d{4})-(\d{4,})/i
GHSA_PATTERN = /\AGHSA(-[23456789cfghjmpqrvwx]{4}){3}\z/
# Keep in sync with `PATCH_TYPES` in `Library/Homebrew/rubocops/patches.rb`.
TYPES = T.let([:unofficial, :monkey, :backport, :cherry_pick].freeze, T::Array[Symbol])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could each of these get a description?

Also probably want to get a Formula Cookbook entry for these.

Also also: do you have a sense of how many of each you might have for backfilling so far?

@Bo98 Bo98 May 30, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the likes of :monkey even makes sense. A patch in Homebrew is a diff that gets applied on the source directory but a monkey patch is a patch which does not touch the source code.

When writing descriptions in the Cookbook, it would probably be worth including examples of how each type would be used using real-world samples of existing formula patches - there will probably be a lot of confusion in particular on how a backport and cherry_pick differs given a backport is often just a cherry pick.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 92a7bfa: TYPES is now a hash with a description per value, and :monkey is dropped since, as @Bo98 says, it can't apply to a source diff. Cookbook entry in b71d271 has real formula examples for each and a note on choosing between :backport and :cherry_pick.

Rough backfill shape from current core: ~935 patch do blocks in 655 formulae. ~394 formulae pull from upstream /commit/ URLs (:backport), ~214 use Homebrew-hosted patches (:unofficial), ~41 use Debian tarballs (mixed). :cherry_pick will be rare. 36 distinct CVE IDs already appear in core and the URL/apply inference picks most of them up with no formula edits.

@andrew

andrew commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

Added a Formula Cookbook section in b71d271. Will follow up with a homebrew/core PR using libquicktime and glibc as the demo.

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Will review homebrew-core PR and tiny docs change and this is good to go. Thanks @andrew!

Comment thread docs/Formula-Cookbook.md Outdated

When in doubt between `:backport` and `:cherry_pick`, prefer `:backport` for anything taken from the upstream development tip.

`:unofficial` is a patch not authored by the upstream maintainers, such as a Homebrew- or Debian-specific build fix. The widely-shared libtool/Big Sur configure fix is one example:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`:unofficial` is a patch not authored by the upstream maintainers, such as a Homebrew- or Debian-specific build fix. The widely-shared libtool/Big Sur configure fix is one example:
`:unofficial` is a patch not authored by the upstream maintainers, such as a Homebrew- or Debian-specific build fix. As-per the Homebrew patching guidelines, all `:unofficial` patches should have had an attempt to submit them upstream. The widely-shared libtool/Big Sur configure fix is one example:

or get an LLM to write something better with an actual link 😁

@Bo98 Bo98 May 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The libtool Big Sur example is an interesting one as in terms of libtool the patch is actually official - it is a backport of a bug fix from newer versions of libtool. It just gets applied to downstream sources because libtool is used in generating configure and then isn't a dependency anymore. The patch gets dropped whenever upstream uses a newer libtool when generating a future release tarball. Technically still can count as unofficial but bit of a special case when it comes to how you "upstream" it (basically: ask the dev to update their machine). In 2020, this was largely because latest libtool wouldn't have made it to the most used Ubuntus etc but in 2026 this is usually simply because there hasn't been a new release tarball generated in a few years.

Anyhow my point is: if we're adding the note about submitting patches upstream, a less confusing example might be unzip etc which use Debian patches. Or something similar that gets applied to hand-written (or AI-written these days) source code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1d459d5: swapped to unzip per @Bo98 (picked three non-CVE Debian patches so the :unofficial classification is clear-cut), added the upstream-submission note linking to the existing guidance in the Patches section above, and dropped the broken CycloneDX anchor that was failing html-proofer.

@andrew

andrew commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

Demo homebrew/core PR: Homebrew/homebrew-core#285534 (draft, will be red until this lands).

@MikeMcQuaid

Copy link
Copy Markdown
Member

Demo homebrew/core PR: Homebrew/homebrew-core#285534 (draft, will be red until this lands).

This looks good! Final comments here and then will merge.

@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@github-actions github-actions Bot added the stale No recent activity label Jun 22, 2026
andrew added 3 commits June 22, 2026 11:35
`:monkey` is CycloneDX's value for runtime modification, which a
source-level `patch do` block cannot express, so omit it from the DSL.
@andrew andrew force-pushed the patch-fixes-cve branch from b71d271 to 177c8f9 Compare June 22, 2026 15:39
…oneDX link

Swap the libtool/Big Sur example for unzip per review: the libtool fix is
technically a backport of upstream libtool applied to third-party tarballs,
which muddies the :unofficial definition. unzip's Debian-maintained patches
against a dormant upstream are a cleaner example.

Add a note that :unofficial patches should be reported upstream, linking to
the existing guidance in the Patches section.

Drop the #components_items_pedigree_patches fragment from the CycloneDX link;
the page is a JS-rendered schema viewer and html-proofer cannot find the
anchor in the static HTML.
@andrew andrew requested a review from MikeMcQuaid June 22, 2026 18:16
@github-actions github-actions Bot removed the stale No recent activity label Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants