patch: add type and resolves DSL for annotating patches#22466
Conversation
There was a problem hiding this comment.
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
fixesinto formula patch metadata for both external and local patches. - Adds RuboCop audit + autocorrect for
patch do ... fixes ... endCVE 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.
9fbf795 to
1530545
Compare
MikeMcQuaid
left a comment
There was a problem hiding this comment.
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?
| 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]) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
Added a Formula Cookbook section in b71d271. Will follow up with a homebrew/core PR using |
MikeMcQuaid
left a comment
There was a problem hiding this comment.
Looks good! Will review homebrew-core PR and tiny docs change and this is good to go. Thanks @andrew!
|
|
||
| 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: |
There was a problem hiding this comment.
| `: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 😁
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
|
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. |
|
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. |
`:monkey` is CycloneDX's value for runtime modification, which a source-level `patch do` block cannot express, so omit it from the DSL.
…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.
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 doblock so formulae can declare what kind of patch is being applied and what it resolves:Both stanzas and the JSON output use CycloneDX
pedigree.patchesterminology so a future CycloneDX SBOM emitter can consume the data directly:typeis one of:unofficial,:monkey,:backport,:cherry_pickresolvestakes one or more CVE identifiers, GHSA identifiers, or issue URLsSerialised in
Formula#to_hash/ the JSON API as:CVE/GHSA identifiers are emitted as
"type": "security"; URLs are emitted as"type": "defect", so non-security build-fix patches can also be annotated:CVE identifiers are also inferred automatically from the patch
url,applypaths and localfilepath, so formulae likeglibc,unzip,zipandlibquicktimethat already apply Debian-styleCVE-YYYY-NNNN.patchfiles getresolvesdata with no formula changes. With current homebrew-core that yields 22 CVEs forglibc/unzipalone. Explicitresolvesdeclarations are merged with anything inferred.The
FormulaAudit/Patchescop validates thatresolvesarguments are CVE/GHSA identifiers or URLs (autocorrecting CVE case/hyphen variants) and thattypeis one of the permitted symbols.The immediate consumer is
brew vulns, which can useresolvesto suppress false positives where the upstream version is affected but Homebrew's bottle is patched. Related: #22467.brew lgtm(style, typechecking and tests) with your changes locally?Written with Claude Code; I've reviewed the diff and run
brew lgtmlocally, and verified the inference output againstglibcandunzipwithHOMEBREW_NO_INSTALL_FROM_API=1 brew info --json=v2.