Safeguard
Product•

When Upstream Won't Fix It: Fork and Patch, Explained Honestly

When upstream has no fix, Safeguard tries upstream first, then forks, patches and verifies the package so you can ship a fixed build and return later.

Safeguard Research Team
37 min read

When a vulnerable open source package has no fixed release, you have three honest options: wait, work around it, or run a patched copy. Fork and patch is Safeguard's managed version of the third. Safeguard first tries to get the fix accepted upstream; if the issue is severe or upstream will not move, it forks the package, fixes the vulnerability, verifies the build and keeps it monitored until you can return to an official release.

Fork and patch is generally available in OSM, Safeguard's Open Source Manager. This post explains when it is the right tool, exactly what happens to a component from request to delivery, how a patched build should appear in your SBOMs and VEX documents, how policies treat it, and how to move back to upstream cleanly once the maintainer ships a fix. If you are evaluating it, the buyer FAQ near the end covers the questions procurement and security reviewers usually ask.

Key takeaways

  • A missing fix is not one situation. "Fix coming next week", "fix stuck in review", "maintainer gone" and "maintainer says won't fix" call for different responses, and only some of them justify a fork.
  • Safeguard's order is fixed: patch upstream first, fork only when the issue is severe or upstream will not move. A fork is a route to a fixed component, not a shortcut around review.
  • A patched build is a new component. It needs its own name or namespace, a version you can tell apart from upstream, and a record of what was changed and why.
  • Semantic versioning works against you if you are careless: a pre-release suffix sorts below the release it patches, and build metadata is ignored when tools compare versions. Pin the patched build explicitly instead of relying on version ordering.
  • SBOMs and VEX can describe a fork precisely. CycloneDX has a pedigree structure with ancestors and typed patches, and OpenVEX, CycloneDX and CSAF can all state that a product is fixed or not affected because of a patch.
  • Policies and gates should treat the fork as a time-boxed exception with an owner and an expiry date, not as a permanent state.
  • The exit plan matters as much as the fork. When upstream ships a fix, move back, retire the fork and update your VEX statements.

When upstream has no fix, or will not ship one

Most vulnerability work assumes a simple shape: an advisory is published, a fixed version exists, you upgrade. A large share of real backlogs does not look like that. The advisory lists affected versions and nothing under "fixed". The package page shows a last release from years ago. The issue tracker has a pull request with the fix, three approving comments and no merge. Or the maintainer has replied that the behaviour is intended and will not change.

Machine-readable advisory formats already recognise these cases. The OASIS Common Security Advisory Framework (CSAF) 2.0 defines remediation categories that include vendor_fix, none_available and no_fix_planned. In the CSAF text, none_available means there is currently no fix or other remediation available, while no_fix_planned means there is no fix and none is planned "at any time", which the standard notes is often the case when a product has been orphaned, declared end of life or otherwise deprecated. Those two categories are the formal names for the problem this post is about.

The four shapes of "no fix"

SituationWhat you usually seeTypical right response
Fix in progressMaintainer acknowledges the report, a release is plannedMitigate, watch for the release, upgrade when it lands
Fix stuckA patch exists in a branch or pull request but has not been releasedHelp get it merged and released; consider a fork if the issue is severe
Maintainer absentNo releases or responses for a long period; advisory marked with no fixPlan a replacement or a maintained fork
Won't fixMaintainer disputes the report or declines to change behaviourDecide whether the risk applies to you; mitigate, fork or replace

The first row rarely needs a fork. The second and third are where fork and patch earns its keep. The fourth needs judgment: sometimes the maintainer is right that the behaviour is not a vulnerability in their threat model, and the correct answer is a VEX statement explaining why your product is not affected, not a code change.

Why "just wait" is a decision, not a default

NIST's Secure Software Development Framework (SP 800-218, published February 2022) states the goal plainly: practices that help producers "reduce the number of vulnerabilities in released software" and mitigate the impact of vulnerabilities that remain unaddressed. An advisory with no fix is exactly the unaddressed case, and you still need a documented answer for it.

