Safeguard
Developer Tools•

How do you pin a runtime version? engines, global.json and go.mod

Pinning the runtime keeps laptops, CI and production aligned. How npm engines, .NET global.json and Go go.mod lines behave, and where they fall short.

Safeguard Security Team
3 min read

Pin the runtime in a file that your tools read, then enforce it in CI. For Node.js use the engines field with engine-strict, for .NET use global.json with a roll forward policy, and for Go use the go and toolchain lines. Each behaves differently, and the defaults are advisory in some cases.

Why pin

Without a pin, a developer laptop, the CI runner and production can each run a different runtime. A bug appears in one place only, and a runtime security patch is applied unevenly. A pin makes the expected version explicit, and a failed check makes drift visible.

Node.js: the engines field

The npm documentation says the engines field declares which Node.js and npm versions a package works with. Unless the user sets the engine-strict config flag, it is advisory only and produces warnings when the package is installed as a dependency.

{
  "engines": {
    "node": ">=24 <27"
  }
}

If you want a mismatch to fail, enable engine-strict in CI. Also align the base image tag and the CI setup step.

.NET: global.json

The global.json file selects the SDK version, which is independent of the runtime a project targets. The version field needs a full number such as 10.0.100 and does not accept ranges or wildcards. The rollForward policy sets how far it may move. For example, latestFeature uses the highest installed feature band and patch for the same major and minor version that is at or above the requested one. Setting disable demands an exact match, which Microsoft suggests when using package lock files.

{
  "sdk": {
    "version": "10.0.100",
    "rollForward": "latestFeature"
  }
}

Go: go and toolchain lines

The go line is the minimum version, and a toolchain refuses to load a module that needs a newer one. The toolchain line suggests a toolchain. GOTOOLCHAIN decides whether the go command may switch or download. See go.mod toolchain line explained.

Comparison

RuntimeFileEnforced by default?Strict option
Node.jspackage.json enginesNo, advisoryengine-strict
.NETglobal.jsonYes for the SDK choicerollForward disable
Gogo.modYes for the minimumGOTOOLCHAIN local

A CI pattern

  1. Read the pin from the file, not from a duplicated variable.
  2. Print the runtime version at the start of every job.
  3. Fail the job if the printed version is outside the pin.
  4. Update the pin, the base image and the CI step in one pull request.

Pins also support audits. A dated pin change in version control is evidence that you moved off an unsupported line.

Try it yourself

The gate pull request demo shows a rule stopping a change. A runtime pin check is a simple rule to add.

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

Safeguard policies, gates and guardrails can enforce checks on pull requests. The free scan is at gold.safeguard.sh/scan.

Quick check

Does the engines field block installs?

Not by default. It is advisory unless engine-strict is set.

Does global.json pin the runtime?

No, it selects the SDK. The runtime comes from the project's target framework.

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.