To scan a Mercurial repository for vulnerable dependencies, give a scanner read-only access to the repository URL, pick the named branch, bookmark or tag that actually ships, fetch only that revision's history, build a software bill of materials (SBOM) from it, and record the full changeset hash so every finding points at an exact state of the code. Then decide how new changesets trigger a rescan (polling with hg identify, or a changegroup hook on the server) and how fixes come back (a patch you apply with hg import, not a push to your branch). This post walks through that design for any self-hosted Mercurial server, then shows how Safeguard's Mercurial integration does each step.
Key takeaways
- Mercurial is mostly self-hosted, so a scanner has to work with whatever your server enforces: hgweb's
[web]access settings, a web server's authentication, SSH keys restricted withhg-ssh, or the repository roles of a hosted forge. - Decide what "the code we ship" means before you scan. In Mercurial that can be a named branch, a bookmark or a tag, and each behaves differently when history moves.
- Record the 40 character changeset hash, never the local revision number. Revision numbers differ between clones of the same repository.
- Read access is enough for scanning. Push access is only needed if you want fixes delivered as a new named branch, and it should stay off otherwise.
- Pin self-signed or private CA certificates by fingerprint with Mercurial's
[hostsecurity]section instead of turning off verification. - Safeguard's Mercurial (hg) integration (GA) connects repositories on hgweb, Heptapod, RhodeCode, SourceHut or
hg serve, scans each chosen branch, bookmark or tag, and records the changeset hash as the scan's commit. - SBOM generation and analysis (GA) and software composition analysis and vulnerability management (GA) run on every scan; policies and gates (GA) then apply Block, Warn or Notify, with approval handled through exceptions.
- Autonomous remediation (GA, ships switched off with every category set to Manual) delivers Mercurial fixes as a patch in
hg exportformat, or as a new named branch if you grant write access. It never pushes to an existing branch or moves a bookmark. - Repositories that only resolve on a private network need on-premise deployment (GA); the hosted service never connects to private, loopback, link-local or metadata addresses.
The situation
You run Mercurial. Maybe it is a build tools team that never migrated, a research group with a decade of history in one repository, a product line that lives on a Heptapod instance, or an hgweb server behind a reverse proxy that one platform engineer has kept alive for years. The code ships. Its dependencies come from the same public package registries as everyone else's.
Then an advisory lands for a widely used library. Someone from security asks a short question: which of our repositories pull in this package, on which branch, at which changeset, and is the release we shipped last month affected?
In a Git shop that question often has a tool behind it, because most dependency scanners assume a Git host with an app install, an API and webhooks. In a Mercurial shop the honest answer is often a shell script. Someone wrote a cron job that clones every repository with a shared administrator password, runs a manifest scanner over the working copy, and mails a report nobody reads. The script scans default even though releases come from a stable named branch. It records "revision 4127", which means nothing on any other machine. The password in the cron job can also push.
A bad week looks like this:
- The advisory is public on Monday. Security asks for exposure by Tuesday.
- The cron job's output covers the wrong branch, so nobody trusts it.
- Someone clones every repository by hand, which takes the hgweb server down for an afternoon because full clones of large histories are expensive.
- The fix is a one line version bump in a lock file, but the only way to land it is for a developer to make the change by hand in each repository, because nothing can propose a change without push rights.
- The auditor asks later how you know the scanned code is the code you shipped, and the scan record has no changeset hash.
None of this is a Mercurial problem. It is a scanning design problem that Mercurial makes visible, because there is no single hosting platform that does the plumbing for you. The rest of this post is the plumbing, done properly.
The rules behind it
This is not legal or audit advice. It is a map of the published requirements that make "scan every repository, on the right revision, with evidence" a reasonable expectation rather than a nice extra.
NIST SP 800-218, the Secure Software Development Framework
NIST's SSDF (SP 800-218) is a core set of high level secure software development practices that software producers and their customers use as a shared vocabulary. Four of its practices bear directly on how you scan a Mercurial estate:
- PS.1, protect all forms of code from unauthorized access and tampering. Task PS.1.1 asks producers to store code based on the principle of least privilege, so only authorized people, tools and services have access. A scanner is one more such tool, so it should get the least privilege that works, which for scanning is read access to the repositories it covers and nothing else.
- PS.3, archive and protect each software release. The SSDF's task PS.3.2 asks producers to collect and maintain provenance data for the components of each release, and names the SBOM as one way to hold it. An SBOM tied to a full changeset hash is that record for a Mercurial release.
- PW.4, reuse existing, well secured software. The practice covers checking third party components you acquire, which in most repositories means the open source packages named in manifests and lock files.
- RV.1, identify and confirm vulnerabilities on an ongoing basis. "Ongoing" is the important word. A scan at release time is not enough if a new advisory lands for a component you shipped last quarter.
CISA's SBOM guidance
CISA maintains the US government's SBOM resources, including its 2026 update to the Minimum Elements for a Software Bill of Materials, which replaced the NTIA 2021 minimum elements as the current baseline. The practical consequence for Mercurial teams is simple: an SBOM is only useful as evidence when it is tied to an exact version of the software. In Mercurial that means the changeset hash, not a branch name that will move tomorrow.
SBOM formats: CycloneDX and SPDX
The two SBOM formats you will meet in contracts and questionnaires are OWASP CycloneDX and SPDX, the latter maintained by the Linux Foundation and published as ISO/IEC 5962. Whatever produces the SBOM for your Mercurial repositories should emit one of them, so the output can be exchanged with customers and regulators without conversion.
What those rules ask of a Mercurial scan
| Requirement | What it means for an hg repository | Evidence to keep |
|---|---|---|
| SSDF PS.1 | The scanner identity can read the repositories it covers, and cannot push, strip or change hooks | The server's access configuration for that account or key |
| SSDF PS.3.2 | Each release has an SBOM tied to the changeset that was built | SBOM plus full changeset hash, per release tag or bookmark |
| SSDF PW.4 | Third party components are checked when they enter and while they stay | Findings per component and version, with dates |
| SSDF RV.1 | Scans repeat when code changes and when new advisories appear | Scan history per branch, bookmark or tag |
| CISA SBOM minimum elements | The SBOM identifies the software version precisely | The changeset hash recorded with the SBOM |
Mercurial concepts that change how you scan
Teams that come to Mercurial from Git, or the other way round, trip over the same few differences. Each one changes what a scanner should do.
Named branches, bookmarks and tags
Mercurial gives you three ways to name "the code that ships", and they are not interchangeable.
- Named branches are part of each changeset. By default a child of a changeset on
stableis also onstable, and the branch name stays in history. You retire a named branch by closing it withhg commit --close-branch, not by deleting it. Named branches suit long lived lines of development such asdefaultandstable. - Bookmarks are movable pointers to a changeset, much closer to what Git calls a branch. They move forward as you commit, they can be pushed and pulled, and they can be deleted. Many teams use bookmarks for feature work or for a
releasepointer. - Tags are stored in a file named
.hgtagsthat is versioned like any other file, so creating a tag is recorded in history and shared by push and pull. A tag names one changeset and is meant to stay put.
For scanning, that means:
- Scan a named branch when you want to know the state of a line of development. The head of the branch moves, so rescans matter.
- Scan a bookmark when your release process moves a bookmark to the changeset that ships. Expect the bookmark to be moved, and sometimes deleted.
- Scan a tag when you need a fixed record of a release. The result should not change unless an advisory changes, which is exactly what you want for audit evidence.
A named branch can also have more than one head. If your team allows that, decide which head the scan follows, or merge before scanning.
Changeset hashes, not revision numbers
Every Mercurial changeset has a local revision number and a global changeset ID. The revision number is an integer that depends on the order changesets arrived in a particular clone, so the same number can point at different changesets in two clones of the same repository. The changeset ID is a hash that identifies the same changeset everywhere.
Any scan record, ticket or SBOM should carry the full changeset hash. "Revision 4127" is useful on your laptop and nowhere else.
Phases
Mercurial tracks a phase for each changeset: public, draft or secret. By default, any changeset exchanged with a publishing server is put in the public phase, and the Mercurial wiki describes public changesets as immutable: history rewriting extensions refuse to delete them. This matters for fixes: a fix delivered to your server becomes part of shared history, which is a good reason to deliver fixes as patches or as a new, clearly named branch that a person reviews, rather than straight onto a branch others build from.
Where Mercurial is served from
Mercurial does not have one hosting platform. The common ways to serve it are:
- hgweb, the CGI or WSGI application that ships with Mercurial, usually behind a web server that handles authentication and TLS. Access is controlled by the
[web]section of the repository's configuration, for exampleallow_readand the push setting, plus whatever the web server enforces. - SSH, where clients run
hg serve --stdioon the server through an SSH login. Mercurial'scontrib/hg-sshscript restricts a key to named repository paths and can make it read-only. - Hosted forges such as Heptapod, RhodeCode and SourceHut, each with its own repository roles and access tokens.
hg serve, the built-in web server. The Mercurial wiki notes that its push has no authentication, so it suits only trusted internal networks.
A scanner that only speaks one hosting API cannot cover that spread. One that speaks the Mercurial wire protocol over HTTPS and SSH can.
Step by step: scanning Mercurial, with or without Safeguard
These steps work whatever tools you use. The commands are standard Mercurial and OpenSSH commands; the snippets below were written for this post from the Mercurial and Safeguard documentation and were not run as part of writing it, so test them on a scratch repository first.
1. Inventory the repositories
On an hgweb server, the list of served repositories lives in the hgweb configuration file, under [paths] or [collections]. On a forge, export the project list. On SSH only servers, list the repository directories. Write down, for each repository:
- the URL clients use (https:// or ssh://)
- who owns it
- which named branch, bookmark or tag represents what ships
- whether releases are tagged
2. Decide the revision per repository
Use the rules from the concepts section. A reasonable default is: scan the release named branch or release bookmark continuously, and scan each release tag once at release time so you have a fixed record.
3. Create a read-only identity
Give the scanner its own identity, never a person's account and never an administrator account.
- hgweb over HTTPS: add the account to
allow_read(or the web server's equivalent), and keep it out of the push list. Mercurial's own defaults refuse pushes unless you configure them, and require HTTPS for push. - Hosted forges: create a read-only access token for a service account, with read access to the projects you will scan.
- SSH: create a dedicated key without a passphrase for unattended use, and restrict it on the server with
hg-sshinauthorized_keys, listing only the repository paths it may read and adding the read-only option.
# Dedicated key for the scanner (no passphrase, unattended use)
ssh-keygen -t ed25519 -N "" -f safeguard
# On the server, in the scanner account's authorized_keys (one line):
command="hg-ssh --read-only /srv/hg/app /srv/hg/lib",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... scanner
Set an expiry on tokens that matches your normal rotation schedule, and write down how to rotate them.
4. Trust the server's certificate properly
If your Mercurial server uses a self-signed or private CA certificate, do not turn off verification. Mercurial supports pinning a certificate by fingerprint in the [hostsecurity] section, which trusts exactly that certificate for that host and nothing else.
# Get the SHA-256 fingerprint of the server's certificate
openssl s_client -connect hg.example.com:443 < /dev/null | openssl x509 -noout -fingerprint -sha256
# Scanner's private Mercurial configuration
[hostsecurity]
hg.example.com:fingerprints = sha256:AA:BB:CC:...
Compare the fingerprint with one the server administrator gives you through a separate channel. When the certificate is renewed, the pin has to be updated, so put that on the renewal checklist.
5. Check access before you build anything
hg identify against a remote URL prints the changeset ID of a revision without cloning anything. It is the cheapest possible test that the credential, URL and certificate all work.
hg identify -r default https://hg.example.com/repos/app
hg identify -r default ssh://hg@hg.example.com//srv/hg/app
It is also the cheapest way to detect change: if the ID has not moved since the last scan, there is nothing new to scan.
6. Fetch only what you need
A scan needs the files of one revision, not a working copy of the whole history.
# Clone only the chosen revision and its ancestors, with no working copy
hg clone -U -r stable https://hg.example.com/repos/app app-scan
# Write that revision's files to a plain directory for scanning
hg -R app-scan archive -r stable ../app-tree
# Record the full changeset hash for the scan record
hg -R app-scan log -r stable --template '{node}\n'
-U skips the working copy, -r limits the clone to that revision and its ancestors, and hg archive writes an unversioned copy of the revision's files. Delete the clone and the archive when the scan ends.
Run the scanner with a private Mercurial configuration that loads no extensions and no hooks. A clone never brings the server's .hg/hgrc with it, but the scanning machine's user and system configuration can still enable extensions and hooks you do not want running during a scan. Mercurial's HGRCPATH environment variable sets which configuration files are read, so pointing it at a file you control isolates the run.
7. Generate the SBOM and findings
Run your SBOM tool over the archived tree, emit CycloneDX or SPDX, and attach the changeset hash, repository URL and the branch, bookmark or tag name to the record. Then match the components against vulnerability data and keep the findings with the same identifiers.
8. Choose how new changesets trigger a rescan
| Trigger | How it works | Good for | Watch out for |
|---|---|---|---|
| Polling | Run hg identify on a schedule and rescan when the changeset ID changes | Any server, no server changes | Delay between a push and the scan |
changegroup hook | Mercurial runs a command on the server after a group of changesets is added by push, pull or unbundle | Fast rescans after each push | Needs access to the server's configuration; the hook script holds a credential |
| Scheduled rescan | Rescan on a calendar even when code has not changed | Catching new advisories for old code | Not a substitute for change triggers |
Mercurial runs the changegroup hook after the transaction has completed, so it cannot stop a push. That is the right hook for a scan trigger. The pretxnchangegroup hook runs before the transaction commits and can reject a push; it is the place for an enforcement check, not for a notification, and putting a network call there means a scanner outage can block every developer.
A minimal hook entry looks like this:
# In the repository's .hg/hgrc on the server
[hooks]
changegroup.scan = /path/to/scan-hook.sh
Mercurial passes details to the hook through environment variables, including the first changeset added (HG_NODE) and the last (HG_NODE_LAST).
9. Decide how fixes come back
Mercurial has a long standing patch workflow: hg export writes a changeset as a patch with its author and message, and hg import applies it and commits it. For scanner proposed fixes, that is the safest delivery: a person downloads the patch, reviews it, applies it with hg import, runs the build and pushes as normal.
If you want fixes on the server for review, deliver them as a new named branch with a clear prefix, pushed with hg push --new-branch. Never let a scanner push onto an existing branch or move a bookmark. Moving a release bookmark changes what ships.
10. Rotate and disconnect cleanly
Write down how to rotate the token or key, how to replace the hook credential, and how to remove everything: the server side credential, the hook line, the hook script and any credential file it uses.
Least privilege checklist for a Mercurial scanner
Copy this into your change ticket.
- Dedicated account or key, not a person's
- Read access only to the repositories being scanned
- No push access unless branch delivery of fixes is turned on, and then only to the repositories that need it
- No administrator or owner rights, no ability to strip changesets, no access to server hook or extension configuration
- SSH key restricted with
hg-sshto named paths, read-only where possible - No passwords over plain http://
- Self-signed certificates pinned by fingerprint, not trusted blindly
- Token expiry set and rotation documented
- Hook credential file readable only by the server's service user
- Disconnect steps written down
Should we scan named branches, bookmarks or tags?
Scan what ships, then scan what is about to ship. For most teams that means the release named branch or release bookmark, continuously, plus each release tag once. Scanning default only is the most common mistake, because it reports on code that may never be released and misses the branch customers run.
Polling or a commit hook?
Start with polling. It needs no server access and works against any Mercurial server, including hosted forges where you cannot install hooks. Add a changegroup hook when you need faster rescans and you control the server. Keep scheduled rescans either way, because new advisories arrive for code that has not changed.
What access does a scanner really need on an hgweb server?
Read, pull and archive access to each repository it covers. Mercurial has no token scopes of its own; what the account can do is what hgweb's [web] settings and your web server's authentication allow. Push access is optional and only for fix delivery as a new branch.
How do we handle a self-signed certificate?
Pin it. Get the SHA-256 fingerprint, compare it with the server administrator's copy, and put it in the scanner's [hostsecurity] configuration. Do not disable certificate checks, and do not send a password over plain http://.
Can fixes land without anyone pushing to our main branch?
Yes. Deliver fixes as hg export patches that a developer applies with hg import, or as a new named branch that someone reviews and merges. Either way, your existing branches and bookmarks stay exactly where your team put them.
What about repositories on a private network?
A hosted scanner has to reach the server, so a repository that only resolves on a private network is out of reach for any hosted service. Your options are to publish the server on a public address with TLS and restrict access by credential, or run the scanner inside your network.
How Safeguard does this
Safeguard's Mercurial (hg) integration (GA) is a card on the Integrations page that takes any repository URL your Mercurial server serves, from hgweb, Heptapod, RhodeCode, SourceHut or hg serve. The steps below follow the Safeguard docs for the integration. The integration is switched on per workspace; if the Mercurial card does not appear under Integrations, ask your Safeguard contact.
1. Create the credential on your server
Create an account or a token that can read the repositories. On hgweb, allow the account in [web] allow_read or give it access in your web server's authentication; on Heptapod or RhodeCode, give it read access to the project. For SSH, create a key pair without a passphrase, add the public key on the server, and capture the host key line:
ssh-keygen -t ed25519 -N "" -f safeguard
ssh-keyscan -t ed25519 hg.example.com
Safeguard's docs recommend a dedicated SSH key whose authorized_keys entry restricts it to hg-ssh and the repository paths, read-only unless you turn on branch delivery. A key with a passphrase cannot be used, because scans run unattended, and client TLS certificates are not available as a sign-in method.
Check access from the command line before you connect, exactly as in the generic steps:
hg identify -r default https://hg.example.com/repos/app
hg identify -r default ssh://hg@hg.example.com//srv/hg/app
2. Connect the card
- Open Integrations and choose the Project/Product tab.
- Click Connect on the Mercurial card.
- Choose Public or Private, enter a Name, and put one repository URL per line in Repository URLs.
- For Private, choose Sign in with: user name and password or token, or SSH key (ssh://) with the SSH private key and Server host key.
- Choose how often to Check for new commits, and how to Deliver fixes as: a patch, or a new named branch (which needs write access and a Fix branch name prefix).
- Click Check access. If the server's certificate is not trusted, compare the fingerprint shown, tick This is my server's certificate, and the check runs again.
- Click Next, pick each repository's branch, bookmark or tag, and connect with the scan depth you want.
Public connections need no credential, but the server must allow anonymous read. Plain http:// is accepted only on the Public tab; Safeguard never sends a password over plain http://. Where your server offers tokens, the docs prefer a token over a password, and the token is written to a private configuration file for each run rather than placed on a command line.
3. What happens on each scan
Each repository you select is scanned once on connect. For each scan, Safeguard clones only the history of the chosen revision without a working copy, archives it, generates an SBOM and findings, and records the full changeset hash as the scan's commit. The commands it uses are the ones in the generic steps: hg identify for the newest changeset, hg clone -U -r and hg archive for scans, hg branches, hg bookmarks and hg tags for listing, and hg cat for the files a fix changes.
Each run uses a private Mercurial configuration with no extensions and no hooks, removed afterwards. The clone and the archived tree are deleted when the scan ends. What Safeguard stores is the SBOM, the findings, project metadata, the changeset hash and, for fixes, the patch and the files it changes. The password, token or SSH key is encrypted at rest and never shown again after you save it. Safeguard's docs state that no customer data from this integration is sent to third party LLMs.
The SBOM comes from SBOM generation and analysis (GA), and the findings from software composition analysis and vulnerability management (GA), the same engines Safeguard uses for every other source. A Mercurial project appears in Projects next to your Git, container and upload projects, so one exposure question covers all of them.
4. Self-signed and private CA certificates
A server with a self-signed or private CA certificate works only after you confirm its fingerprint in the dialog. Safeguard then trusts exactly that certificate, using Mercurial's [hostsecurity] fingerprint pinning, and nothing else. If the certificate is later renewed, scans fail with a message saying the server presented a different certificate from the pinned one; you check the new fingerprint and confirm it in the connection's edit dialog.
5. Rescans: polling, commit hook and schedules
- Polling (default). Safeguard checks the newest changeset of each scanned branch, bookmark or tag every 15 minutes by default, configurable from 5 minutes to 24 hours or off, using
hg identify, and rescans when it changed. - Commit hook. In the connection's edit dialog, Scan on commit then Set up commit hook gives you
safeguard-hg-hook.shand a credential file,safeguard-hook.conf. Save both on the server that hosts the repository, make the script executable, keep the credential file readable only by the user the server runs as (chmod 600), and add the hook to the repository's.hg/hgrc:
[hooks]
changegroup.safeguard = /path/to/safeguard-hg-hook.sh
The server needs curl. After each push the hook sends the newest changeset, its author and the branches and bookmarks it touched to Safeguard's hook endpoint for that connection, authenticated with the credential, and Safeguard rescans the projects on those branches or bookmarks. The credential is shown once. Issue a new hook credential rotates it and Revoke stops deliveries. The docs are explicit that the hook never blocks or changes a push, which matches Mercurial's own design for changegroup hooks.
- Schedules. You can also rescan on a schedule from Integrations then Scheduled, which covers new advisories for code that has not changed.
6. Policies and gates
Every Mercurial scan feeds policies and gates (GA). Policies evaluate the SBOM and findings against conditions such as vulnerability severity, CVSS score, EPSS score, license and package maintenance. Each policy's action decides what a failed policy does to its gate:
| Action | What the gate does |
|---|---|
| Block | The gate fails |
| Warn | The gate passes, with a warning naming the policy |
| Notify | The gate passes, and a notification is sent |
When a team needs a legitimate exception, a developer requests it against the policy and finding, gives a business justification and an expiry date, and submits it; approvers are notified and decide. Approval lives in that exception workflow, not in the gate action. For findings that do not apply, the project's Mitigations tab records risk acceptances, false positives, workarounds and not applicable decisions with a justification and an approver.
Mercurial has no pull request status API, so there is no check run to write back to an hg server. Where a Mercurial repository is also built in CI, the Safeguard CLI (GA) can fail the job on a gate with safeguard ci scan --wait --fail-on-gate when policy outcomes are on for your workspace, as the policies and CI/CD integration docs describe, passing the repository, branch and commit as flags on systems where they are not detected automatically.
7. Fixes as patches or a new named branch
With autonomous remediation (GA), Safeguard can propose fixes for findings. It ships switched off, with every category set to Manual, so nothing is generated or delivered until your team turns it on and chooses how.
For Mercurial, fixes arrive as a patch in hg export format that you apply with hg import. If you give the connection write access and choose branch delivery, Safeguard can instead push the fix as a new named branch using hg push --new-branch, named with your fix branch prefix. It never pushes to an existing branch or moves a bookmark. If a branch with the fix's name already exists, nothing is pushed and the connection reports it.
The least privilege default in the docs is read access to the repositories you scan and nothing else, with fixes left as patch downloads.
8. Rotate and disconnect
To rotate, create a new token or key on the server, open the connection's edit dialog, enter it, click Check access and save, then revoke the old one. To rotate the hook credential, click Issue a new hook credential and replace safeguard-hook.conf on the server.
To disconnect, delete the connection under Integrations, then revoke the token or remove the SSH key on the server, and remove the changegroup.safeguard line and the two hook files.
Troubleshooting the common failures
| Message (shortened) | Usual cause | Fix |
|---|---|---|
| The server refused the user name and password or token, or the SSH key | Wrong or expired credential | Check with hg identify -r default against the URL, save the right credential |
| This user is not allowed to read that repository | No read access | Grant read access on the server |
| Certificate not issued by a trusted authority | Self-signed or private CA certificate | Compare the fingerprint and confirm it in the dialog |
| The server presented a different certificate from the pinned one | Certificate renewed | Check the new fingerprint and confirm it |
| The SSH host key does not match | Wrong or changed host key line | Run ssh-keyscan -t ed25519 against the server and paste the line |
| That branch, bookmark, revision or file does not exist | Typo, closed branch or deleted bookmark | Pick the branch again in Review |
| Passwords are never sent over plain http:// | Private connection with an http:// URL | Use the server's https:// address |
| This server address isn't allowed | Server resolves to a private, loopback, link-local or metadata address | Expose it on a public address with TLS, or use on-premise deployment |
| A branch with that name already exists. Nothing was pushed | Branch delivery collided with an existing branch | Delete or rename that branch, or change the prefix |
Where your Mercurial work sits in the rest of Safeguard
The Mercurial card sits in the source code group alongside GitHub, GitLab, Bitbucket, Azure DevOps, Azure DevOps TFVC, Oracle Cloud DevOps, Subversion, Perforce and generic Git. Projects from all of them land in the same Projects list, so a question like "which projects ship this package" covers every version control system you connect. If your estate also has Subversion or Perforce, the setup pattern is the same: least privilege credential, pinned certificate where needed, a revision choice, polling or a server side trigger, and fixes as patches.
What Safeguard does not do
- It does not reach servers on a private network from the hosted service. If your Mercurial server resolves to a private, loopback, link-local or metadata address, the hosted service will not connect. You need to expose it on a public address with TLS, or run Safeguard's on-premise deployment (GA).
- It does not use passphrase protected SSH keys or client TLS certificates. Scans run unattended, so the key must have no passphrase, and client certificate sign-in is not available. Restrict the key on the server instead.
- It does not push to your existing branches or move your bookmarks. That is a deliberate limit. Applying a patch, merging a fix branch and moving a release bookmark stay your team's decisions.
- It does not block pushes on your Mercurial server. The commit hook is a
changegrouphook that triggers a rescan; it never blocks or changes a push. If you want a server side push check, that is your ownpretxnchangegrouphook and your own decision. - It does not write status back to Mercurial. There is no check run or commit status on an hg server. Enforcement happens in Safeguard's gates and, where you build in CI, in the CLI step that fails the job.
- It does not manage your server's access model. Mercurial has no token scopes of its own; what the scanner can do is what your hgweb configuration, web server or forge allows. Getting that right is your server administrator's job, and the docs list what Safeguard never needs: administrator or owner rights, the ability to strip changesets, server hook or extension configuration, or any repository you do not add.
- It does not decide which revision ships. You choose the branch, bookmark or tag per repository. If you scan
defaultand shipstable, the results describedefault. - It does not replace your release sign off. Findings, gates and patches inform the decision. Signing a release, accepting a risk and telling customers remain people's jobs.
Common mistakes
- Scanning the wrong revision. Scanning
defaultwhen releases come from astablenamed branch or a release bookmark. Pick per repository, and write the choice down. - Recording revision numbers. A local revision number is meaningless on any other clone. Record the full changeset hash.
- Using an administrator account. The scanner needs read access. An account that can push, strip or change hooks is a standing risk with no benefit to scanning.
- Turning off certificate checks. It is faster than pinning and it removes the protection TLS gives you. Pin the fingerprint instead.
- Putting the scan trigger in
pretxnchangegroup. That hook runs inside the push transaction and can reject it. A scanner outage then blocks every developer. Usechangegroupfor triggers. - Leaving the hook credential world readable. The hook's credential file should be readable only by the user the server runs as.
- Full clones on every scan. Cloning every revision of a large repository with a working copy on each run is slow and loads the server. Clone only the chosen revision, without a working copy, and archive it.
- Letting a bot move bookmarks. A moved release bookmark changes what ships. Fixes should arrive as patches or new branches.
- Forgetting scheduled rescans. Code that has not changed still gains new vulnerabilities as advisories appear.
- No disconnect plan. When a repository is retired, remove the credential, the hook line and the hook files, not just the connection.
FAQ
Can Safeguard scan Mercurial repositories?
Yes. The Mercurial (hg) integration (GA) connects repositories on any server that serves Mercurial over HTTPS or SSH, including hgweb, Heptapod, RhodeCode, SourceHut and hg serve. Each chosen branch, bookmark or tag is scanned for an SBOM and findings.
Does Safeguard need write access to my Mercurial server?
No. Read access is enough for scanning. Write access is optional and only used if you choose to receive fixes as a new named branch. The docs recommend leaving fixes as patch downloads for least privilege.
Which Mercurial hosting servers work?
Any server that serves Mercurial over https:// or ssh://. The docs name hgweb, Heptapod, RhodeCode, SourceHut and hg serve. Public repositories can also be connected over http:// if the server allows anonymous read.
How does Safeguard know when new changesets arrive?
By polling with hg identify (the default, every 15 minutes unless you change it), or through a changegroup hook you install on the server. You can also add scheduled rescans.
Will the commit hook slow down or block pushes?
The hook runs after the push is complete and the docs state it never blocks or changes a push. It needs curl on the server.
Can I use a self-signed certificate on my hg server?
Yes. Confirm the server's fingerprint in the connection dialog, and Safeguard pins exactly that certificate using Mercurial's [hostsecurity] fingerprint pinning.
How are fixes delivered for Mercurial?
As a patch in hg export format that you apply with hg import, or, with write access and branch delivery turned on, as a new named branch. Autonomous remediation ships switched off with every category set to Manual.
What does Safeguard store from my repository?
The SBOM, findings, project metadata and the changeset hash, plus the patch and changed files for fixes. The clone and archive are deleted after each scan, and credentials are encrypted at rest.
Does Safeguard work with Mercurial bookmarks?
Yes. You can scan a bookmark, and the commit hook reports which bookmarks a push touched. Safeguard never moves a bookmark.
Our Mercurial server is only on the internal network. What are the options?
The hosted service never connects to private, loopback, link-local or metadata addresses. Expose the server on a public address with TLS, or use Safeguard's on-premise deployment.
Related reading
- Argument injection in Git and Mercurial
- Repository health and source code manager risk
- The ESSCM supply chain manager workflow
- Version control market share in 2026: Git, SVN and Perforce
The Mercurial setup above gets your hg repositories scanned on the right revision with read-only access. When a scan does turn up a vulnerable dependency, the next question is how fast a fix reaches the code you ship, and that does not change because your history lives in Mercurial.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The Safeguard CLI demo is a real terminal session: the CLI is asked to fix a lodash CVE in a repository, reads the finding and the advisory, edits package.json, runs the install and tests with approval, and pushes nothing. Since CLI v11.10.0 it asks before any command that changes something, not before every command. The demo uses a Git checkout, but the review first, push nothing habit is the same one this post recommends for Mercurial fixes.
Click through it at your own pace, or open it full screen.
The local runner demo shows setting up the runner on your own machine, approving it with its device code, and then scanning from the terminal. It does not show the Mercurial card, but it is a useful picture of Safeguard work running on a machine you control.
Click through it at your own pace, or open it full screen.
The Claude demo shows connecting Claude to the Safeguard MCP server with OAuth and choosing which tools Claude can use. Once your Mercurial projects are scanned, the exposure question from the opening of this post can be asked from there in plain language.
Click through it at your own pace, or open it full screen.
Quick check
Your releases ship from a stable named branch, but your scanner only scans default. What should change?
Scan the stable named branch continuously, and scan each release tag once at release time, recording the full changeset hash with each SBOM. Scanning default describes code that may never ship.
Why should a scan trigger use the changegroup hook rather than pretxnchangegroup?
The changegroup hook runs after the push has completed and cannot reject it, so a scanner outage never blocks developers. The pretxnchangegroup hook runs inside the transaction and can reject the push, which makes it the place for deliberate enforcement, not notifications.
Sources
- Safeguard docs: Mercurial (hg) integration
- Safeguard docs: Integrations
- Safeguard docs: ESSCM integrations
- Safeguard docs: Policies and gates
- Safeguard docs: AI Remediate
- Safeguard docs: Mitigations
- Safeguard docs: Subversion (SVN) integration
- Mercurial project home
- Mercurial wiki: Publishing repositories
- Mercurial wiki: Hooks
- Mercurial wiki: Shared SSH and hg-ssh
- Mercurial wiki: Bookmarks
- Mercurial wiki: Named branches
- Mercurial wiki: Phases
- Mercurial wiki: Secure connections
- NIST SP 800-218, Secure Software Development Framework
- CISA: Software Bill of Materials
- CycloneDX specification overview
- NIST SP 800-218 (PDF)
- Mercurial help: config (hgrc)
- Mercurial help: glossary
- Mercurial help: clone
- Mercurial help: identify
- Mercurial help: archive
- Mercurial help: export
- Mercurial help: import
- Mercurial help: tag
- Mercurial help: environment
- Mercurial contrib/hg-ssh source
- SPDX specifications
Get started: download Safeguard at safeguard.sh/download and read the setup guides at docs.safeguard.sh/docs.
Last reviewed: 25 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.