Waiting is sometimes correct. If the vulnerable code is not reachable from your application, or the affected feature is switched off in your configuration, a documented "not affected" decision is cheaper and more honest than a patch. What goes wrong is waiting by default, with no record of the decision, no owner and no review date. That is how a "temporary" exposure becomes a finding that survives three audits.

A useful test: if you cannot write one sentence explaining why the vulnerable code cannot be reached or exploited in your product, you should not be waiting without a mitigation. Reachability analysis helps answer that question (Safeguard ranks findings by whether your code actually calls the vulnerable path), but the effect on any given backlog depends on the codebase and should be measured during an evaluation rather than assumed.

Why "fork it ourselves" goes wrong so often

Engineering teams know how to fork a repository and apply a patch. The difficulty is everything after the first commit. A self-maintained fork needs someone to rebase it on new upstream releases, rerun the tests, rebuild and republish artifacts, watch for new advisories against the forked code, keep the SBOM honest about what is really shipping, and eventually retire the fork. When the engineer who made it changes teams, the fork becomes an unowned component with your organisation's name on it. That ongoing ownership is the real cost, and it is the part a managed fork is meant to take off your plate.

Patch upstream first, fork second

Safeguard's process for vulnerable open source packages starts from your inventory and follows a fixed order.

  1. Inventory. OSM holds your open source package inventory and identifies packages that could be replaced with hardened Safeguard equivalents. OSM is integrated with ESSCM, Safeguard's Enterprise Software Supply Chain Manager, which matches vulnerabilities against the components in your SBOMs, so the inventory reflects what your builds actually contain.
  2. Patch first. When a package carries vulnerabilities, Safeguard attempts to get the fix into the upstream project.
  3. Fork when necessary. If the issue is severe or upstream will not move, Safeguard builds a fork and fixes the vulnerability there.
  4. Hold it to the Gold bar. The resulting package carries no critical or high vulnerabilities and has a completed license check.

The order matters for you as well as for the open source project. An upstream fix is the best outcome for everyone: you get an official release, every other user gets the fix, and nobody has to maintain a divergent copy. A fork is the fallback for when that outcome is not available on a timeline your risk can tolerate.

There is also a practical reason. Every fork drifts. The smaller the gap between the fork and upstream, the easier it is to verify, the easier it is to explain to an auditor and the easier it is to leave later. Starting with an upstream contribution keeps the patch small and reviewable, because it was written to be accepted by the original maintainers.

How Safeguard forks, patches and verifies a component

Here is the life of one component through fork and patch, from the moment someone on your team asks for it.

Step 1: the request

In Safeguard, requests start from Open Source Manager. Under Open Source Manager › Inventory, each vulnerable package row has a Request Gold action. Selecting it submits the request, and the row then shows Gold Requested. If you want to handle the whole backlog at once, Source Gold above the table lets you review every vulnerable package and the risks listed, then submit one request covering them. Request Premium Gold works the same way for Premium Gold versions. Each request reaches the Safeguard team as a task to source a Gold version for that package.

These actions appear when your workspace includes them. If you do not see them, ask your Safeguard administrator.

A good request carries context that makes the fork smaller and faster to verify:

  • The exact package, ecosystem and version range you run, ideally as a package URL (purl), the identifier format standardised as ECMA-427.
  • Which advisory or advisories you need resolved.
  • Whether the package is a direct or transitive dependency, and which of your products ship it.
  • Any constraints, such as a runtime version you must stay compatible with.

Step 2: try upstream

Before any fork, Safeguard works to get the fix accepted upstream. In practice that means checking whether a fix already exists somewhere (an unreleased commit, an open pull request, a patch carried by a downstream distribution) and helping it land in an official release. When that works, there is no fork at all: you upgrade to the official fixed version, and if autonomous remediation is switched on for dependencies, Griffin, Safeguard's security model, can open the upgrade pull request for you.

Step 3: fork and write the smallest correct patch

