Write the policy as outcomes that hold in every language, such as locked installs and a recorded owner, then keep a short appendix per language with the commands and settings. Enforce what you can in CI and track the rest as exceptions. A policy people can check in a pull request is better than a long document.
The outcomes layer
An outcome is a statement that is true or false for any repo. These eight work across ecosystems:
- Every deployable repo has a lock mechanism, and the lock is committed.
- CI installs in the ecosystem's locked mode and fails on drift.
- Integrity checks are on wherever the tool supports them.
- Dependencies that run code at install or build time are reviewed before they are added.
- Every release produces an SBOM in an open format and stores it.
- New direct dependencies need a recorded reason and an owner.
- Security updates are triaged within a stated number of days.
- Exceptions are written down with an expiry date.
Pick the numbers for item seven yourself, and make them match what your teams can sustain. A target nobody meets teaches people to ignore the policy.
The per-language appendix
The appendix answers "what does this mean in my tool?" Keep it to a table:
| Language | Lock | Locked install | Integrity | Audit |
|---|---|---|---|---|
| JavaScript with npm | package-lock.json | clean install command | registry signature check on request | npm audit |
| Rust | Cargo.lock | build with the locked flag | see Cargo docs | a Cargo audit tool of your choice |
| .NET | packages.lock.json | restore in locked mode | see NuGet docs | NuGet audit |
| Go | go.sum | readonly module mode | go.sum plus checksum database | govulncheck |
| Python | pylock.toml or hashed requirements | install with require-hashes | hashes in the file | an audit tool of your choice |
Every cell must come from your own documentation, so treat the table above as a starting shape, and verify each entry against the tool versions you use. Blank cells are fine at first; they show where the gaps are.
Enforce in CI, not in a wiki
A policy that lives only in a document drifts. Turn each outcome into a check where you can:
- Fail the build if the lockfile changes during install.
- Fail the pull request if a manifest changes without a lock change.
- Fail the release if no SBOM is produced.
- Warn on new direct dependencies, and require a label with the reason.
For the rest, such as ownership and triage times, use a lightweight review in your regular meeting.
Handling exceptions
Exceptions will happen: a legacy service that cannot install in locked mode, a language where a check does not exist. Record the repo, the rule, the reason, the compensating control, the approver and an expiry date. Review the list monthly. An exception with no expiry is just a new rule.
What to leave out
Leave out brand names of tools in the outcomes layer, and anything you cannot verify. Do not promise controls the tooling cannot show evidence for. Auditors and customers read policies for what you can prove, so write what is true today and a dated plan for the rest.
Try it yourself
Open the demo and look at how a pull request gate blocks a change that breaks a rule. Notice which rule fired and what the developer sees.
Click through it at your own pace, or open it full screen.
Safeguard can help here: it scans dependencies and generates SBOMs, applies reachability analysis so you can sort findings by what your code can actually call, and enforces policies and gates on pull requests. You can try a free scan on a repository to see what it finds.
Quick check
Should the policy name specific tools?
Keep the outcomes tool neutral and put tool names in the appendix, where they can change without rewriting the policy.
How many rules should a policy have?
Few enough that each can be checked. Eight to ten outcomes is a practical range.
Sources
Last reviewed: 19 October 2026.