# Maven, Go and Cargo settings against malicious packages

> Maven mirrors and checksums, Go checksum database and GOPRIVATE, Cargo.lock and source replacement: what each setting stops and where a firewall fits.

Source: https://chain305.com/learn/maven-go-cargo-package-security/

---

# Maven, Go and Cargo package security

The settings that keep JVM, Go and Rust builds on known packages.

What each setting in settings.xml, the go command and .cargo/config.toml does, what it misses, and where a package firewall fits.

By [Muhammad Zeeshan Sultan](https://chain305.com/about/#founder), founder of Chain305 · October 2026

Questions and answers

## How do malicious packages reach a Maven, Go or Cargo build?

The same ways they reach an npm one: a new release from a [hijacked maintainer account](https://chain305.com/learn/maintainer-account-takeover/), a lookalike name, a public package that takes the name of a private one, or a dependency that is pulled in by another dependency. Cargo adds one more: a crate’s `build.rs` is compiled and run just before the crate is built, so a malicious crate can [run code on the build machine](https://chain305.com/learn/install-script-attacks/) without anything importing it. That is how `proc-macro1` ran its payload in August 2026.

Each of the three tools has settings that narrow these paths. They are below, checked against the official documentation, with what each one does not catch.

## Which Maven settings help?

### Send every request to one repository

Without a mirror, Maven queries repositories in order “until one returns a valid result”: global and user `settings.xml`, the project POM, its parent POMs, the standard repositories in the Super POM, then repositories declared in the POMs of your dependencies. A dependency can bring its own repository into your build that way.

A mirror whose `mirrorOf` is `*` sends every one of those requests to a single URL:

```
<!-- ~/.m2/settings.xml -->
<mirrors>
  <mirror>
    <id>internal</id>
    <url>https://repo.yourco.example/maven/</url>
    <mirrorOf>*</mirrorOf>
  </mirror>
</mirrors>
```

The Maven guide says this “is most useful when using an internal company repository” that proxies external requests. `external:*` mirrors everything except localhost and file repositories, and `!id` excludes one repository by its id.

### Fail the build on a bad checksum

`checksumPolicy` sets what Maven does with a missing or incorrect checksum, separately for releases and snapshots: `ignore`, `warn` or `fail`. On the command line, `-C` (`--strict-checksums`) fails the build when checksums do not match.

```
<releases>
  <checksumPolicy>fail</checksumPolicy>
</releases>
```

The checksum files are deployed alongside the artifact by whoever published it, so this catches corruption and tampering on the way to you. A malicious artifact published with correct checksums passes.

### Own your groupId on Maven Central

A `groupId` such as `com.yourco` is a namespace on Maven Central, and Sonatype only grants publishing rights under a domain-based namespace after you prove ownership with a DNS TXT record. Registering yours means nobody else can publish under your internal group names there.

## Which Go settings help?

### Keep go.sum and the checksum database on

`go.sum` holds a SHA-256 hash for every module version your build uses. When the `go` command downloads a module whose hash differs from the one in `go.sum`, it reports a security error and does not add the file to the module cache. When `go.sum` has no line for a module, the command checks the hash against the checksum database at `sum.golang.org`, a transparent log run by Google. The Go documentation says this “makes untrusted proxies possible since they can’t serve the wrong code without it going unnoticed.”

`GOSUMDB=off` turns that check off for every module: the `go` command “will not authenticate downloaded modules unless they’re already in go.sum”. If private modules fail the lookup because the checksum database has never seen them, set `GONOSUMDB` (or `GOPRIVATE`) to their path prefixes instead; that skips the check for those modules only. Leave `GOSUMDB` at its default for public modules, including when `GOPROXY` points at a package firewall.

Both checks confirm that you get the same bytes as everyone else for a given version. A malicious version that was published normally gets a valid entry like any other.

### Mark private modules with GOPRIVATE

```
go env -w GOPRIVATE=corp.example.com
```

`GOPRIVATE` takes glob patterns of module path prefixes and is the default for `GONOPROXY` and `GONOSUMDB`. Matching modules are fetched directly from their repositories and are never sent to the public proxy or checksum database. If you run a proxy that serves every module, set `GONOSUMDB` for your private prefix instead and leave `GONOPROXY` unset.

### Know when GOPROXY falls back

`GOPROXY` is a comma-separated list, and its default is `https://proxy.golang.org,direct`. The `go` command moves to the next entry only when a proxy answers 404 or 410. Any other error stops the download, and the Go documentation gives the example of a proxy answering 403 for a module with known security vulnerabilities. A proxy that refuses a module therefore has to answer with something other than 404 or 410, or the list will fall through to `direct` (with `|` instead of `,`, it falls back on any error).

### Do not let builds edit go.mod

Since Go 1.16, the `go` command acts as if `-mod=readonly` were set: if `go.mod` needs changes, it reports an error instead of making them. Setting `GOFLAGS=-mod=readonly` in CI keeps that true even when a `vendor` directory is present. `-mod=vendor` builds from the `vendor` directory and does not use the network or the module cache. `go mod verify` checks that the modules in your cache have not been modified since they were downloaded.

## Which Cargo settings help?

### Commit Cargo.lock and build with —locked

`Cargo.lock` records the exact version and source of every dependency. `--locked` makes Cargo exit with an error if the lockfile is missing or would change, and the Cargo docs suggest it for CI. `--frozen` is `--locked` plus `--offline`.

`cargo install` ignores the `Cargo.lock` inside a package by default and resolves dependency versions again, possibly to releases newer than the package. Use `cargo install --locked` to keep the published lockfile.

### Replace crates.io with your mirror or a vendor directory

Source replacement in `.cargo/config.toml` sends crates.io traffic somewhere else:

```
[source.crates-io]
replace-with = "internal"

[source.internal]
registry = "https://crates.yourco.example/index"
```

A replacement may be a registry, a local registry or a directory. `cargo vendor` copies every crates.io and git dependency into a directory and prints the configuration that points Cargo at it. Cargo assumes a replacement source contains exactly the same code as the original.

One gap: `cargo install` reads configuration from `$CARGO_HOME/config.toml`, not from the project, unless you install with `--path`. A replacement that only lives in the repository does not apply to `cargo install`.

### Treat build.rs as code that runs on your machine

Cargo compiles a package’s `build.rs` and runs it just before building the package, and the script “may perform any number of tasks”. Its intended uses include building a bundled C library or generating code, but nothing limits it to those. Build untrusted dependency updates where a build script can do little harm, and read new build scripts in a dependency diff.

### Audit the lockfile

`cargo-audit` checks `Cargo.lock` against the RustSec Advisory Database. `cargo-deny`’s sources check limits where crates may come from: `allow-registry` defaults to crates.io, and setting `unknown-registry` and `unknown-git` to `deny` fails the check for any crate from another registry or git repository. Both default to `warn`.

## What do these settings not cover?

They check that you got the bytes you expected from the place you expected. None of them judges the bytes. A malicious release published normally has a valid checksum, a valid `go.sum` line on first use and a valid Maven Central listing. A lockfile protects versions already locked; adding or updating a dependency resolves it again. And each setting lives on one machine or in one repository, where it can be forgotten or switched off.

## Where does a package firewall fit?

A package firewall sits on the one URL your `settings.xml` mirror, `GOPROXY` and Cargo source replacement point at, and decides on each package before it is served. For Maven, Go and Cargo, Chainsaw runs its typosquat matchers and malware feed, a cooldown policy rule (`cooldownDays`), seeded in monitor mode, that quarantines versions published inside your window once you set it to block, and a reserved-namespaces setting that refuses public packages under names you declare. On crates.io it also checks build scripts: the default rule on install scripts that fetch remote URLs refuses `onering 1.4.1`, whose `build.rs` runs `curl`. In our [re-run of 58 attacks from 2026](https://chain305.com/attack-coverage-2026/), the default typosquat rule refused `proc-macro1` on its first download, so the three crates that depended on it would have failed to build for anyone installing through Chainsaw.

Keep the settings above as well. A firewall decides what is served; checksums, `go.sum` and `--locked` make sure the build uses what was served. [What a package firewall is and how it works.](https://chain305.com/what-is-a-package-firewall/)

## Sources

-   Apache Maven, [Setting up multiple repositories](https://maven.apache.org/guides/mini/guide-multiple-repositories.html), [Using mirrors for repositories](https://maven.apache.org/guides/mini/guide-mirror-settings.html), [Settings reference: `checksumPolicy`](https://maven.apache.org/settings.html) and [command-line options](https://maven.apache.org/ref/current/maven-embedder/cli.html).
-   Sonatype, [Maven Central namespace registration](https://central.sonatype.org/register/namespace/).
-   Go, [Go Modules Reference](https://go.dev/ref/mod): [authenticating modules and `go.sum`](https://go.dev/ref/mod#authenticating), [private modules](https://go.dev/ref/mod#private-modules), [`go mod verify`](https://go.dev/ref/mod#go-mod-verify) and [build flags](https://go.dev/ref/mod#build-commands).
-   The Cargo Book, [`cargo build` manifest options](https://doc.rust-lang.org/cargo/commands/cargo-build.html), [`cargo install`](https://doc.rust-lang.org/cargo/commands/cargo-install.html), [source replacement](https://doc.rust-lang.org/cargo/reference/source-replacement.html), [`cargo vendor`](https://doc.rust-lang.org/cargo/commands/cargo-vendor.html), [build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and [Cargo.toml vs Cargo.lock](https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html).
-   [RustSec Advisory Database and cargo-audit](https://rustsec.org/); cargo-deny, [sources check configuration](https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html).
-   JFrog Security Research, [Compromised Rust crates on crates.io silently execute malware at build time](https://research.jfrog.com/post/arrayref-proc-macro1-crates-io/), August 2026.

One URL per package manager

## Apply the same checks to Maven, Go and Cargo installs

Start a free org in monitor mode and see which Maven, Go and Cargo packages Chainsaw would refuse, then turn rules on one at a time.

[Start in monitor mode](https://chain305.com/chainsaw/signup) [Dependency confusion →](https://chain305.com/learn/dependency-confusion/)