If upstream is not going to move in time, Safeguard forks the package and fixes the vulnerability in the fork. The engineering goal is the smallest change that removes the vulnerable behaviour without changing the package's public interface. The CycloneDX specification gives useful vocabulary for the kinds of change involved. Its pedigree model defines patch types:

CycloneDX patch typeMeaning in the specificationWhere it shows up in fork and patch
backportCode from a newer version applied to an older version of the same softwareUpstream fixed the issue in a new major line you cannot adopt yet
cherry-pickSelected commits from other versions or branches of the same softwareA fix exists on a branch or in a pull request but was never released
unofficialA patch not developed by the creators or maintainers of the softwareUpstream has no fix, so the fix is written fresh
monkeyA patch that changes runtime behaviour dynamicallyGenerally avoided for a maintained fork; a source change is easier to verify

Knowing which kind of patch you are carrying changes how you review it. A backport or cherry-pick of upstream's own commit is a known change written by the maintainers. An unofficial patch is new code and deserves closer review and more tests around the vulnerable function.

Step 4: build and verify

A patched source tree is only a fixed component once it passes checks. Verification covers three questions.

Does the fix work? The vulnerable behaviour should be gone, ideally proven by a regression test that exercises the specific weakness, written defensively (asserting the safe behaviour) rather than as an exploit.

Did anything else change? The fork should pass the upstream project's own test suite and keep the same public interface. Custom Gold requests are described in Safeguard's offering as producing a hardened, compatibility tested build, which is the point: a fixed package that breaks your build is not a fix you can ship.

Does it meet the Gold bar? The resulting package must carry no critical or high vulnerabilities and pass a license check. That second check is easy to forget. A fork is a redistribution of someone else's code, and the license obligations (notices, attribution, source availability for some licenses) carry over to the fork.

Step 5: keep it monitored

A fork is not finished when it ships. New advisories can land against the forked code, and new upstream releases can make the fork unnecessary. Gold artifacts are held to the Gold bar continuously, not once, and an artifact loses its Gold badge if it regresses. For you, that means the fork stays inside the same monitoring loop as every other dependency rather than becoming a static copy that quietly ages.

The whole flow at a glance

StageWhat happensWhat you see
RequestRequest Gold or Source Gold in OSM InventoryRow shows Gold Requested
Upstream attemptSafeguard works to get the fix accepted upstreamIf it lands: an official fixed version to upgrade to
ForkSafeguard forks and writes the smallest fixA hardened build of the package
VerifyFix tested, compatibility checked, Gold bar appliedNo critical or high vulnerabilities, license check complete
DeliverHardened equivalent resolves from SafeguardYour build pulls the patched component
MonitorHeld to the bar continuouslyBadge lost if it regresses; upstream watched for an official fix
ExitMove back to the official release when one existsUpgrade pull request, fork retired

How the patched build is delivered and tracked

Delivery: point your package directory at Safeguard

The delivery model is designed so developers do not change how they install packages. A customer can point their own package directory at Safeguard so hardened equivalents resolve for their own usage. Your build tooling, lockfiles and review process stay the same; the source of that one component changes.

That design choice has two consequences worth planning for:

  1. Your lockfile will change. The resolved URL and integrity hash for the component will differ from upstream's, because it is a different artifact. Review that diff like any other dependency change, and expect your SBOM to show the new source.
  2. Your mirror or proxy rules matter. If your organisation already routes package installs through an internal repository manager, decide where the hardened component sits in the resolution order. The patched build must win over the upstream artifact for the affected versions, and only for those.

Naming and versioning: avoid the semantic versioning trap

Semantic Versioning 2.0.0 is the convention most package ecosystems lean on, and two of its rules catch teams that build their own patched versions.

First, a pre-release version "indicates that the version is unstable", and the specification states that "a pre-release version has lower precedence than a normal version". A build you label 1.4.2-patched.1 therefore sorts lower than the vulnerable 1.4.2. A range like ^1.4.2 will not pick it, and a tool asked for "the newest 1.4.x" will prefer the vulnerable release.

