github artifact-keeper/artifact-keeper v1.11.2
Artifact Keeper 1.11.2

3 hours ago

Artifact Keeper 1.11.2

A reliability, audit and detection patch release on the 1.11 line: 50 changelog entries from 52 pull requests since 1.11.1. The headline is a database outage now answers a retryable 503 with Retry-After: 1 instead of 401 or 500 (#4750, #4756, #4765), so Docker, twine, cargo, npm and CI runners retry through a database blip instead of aborting or asking for a login. Around it are one security fix (plugin reload now runs the outbound-URL check before re-cloning, #4757, GHSA-r8fc-5fvw-rvh4), a lockstep move of the bundled trivy and the trivy server sidecar to 0.76.0 that operators who run their own trivy server must follow (#4712), audit rows for every request now carrying the client IP and many previously unaudited admin actions now writing audit rows (#4762, #4710, #4709), new detection metrics and log fields (#4676, #4677, #4674), and 33 fixes across proxy caching, migration, replication, Maven, npm, Conan, conda and SSO. No schema migrations. Four new settings: LOG_ANSI, PLUGINS_ENABLED, DATABASE_TCP_KEEPALIVES and HTTP_LISTEN_BACKLOG; PLUGINS_ENABLED and HTTP_LISTEN_BACKLOG keep the previous behaviour by default, while LOG_ANSI defaults to auto (no colour codes in non-terminal logs) and DATABASE_TCP_KEEPALIVES is on by default.

Read this first: what now answers with an error or refuses

  • Scanning stops unless a self-run trivy server moves to 0.76.0 together with the backend (#4712). The scanner adapter's bundled trivy and the trivy server sidecar move together from 0.74.0 to 0.76.0, published as scanner-adapter 1.5.0. In client/server mode (SCANNER_TRIVY_SERVER) the adapter refuses to scan unless the server reports the same trivy release as its bundled client: /probe/ready stays 503 and every scan fails closed with "client/server mode needs matching releases". The stock docker-compose.yml, docker-compose.local-dev.yml and docker-compose.test.yml now run ghcr.io/aquasecurity/trivy:0.76.0, and the Helm chart's trivy.image.tag moves in artifact-keeper-iac. A deployment that pins its own trivy server image, or points the adapter at an external one, has to move that pin to 0.76.0 when it takes this release. Client/server mode now carries cryptographic assets and modular-package details over the RPC, so modular packages on RHEL-family targets, which client/server mode used to skip silently, are now detected as in standalone mode.
  • A database outage answers 503 with Retry-After: 1, not 401 or 500. During credential checks (#4750): a closed pool, a lost or refused connection, a statement or lock timeout or a server shutting down used to return 401 "Invalid or expired token" from the auth middleware, and every such failure on /v2, /v2/ and /v2/token returned 401; they now return 503 on the auth middleware, the format-endpoint resolver and every /v2 verb. During login and other handlers (#4756): POST /api/v1/auth/login and any handler whose own query hits the outage return 503 DATABASE_UNAVAILABLE instead of 500 DATABASE_ERROR. A pool-acquire timeout still returns 503 POOL_EXHAUSTED. On the format endpoints (#4765): npm, OCI, Git LFS and protobuf Connect answer 503 in their own error shapes (npm {"error": ...}, Git LFS {"message": ...}, OCI UNAVAILABLE, Connect unavailable), and OCI and Connect also use those codes now for a saturated pool, which said INTERNAL_ERROR / internal before. An unknown, wrong, expired or revoked credential still returns 401, and query or data errors still return 500. Alerting that counted 401s or 500s during an outage will see 503s instead.
  • Reloading a git-sourced WASM plugin validates its stored source first: 400 (#4757, GHSA-r8fc-5fvw-rvh4). POST /api/v1/plugins/{id}/reload re-cloned the stored source_url with no SSRF validation. Every git re-fetch now runs the same outbound-URL check install does and refuses a blocked source with the same 400, naming AK_SSRF_ALLOW_PRIVATE_CIDRS for an address an operator can admit, before any clone is attempted. Reload is admin-only. A plugin whose source now resolves to a private address and that you mean to keep reloading needs that address in AK_SSRF_ALLOW_PRIVATE_CIDRS.
  • A Remote whose upstream promises a Content-Length and sends no body answers 502 UPSTREAM_SHORT_BODY (#4759), naming the repository and the expected and received byte counts (or serving the stale cached copy when stale-if-error has one), instead of dropping the client connection with no status. The streaming path now waits for the first body byte before it answers. A body that ends short after bytes were forwarded still closes the connection, now with a warn naming the cache key.
  • An IdP that answers the token request without an id_token gets 502 OIDC_NO_ID_TOKEN (#4699) naming the provider (and the IdP's OAuth error code when sent), instead of 500 INTERNAL_ERROR.
  • Smaller refusals. A PUT /{pkg}/-rev/{rev} that removes no npm version is refused with 400, so other packument edits sent there (such as npm owner) fail visibly; npm unpublish is refused with 405 on Remote and 400 on virtual repositories (#4025). An npm version whose metadata contains a NUL character is rejected with 400 instead of being stored without its metadata (#4643). A Git LFS upload whose declared Content-Length exceeds MAX_UPLOAD_SIZE is refused before the body is read (#4595). An OCI chunked-upload commit whose stored part does not match the length recorded at PATCH time is a 500 naming the part instead of DIGEST_INVALID (#4646). Clearing a repository's project with {"project_id": null} from a repository-level admin alone gets 403 (#4026).

Read this second: behaviour that changes without an error

  • Every audit row written while handling a request now records the client IP (#4762), including REPOSITORY_CREATED, USER_CREATED, role, password, TOTP, token, settings and policy changes, which stored an empty ip_address (and a null source_ip in the audit stream) before. The address is the request's verified client IP, the same one login records: the TCP peer, or the forwarded address only behind a trusted proxy with XFF_TRUSTED_FOR_AUTHZ=true. Background-job rows, and requests behind a trusted proxy without that setting, still record no IP.
  • Collected logs lose their ANSI colour codes by default (#4674). The new LOG_ANSI (auto, true, false) defaults to auto: colour only when stdout is a terminal and NO_COLOR is unset, so container and systemd logs are plain and Loki or SIEM filters on uri=, status= or correlation_id= start matching. LOG_ANSI=true restores the previous output. LOG_FORMAT=json lines logged during a request gain top-level method, uri, correlation_id and http.route, and the Request completed line adds status, user (omitted when anonymous), ip and latency_ms; existing keys are unchanged. The text format's Request completed line also records user, ip and latency_ms. LOG_FORMAT accepts text as the default format's name (pretty still works). Field reference: docs/operations/logging.md.
  • OCI requests in the ak_http_* series are labelled with route templates (#4676). They were all path="unmatched"; they now carry the spec's template, such as /v2/{name}/manifests/{reference}. Dashboards that filtered on unmatched to find registry traffic need the new labels.
  • A trusted gateway's X-Request-ID becomes the correlation ID (#4677): up to 128 visible ASCII characters from a peer in RATE_LIMIT_TRUSTED_PROXY_CIDRS, after an explicit X-Correlation-ID and before traceparent. From any other peer it is ignored as before.
  • The OIDC redirect_uri behind a reverse proxy follows only trusted forwarding headers (#4143). With AK_EXTERNAL_URL unset and RATE_LIMIT_TRUSTED_PROXY_CIDRS set, X-Forwarded-Proto / X-Forwarded-Host are honoured only from a listed proxy; with the list empty the previous behaviour is kept. An http:// redirect_uri (which Microsoft Entra rejects with AADSTS50011) now logs a warning naming AK_EXTERNAL_URL, the setting that fixes it.
  • The OIDC authorization-code exchange sends the provider's configured scopes on the token request (#4699), default openid profile email, which Microsoft Entra's v2 token endpoint needs to return an id_token.
  • Conan files on S3, GCS or Azure are written under repository-scoped keys (#4694): new recipe and package files go to conan/{repository_id}/..., so the same revision can be uploaded to more than one Conan repository instead of being refused with 409 ... refusing cross-repository overwrite. Existing files stay where they are and keep serving from the key their artifact row records; filesystem storage and STORAGE_KEY_SCHEME=flat keep the flat key.
  • A Maven version's catalog size is the total of its files (#4633), not whichever file had the smallest checksum. The Packages list, package detail and Versions tab show the new value; quotas and storage totals are unaffected. Existing versions are corrected by the next upload into the version, or at once by POST /api/v1/admin/packages/backfill, which records the same total for Maven and Gradle.
  • A grant on a virtual repository still does not grant its members, and the REST listings now say so (#4690). GET /api/v1/repositories/{key}/members and GET /api/v1/repositories/{key}/artifacts add an X-AK-Virtual-Members-Notice header when the caller can read no member. Format routes still answer 404. docs/repository-visibility.md documents the rule with a group-grant example.
  • Audit retention now runs on a schedule, and an unsaved period deletes nothing (#4718). An hourly job deletes rows older than audit_retention_days, in batches of 5,000, on one replica at a time. 0, or a period that was never saved, leaves retention off, and GET /api/v1/admin/settings reports audit_retention_days: 0 in that case instead of a 90 that was never applied, so upgrading deletes no audit rows. POST /api/v1/admin/cleanup with the period at 0 or unsaved now deletes no audit rows; at 0 it used to delete all of them.
  • Postgres connections arm server-side TCP keepalives and startup retries the initial connect (#4549). Every pool and single-flight lock connection sets tcp_keepalives_idle=15, tcp_keepalives_interval=5 and tcp_keepalives_count=3, so a force-killed pod's sessions are reaped in about 30 s instead of about 2 h; DATABASE_TCP_KEEPALIVES=false turns this off (for example behind a pooler in transaction mode). Startup retries the first connect up to five times with backoff, and a database that stays down fails the process in about 40 s with the defaults. .env.example now documents the connection budget, whose shipped defaults (3 × 50 = 150) exceed a stock server's 100 max_connections.
  • The setup gate and the startup password regeneration concern only the built-in admin (#4634). Another admin with a pending password change no longer arms the gate (which made every route answer 403 SETUP_REQUIRED) or gets admin's password overwritten on restart. If admin has been renamed or removed and another local admin exists, boot creates and arms nothing.
  • Smaller changes. Git LFS uploads follow MAX_UPLOAD_SIZE (10 GiB by default, 0 for no limit) instead of a fixed 2 GB (#4595). PATCH /api/v1/repositories/{key} with {"project_id": null} now clears the project, and the REPOSITORY_UPDATED row records project_change (#4026). npm unpublish now removes the version or package, needing the delete:artifacts scope and the repository delete action (#4025). LDAP login and the SSO exchange now return expires_in and size the access cookie to the token's real lifetime (#4648). Hosted npm packages and Go modules, and re-uploads to the same path, now reach push and mirror peers (#4643, #4658). Remote and virtual conda channels serve real current_repodata.json, run_exports.json and repodata.json.sig, and patch_instructions.json on hosted and virtual channels answers 404 instead of an empty 200 (#4708). RubyGems indices list hyphen-digit gem names such as qa-gem-2 correctly (#4645). Debian and Maven repositories migrated from Artifactory or Nexus now register their packages; repositories migrated earlier are repaired by re-running the job (Debian) or by the catalog backfill (Maven) (#4657, #4691). PLUGINS_ENABLED=false disables the WASM plugin runtime and leaves its routes unregistered; the default is enabled (#4650). HTTP_LISTEN_BACKLOG sets the API listener's accept queue; unset keeps the OS maximum, and the granted backlog is logged at startup (#4647).

Read this third: hardening and detection

  • New metrics (#4676, #4677, #4642, #4711, #4549). ak_http_connections_open, ak_rate_limit_rejections_total{limiter}, ak_ssrf_blocks_total{surface}, ak_auth_failures_total{surface,reason} (reason bucketed, never a username or token) and ak_proxy_scan_verdicts_total{verdict,action} (#4676); ak_permission_condition_evaluations_total{condition,outcome} (#4677); ak_artifact_uploads_total, ak_artifact_upload_size_bytes and ak_artifact_downloads_total, which were defined but never recorded (#4642); ak_conda_attestation_total, ak_conda_install_script_findings_total and ak_conda_allowlist_dropped_total, labelled by repository key and never by package name (#4711); ak_db_pool_connection_failures_total{stage} (#4549). Scheduled Remote cache evictions now count toward ak_cleanup_items_removed_total{type="lifecycle"} (#4451). Counters restart from zero with the process; query them with increase().
  • New audit events. LOGIN_RATE_LIMITED for a login refused 429 by the login limiters, at most once per source IP per limiter window, and EXTERNAL_FINDINGS_INGESTED for POST /api/v1/security/findings/external (#4677). Quarantine, promotion approval and promotion, permission rule, SSO provider, signing-key, webhook and sync-policy changes (#4710). Conda publishes, withdrawals, attestation outcomes and environment registrations and deletions (#4709). REPOSITORY_UPDATED on PUT /api/v1/repositories/{key}/upstream-auth, recording the auth-type change and a credentials_changed flag (#4529). No row carries a secret.
  • New and updated documentation. docs/operations/detection-signals.md (#4677), docs/operations/detection-metrics.md (#4676), docs/operations/logging.md (#4674), docs/operations/artifact-metrics.md (#4642), docs/operations/conda-security-metrics.md (#4711), docs/operations/load-shedding.md with sizing guidance for CPU-limited replicas (#4647), docs/proxy-cache-freshness.md on the yank asymmetry (#4028), and the action and details reference in docs/audit-events.md (#4709, #4710, #4762).
  • Proxy-cache integrity. A publish that lands a zero-byte or truncated object writes no sidecar and drops the object, and entries already poisoned that way become a cache miss and heal on the next request (#4630). On filesystem storage, where sidecars pin no ETag, a body whose size disagrees with its sidecar is evicted and refetched instead of being served as Content-Length: N over a short body (#4758). A failed final filesystem write now returns an error instead of publishing a short object (#4646). A warm npm Remote tarball is served without reading the packument (#4692).
  • Clearer private-network refusals. Refusing an LDAP server, identity provider, upstream or webhook on an RFC 1918, CGNAT or IPv6 unique-local address now names AK_SSRF_ALLOW_PRIVATE_CIDRS with the refused address as the example (#4638). Cloud metadata, loopback and link-local addresses stay refused even when a containing range is listed.

Dependency updates

  • object_store 0.14.2 (#2158), which backs S3 storage, depends on quick-xml ^0.41, so the vulnerable quick-xml 0.39.2 it pulled in leaves the graph and the two .cargo/audit.toml ignores for RUSTSEC-2026-0194/0195 are removed. The upgrade also drops the second reqwest (0.12). S3 TLS trust rules are unchanged. SigV4 signing failures on the AWS upstream-auth and S3 REST fallback paths now return an error instead of panicking.
  • The backend image's bundled grype moves from v0.118.0 to v0.120.1 (#3698), which pins golang.org/x/crypto v0.57.0 (past CVE-2026-56855 and CVE-2026-78662, x/crypto/ssh denial of service) and google.golang.org/grpc v1.83.2 itself. The build now fails if the binary records x/crypto below v0.56.0, the gRPC override from #3655 and #3790 is deleted, and the golang.org/x/net v0.60.0 override for CVE-2026-78669 stays. grype --version prints 0.120.1.
  • scanner-adapter 1.5.0 bundles artifact-keeper/trivy 0.76.0-r1, upstream v0.76.0 on go1.27.2 with no dependency overrides, because 0.76.0 already pins grpc v1.84.0, golang.org/x/net v0.60.0 and containerd v2.4.1 (#4712). See the first section for the trivy server pairing.
  • Dependabot group bumps: the cargo minor/patch group (serde_json 1.0.154, zeroize 1.9.1, http-body 1.1.0, uuid 1.28.0, tokio-util 0.7.20, redis 1.7.1, hyper 1.12.0, zstd 0.14.2, sigstore_protobuf_specs 0.5.2; #4689) and the GitHub Actions group (actions/download-artifact 8.0.2, actions/setup-node 7.1.0, github/codeql-action 4.38.3, azure/setup-kubectl 5.2.0; #4748).

The full entries are in the ## [1.11.2] section of CHANGELOG.md.

Sponsors

Thank you to our sponsors for supporting ongoing development of Artifact Keeper.

Backers

Become a sponsor to support the project and get your name listed here.

Thank You

  • @knowinglyAnonymous for reporting that npm unpublish exited 0 while the version stayed installable (#4025), that a repository's project assignment could not be cleared with PATCH ... {"project_id": null} (#4026), and the proxy-cache yank asymmetry the new cache-freshness guide documents (#4028).
  • @Tartanpion27 for reporting that scan-on-upload did not start after mvn deploy (#4124).
  • @DhyaneshNaik for reporting that Docker/OCI pushes never triggered a scan automatically (#4125).
  • @davidarcese for reporting the Microsoft Entra AADSTS50011 sign-in failure behind a TLS-terminating reverse proxy, caused by an http:// OIDC redirect_uri (#4143).
  • @Spenhouet for reporting and fixing the fixed 2 GB cap on Git LFS uploads, which now follow MAX_UPLOAD_SIZE (#4595, fixed in #4602).
  • @Quantum-Sicarius for reporting and fixing the proxy-cache entry whose publish landed a zero-byte object and then returned HTTP 500 forever (#4630, fixed in #4661).
  • @der-eismann for reporting that a Maven package version's size showed one file's size instead of the version total (#4633).
  • @BoyanYK for reporting and fixing the setup gate that any admin with a pending password change could arm, overwriting the built-in admin password on restart (#4634, fixed in #4635).
  • @trourance for reporting that a private-network LDAP server could not be saved (#4638), that Debian repository migration was only partial (#4657), and that a grant on a virtual repository did not make its members readable (#4690).
  • @axi92 for reporting that the permissions API returned target_name: null for group, project and artifact targets (#4639), and that a Maven repository migrated from Nexus showed an empty Packages tab (#4691).
  • @dsiebel for reporting that ak_artifact_uploads_total and ak_artifact_downloads_total were never emitted (#4642).
  • @vkhilin-top for reporting and fixing three replication and proxy issues: npm publishes and Go module uploads never queued for peer replication (#4643, fixed in #4644), a re-upload to the same path never reaching push peers (#4658, fixed in #4659), and warm npm Remote tarballs re-reading the whole packument on every request (#4692, fixed in #4693).
  • @cbachhuber for reporting that a Conan revision already held by another repository on shared object storage was refused with 409 (#4694).
  • @huylv95 for reporting that the OIDC token exchange omitted scope, so Microsoft Entra returned no id_token (#4699).
  • @gowens-leolabs for requesting and contributing PLUGINS_ENABLED=false to disable the WASM plugin runtime (#4650, fixed in #4651).
  • @macmoonshine for the proxy cache-freshness guide (#4028, in #4696) and the fixes for the PUT .../upstream-auth audit row (#4529, in #4631), Postgres TCP keepalives and the startup connect retry (#4549, in #4695), and scheduled Remote cache evictions in the cleanup summary and metric (#4451, in #4752).
  • @vadim-belous for reporting that plugin reload re-cloned a stored git source without SSRF validation (#4757, GHSA-r8fc-5fvw-rvh4).

Full changelog: v1.11.1...v1.11.2

Don't miss a new artifact-keeper release

NewReleases is sending notifications on new releases.