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/readystays503and every scan fails closed with "client/server mode needs matching releases". The stockdocker-compose.yml,docker-compose.local-dev.ymlanddocker-compose.test.ymlnow runghcr.io/aquasecurity/trivy:0.76.0, and the Helm chart'strivy.image.tagmoves 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
503withRetry-After: 1, not401or500. During credential checks (#4750): a closed pool, a lost or refused connection, a statement or lock timeout or a server shutting down used to return401 "Invalid or expired token"from the auth middleware, and every such failure on/v2,/v2/and/v2/tokenreturned401; they now return503on the auth middleware, the format-endpoint resolver and every/v2verb. During login and other handlers (#4756):POST /api/v1/auth/loginand any handler whose own query hits the outage return503 DATABASE_UNAVAILABLEinstead of500 DATABASE_ERROR. A pool-acquire timeout still returns503 POOL_EXHAUSTED. On the format endpoints (#4765): npm, OCI, Git LFS and protobuf Connect answer503in their own error shapes (npm{"error": ...}, Git LFS{"message": ...}, OCIUNAVAILABLE, Connectunavailable), and OCI and Connect also use those codes now for a saturated pool, which saidINTERNAL_ERROR/internalbefore. An unknown, wrong, expired or revoked credential still returns401, and query or data errors still return500. Alerting that counted401s or500s during an outage will see503s instead. - Reloading a git-sourced WASM plugin validates its stored source first:
400(#4757, GHSA-r8fc-5fvw-rvh4).POST /api/v1/plugins/{id}/reloadre-cloned the storedsource_urlwith no SSRF validation. Every git re-fetch now runs the same outbound-URL check install does and refuses a blocked source with the same400, namingAK_SSRF_ALLOW_PRIVATE_CIDRSfor 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 inAK_SSRF_ALLOW_PRIVATE_CIDRS. - A Remote whose upstream promises a
Content-Lengthand sends no body answers502 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_tokengets502 OIDC_NO_ID_TOKEN(#4699) naming the provider (and the IdP's OAutherrorcode when sent), instead of500 INTERNAL_ERROR. - Smaller refusals. A
PUT /{pkg}/-rev/{rev}that removes no npm version is refused with400, so other packument edits sent there (such asnpm owner) fail visibly; npm unpublish is refused with405on Remote and400on virtual repositories (#4025). An npm version whose metadata contains a NUL character is rejected with400instead of being stored without its metadata (#4643). A Git LFS upload whose declaredContent-LengthexceedsMAX_UPLOAD_SIZEis 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 a500naming the part instead ofDIGEST_INVALID(#4646). Clearing a repository's project with{"project_id": null}from a repository-level admin alone gets403(#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 emptyip_address(and anullsource_ipin 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 withXFF_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 toauto: colour only when stdout is a terminal andNO_COLORis unset, so container and systemd logs are plain and Loki or SIEM filters onuri=,status=orcorrelation_id=start matching.LOG_ANSI=truerestores the previous output.LOG_FORMAT=jsonlines logged during a request gain top-levelmethod,uri,correlation_idandhttp.route, and theRequest completedline addsstatus,user(omitted when anonymous),ipandlatency_ms; existing keys are unchanged. The text format'sRequest completedline also recordsuser,ipandlatency_ms.LOG_FORMATacceptstextas the default format's name (prettystill works). Field reference:docs/operations/logging.md. - OCI requests in the
ak_http_*series are labelled with route templates (#4676). They were allpath="unmatched"; they now carry the spec's template, such as/v2/{name}/manifests/{reference}. Dashboards that filtered onunmatchedto find registry traffic need the new labels. - A trusted gateway's
X-Request-IDbecomes the correlation ID (#4677): up to 128 visible ASCII characters from a peer inRATE_LIMIT_TRUSTED_PROXY_CIDRS, after an explicitX-Correlation-IDand beforetraceparent. From any other peer it is ignored as before. - The OIDC
redirect_uribehind a reverse proxy follows only trusted forwarding headers (#4143). WithAK_EXTERNAL_URLunset andRATE_LIMIT_TRUSTED_PROXY_CIDRSset,X-Forwarded-Proto/X-Forwarded-Hostare honoured only from a listed proxy; with the list empty the previous behaviour is kept. Anhttp://redirect_uri(which Microsoft Entra rejects with AADSTS50011) now logs a warning namingAK_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 anid_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 with409 ... refusing cross-repository overwrite. Existing files stay where they are and keep serving from the key their artifact row records; filesystem storage andSTORAGE_KEY_SCHEME=flatkeep 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}/membersandGET /api/v1/repositories/{key}/artifactsadd anX-AK-Virtual-Members-Noticeheader when the caller can read no member. Format routes still answer404.docs/repository-visibility.mddocuments 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, andGET /api/v1/admin/settingsreportsaudit_retention_days: 0in that case instead of a90that was never applied, so upgrading deletes no audit rows.POST /api/v1/admin/cleanupwith the period at0or unsaved now deletes no audit rows; at0it 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=5andtcp_keepalives_count=3, so a force-killed pod's sessions are reaped in about 30 s instead of about 2 h;DATABASE_TCP_KEEPALIVES=falseturns 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.examplenow documents the connection budget, whose shipped defaults (3 × 50 = 150) exceed a stock server's 100max_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 answer403 SETUP_REQUIRED) or getsadmin's password overwritten on restart. Ifadminhas 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,0for no limit) instead of a fixed 2 GB (#4595).PATCH /api/v1/repositories/{key}with{"project_id": null}now clears the project, and theREPOSITORY_UPDATEDrow recordsproject_change(#4026).npm unpublishnow removes the version or package, needing thedelete:artifactsscope and the repositorydeleteaction (#4025). LDAP login and the SSO exchange now returnexpires_inand 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 realcurrent_repodata.json,run_exports.jsonandrepodata.json.sig, andpatch_instructions.jsonon hosted and virtual channels answers404instead of an empty200(#4708). RubyGems indices list hyphen-digit gem names such asqa-gem-2correctly (#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=falsedisables the WASM plugin runtime and leaves its routes unregistered; the default is enabled (#4650).HTTP_LISTEN_BACKLOGsets 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) andak_proxy_scan_verdicts_total{verdict,action}(#4676);ak_permission_condition_evaluations_total{condition,outcome}(#4677);ak_artifact_uploads_total,ak_artifact_upload_size_bytesandak_artifact_downloads_total, which were defined but never recorded (#4642);ak_conda_attestation_total,ak_conda_install_script_findings_totalandak_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 towardak_cleanup_items_removed_total{type="lifecycle"}(#4451). Counters restart from zero with the process; query them withincrease(). - New audit events.
LOGIN_RATE_LIMITEDfor a login refused429by the login limiters, at most once per source IP per limiter window, andEXTERNAL_FINDINGS_INGESTEDforPOST /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_UPDATEDonPUT /api/v1/repositories/{key}/upstream-auth, recording the auth-type change and acredentials_changedflag (#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.mdwith sizing guidance for CPU-limited replicas (#4647),docs/proxy-cache-freshness.mdon the yank asymmetry (#4028), and the action anddetailsreference indocs/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: Nover 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_CIDRSwith 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_store0.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.tomlignores 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
grypemoves from v0.118.0 to v0.120.1 (#3698), which pinsgolang.org/x/cryptov0.57.0 (past CVE-2026-56855 and CVE-2026-78662,x/crypto/sshdenial of service) andgoogle.golang.org/grpcv1.83.2 itself. The build now fails if the binary recordsx/cryptobelow v0.56.0, the gRPC override from #3655 and #3790 is deleted, and thegolang.org/x/netv0.60.0 override for CVE-2026-78669 stays.grype --versionprints0.120.1. - scanner-adapter 1.5.0 bundles
artifact-keeper/trivy0.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/netv0.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_json1.0.154,zeroize1.9.1,http-body1.1.0,uuid1.28.0,tokio-util0.7.20,redis1.7.1,hyper1.12.0,zstd0.14.2,sigstore_protobuf_specs0.5.2; #4689) and the GitHub Actions group (actions/download-artifact8.0.2,actions/setup-node7.1.0,github/codeql-action4.38.3,azure/setup-kubectl5.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
- Advailo, SIA (@advailo)
- stringke (@StringKe)
- Ash A. (@dragonpaw)
- Mike (@huylv95)
- Gabriel Rodriguez (@injectedfusion)
- Narek Engibaryan (@narek01)
- and one sponsor who chose to stay private
Become a sponsor to support the project and get your name listed here.
Thank You
- @knowinglyAnonymous for reporting that
npm unpublishexited 0 while the version stayed installable (#4025), that a repository's project assignment could not be cleared withPATCH ... {"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
AADSTS50011sign-in failure behind a TLS-terminating reverse proxy, caused by anhttp://OIDCredirect_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
adminpassword 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: nullfor 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_totalandak_artifact_downloads_totalwere 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 noid_token(#4699). - @gowens-leolabs for requesting and contributing
PLUGINS_ENABLED=falseto 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-authaudit 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