Second, the specification says "Build metadata MUST be ignored when determining version precedence." A build labelled 1.4.2+patched.1 has the same precedence as 1.4.2, so tools comparing versions cannot tell which one is newer.

Python has its own convention. The Python packaging version specification defines local version identifiers: a public version followed by a plus sign and a local label. The specification says they should not be used when publishing upstream projects to a public index, may identify private builds made from the project source, and should be used by downstream projects releasing a version that is API compatible with the upstream release. That is a close description of a patched fork used inside one organisation.

The lesson for any fork, managed or not: do not rely on version ordering to select the patched build. Give it a distinct identity (a different namespace, scope or group) or pin it exactly, and make your SBOM say what it is.

Tracking: one inventory, one set of findings

Once a hardened component is in use, you want three things to stay true:

  • The inventory shows the real component. Because ESSCM scans flow into OSM, the hardened package appears in the same inventory as everything else, attached to the projects that use it.
  • Findings reflect the fix. The advisory that triggered the request should stop appearing as an open, exploitable finding for projects running the patched build, and any new advisory against the forked code should appear like any other finding.
  • The fork has a visible end. The question "is there an official fixed release yet?" should be asked continuously, not only when someone remembers. When the answer becomes yes, the path back to upstream is an ordinary upgrade.

Doing it by hand: how each ecosystem swaps in a patched build

Whether the patched build comes from Safeguard or from your own team, each package manager has an official mechanism for substituting one component for another. Knowing them helps you review what changes in your repository and helps you exit cleanly later. The examples below use a made-up package called example-parser and a made-up organisation scope; adapt names and URLs to your environment.

npm: overrides

The npm documentation describes overrides as the way to make changes to "dependencies of your dependencies", explicitly including "replacing an existing dependency with a fork". Overrides are only read from the root package.json.

{
  "name": "your-app",
  "dependencies": {
    "some-framework": "^3.2.0"
  },
  "overrides": {
    "example-parser": "npm:@your-org/example-parser@1.4.2-fix.1"
  }
}

Two rules from the npm documentation trip people up. You cannot set an override for a package you depend on directly unless the dependency and the override use exactly the same spec; for a direct dependency, change the dependency itself (or reference it with the $name form the docs describe). And because the patched build uses a distinct scoped name through the npm: alias, version ordering against the upstream package no longer matters. After installing, confirm what resolved:

npm install
npm ls example-parser

Go modules: the replace directive

The Go modules reference says a replace directive "replaces the contents of a specific version of a module, or all versions of a module, with contents found elsewhere". If a version appears on the left of the arrow, only that version is replaced; if the right side is a module path rather than a local directory, a version is required.

module example.com/your-app

go 1.22

require example.com/upstream/parser v1.4.2

replace example.com/upstream/parser v1.4.2 => example.com/your-org/parser v1.4.2-fix.1

Two details from the reference matter for forks. If the replacement module has a go.mod file, "its module directive must match the module path it replaces", so the fork keeps the original module path in its own go.mod even though it is hosted elsewhere. And replace directives only apply in the main module's go.mod and are ignored in other modules. A library you publish cannot force the replacement on its consumers, which is one reason a fork maintained at the source of distribution is easier for multi-team organisations than a replace line copied into every service.

go mod tidy
go list -m example.com/upstream/parser

Cargo: the patch section

The Cargo book documents the [patch] section for exactly this case: you have a fix for an upstream crate and want your application to use it "to avoid blocking on the bug fix getting merged".

[dependencies]
example-parser = "1.4.2"

[patch.crates-io]
example-parser = { git = "https://git.example.com/your-org/example-parser", tag = "v1.4.2-fix.1" }

The Cargo book warns that the version number of the patched source "is significant and will affect whether the patch is used": the patched crate has to satisfy the version requirement in your manifest. Keep the version in the fork's Cargo.toml aligned with the release you are replacing, and check the result afterwards:

