Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Harbor is an open-source container registry that provides security features including vulnerability scanning (integrated Trivy), image signing (Notary/Cosign), RBAC, content trust policies, replicatio
.claude/skills/securing-container-registry-with-harbor/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-16 | ✗→✓ | ▲ Improved | — | — |
Harbor is an open-source container registry that provides security features including vulnerability scanning (integrated Trivy), image signing (Notary/Cosign), RBAC, content trust policies, replication, and audit logging. Securing Harbor involves configuring these features to enforce image provenance, prevent vulnerable image deployment, and maintain registry access control.
yaml# harbor-values.yaml for Helm deployment expose: type: ingress tls: enabled: true certSource: secret secret: secretName: harbor-tls notarySecretName: harbor-tls ingress: hosts: core: harbor.example.com notary: notary.example.com externalURL: https://harbor.example.com persistence: enabled: true resourcePolicy: "keep" harborAdminPassword: "<strong-password>" trivy: enabled: true gitHubToken: "<github-token>" severity: "CRITICAL,HIGH,MEDIUM" autoScan: true notary: enabled: true core: secretKey: "<32-char-secret>" database: type: external external: host: postgres.example.com port: "5432" username: harbor password: "<db-password>" sslmode: require
bashhelm repo add harbor https://helm.getharbor.io helm install harbor harbor/harbor -f harbor-values.yaml -n harbor --create-namespace
bash# Enable auto-scan on push (via Harbor API) curl -k -X PUT "https://harbor.example.com/api/v2.0/projects/myproject" \ -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "auto_scan": "true", "severity": "critical", "prevent_vul": "true", "reuse_sys_cve_allowlist": "true" } }'
bash# Enable content trust at project level curl -k -X PUT "https://harbor.example.com/api/v2.0/projects/myproject" \ -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "enable_content_trust": "true", "enable_content_trust_cosign": "true" } }' # Sign image with Cosign cosign sign --key cosign.key harbor.example.com/myproject/myapp:v1.0.0 # Verify signature cosign verify --key cosign.pub harbor.example.com/myproject/myapp:v1.0.0
bash# Create project with private visibility curl -k -X POST "https://harbor.example.com/api/v2.0/projects" \ -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \ -H "Content-Type: application/json" \ -d '{ "project_name": "production", "metadata": { "public": "false", "auto_scan": "true", "prevent_vul": "true", "severity": "high" } }' # Harbor roles: ProjectAdmin, Maintainer, Developer, Guest, LimitedGuest # Add member with specific role curl -k -X POST "https://harbor.example.com/api/v2.0/projects/production/members" \ -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \ -H "Content-Type: application/json" \ -d '{ "role_id": 3, "member_user": {"username": "developer1"} }'
bash# Create tag immutability rule (prevent overwriting release tags) curl -k -X POST "https://harbor.example.com/api/v2.0/projects/production/immutabletagrules" \ -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \ -H "Content-Type: application/json" \ -d '{ "tag_filter": "v*", "scope_selectors": { "repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}] } }' # Configure retention policy (keep last 10 tags, delete untagged after 7 days) curl -k -X POST "https://harbor.example.com/api/v2.0/retentions" \ -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" \ -H "Content-Type: application/json" \ -d '{ "algorithm": "or", "rules": [ { "action": "retain", "template": "latestPushedK", "params": {"latestPushedK": 10}, "tag_selectors": [{"kind": "doublestar", "decoration": "matches", "pattern": "**"}], "scope_selectors": {"repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]} } ], "trigger": {"kind": "Schedule", "settings": {"cron": "0 0 * * *"}} }'
yaml# Harbor configuration for OIDC auth_mode: oidc_auth oidc_name: "Okta" oidc_endpoint: "https://company.okta.com/oauth2/default" oidc_client_id: "harbor-client-id" oidc_client_secret: "harbor-client-secret" oidc_groups_claim: "groups" oidc_admin_group: "harbor-admins" oidc_scope: "openid,profile,email,groups" oidc_verify_cert: true oidc_auto_onboard: true
bash# Test vulnerability prevention (should block pull of vulnerable image) docker pull harbor.example.com/production/vulnerable-app:latest # Expected: Error - image blocked due to vulnerabilities # Verify content trust enforcement DOCKER_CONTENT_TRUST=0 docker push harbor.example.com/production/unsigned:latest # Expected: Push rejected due to content trust policy # Check scan results via API curl -k "https://harbor.example.com/api/v2.0/projects/production/repositories/myapp/artifacts/v1.0.0/additions/vulnerabilities" \ -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)" # Audit log check curl -k "https://harbor.example.com/api/v2.0/audit-logs?page=1&page_size=10" \ -H "Authorization: Basic $(echo -n admin:Harbor12345 | base64)"
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.