Safeguard
Developer Tools•

What does a go.mod replace to a fork change, and what does it hide?

A replace directive swaps a module's contents for your fork but applies only in the main module. Know what it fixes, what it misses and how to track it.

Safeguard Security Team
4 min read

A replace directive in go.mod swaps the contents of a module, or one version of it, for code from somewhere else, such as your fork. It applies only in the main module. Libraries that depend on you ignore it, so the swap can quietly differ between your build and your consumers' builds.

What the Go reference says

The Go modules reference describes a replace directive as replacing the contents of a specific version of a module, or all versions, with contents found elsewhere. The right side can be another module path with a version, or a local file path. A few rules matter for forks:

  1. If a version appears on the left of the arrow, only that version is replaced. If it is omitted, all versions are replaced.
  2. With a local path on the right, the version is omitted and the directory must contain a go.mod file.
  3. If the replacement has a go.mod file, its module directive must match the module path it replaces.
  4. Replace directives apply only in the main module's go.mod and are ignored in other modules.
  5. A replace alone does not add a module to the graph. A require for the module must exist.

A typical fork replace

require example.com/lib v1.4.2

replace example.com/lib v1.4.2 => github.com/acme/lib v1.4.2-acme.1

Your code still imports example.com/lib. The build fetches your fork instead. Because the replacement's module directive must match the original path, your fork keeps the original's module identity inside its own go.mod.

What it fixes

You get your patched code without editing every import, and you can scope the swap to one version so it falls away when you move on. That makes replace a clean way to carry a single security patch, as described in carrying a patch while waiting for an upstream merge.

What it hides

Hidden effectWhy it matters
Ignored by downstream modulesIf you publish a library, your users get the unpatched original
Name stays the originalTools that read go.mod may report the original and its advisories, not your fork's state
Version stays the original in the require lineA reader may think you run upstream 1.4.2 when you run a fork
Easy to forgetA replace added for a quick fix can live for years

The second row cuts both ways. A scanner reading the require line may flag an advisory your fork already fixed, or miss one that exists only in your changes. See will scanners flag a renamed fork.

Habits that keep it honest

  1. Put a comment above every replace saying why, who owns it and when to remove it.
  2. Use a version specific left side where possible, so moving to a newer version drops the swap.
  3. Review replaces in code review like any dependency change.
  4. List active replaces in your fork register and review them each sync.
  5. If you publish a library, do not rely on replace to ship a fix. Release a fixed version instead.

Where Safeguard fits

Safeguard's dependency scanning and policy gates can flag changes to dependency manifests in a pull request, which is where a new replace shows up. Try a free scan at gold.safeguard.sh/scan.

Try it yourself

Click through it at your own pace, or open it full screen. Watch how a change in a pull request is checked before merge.

Quick check

Does my replace protect people who import my library?

No. The Go reference says replace directives apply only in the main module and are ignored in other modules.

Can a replace add a new dependency?

No. A replace alone does not add a module to the graph. A require for it must also exist.

Sources

Last reviewed: 19 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.