cargo update -p example-parser
cargo tree -i example-parser

pip: constraints and an internal index

pip's user guide describes constraints files as requirements files that "only control which version of a requirement is installed, not whether it is installed or not". That makes them the right tool for pinning a transitive dependency to a patched build without adding it as a direct requirement.

# constraints.txt
example-parser==1.4.2+yourorg.1
pip install -r requirements.txt -c constraints.txt \
  --index-url https://pypi.internal.example.com/simple
pip show example-parser

The +yourorg.1 suffix is a local version label under the Python version specification. It keeps the patched build visibly separate from the public release, and it belongs on an internal index, not on a public one.

Maven: dependency management

Maven's documentation describes dependency management as the way for project authors "to directly specify the versions of artifacts to be used when they are encountered in transitive dependencies".

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.example</groupId>
      <artifactId>example-parser</artifactId>
      <version>1.4.2-yourorg-1</version>
    </dependency>
  </dependencies>
</dependencyManagement>
mvn dependency:tree -Dincludes=com.example:example-parser

What every ecosystem has in common

In every case the substitution is a line in a file your team owns. That is good for review, because the change is visible in a pull request. It is bad for longevity, because nothing in the line itself says why it is there or when it should go away. Whatever you use, put the advisory identifier and an expiry or review date in the commit message, and track the fork as a finding with an owner so it does not outlive its reason.

Fork and patch, SBOMs and VEX

A patched component changes what your software contains. Your SBOM and your vulnerability statements should say so precisely, because the people reading them (customers, auditors, your own incident responders) will otherwise see a known-vulnerable package name and a version that looks familiar.

Describing a fork in an SBOM

Safeguard generates and analyses SBOMs in both SPDX and CycloneDX formats. CycloneDX has structures built for this exact situation:

  • Pedigree. The CycloneDX 1.6 specification describes component pedigree as "a way to document complex supply chain scenarios where components are created, distributed, modified, redistributed, combined with other components". Its ancestors array "is commonly used to describe forks from existing projects where the forked version contains a ancestor node containing the original component it was forked from".
  • Patches. Within pedigree, each patch carries a type (unofficial, monkey, backport or cherry-pick), and can reference the issues it resolves.

A pedigree entry for a patched build might look like this:

{
  "type": "library",
  "name": "example-parser",
  "group": "your-org",
  "version": "1.4.2-fix.1",
  "purl": "pkg:npm/%40your-org/example-parser@1.4.2-fix.1",
  "pedigree": {
    "ancestors": [
      {
        "type": "library",
        "name": "example-parser",
        "version": "1.4.2",
        "purl": "pkg:npm/example-parser@1.4.2"
      }
    ],
    "patches": [
      {
        "type": "backport",
        "resolves": [
          { "type": "security", "id": "CVE-0000-00000" }
        ]
      }
    ]
  }
}

The identifier CVE-0000-00000 is a placeholder; use the real advisory identifier. The important parts are the distinct purl for the patched build, the ancestor pointing at the upstream version, and the typed patch tied to the advisory it resolves. Anyone reading the SBOM can see that the component is derived from a known vulnerable release and why it should not be treated as that release.

If your build produces SLSA provenance for your own artifacts, the SLSA v1.0 provenance format records resolvedDependencies, the artifacts fetched during the build. That gives you a second, independent record that the patched component, not the upstream one, went into the build.

Stating the result in VEX

A VEX (Vulnerability Exploitability eXchange) document states whether a product is affected by a vulnerability. CycloneDX describes the purpose as conveying "the exploitability of vulnerable components in the context of the product in which they're used", which is exactly the distinction a fork creates: the component name is familiar, but the product context has changed. CISA published Minimum Requirements for VEX on 21 April 2023, and three formats are in common use. Safeguard accepts all three for upload: CycloneDX VEX, OpenVEX and the CSAF VEX profile.

