The username is admin. The password is Harbor12345. It is printed in the install docs, sitting in plain text in harbor.yml, and — on every Harbor registry whose operator skipped one manual hardening step — it still works. In March 2026, that skipped step got a CVE number and a CVSS 9.4: CVE-2026-4404, hardcoded default credentials in Harbor 2.15.0 and below, letting anyone who can reach your registry log in as administrator and pull, push, delete, or quietly poison every image inside it.
This one stings because Harbor is not some sketchy side project. It is a CNCF-graduated registry, originally from VMware, and the default answer to "how do I own my container registry" — the proxy-cache people stand up to dodge Docker Hub rate limits, the registry behind countless self-hosted build pipelines. If you run one, stop skimming and do the five-minute audit below. Verdict up front: if you installed Harbor and never changed the admin password, treat the registry as exposed until you prove otherwise — CERT/CC warns attackers actively scan the internet for Harbor instances and try exactly these credentials.
Are you exposed? The five-minute audit
Three checks, in order. You need nothing but curl and your registry's hostname.
1. What version are you running? Harbor reports its own version from the system-info endpoint, which typically answers without authentication:
curl -s https://harbor.example.com/api/v2.0/systeminfoLook for the harbor_version field. Anything at or below 2.15.0 carries the vulnerable default-credential behavior; 2.15.1 and later include the fix. If this endpoint answers from the public internet at all, note that down — it also answers check 3 for you.
2. Do the default credentials still work? Ask the API who you are when you log in as the factory admin:
curl -s -o /dev/null -w "%{http_code}\n" -u 'admin:Harbor12345' \
https://harbor.example.com/api/v2.0/users/currentHTTP 200 means the defaults are live and anyone on the network can do what you just did. HTTP 401 means the password was rotated and this specific hole is closed (you still want the upgrade in step 3 below, but the bleeding has stopped). Run this from outside your cluster, the way an attacker would — a 200 from the internet and a 200 from localhost are very different emergencies.
3. Can the internet reach your registry at all? Check your load balancer, ingress, and firewall rules: is port 443 on the Harbor hostname reachable from outside your network? A registry that only answers inside the VPC still needs the password rotated, but the blast radius is coworkers and compromised workloads, not the entire scanning internet. A registry answering the world with default credentials is the exact scenario CERT/CC wrote its warning about.
If all three checks come back bad — old version, working defaults, public exposure — skip the rest of this section and go straight to "The fix." Then come back for the compromise hunt, because at that point you should assume someone else found it first.
The fix: rotate first, upgrade second, verify third
Order matters here, and the intuitive order is wrong. Upgrading first feels responsible, but the upgrade does not change an existing admin password — on a running Harbor, the harbor_admin_password value in harbor.yml is only read at first launch and ignored forever after. An upgraded registry with the old password is still a compromised registry. So: close the hole first, patch second, prove it third.
Rotate the admin password now. On a running instance, change it through the Portal (Administration → Users → admin) or the API — admin is user ID 1, and you must supply the current password:
curl -s -X PUT -u 'admin:Harbor12345' \
-H 'Content-Type: application/json' \
-d '{"old_password":"Harbor12345","new_password":"<long-random-value>"}' \
https://harbor.example.com/api/v2.0/users/1/passwordGenerate the replacement properly — 32 random characters from a password manager or openssl rand -base64 24 — and store it where your team actually keeps secrets, not in a chat thread. For fresh installs, set a unique harbor_admin_password in harbor.yml before the first ./install.sh, or set HARBOR_ADMIN_PASSWORD so the installer generates one instead of falling back to the documented default.
Helm users get a special gotcha: the chart's existingSecretAdminPassword is read exactly once, at first install, and persisted into Harbor's Postgres. Updating the Secret later does nothing — Harbor never re-reads it. If your admin password and your Secret disagree, the database wins, and you fix it through the Portal or API like everyone else, then reconcile the Secret to match reality.
Upgrade to 2.15.1 or later. Follow the standard Harbor upgrade path for your install method (offline installer or Helm chart upgrade), with a database backup first — the registry holds your entire image supply chain, and an upgrade without a tested backup is just a different kind of incident. The patched releases enforce the fixed credential behavior; the project's longer-term direction is to remove or randomize the default entirely rather than keep documenting a password everyone knows.
Verify by re-running the audit. The default-credential check from section 2 must now return 401. The version endpoint must report 2.15.1+. If either fails, you changed the wrong thing — the most common miss is editing harbor.yml on a running system and assuming it took effect. It did not; see above.
If it was exposed, assume compromise
Rotating the password evicts the attacker from the front door. It does nothing about anything they installed while they had the keys. If your Harbor was reachable with working defaults — especially from the internet — work through the persistence hunt before you declare victory:
- Read the audit logs. Harbor records who did what (Recent Logs under each project, plus operator audit entries). Look for
adminlogins from IPs you do not recognize, at hours your team does not work. No log review, no clean bill of health. - Enumerate everything admin-shaped. List all users and check for unfamiliar accounts or unexpected admin grants; list robot accounts and their secrets; list replication rules and policies — a replication endpoint pointing at an attacker's registry is a quiet exfiltration channel that survives your password rotation.
- Rotate more than the admin password. Every robot-account secret, every replication credential, every webhook and OIDC client secret that lived in the registry while defaults were active. Attackers with admin export first and persist second; assume they have copies.
- Quarantine the images, not just the accounts. An admin can push. That means any image in the registry could have been replaced or retagged with a poisoned twin. Before trusting the contents again, re-pull critical images from their trusted upstream sources, compare digests against known-good values, and turn on the controls that make a repeat visible: scan-on-push vulnerability scanning, immutable tags on release artifacts, and Cosign signature verification on the consuming clusters so an unsigned replacement fails closed at admission time.
This is the unglamorous half of the fix and the half most "change the default password" advisories skip. A registry is a supply-chain control point: every cluster pulling from it inherits whatever is inside. Treating a default-credential exposure as a login problem rather than a supply-chain incident is how poisoned images outlive the vulnerability that planted them.
Why a graduated project ships a hardcoded password
Harbor earned its reputation honestly — graduated CNCF status, enterprise RBAC, replication, vulnerability scanning, proxy caching — which is exactly why the failure mode matters. The default was never a secret: admin / Harbor12345 sits in the install guide's example harbor.yml, in a thousand blog posts and Stack Overflow answers, and now in every attacker's default-credential list. The project's mistake was not having an initial password; it was making rotation advisory instead of enforced. No forced reset on first login, no install blocker, no loud warning — just documentation asking every operator, forever, to remember one manual step.
We have seen this film before. In 2019, CVE-2019-16097 gave attackers a zero-to-admin path in Harbor, and Unit 42's scans found roughly 1,300 Harbor registries open to the internet with vulnerable settings. Seven years later the mechanism is duller — no exploit chain, just the documented password — but the shape is identical: a registry people install quickly to solve an immediate problem (usually Docker Hub rate limits), exposed a little too broadly, with the hardening step deferred to a someday that never comes.
To the project's credit, the response points the right way: not better documentation of the same default, but removing or randomizing the default during install so there is no well-known value to try. Defaults that must be changed to be safe are not defaults; they are traps with instructions attached. The fix for a trap is not a bigger sign.
The first-boot rule for your own platform
Here is the generalizable lesson, and it applies far beyond Harbor: no install path you ship may depend on the operator remembering to rotate a credential. If your platform, chart, or appliance creates an admin account with a known password and hopes someone changes it, you have shipped CVE-2026-4404 with your own name on it. The rule has three parts:
- Generate, don't document. Create random credentials at install time and print or store them once — never a value that also appears in your docs, your repo, or anyone's tutorial. Harbor's move toward generated passwords is the pattern; copy it.
- Make the secure path the only path. If a manual step can be skipped, it will be skipped at 2 AM during an outage rebuild. Script rotation into the installer, force a reset on first login, or refuse to serve traffic until the default is gone. Advisory hardening is decoration.
- Shrink the blast radius by default. Registries, dashboards, and admin APIs should bind to private networks unless the operator explicitly opts into exposure. Authentication is the second lock; not being reachable is the first.
Apply the same audit to the rest of your self-hosted stack while you are at it. The Harbor instance is fixed; the Grafana, the Argo CD, the MinIO next to it may be running their own documented defaults behind the same load balancer. Default credentials are a fleet problem wearing a single-CVE costume.
Your registry holds every image your clusters trust. A five-minute audit, a rotated password, and an upgrade you should have scheduled months ago — that is the whole price of keeping it yours.
Self-hosting the registry is only half the job — the other half is a deploy pipeline that treats credential hygiene as code, not folklore. Bex.co is the open-source, AI-native Render alternative for machines you own: star the repo on GitHub.



