Reference the fork by Git URL and end it with a full commit hash. In pip use the git plus https form with an at sign and the hash. In npm use a Git URL ending in a hash and the commit-ish. A branch or default-branch reference moves, so your build can change without any commit in your repo.
Why a commit, not a branch
If your manifest says "my fork, branch main", every install may get different code. Someone with push access to the fork, or an attacker who took over that account, changes what you ship without touching your repository. A full commit hash fixes the code to one exact state. Reviewing the dependency then means reviewing one commit.
pip
The pip documentation shows Git references with a branch, a tag or a commit hash, and states that a full hash is preferable to a partial one because it lets pip work more efficiently with fewer network calls. In a requirements file:
examplelib @ git+https://github.com/acme/examplelib.git@FULL_COMMIT_HASH
Tags are better than branches but can be moved, so the hash is the stronger pin.
npm
The npm package.json documentation says a Git URL dependency can end with a commit-ish, and that when one is provided it is used to clone exactly that commit. If no commit-ish or semver is given, the default branch is used. In package.json:
"dependencies": {
"examplelib": "git+https://github.com/acme/examplelib.git#FULL_COMMIT_HASH"
}
npm also supports a semver form after the hash symbol, where it looks for tags matching a range. That is convenient but looser than a hash.
Tradeoffs against a published package
| Approach | Pro | Con |
|---|---|---|
| Git URL plus full hash | No registry needed, exact code | No registry advisories, slower installs, needs repo access in CI |
| Fork published under a new name | Normal installs, version numbers | Advisories for the original may not match |
| Fork published under a scope you control | Familiar tooling, signing options | You now run a package, with all the duties |
Git URL pins suit a short term measure while you wait for upstream. See carrying a patch while waiting for an upstream merge.
Checklist
- Use the full hash and write the upstream base version in a comment or a nearby note.
- Make the fork repository private or access controlled if it contains unreleased fixes, and restrict who can push.
- Protect the fork's branches so the pinned commit cannot be rewritten out of existence.
- Add the dependency to your fork register with an owner and a removal condition.
- Rescan after each change, since registry based tools may not see Git sourced packages the same way.
The last point is the quiet one. Scanners vary in how they treat Git URL dependencies, so test yours with a known fork before relying on it. See will scanners flag a renamed fork.
Where Safeguard fits
Safeguard's dependency scanning and policies can be used to review what your builds resolve, including changes to dependency sources in a pull request. Try a free scan at gold.safeguard.sh/scan.
Quick check
Why not pin to a tag?
Tags can be moved or deleted and recreated. A full commit hash cannot point anywhere else.
What does npm do with no commit-ish on a Git URL?
It uses the default branch, which can change between installs.
Sources
Last reviewed: 19 October 2026.