For a product that ships a patched fork, there are two honest ways to say it, depending on how you model the product:

  • The product version contains the fix. OpenVEX defines a fixed status meaning "these product versions contain a fix for the vulnerability". This is the cleanest statement when a specific release of your product moved to the patched component.
  • The product is not affected because the vulnerable code is gone. OpenVEX's not_affected status requires a justification or an impact statement. The OpenVEX specification's own example uses component_not_present with the impact statement "The vulnerable code was removed with a custom patch". The vulnerable_code_not_present justification fits when the component is present but the vulnerable code was excluded.

An OpenVEX statement for a release that ships the patched build:

{
  "@context": "https://openvex.dev/ns/v0.2.0",
  "@id": "https://example.com/vex/your-app-2026-001",
  "author": "Your Company Product Security",
  "timestamp": "2026-10-27T00:00:00Z",
  "version": 1,
  "statements": [
    {
      "vulnerability": { "name": "CVE-0000-00000" },
      "products": [
        {
          "@id": "pkg:generic/your-app@5.8.1",
          "subcomponents": [
            { "@id": "pkg:npm/%40your-org/example-parser@1.4.2-fix.1" }
          ]
        }
      ],
      "status": "fixed"
    }
  ]
}

In CSAF terms, the same product would appear in product_status under fixed, while the upstream package's own advisory may still carry none_available or no_fix_planned. Those statements are not in conflict: one describes the upstream product, the other describes your product with the patched component.

Two rules keep VEX honest:

  1. Write the statement about your product, not about the package. The fork fixes the package; whether your product is fixed depends on which of your releases include it.
  2. Revisit the statement when you leave the fork. When you move back to an official upstream release, the reason for the statement changes. Issue an updated document rather than leaving a statement that cites a patch you no longer ship.

Getting VEX into Safeguard

Safeguard's CSAF and VEX upload lives under Integrations, filtered by Upload: connect the Upload CSAF/VEX card, name the upload, add the file (it is validated on upload), then set the project name and version. Uploaded VEX can mark vulnerabilities as not affected for your products, record mitigation status and reduce false positives in vulnerability reports. That is how a fork-and-patch decision becomes visible in the same findings view your team and auditors already use.

Fork and patch and policies

Policies are where a fork either stays governed or quietly becomes permanent. In Safeguard, policies evaluate your SBOMs and generate findings, and gates are the checkpoints that enforce them, in CI and before release.

How policies see a patched component

Policy conditions in Safeguard include vulnerability severity, CVSS score, EPSS score, license, component age, and maintenance status. A few practical consequences:

  • A "no critical vulnerabilities" policy will fail on the upstream vulnerable version and should pass once the patched build is in place and the finding no longer applies.
  • A "no abandoned packages" policy may keep failing even after the patch, because the upstream project is still unmaintained. That is correct. The patch removes one vulnerability; it does not make the project maintained. Treat that remaining finding as your reminder to plan a long-term replacement.
  • A license policy will evaluate the fork's license the same way it evaluates the original, which is why the license check is part of the Gold bar.

Actions, gates and CI

Each policy has one action that decides what a failure does to the gate. Block fails the gate. Warn lets the gate pass with a warning that names the policy. Notify lets the gate pass and sends a notification to the workspace's configured channels. A gate with no linked policies shows Not evaluated, and an Error is never counted as a pass.

In CI, the documented command waits for the scan and the gate's policies, then fails the job if the gate fails:

safeguard ci scan --wait --fail-on-gate "release-gate"

It exits zero when the gate is Passed or Warning, and non-zero when the gate is Failed, Error or Not evaluated (including when policies were not evaluated before the timeout). Policy outcomes are switched on per workspace; if your workspace does not show an Outcome column on the Gates and Policies tables, ask your Safeguard contact.

Use exceptions with an expiry, not silence

Between the moment a vulnerability is found and the moment the patched build is in place, a blocking gate may stop releases you genuinely need to ship. Safeguard's policy exceptions exist for this. An exception request names the policy and the finding, gives a business justification and sets an expiration date, then goes to approvers, with an audit trail recorded either way.

