To scan the images in a private registry properly, give the scanner a dedicated read-only credential, have it list every repository and tag it can see, and pin each scan to the manifest digest the tag points at when the scan starts. Then trigger a rescan on every push through an authenticated webhook, and rescan on a schedule because vulnerability data changes after the image stops changing. The four registries most platform teams run on premises, JFrog Artifactory, Sonatype Nexus Repository, Harbor and Red Hat Quay, each do this differently: different repository types, different permission models, different webhook signing, and a few traps that only show up on the day you connect.
This post is for the platform engineer who owns the registry and the AppSec lead who has to show that "every image we ship was scanned." The first two thirds work with any scanner. Safeguard enters at "How Safeguard does this," with one numbered setup per registry taken from docs.safeguard.sh.
Key takeaways
- Scan by digest, not by tag. A tag is a pointer that can move; a manifest digest names exactly one image. Record the digest with every report, or the report describes an image nobody can find again.
- Give the scanner read and nothing else. Each registry has a way to do that: a scoped access token on a read-only group in Artifactory, a role with only the Docker browse and read privileges in Nexus, a project robot account with List, Pull and Read in Harbor, and an OAuth application token with
repo:reador a read-only robot in Quay. - Watch the aggregate repository types. Artifactory virtual repositories and Nexus group repositories show the same images as their members. Scan the members, or you will scan the same image twice and argue about which report is right.
- Webhook security differs by registry. Artifactory can sign the payload with HMAC-SHA256, Nexus signs it with HMAC-SHA1, Harbor sends a shared secret in the Authorization header, and Quay signs nothing, so its secret has to ride in the URL. Treat that URL as a secret.
- Safeguard connects to all four as GA integrations: the JFrog Artifactory card, the Sonatype Nexus Repository card, the Harbor card and the Red Hat Quay card. Each selected image becomes a project with an SBOM (SBOM generation and analysis, GA) and vulnerability findings (vulnerability management with EPSS and KEV enrichment, GA), every tag pinned to its digest.
- Scan on push is optional and secured per registry. Without a webhook, rescans run on the schedule you set under Integrations, Scheduled.
- Gating happens in Safeguard policies and gates (GA): Block, Warn and Notify, with approval through exceptions. Fixes for what the scan finds come from autonomous remediation (GA), which ships switched off with every category set to Manual.
- Safeguard only reads, does not store image layers, and sends no customer data from these integrations to third-party LLMs.
The situation
The trigger is usually a question with a deadline. An auditor, a customer security review or an internal risk committee asks for an SBOM and a current vulnerability report for every container image in production, and asks how you know the image you scanned is the image that runs. The first answer most teams give is "the registry scans them." Then the follow-up questions arrive:
- Which registry? Many organisations run more than one: a central Artifactory or Nexus instance that also serves Docker images, a Harbor instance run by the Kubernetes platform team, a Quay organisation on quay.io or self-hosted.
- Which repositories? Artifactory and Nexus mix local repositories with remote or proxy repositories that cache images from public registries, and with virtual or group repositories that merge both. A scanner that lists everything can count the same base image three times.
- Which version of the image? The tag
payments-api:2.14has pointed at several different images over its life. The report from last Tuesday describes one of them. - Scanned with what credential? Someone connected the old scanner with an admin password in 2022. That person has left, and nobody is sure the password still works or what else it can do.
- How fresh is the report? An image pushed once and deployed for a year accumulates new CVEs without changing a byte.
A bad week looks like this: the report is due Friday, the built-in scanner covers one of three registries, the export has tags but no digests, and the second registry's credential turns out to be the administrator's own token.
The rules and standards behind it
None of the standards below names a registry product. They say what a defensible image inventory looks like, and every step in this post maps back to one of them.
NIST SP 800-190, Application Container Security Guide. Section 3.1.1 explains why image vulnerabilities are a standing risk: images are effectively static archives, so an image built with up-to-date components stops being up to date as soon as a vulnerability is found in one of them, and the fix has to be made in the image and redeployed. The countermeasures in section 4.1.1 ask for tools that integrate with the whole image lifecycle, "from the beginning of the build process, to whatever registries the organization is using, to runtime," with centralised visibility across the organisation and policy-driven quality gates that stop non-compliant images from progressing. Section 3.2 lists three registry risks: insecure connections to registries, stale images in registries, and insufficient authentication and authorization restrictions. The matching countermeasures in 4.2.1 to 4.2.3 say to reach registries only over encrypted channels, to prune images that should no longer be used and deploy by immutable names for discrete versions, and to require authentication for any registry holding proprietary or sensitive images, with read actions on sensitive images logged. A scanner is a registry client, so the same rules apply to it.
NIST SP 800-218, Secure Software Development Framework (SSDF) 1.1. Task PS.3.2 asks producers to collect, safeguard, maintain and share provenance data for all components of each release, with an SBOM as the example. Task PW.4.4 asks you to verify that acquired open source and third-party components meet your requirements throughout their life. Practice RV.1 asks you to identify and confirm vulnerabilities on an ongoing basis, including by monitoring public vulnerability sources. Ongoing is the word that matters: a one-time scan at push does not meet it.
CISA, 2026 Minimum Elements for a Software Bill of Materials. Published on 29 July 2026 by CISA with the NSA, the FBI and international partners, this guidance updates and replaces the minimum elements NTIA published in 2021. Among its new elements are a Component Hash Value and the Component Hash Algorithm that produced it. For a container image, the manifest digest already is a cryptographic hash of the manifest, which makes it the natural key for every report. An SBOM keyed only by tag cannot be matched back to a deployed image.
The OCI Distribution and Image specifications. The Distribution spec defines how clients list tags and pull manifests by either a tag or the manifest's digest, and requires that a digest the registry returns matches the content it serves. The Image spec defines the manifest that the digest names. These two documents are why digest pinning works the same way across all four registries.
What the standards leave to you is the registry-specific work: which repositories to scan, what the read-only credential looks like in each product, and how to trust a push notification. That is the rest of this post.
Step by step, with or without Safeguard
These steps work with any scanner that can pull from a registry. Each one is followed by what changes from registry to registry.
Step 1: inventory your registries and their repository types
Write down every registry that serves container images, and for each one, every repository type it uses. The four products name their types differently:
| Registry | Where images live | Types that hold their own images | Types that aggregate or cache |
|---|---|---|---|
| JFrog Artifactory or JFrog Container Registry | Docker repositories (SaaS or self-hosted) | Local, federated | Remote (caches an upstream registry), virtual (merges others) |
| Sonatype Nexus Repository 3 | Docker repositories (Community or Pro) | Hosted | Proxy (caches an upstream), group (merges others) |
| Harbor (2.0 or later) | Projects, each with repositories | Projects | Proxy cache projects, where configured |
| Red Hat Quay (quay.io or self-hosted) | Namespaces (organisations and users), each with repositories | Repositories | Mirrored repositories, where configured |
The aggregate types are where inventories go wrong. A virtual repository in Artifactory and a group repository in Nexus present the images of their members under a second name. If you scan both the member and the aggregate, you have two reports for one image, and when a vulnerability database update lands between the two scans, they disagree.
Step 2: decide which repositories are in scope
A workable default:
- Scan every local, hosted or federated repository, every Harbor project and every Quay repository that holds images your teams build.
- Scan remote and proxy repositories that cache base images, because those caches are what your builds actually pull. Scan them by digest, so the report describes the cached copy, not whatever upstream holds today.
- Skip virtual and group repositories unless a team pulls only through one and you cannot reach the members. If you do include one, exclude its members.
- Mark stale repositories. NIST SP 800-190 section 3.2.2 warns about stale images left in registries. A repository nobody has pushed to in a year still holds images somebody may deploy. Scan it, and decide whether it should exist.
Step 3: create a read-only credential in each registry
The scanner needs to list repositories, list tags, read manifests and pull layers. It needs nothing else. The table below is the least-privilege credential for each product, as the vendors' permission models allow it.
| Registry | Recommended credential | What it needs | What it never needs |
|---|---|---|---|
| Artifactory | Scoped access token for a group (or user) that has only Read on the Docker repositories | Read on each Docker repository | Annotate, Deploy/Cache, Delete/Overwrite, Manage, admin rights, any permission on non-Docker repositories |
| Nexus | A dedicated local user with one role holding the Docker browse and read privileges; on Pro, that user's user token | nx-repository-view-docker-<repository>-browse and -read for each Docker repository | nx-admin, any add, edit or delete privilege, nx-repository-admin-*, security, blob store or task privileges |
| Harbor | A project robot account per project, or one system robot for several | Repository: List and Pull; Artifact: List and Read; Project: Read for a system robot | Push, Delete, Project admin or Maintainer roles, system administrator rights |
| Quay | An OAuth application token with repo:read (and user:read to discover namespaces), or a robot with Read | Read on each repository | repo:write, repo:admin, repo:create, org:admin, user:admin, super:user |
Three rules apply everywhere:
- A dedicated identity. Never a person's account and never the administrator's token.
- An expiry. Artifactory access tokens and Harbor robot accounts take an expiry at creation. Nexus user tokens expire on the schedule the administrator sets. Quay OAuth and robot tokens do not expire by default, so put rotation on a calendar. Ninety days or less is a sensible default for all four.
- One place to revoke it. Write down where the credential lives in the registry and in the scanner, so offboarding is quick.
Step 4: pin every scan to a digest
Before the scanner pulls an image, it should resolve the tag to the manifest digest and pull by that digest. The report then carries the digest, and the digest is what you compare with the image running in your cluster. If the tag moves while the scan runs, the report still describes one specific image.
For multi-architecture images, the tag points at an index, and the index points at one manifest per platform. Decide which platform you scan and record that manifest's digest, not just the index's.
Step 5: rescan on push with an authenticated webhook
All four registries can call a URL when an image is pushed. The difference is how the receiver knows the call is genuine:
| Registry | Event to subscribe to | Where the secret goes | How the receiver checks it |
|---|---|---|---|
| Artifactory | Docker domain, "Docker tag was pushed" | Secret token, with "Use secret for payload signing" ticked | HMAC-SHA256 of the body in the X-JFrog-Event-Auth header (without signing, the header carries the secret itself) |
| Nexus | Webhook: Repository capability, event type component, one per Docker hosted or proxy repository | Secret Key | HMAC-SHA1 of the body in X-Nexus-Webhook-Signature |
| Harbor | Project webhook, event type Artifact pushed, one per project | Auth Header | The secret arrives in the Authorization header |
| Quay | Repository notification, event Push to Repository, method Webhook POST, one per repository | In the webhook URL as a query parameter | Quay sends no custom headers and does not sign notifications, so the receiver compares the token in the URL |
What this means in practice:
- Prefer payload signing wherever it exists.
- Nexus group repositories cannot carry a webhook. Put the capability on each hosted or proxy member.
- Quay's design means the full webhook URL is a credential. Keep it out of tickets, chat and screenshots, and rotate it if it leaks.
- Keep certificate verification on in Harbor's webhook settings, and make sure the receiving endpoint presents a certificate the registry trusts.
Step 6: rescan on a schedule as well
Push webhooks catch new images. They do not catch new vulnerabilities in old images. Schedule a rescan of every in-scope image, at least weekly for anything deployed, and immediately after a high-profile advisory touches a package that is common in your base images. NIST SP 800-190 is explicit that images should be scanned throughout their lifecycle, not only at build.
Step 7: decide what blocks, what warns and who approves exceptions
Write the rule before the first report arrives. A starting policy for registry images:
| Condition | Action |
|---|---|
| Critical or high vulnerability with a fixed version available, in an image tagged for production | Block |
| Vulnerability listed in CISA's Known Exploited Vulnerabilities catalog, any severity | Block |
| Critical or high vulnerability with no fixed version yet | Warn, and notify the owning team |
| Image in a stale repository that is still deployed | Notify the registry owner |
| Base image cached in a proxy or remote repository with a newer patched digest upstream | Warn the teams that build on it |
| Anything blocked that the business must ship | An exception with an owner, a reason and an expiry date |
Keep the exception path quick, or people will switch the gate off instead.
Step 8: rotate, and plan the disconnect on day one
When you rotate, create the new credential first, update the scanner, verify, and only then revoke the old one. When you retire a scanner, remove its credential in the registry, its webhooks or notifications, and its connection in the scanner.
Registry by registry: what is different
The steps above are the same for every registry. The traps are not.
JFrog Artifactory and JFrog Container Registry
- Three ways to address a Docker repository. Artifactory can serve Docker clients by repository path (
host/repository-key/image, the SaaS default), by subdomain (on SaaS, the server name and repository key joined into a jfrog.io subdomain; self-hosted, the repository key as a subdomain of your host), or by a dedicated port per repository. Your scanner has to use the same method your Docker clients use, or pulls fail even though listing works. - API keys are on the way out. JFrog has deprecated API keys and may turn them off on your platform. Use scoped access tokens.
- Reference tokens hide their user. A scoped token carries its user, so a client can read the user name from it for Docker pulls. A reference token does not, so the user name has to be supplied with it.
- The platform URL is not the UI URL. The REST API lives under the platform URL, without
/ui.
Sonatype Nexus Repository
- Docker needs a connector or a reverse proxy. Nexus serves Docker clients only on a repository's own HTTP or HTTPS connector port, or behind a reverse proxy that routes to it. A repository with neither can be listed through the REST API but cannot be pulled by any Docker client, scanner included.
- Community Edition refuses Docker requests until onboarding is done. An administrator has to accept the Community Edition end user licence agreement once, through the onboarding wizard. Until then, every Docker request fails.
- User tokens are a Pro feature. On Community, use a dedicated local user and password.
- LDAP and SAML users. These work only if that sign-in method accepts Basic authentication and is switched on in the Nexus security settings.
Harbor
- Robot account names include a prefix. A project robot is named
robot$project+name; a system robot isrobot$name. Copy the whole name, includingrobot$. - Harbor often runs its own scanner. Keep its report and any second scan clearly labelled by source (see the next section).
- OIDC users sign in with a CLI secret. When Harbor signs users in through OIDC, the Docker password is the user's CLI secret, found under User Profile.
- Harbor 1.x is out of scope for v2 APIs. Integrations built on the v2.0 API need Harbor 2.0 or later.
Red Hat Quay
- Robots cannot call the Quay API. A Quay robot account can pull and use the registry catalog, but cannot call the
/api/v1endpoints. A scanner that uses a robot lists repositories and tags through the registry protocol and resolves each digest when it scans. An OAuth application token can do both. - Scopes do not override roles. An OAuth token with
repo:readcan read only the repositories its user can read. Give the token's user read access to what you want scanned.
Is the registry's built-in scan enough?
For a single registry with one team, it may be. It stops being enough when you run several registries and need one inventory, when its export lacks digests, when you need a standard-format SBOM per image, or when gating and exceptions must live in one place. Where two vulnerability sources disagree, keep both reports, label each by source, and settle it against the package's own advisory.
How do you trust a push notification?
Check it the way the registry signs it (see the table in Step 5), and treat the notification as a hint to rescan, not as data to trust. The receiver should fetch the manifest from the registry itself and scan by the digest it resolves, never by a digest the notification body claims.
How Safeguard does this
Safeguard connects to each of the four registries through a card on the Integrations page. The steps below follow the docs.safeguard.sh integration pages for JFrog Artifactory, Sonatype Nexus Repository, Harbor and Red Hat Quay as read on 25 October 2026. Commands and API calls are copied from those pages; we did not run them for this post.
What the four cards have in common:
- Each card lists the registry's repositories (or projects, or namespaces), every image in them and every tag. Each image you select becomes a Safeguard project that is scanned for an SBOM (SBOM generation and analysis, GA) and vulnerability findings (vulnerability management, GA).
- Each scan is pinned to the digest the tag points at when the scan starts, and pulls the image with the credential you connect.
- Each card has a Public tab for anonymous reads and a Private tab for a credential. The credential is encrypted at rest and never shown again after you save it.
- Safeguard reads only. It stores the connection's settings, the SBOM and findings of each scanned image with its digest, and the credential. It does not store image layers: the image is read for the scan and not kept. No customer data from these integrations is sent to third-party LLMs.
- Scan on push is optional. Without a webhook, you rescan on a schedule under Integrations, Scheduled.
- Each card is switched on per workspace. If you do not see a card under Integrations, ask your Safeguard contact.
JFrog Artifactory (GA)
The card covers Docker and OCI images in JFrog Artifactory or JFrog Container Registry, on SaaS (jfrog.io) or self-hosted. It lists local, remote, virtual and federated Docker repositories.
Create the credential in JFrog:
- As an administrator, open Administration, User Management, Groups, and create a group (for example
safeguard-read), or use the defaultreadersgroup. - Open Administration, User Management, Permissions, New Permission. Add the Docker repositories, give the group Read only, and save.
- Open Administration, User Management, Access Tokens, Generate Token. Choose Scoped Token, token scope Group with the group above (or User with a read-only user), set an expiration and generate. Copy the token; JFrog shows it once.
Or with the JFrog CLI, as the docs show it:
# A read-only token for the safeguard-read group, valid for 90 days
jf access-token-create --groups safeguard-read --expiry 7776000
Connect it in Safeguard:
- Open Integrations and choose the Project/Product tab. Click Connect on the JFrog Artifactory card.
- Choose Private (or Public for anonymous access), enter a Name and the JFrog platform URL, for example
https://acme.jfrog.ioorhttps://artifactory.example.com. - For Private, pick the Sign-in method and paste the access token, or enter the Username and the password, identity token or API key. Access token is the recommended method. For a reference token, enter the token's user in Username too.
- Set the Docker access method to match how your Docker clients reach Artifactory: Repository path, Subdomain, or Port (one
repository-key=host:portentry per repository). - Optionally, limit listing under Repositories. A virtual repository is listed only when you name it here, so its images are not listed twice.
- Click Verify Credentials. "Connected. N Docker repositories are readable." means JFrog accepted the credential.
- Click Next, select images and tags, and choose a connect option: fast, progressive or deep scan.
Scan on push: open the connection's edit dialog and expand Scan on push (optional) for the Webhook URL and the Secret. In the JFrog Platform, open Administration, General, Webhooks, Create a Webhook. Choose the Docker event "Docker tag was pushed" for the connected repositories, paste the URL, put the secret in Secret token and tick Use secret for payload signing. With signing on, JFrog sends an HMAC-SHA256 of the body in X-JFrog-Event-Auth; without it, the header carries the secret itself. Safeguard accepts either and refuses anything else. A pushed tag is scanned as the version named by the tag plus the first 12 characters of the digest.
What the card does not read: npm, Maven, PyPI or any other package repository in Artifactory. It covers Docker and OCI repositories only.
Sonatype Nexus Repository (GA)
The card covers Docker repositories in a Nexus Repository 3 server, Community or Pro: hosted, proxy and group repositories, every image and every tag.
Create the credential in Nexus:
- Sign in as an administrator and open Settings (the gear icon), Security, Roles, Create Role.
- Name the role (for example
safeguard-read) and add the privilegesnx-repository-view-docker-<repository>-browseandnx-repository-view-docker-<repository>-readfor each Docker repository, or the*versions for all of them. - Open Security, Users, Create local user. Give it the
safeguard-readrole only, set a password and save. - On Nexus Repository Pro with user tokens turned on, sign in as that user, select the user name at the top right, open User Token, Access User Token, re-enter the password, and copy the name code and pass code. The dialog closes after one minute.
- On Nexus Community only, make sure an administrator has accepted the Community Edition end user licence agreement once, through the onboarding wizard. Until then, Nexus refuses every Docker request.
Or with the Nexus REST API, as the docs show it:
# As an administrator. Creates the read-only role and user.
curl -u admin -X POST -H "Content-Type: application/json" \
https://nexus.example.com/service/rest/v1/security/roles \
-d '{"id":"safeguard-read","name":"safeguard-read","privileges":["nx-repository-view-docker-*-browse","nx-repository-view-docker-*-read"],"roles":[]}'
curl -u admin -X POST -H "Content-Type: application/json" \
https://nexus.example.com/service/rest/v1/security/users \
-d '{"userId":"safeguard","firstName":"Safeguard","lastName":"Scanner","emailAddress":"safeguard@example.com","password":"<password>","status":"active","roles":["safeguard-read"]}'
Safeguard does not read the per-repository settings endpoint, so no nx-repository-admin privilege is needed.
Connect it in Safeguard:
- Open Integrations and choose the Project/Product tab. Click Connect on the Sonatype Nexus Repository card.
- Choose Private (or Public for anonymous access), enter a Name and the Nexus Repository URL, for example
https://nexus.example.com, adding the context path such as/nexusif your server has one. - For Private, pick the Sign-in method (Password or User token), then enter the Username (or Name code) and the password (or pass code).
- Optionally, set the Docker pull host. Nexus Community serves Docker clients only on a repository's connector port or behind a reverse proxy, so enter that host and port (for example
nexus.example.com:8443), or onerepository=host:portentry per repository. Leave it empty to use the URL's host with the connector port Nexus reports. - Optionally, limit listing under Repositories.
- Click Verify Credentials (or Check anonymous access). "Connected. N Docker repositories are readable." means Nexus accepted the credential.
- Click Next, select images and tags, and choose a connect option: fast, progressive or deep scan.
Scan on push: open the connection's edit dialog and expand Scan on push (optional) for the Webhook URL and the Secret. In Nexus, open Settings, System, Capabilities, Create capability, Webhook: Repository. Choose one Docker hosted or proxy repository (group repositories cannot have one), set Event Types to component, paste the URL and put the secret in Secret Key. Add one capability per repository. Nexus signs every delivery with an HMAC-SHA1 of the body in X-Nexus-Webhook-Signature, and Safeguard refuses deliveries that do not match. A pushed tag is scanned as the version named by the tag, pinned to its digest.
Harbor (GA)
The card covers projects in a Harbor registry, Harbor 2.0 or later: every repository and every tag. Beside each tag, Safeguard shows Harbor's own scan summary and whether the image is signed, labelled as Harbor's. Safeguard's findings come from its own scan, and Harbor's vulnerability reports are not imported as Safeguard findings.
Create the credential in Harbor:
- Open the project, then Robot Accounts, New Robot Account.
- Give it a name and an expiry, and tick only Repository: List, Pull and Artifact: List, Read.
- Click Add. Copy the robot's full name (it starts with
robot$) and its secret; Harbor shows the secret once.
For several projects, a system robot works too, with Project: Read added so it can list the projects it covers. If no robot account is allowed, a dedicated user with the Limited Guest (or Guest) role on each project works, signing in with its password, or with its CLI secret when Harbor uses OIDC.
Or with the Harbor API, as the docs show it:
curl -u admin -X POST -H "Content-Type: application/json" https://harbor.example.com/api/v2.0/robots -d '{
"name": "safeguard", "level": "project", "duration": 90,
"permissions": [{"kind": "project", "namespace": "<project>", "access": [
{"resource": "repository", "action": "list"}, {"resource": "repository", "action": "pull"},
{"resource": "artifact", "action": "list"}, {"resource": "artifact", "action": "read"}]}]}'
Connect it in Safeguard:
- Open Integrations and choose the Project/Product tab. Click Connect on the Harbor card.
- Choose Private (or Public), enter a Name and the Harbor URL, for example
https://harbor.example.com. Public reads only projects marked Public in Harbor. - For Private, pick Robot account and enter the robot account name and the secret, or pick Password with a user name.
- Optionally, limit listing under Projects.
- Click Verify Credentials (or Check anonymous access).
- Click Next, select repositories and tags, and choose a connect option: fast, progressive or deep scan.
Scan on push: open the connection's edit dialog and expand Scan on push (optional) for the Webhook URL and the Secret. In Harbor, open the project, then Webhooks, New Webhook: Notify Type http, Event Type Artifact pushed, Endpoint URL the URL from Safeguard, Auth Header the secret, and keep certificate verification on. Payload format Default or CloudEvents both work. Add one webhook per project. Harbor sends the Auth Header in Authorization, and Safeguard refuses deliveries without the matching secret. A pushed tag is scanned as the version named by the tag plus the first 12 characters of the digest.
Red Hat Quay (GA)
The card covers repositories on quay.io or a self-hosted Red Hat Quay: the repositories of your namespaces (organisations and users) and every tag.
Create the credential in Quay, OAuth token (recommended):
- Open the organisation, then Applications, Create New Application, and name it (for example
safeguard). - Open the application, choose Generate Token, tick View all visible repositories (and Read User Information if you want namespaces discovered), and authorise it.
- Copy the access token; Quay shows it once.
Or a robot account (registry-only access):
- Open the organisation, then Robot Accounts, Create Robot Account.
- Give it Read on each repository to scan.
- Open the robot and copy its name and token.
Robots cannot call the Quay API, so with a robot Safeguard lists through the registry, and tags come without digests until a scan resolves them. To grant an existing robot Read on one repository through the API, as the docs show it:
# Give an existing robot Read on one repository (as an organization administrator).
curl -X PUT -H "Authorization: Bearer <admin token>" -H "Content-Type: application/json" \
https://quay.io/api/v1/repository/<namespace>/<repository>/permissions/user/<namespace>+<robot> \
-d '{"role": "read"}'
Connect it in Safeguard:
- Open Integrations and choose the Project/Product tab. Click Connect on the Red Hat Quay card.
- Choose Private (or Public) and enter a Name. The Quay URL defaults to
https://quay.io; change it for a self-hosted Quay. - For Private, pick OAuth token and paste the token, or Robot account with the robot account name and its token. A Quay user with its password or encrypted password also works, registry-only like a robot.
- Enter Namespaces: the organisations or users to list. They are required for Public. With an OAuth token you can leave them empty to list your user and organisations.
- Click Verify Credentials (or Check anonymous access).
- Click Next, select repositories and tags, and choose a connect option: fast, progressive or deep scan.
Scan on push: open the connection's edit dialog and expand Scan on push (optional) for the Webhook URL and the Secret. In Quay, open the repository, then Settings, Events and Notifications, Create Notification: event Push to Repository, notification Webhook POST, and the Webhook URL followed by ?token= and the secret. Quay sends no custom headers and does not sign notifications, so the secret travels in the URL; keep the URL private. Add one notification per repository. Each delivery rescans the pushed tags, pinned to their digests at scan time.
Side by side
| JFrog Artifactory | Sonatype Nexus Repository | Harbor | Red Hat Quay | |
|---|---|---|---|---|
| Recommended credential | Scoped access token on a read-only group | Dedicated local user (Community) or user token (Pro) | Project robot account | OAuth application token with repo:read |
| What Safeguard lists | Local, remote, virtual and federated Docker repositories | Hosted, proxy and group Docker repositories | Projects and their repositories | Namespaces and their repositories |
| Scan pinned to digest | Yes | Yes | Yes | Yes |
| Push webhook check | HMAC-SHA256 in X-JFrog-Event-Auth, or the secret itself | HMAC-SHA1 in X-Nexus-Webhook-Signature | Secret in Authorization | Token in the URL |
| Webhook scope | Connected repositories | One capability per hosted or proxy repository | One webhook per project | One notification per repository |
| Version name for a pushed tag | Tag plus first 12 characters of the digest | Tag, pinned to its digest | Tag plus first 12 characters of the digest | Pushed tags, pinned to their digests |
| Registry's own scan shown | No | No | Yes, labelled as Harbor's | No |
Rotation and disconnect
The four pages describe the same pattern:
- Rotate. Create the new credential in the registry (new access token, new password or reset user token, refreshed robot secret, new OAuth or robot token). In Safeguard, find the connection under Project Configurations on the Project/Product tab, choose Update, paste the new value, verify and save. Then revoke the old credential. On Nexus, a reset user token invalidates the old one at once, so update Safeguard straight after.
- Disconnect. Choose Delete from the connection's menu under Project Configurations and confirm. Then remove the credential in the registry (revoke the token, delete the user or the robot) and delete every webhook, capability or notification that points at Safeguard.
Bring the results together
- One inventory. Every image you select becomes a project, so images from all four registries sit alongside the rest of your projects, each with its SBOM and findings and the digest it was scanned at.
- Gate on findings. Use Safeguard policies and gates (GA). The policies and gates page defines three actions: Block fails the gate, Warn passes it with a warning naming the policy, and Notify passes it and sends a notification. When a release must ship past a failing policy, someone requests an exception and an approver signs it off. The policy table in Step 7 maps onto these actions directly.
- Fix at the source. Findings in a registry image point at OS packages and language dependencies inside it. The fix lands in the Dockerfile or Containerfile and in the repository that builds the image. Connect that repository to Safeguard through its source code card, so its dependencies are covered by autonomous remediation (GA), which ships switched off with every category set to Manual.
- Keep scanning. Use scan on push where you can, and a schedule under Integrations, Scheduled everywhere, so images that do not change still get checked against new advisories.
What Safeguard does not do
- It does not write to your registry. Safeguard never pushes, deletes, retags, promotes or deploys images, and never changes registry or project settings. The only thing that touches the registry's configuration is the webhook, capability or notification you add yourself.
- It does not keep your image layers. The image is read for the scan and not kept. Safeguard stores the SBOM, the findings and the digest. If you need the image itself for forensics later, your registry's retention policy is what keeps it.
- It does not read package repositories through the Artifactory card. npm, Maven, PyPI and other package repositories in Artifactory are outside this card. Cover the code that uses those packages through Safeguard's source code integrations instead.
- It does not import Harbor's own scan as findings. Harbor's scan summary and signature status are shown beside each tag, labelled as Harbor's. Safeguard's findings come from Safeguard's scan only. If the two disagree, you decide which advisory is right.
- It does not make Quay notifications signed. Quay does not sign them. Safeguard checks the token in the URL, and keeping that URL private is your job.
- It does not put webhooks on Nexus group repositories. Nexus does not allow one there. Add a capability on each hosted or proxy member.
- It does not accept self-signed or privately issued TLS certificates on these registries. Each troubleshooting table says to serve the registry with a certificate from a public certificate authority, including intermediates for Nexus.
- It does not choose your scope or your exceptions. Which repositories are in scope, which aggregate repositories to skip, who approves an exception and for how long: those are your decisions, and the tool records them rather than making them.
Common mistakes
- Connecting with the administrator's token. It works, so nobody changes it. Every one of the four products has a read-only path; use it from the start.
- Scanning the virtual or group repository and its members. Two reports per image, and they will drift apart.
- Forgetting the Docker access method in Artifactory. Listing succeeds over the REST API, then every pull fails because the scanner uses path addressing and your platform uses subdomains or ports.
- Expecting a Nexus repository without a connector port to be pullable. It can be listed but not pulled. Give it a connector port or a reverse proxy route.
- Skipping the Nexus Community onboarding wizard. Every Docker request fails until an administrator accepts the licence agreement.
- Copying a Harbor robot name without the
robot$prefix. The registry rejects the credential and the error looks like a wrong secret. - Pasting a Quay webhook URL into a ticket. The token is in the URL. Anyone who sees the URL can trigger rescans.
- Leaving webhooks behind after a disconnect. The registry keeps calling an endpoint you no longer control.
FAQ
Can Safeguard scan images in a self-hosted JFrog Artifactory?
Yes. The JFrog Artifactory card works with SaaS (jfrog.io) and self-hosted Artifactory, and with JFrog Container Registry. Enter your platform URL, without /ui, and match the Docker access method to how your Docker clients reach the platform.
Which Nexus privileges does a scanner need for Docker images?
The browse and read privileges for each Docker repository: nx-repository-view-docker-<repository>-browse and nx-repository-view-docker-<repository>-read, or the * versions for every Docker repository. Safeguard does not need nx-admin, any add, edit or delete privilege, or nx-repository-admin.
Does Safeguard work with Nexus Community Edition?
Yes, with a dedicated local user and password. User tokens are a Nexus Repository Pro feature. On Community, an administrator must accept the licence agreement through the onboarding wizard once, and each Docker repository needs a connector port or a reverse proxy for pulls.
What Harbor robot account permissions does Safeguard need?
Repository: List and Pull, and Artifact: List and Read, on each project to scan. A system robot covering several projects also needs Project: Read. Nothing that writes or deletes.
Does Safeguard show Harbor's own scan results?
Yes, beside each tag, with whether the image is signed, labelled as Harbor's. They are not imported as Safeguard findings; Safeguard's findings come from its own scan.
Can Safeguard scan private repositories on quay.io?
Yes. Use an OAuth application token with View all visible repositories (repo:read), or a robot account with Read on each repository. The token's user, or the robot, must be able to read every repository you want scanned.
Why do Quay tags show no digest until the scan runs?
With a robot account, Safeguard cannot call the Quay API, so it lists through the registry protocol and resolves each digest when it scans. With an OAuth token, the API returns tags with their manifest digests.
Can I block a deployment when a registry image has a critical vulnerability?
Yes, with a Safeguard policy gate set to Block. Gates also support Warn and Notify, and a release that must ship past a failing policy goes through an exception and an approval.
Does Safeguard keep a copy of my images?
No. The image is read for the scan and not kept. Safeguard stores the connection settings, the encrypted credential, and the SBOM and findings of each scanned image with its digest.
Each of these cards tells you what is inside a registry image and whether a known fix exists for each finding. When the fix sits in an upstream package or base image that has not shipped it yet, the next step is the same one that applies to any image you build.
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 first demo is a real Safeguard CLI session: install, safeguard opens the interactive terminal, and a request to fix a lodash CVE in a repository. The CLI reads the finding and the advisory, asks before any command that changes something, edits package.json, runs npm install and npm test with approval, prints a summary and pushes nothing. That is the same loop you run on the repository that builds a registry image once its scan points at a fixable dependency.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with its device code, and then scans from the terminal. It shows how scans run from a machine you control, which is useful when you want to check an image's build repository locally before you push a rebuilt image to the registry.
Click through it at your own pace, or open it full screen.
Quick check
Your Nexus Docker hosted repository lists fine in the scanner, but every pull fails. What is the likely cause?
The repository has no HTTP or HTTPS connector port and no reverse proxy, so Docker clients cannot pull from it. Give it a connector port or a reverse proxy route, and enter that host and port as the Docker pull host. On Community Edition, also check that the onboarding licence agreement was accepted.
You add Quay scan on push. Where does the webhook secret go, and why does that matter?
In the webhook URL, as the token query parameter, because Quay does not sign notifications or send custom headers. That makes the full URL a secret: keep it out of tickets and chat, and rotate it if it leaks.
Related reading
- Harbor registry security configuration
- Nexus Repository security hardening
- Artifactory security best practices
- Scan Podman, Apptainer SIF and WebAssembly images with Safeguard
- Scan Oracle Cloud DevOps repositories and OCIR images with Safeguard
Sources
- Safeguard Docs: JFrog Artifactory integration
- Safeguard Docs: Sonatype Nexus Repository integration
- Safeguard Docs: Harbor integration
- Safeguard Docs: Red Hat Quay integration
- Safeguard Docs: Policies and gates
- NIST SP 800-190, Application Container Security Guide
- NIST SP 800-218, Secure Software Development Framework 1.1
- CISA: 2026 Minimum Elements for a Software Bill of Materials
- CISA: 2026 Minimum Elements for an SBOM (PDF)
- NIST SP 800-190 full text (PDF)
- NIST SP 800-218 full text (PDF)
- OCI Distribution Specification
- OCI Image Specification: image manifest
Try it yourself: download Safeguard or read the docs.
Last reviewed: 25 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.