A clean pattern for fork and patch:

  1. Open the exception when you submit the Gold request, with a short expiry and the request as the justification.
  2. Close the exception when the patched build lands and the finding clears.
  3. If the fork is still needed when the exception expires, renew it deliberately with a fresh justification. The renewal is your periodic review.

Moving back to upstream once a fix ships

A fork is a bridge. The goal is to stop needing it.

Signals that it is time

  • Upstream publishes a release whose advisory lists it as fixed (in CSAF terms, a vendor_fix remediation).
  • A maintained successor package becomes viable and you decide to migrate.
  • Your product drops the feature that needed the component.

The return path

  1. Confirm the official fix. Check the advisory's fixed versions against the release you plan to adopt. Gold Open Source shows affected and fixed versions for free, without a login.
  2. Compare behaviour. Upstream's fix may differ from the fork's. Run your tests against the official release, and pay attention to anything the fork changed that upstream changed differently.
  3. Remove the substitution. Delete the override, replace directive, patch section, constraint or dependency management entry, and let your lockfile resolve the official release.
  4. Let automation do the routine part. When a fixed version exists, autonomous remediation can open a tested, policy gated pull request that moves the dependency to it, on one repository or in bulk. It ships switched off, with every category set to Manual, so your team decides how much runs on its own.
  5. Update the paperwork. Regenerate the SBOM so the pedigree entry disappears, issue an updated VEX statement for the new product release, and close any policy exception tied to the fork.
  6. Retire the fork for your estate. Make sure no project still resolves the patched build. Your inventory should show the official release everywhere the fork used to be.

What a clean exit looks like

BeforeAfter
Override or replace line points at the patched buildLine removed; official release in the lockfile
SBOM shows pedigree with an ancestor and a patchSBOM shows the official component and version
VEX says fixed or not affected because of a patchVEX updated for the new release, citing the official fix
Policy exception open with an expiryException closed; policy passes on its own
Fork monitored as a separate componentFork no longer used by any project

Choosing between your options

OptionSpeed to a fixed buildOngoing ownershipAudit storyBest when
Wait for upstreamDepends entirely on the maintainerNoneWeak unless documented with VEXA release is imminent and the code is not reachable
Configuration workaroundFastLow, but easy to undo by accidentMedium; needs a VEX justificationThe vulnerable feature can be switched off
Self-maintained forkFast for the first patchHigh: rebase, rebuild, monitor, retireDepends on your own recordsYou have a dedicated owner and a short horizon
Safeguard fork and patchAs fast as the fix can be written and verified, without your engineers doing itMonitored against the Gold barHardened build held to a stated barUpstream is stuck or absent and the issue is serious
Replace the packageSlow; code changes requiredLow once doneStrongThe project is abandoned for good

These options combine. A common sequence is a configuration workaround on day one, a fork and patch for the duration, and a migration to a maintained alternative or an official release later.

Common mistakes

  • Forking before asking upstream. A fix that upstream would have merged becomes a fork you now carry. Always check for an open pull request or unreleased commit first.
  • Using a pre-release tag for the patched version. Under Semantic Versioning, 1.4.2-patched sorts below 1.4.2, so range resolution can pick the vulnerable release.
  • Relying on build metadata to tell versions apart. Semver ignores build metadata when it decides which version is newer. Use a distinct name or an exact pin.
  • Patching more than the vulnerability. Every extra change makes the fork harder to verify and harder to leave. Keep refactors and feature changes out.
  • Forgetting the license. A fork redistributes someone else's code. Keep notices and attribution, and make sure your license policy evaluates the fork.
  • Leaving the SBOM unchanged. If the SBOM still says example-parser@1.4.2 with the upstream purl, scanners and customers will flag a vulnerability you have fixed, and you will spend time explaining it.
  • Writing VEX about the package instead of the product. The statement needs to say which of your product versions contain the fix.
  • Silencing the policy instead of using an exception. Disabling a blocking policy hides every other finding it would have caught. An exception with an expiry hides only the one you have handled.
  • No exit plan. A fork without an owner and a review date is a future unmaintained dependency with your name on it.
  • Assuming the fork covers runtimes you do not build. A patched library does not fix the same code compiled into a vendor appliance or a hosted service.

FAQ

What is fork and patch?

Fork and patch is Safeguard's way of fixing a vulnerable open source package when upstream has no fix or will not ship one. Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, it forks the package, fixes the vulnerability, verifies the result against the Gold bar and keeps it monitored.

Is fork and patch generally available?

Yes. Fork and patch is generally available as part of OSM, Safeguard's Open Source Manager. Requests start from OSM Inventory with Request Gold for one package or Source Gold for every vulnerable package at once.

How is this different from patching the package ourselves?

The first patch is similar. The difference is everything after it: rebasing on new upstream releases, rebuilding, monitoring the forked code for new advisories, keeping license obligations, and retiring the fork. With fork and patch, the hardened build is held to the Gold bar continuously and loses its Gold status if it regresses.

Will developers have to change how they install packages?

No. You can point your package directory at Safeguard so hardened equivalents resolve for your usage while developers keep their usual install commands. Expect the lockfile entry for that component to change, because it is a different artifact.

How do we show auditors that a vulnerable package was patched?

Use your SBOM and VEX together. In CycloneDX, a pedigree entry with an ancestor (the upstream release) and a typed patch tied to the advisory shows what changed. A VEX statement in OpenVEX, CycloneDX or CSAF format then states that the relevant product versions are fixed, or not affected because the vulnerable code was removed. Safeguard accepts all three VEX formats for upload.

Does a fork make an abandoned package safe to keep?

It removes the specific vulnerability. It does not make the project maintained. Treat a fork of an abandoned package as a bridge to a maintained alternative, and expect a "no abandoned packages" policy to keep reminding you.

How long does a fork last?

Until you no longer need it. When upstream ships an official fix, move back: remove the substitution, upgrade (autonomous remediation can open the pull request if you have switched it on), regenerate the SBOM, update VEX and close any exception.

Which packages does fork and patch cover?

Requests are per package. Ask which packages are covered for your ecosystems before you plan a release around a specific fork.

Can fork and patch fix vulnerabilities in vendor products or hosted services?

No. It applies to open source components you build and ship. Code inside a vendor appliance, a hosted service or an operating system runtime you do not build still needs a fix from that vendor or distribution.

Fork and patch is one of several ways to shorten the time you spend exposed. The section below is Safeguard's standard summary of all of them, including where fork and patch sits next to automated upgrades and free vulnerability lookups.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

The first demo is a real Safeguard CLI session in a repository with a vulnerable dependency. You ask it to fix the lodash CVE, it reads the finding and the advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. The same flow is how you would make or remove an override line when a patched build arrives or when you move back to the official release.

Click through it at your own pace, or open it full screen.

The second demo sets up the Safeguard runner on your own machine, approves it with its device code, then scans from the terminal. Scanning locally after you swap in a patched build is a quick way to confirm which version resolved before you open a pull request.

Click through it at your own pace, or open it full screen.

The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, an assistant can look up a package's known vulnerabilities and fixed versions for you, which is a fast way to check whether upstream has shipped the fix that lets you retire a fork.

Click through it at your own pace, or open it full screen.

To try it on your own code, download Safeguard or start with the CLI documentation.

Quick check

Under Semantic Versioning 2.0.0, a pre-release version sorts below the normal version it is attached to, so 1.4.2-patched.1 is lower than 1.4.2. A range that accepts 1.4.2 or newer will prefer the vulnerable release. Give the patched build a distinct name or pin it exactly.

Remove the substitution and resolve the official release, then regenerate the SBOM so the pedigree entry for the fork disappears. Issue an updated VEX statement for the new product release that cites the official fix, and close any policy exception tied to the fork.

Sources

Last reviewed: 27 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.