github artifact-keeper/artifact-keeper-web v1.11.0
Artifact Keeper Web 1.11.0

3 days ago

Added

  • Package allowlist editor for virtual conda channels (#971, backend artifact-keeper#4576) - the backend can restrict what a virtual conda channel takes from its remote members to a list of {name, version, subdirs} entries (GET/PUT/DELETE /api/v1/repositories/{key}/allowlist), but the web could neither show nor change it, so "the lockfile is the allowlist" took a shell script. Virtual conda and conda_native repositories now have an Allowlist section on the Settings tab: an Enforce switch that warns when an enabled list is empty (it then admits nothing from remote members; hosted members are never filtered), a table of entries edited in place with validation that mirrors the backend limits (10,000 entries, 128-byte names of [a-z0-9_.-] plus */?, 256-byte versions, 32 subdirs), a bulk paste of name [version] [subdir,subdir] lines, and an import from pixi.lock or conda-lock.yml that reads the file in the browser, previews the count per subdir, and adds or replaces one entry per (name, exact version) with the subdirs it was locked for, the same entries as the walkthrough's from-lock.sh. Changes are local until saved, because the backend replaces the whole list; a refused save shows the backend's message and marks the entry it names. The repository overview shows "Allowlist: on, 43 entries" linking to the section, a stored list the backend can no longer read is shown as such (nothing from remotes is admitted until it is replaced or removed), and the audit log renders REPOSITORY_ALLOWLIST_CHANGED as the previous and current state and entry count. The repository tabs became controlled so the summary line can switch to Settings. Trade-offs: version specs are length-checked only, their grammar is left to the backend's 400; the backend shows the list to repository admins only, so other users see no summary, and a repository admin who is not an instance admin gets a read-only copy on the Members tab, because the web gates editing on the instance admin flag.
  • Three-state repository visibility: Public / Internal / Private (#839, backend artifact-keeper#3813) - the binary "Public repository" switch in the create dialog, edit dialog and repository Settings tab is replaced by a shared Visibility select (visibility-select.tsx). Internal is readable by every signed-in user with no grant and never anonymously. The repository list marks Internal with a group icon and Private with a padlock (public stays unmarked); the repository detail header and the CVE blast-radius page report internal rather than calling it private. With guest access disabled the control stays and only Public is withdrawn; an already-public repository keeps showing its own value, and saving an unrelated change no longer resends it. The client sends visibility with a matching is_public, and reads visibility falling back to is_public, so against a backend without artifact-keeper#3813 Internal degrades to Private rather than failing.
  • Conda packages show who published them, and whether that was verified (#921, backend artifact-keeper#4155, #4048, #4134) - with CEP-27 attestations now verified by the registry (#4048, #4155), a conda package can carry a cryptographically checked publisher, while the publisher-trust metadata tier (#4134) reads the maintainer a package declares about itself. The web showed neither, and showing one badge for both would have made a self-declared name look checked. The conda package block in the artifact detail dialog now has a Publisher row: a green "Verified publisher: " badge when the registry verified the package's attestation (the certificate-bound owner, or the signing identity or key fingerprint when no owner is bound), and a plain "Declared maintainer: (not verified)" badge for the about.json maintainer otherwise. As in the backend, a stored but unverified attestation never upgrades the declared tier, because CEP-27 carries no claimed publisher. The badge needs the by-id artifact record, so it appears in the detail dialog only, not in the artifact list.
  • Environments tab: advisory changes feed (#919, backend artifact-keeper#4055) - since #4055 the backend re-evaluates stored environments whenever advisory data changes and records each environment that became, or stopped being, affected by an advisory, but the web never showed those events, so finding out that a registered lockfile had just become vulnerable meant polling the API. The repository Environments tab now ends with an Advisory changes table from GET /api/v1/environments/advisory-transitions (newest first, the latest 100 across the environments the viewer can read, filtered to the repository being viewed): when it was detected, Newly affected or No longer affected, the environment, the advisory id (linked to its source when known) with severity and summary, and the package with the version that fixes it. Trade-off: the filter is applied in the browser, so a repository whose changes fall outside the latest 100 across all repositories shows fewer rows; a per-repository query parameter would remove that limit.
  • The scan-on-proxy setting links to the backend's coverage doc (#954, backend artifact-keeper#4114) - the note under "Scan on proxy download" (Settings and Security tabs) said which formats enforce the gate but gave no way to read the detail. It now ends with a "Which formats are covered?" link to docs/security/scan-on-proxy.md in the backend repository, which covers the fail-open / fail-closed / record-only actions, verdict freshness, the Virtual stricter-of-two rule and the per-format coverage table. The link points at the GitHub copy until the docs site publishes one. The optional per-format "stored but not enforced" reading of GET /api/v1/formats is not part of this change; the existing enforced/unenforced wording stays.
  • Curation rules explain the conda version syntax (#916, backend artifact-keeper#4141) - since #4141 a curation rule on a conda repository evaluates its version constraint with conda's own semantics (rattler), where >=1.2,<2 admits the pre-release 2.0a1 and 1.0a orders below 1.0, so a constraint written with pip's rules in mind can match versions its author did not intend. The rule form gave no hint. When the rule targets a conda or conda_native staging repository, or is global (and so also applies to conda), the form now shows short help with 1.2.*, >=1.2,<2 (and how to exclude pre-releases with <2.0a0), and exact versions. It also says what the field is not: unlike a conda MatchSpec, a bare or = version is an exact match rather than a prefix, and curation rules do not match build strings, so the issue's suggested build-string example is replaced by that statement.
  • Scan policies: edit the conda and origin predicates (#911, backend artifact-keeper#4127, #4050, #4165) - scan policies carry a predicate document (predicates.conda: allowed and denied channels, denied licenses and license families, install-script blocking and grading, a minimum attestation state; predicates.origin: allowed and denied upstreams and origin repositories, allowed origin kinds) that the backend enforces on promotion as rule policy-predicate at high severity since #4165, with an unknown conda channel failing closed. The web had no editor, so they could only be set through the API, and editing a policy in the web left them invisible. The create and edit dialogs now have a Conda and origin predicates section, and the policy list shows how many predicates a policy sets. Lists are entered comma-separated. The "Verified" attestation option is offered without a caveat, because CEP-27 verification is wired since artifact-keeper#4155 and #4048; the proxy origin kind carries a note that no production path records it yet (artifact-keeper#4161), so a policy that allows only proxy matches nothing. predicates is not in the generated SDK yet and is read and sent through the adapter. On create, an empty document is not sent; on edit the full document is sent because the backend replaces it wholesale, and a policy from a backend that reports no predicates is edited without one, leaving whatever is stored untouched.
  • Conda: withdraw a package from a channel, and read the channel's notices (#913, backend artifact-keeper#4137, #4148) - the backend can withdraw one package from a hosted conda channel (DELETE /conda/<key>/<subdir>/<file> with a reason: it leaves repodata, is named in removed, and a CEP-6 notice is appended to notices.json), but the web had no way to do it or to read the notices. Administrators now get a Withdraw action in the artifact dialog of conda and conda_native local and staging repositories: a required reason, then a confirmation step that quotes the notice clients will see and says the record is kept under a permanent hold, then the request (hand-written through apiFetch, since the /conda routes are not in the OpenAPI spec; the user's session is sent, which notices.json requires since #4148). A Notices tab on those repositories lists the channel's notices, newest first. The action is not offered for a package that already reads as withdrawn, nor on remote or virtual channels, which the backend refuses.
  • A withdrawn conda package reads as withdrawn, with its CEP-6 notice, instead of "quarantined" (#913, backend artifact-keeper#4059) - withdrawing a package from a hosted conda channel places a permanent hold on it, drops it from every repodata variant, names it in repodata's removed array and publishes a CEP-6 notice in notices.json. The web showed none of that: the row said "Quarantined" and the detail banner promised the hold would be lifted, which is not what a withdrawal means to a channel's consumers. For conda and conda_native hosted and staging repositories, the web reads the channel's /conda/<key>/notices.json once, and a blocked package whose file a notice names now shows a Withdrawn badge; the detail dialog says it was withdrawn from the channel and is listed in removed, with the notice text (the newest notice naming that file). The hold alone does not decide it: a promotion policy or a scan also places a permanent hold with no expiry, and those keep the quarantine wording with their reason. Trade-off: a package withdrawn on a backend that wrote no notice, or a notices fetch that fails, reads as quarantined.
  • Signing page shows the attestation trust policy (#961, backend artifact-keeper#4033 epic, F6) - the registry decides on upload which publish attestations count as verified (whether one is required, which OIDC issuers and certificate identities are trusted for keyless Sigstore bundles, which public keys for key-signed bundles), but nothing in the web showed that policy, so an auditor could not see what "verified" meant on this instance. The Signing page now opens with a read-only Attestation trust policy card from GET /api/v1/attestations/policy (backend 1.11.0, not in the generated SDK yet, read through apiFetch), listing the upload requirement, trusted keys with fingerprint and algorithm, issuers and identities; empty lists say what they mean (no keys: key-signed bundles are refused). Only an explicit require_verified: false reads as not required. On an older backend the card says the policy is not reported. The policy is still set in server configuration; this change does not make it editable.
  • The download audit can be opened for one artifact (#963) - the admin Downloads page could filter by artifact id, but only by pasting a UUID that no screen showed, so "who pulled this package" took a trip through the API. The artifact detail dialog now has a "View who downloaded this artifact" link for administrators (not for proxy-cached rows, which have no artifact record), and the Downloads page accepts ?artifact_id=<uuid> and opens filtered to it; a malformed id is ignored.
  • Environments tab: registered lockfiles, environment SBOMs, and "which environments contain this component" (#912, backend artifact-keeper#4053, #4054, #4128, #4136, #4171) - the backend stores ingested lockfiles (pixi.lock, conda-lock.yml, package-lock.json, Cargo.lock, poetry.lock, uv.lock) as environments, renders a lockfile as one SBOM per environment and platform, and answers "which of our environments contain this package, and what pulls it in?", but the web had no screen for any of it, so the answer during an incident needed curl. A signed-in user now gets an Environments tab on each repository: its registered environments (name, lockfile format, distinct packages, scopes, last update); a Lockfile section that generates CycloneDX or SPDX documents from an uploaded lockfile, one download per environment and platform (nothing is stored, as on the backend), and for administrators registers the lockfile as a named environment; and a lookup that takes a package URL (for example pkg:conda/acme-core@0.1.0) and lists every matching environment the viewer can read, with the repository, the environment and platform scope, the exact PURL as the backend returns it (never rebuilt from the platform), and up to three inclusion chains. The same lookup is on Security > Blast radius, and ?purl= deep-links it. The endpoints are not in the generated SDK yet and go through apiFetch with tolerant parsing; against an older backend the tab says it needs 1.11.0. Trade-offs: registering is offered to administrators only, because the web cannot tell repository write permission cheaply (the backend enforces it either way); a stored environment's SBOM is not downloadable, because the backend stores the graph, not documents.
  • Bulk promotion shows each artifact's result, with the rule and severity of every violation, instead of a count toast (#917, backend artifact-keeper#3979, #4165) - after promoting from a staging repository the dialog closed and a toast said "Promoted 1 of 2 artifacts. 1 failed.", discarding the per-artifact results the server returned, so an operator could not see which package was blocked or why. When a promotion blocks an artifact or reports violations, the dialog now stays open on a Promotion result view: the counts as a summary, then per artifact Promoted or Refused, each policy violation with its severity and rule (quality gate, CVE and licence policy, and the scan-policy predicates reported as policy-predicate), and, from a backend that sends them, per-gate decisions with pass or fail and a reason (gate_results: [{rule, passed, reason}], not in the generated SDK yet, read through the adapter with malformed entries dropped). An artifact with neither shows the response message. A promotion where everything passes and nothing is reported closes as before.
  • Conda package detail shows the publish attestation, its verification and the package's index fields (#961, backend artifact-keeper#4033 epic, F5/F6) - a conda package's CEP-27 attestation and the registry's verification of it were only reachable as a raw JSON dump, where the Sigstore bundle crowded out everything else, and the dialog never said whether the package had been verified at all. For conda and conda_native repositories the detail dialog now has an Attestation block: a Verified / Verification failed / Not verified / No attestation badge, the method (Sigstore keyless or a configured key), the certificate identity or key fingerprint, the issuer, when it was verified, the failure reason, and links to the CEP-50 .sigs sidecar and the stored bundle. Only an explicit verified record reads as verified; a stored bundle with no record reads as not verified. Both the 1.11.0 record (verified, method, key_fingerprint) and the earlier state record are read. A Conda package block lists the subdir, build, license, depends and the uploader (uploaded_by_username from the by-id response), and the raw metadata view shows a placeholder instead of the bundle. The sidecar link needs a backend that serves CEP-50 sidecars (1.11.0); on an older backend the link returns 404.
  • Conda repositories' flat Artifacts list shows each package's subdir and build (#961) - two builds of the same version (linux-64 and noarch, or py310… and py311…) looked identical in the list, because the platform subdir and the build string only appeared inside the path. conda and conda_native repositories now get Subdir and Build columns, read from the package's recorded index.json fields when the listing carries them and parsed from the <subdir>/<name>-<version>-<build> path otherwise. Other formats are unchanged.
  • Virtual repository members show whether each is hosted or remote, and conda virtual channels state the name-ownership rule (#962, backend artifact-keeper F1) - the Members tab listed only keys and priorities: the adapter dropped member_repo_type and member_repo_name, which the members endpoint already returns, so the screen that sets a virtual channel's order could not show which member is hosted. Each member now carries a type badge (local reads as "hosted") and its display name when it differs from the key. For conda and conda_native virtual repositories a note says that hosted members own their package names: remote versions of a name a hosted member publishes are left out of the merged repodata, so a public package cannot shadow an internal one. The note describes backend 1.11.0 behaviour and says so; it is not gated on the reported version, because backend main builds report the last released version.
  • Setup Guide: vscode (Open VSX gallery gateway) onboarding and default upstream URL (#793, backend artifact-keeper#3253) - vscode repositories fell through to the generic curl-upload snippet. The Setup Guide now shows VSCodium's persistent product.json extensionsGallery block (serviceUrl/itemUrl/extensionUrlTemplate/latestUrlTemplate), code-server's EXTENSIONS_GALLERY env, and the official VS Code enterprise-policy route (ExtensionGalleryServiceUrl → /gallery/manifest, desktop only). The gallery routes serve public Remote repositories only; other vscode repos get a note pointing at the direct extension download route instead. New vscode Remotes default their upstream URL to https://open-vsx.org/vscode/gallery, which satisfies the backend's /vscode/gallery-suffix check.
  • Reusable cleanup policies with explicit repository scope (#849, backend artifact-keeper#3943) - new lifecycle policies are created unassigned (no effect) unless the "Automatically apply to all current and future repositories" checkbox is ticked; Repository Settings > Cleanup Policies attaches and detaches shared policies (PUT/DELETE /api/v1/admin/lifecycle/{id}/repositories/{repository_id}) instead of offering policy-wide preview/execute/delete, shows inherited global policies as non-detachable, and links to Lifecycle administration. Creation and assignment writes require GET /api/v1/admin/lifecycle/capabilities to return explicit_repository_assignment: true (checked again before each write); on older backends, writes are blocked and existing policies stay viewable with a read-only legacy scope. Requires backend 1.11.0 (migration 233); upgrade every backend instance first.
  • CI/CD OIDC provider and identity-mapping admin page (#782) - Settings → SSO → CI/CD OIDC Providers (/settings/sso/ci, also in the admin sidebar) lists, creates, edits, enables/disables and deletes CI OIDC providers (GitLab, GitHub Actions, generic) and their priority-ordered identity mappings over the existing /api/v1/admin/ci-oidc endpoints. A mapping carries claim filters (each value a string or a non-empty array of strings, since the server compares claims exactly and a number or object would never match) and a repository scope (all repositories, or a selected list where none selected denies all). Renaming or reprioritising an unrestricted mapping leaves its scope untouched. Known limitation: a mapping restricted to specific repositories cannot be switched back to all repositories yet, because the server reads a null scope on update as "unchanged" (backend artifact-keeper#4198); the option is disabled with that explanation.
  • GitHub release mirrors as github, mise and aqua repository formats (#845, backend artifact-keeper#3656) - the create dialog offers GitHub Releases, mise and aqua (generic-handler aliases, backend migration 234) with https://github.com as the default upstream, and the set-me-up guide for a remote mirror gives the mise url_replacements recipe (URL-encoded Artifact Keeper credentials, /general//…), locked installs (mise lock / mise install --locked), the seven-day finite release-asset cache lifetime and the upstream-token trust caveat; local/virtual repositories get a remote-creation hint. The web proxy now forwards /general to the backend so that recipe works through the web origin. Standalone aqua CLI integration and GitHub API caching (artifact-keeper#3658) are not provided.
  • The repository selector returns to personal access tokens on backend 1.11.0 and later (#902, backend artifact-keeper#4219) - backend 1.11.0 accepts and enforces repo_selector on POST /api/v1/auth/tokens, so the Access Tokens create dialog offers the format, label and name-pattern selector again when the connected backend reports 1.11.0 or later (the version the sidebar shows, from /health). The selector is sent only when it names a filter, since the backend refuses an empty one with a 400; a token with no filter keeps its owner's full access. On an older backend, a pre-release of 1.11.0, or while the version is unknown, the dialog stays as #902 left it: no selector, a note that repository-scoped personal tokens need backend 1.11.0 and later pointing to service accounts, and a request carrying only name, scopes and expires_in_days.
  • Repositories show which storage backend holds their content (#918, backend artifact-keeper#4018) - since backend 1.11.0 the repository list and detail responses return storage_backend (filesystem, s3, azure or gcs), chosen at creation and immutable afterwards. The repository header shows it as a "Storage: …" badge and Settings → Repository Info lists it read-only, with human labels (Filesystem, S3, Azure Blob, GCS) and the raw value for anything unrecognised. Older backends omit the field and nothing is shown.
  • Artifact detail shows where the artifact came from, including "Originally from" for promoted copies (#914, backend artifact-keeper#4135, #4190) - the artifact detail dialog gains an Origin block built from the backend's immutable origin record ({v, kind, repository_key, upstream_url?}): how the artifact entered the registry (uploaded, fetched from upstream through a proxy, stored in a virtual repository, or imported by a migration; a kind this build does not know is shown as the backend sent it), the repository it entered through, and the upstream URL when one was recorded. Since artifact-keeper#4190 a promoted or approved copy keeps the origin of the artifact it was copied from, so when the origin repository differs from the artifact's own repository the dialog reads "Originally from " rather than implying the current repository, and links that repository when the viewer can open it. The listing rows the dialog opens from never carry origin, so the dialog fetches the artifact by id (GET /api/v1/artifacts/{id}), skipped for proxy-cached rows, which have no artifact record; origin is not in the generated SDK yet and is read through the adapter. The record has no timestamp or actor, so the block shows neither. A copy promoted before backend #4190, or a row backfilled at upgrade, records the repository it is in now and reads as such. With no origin (backend before 1.11.0) the block is not shown.

Changed

  • Conda is no longer described or treated as a PyPI alias, and conda_native Remotes show their proxy download counts (#915, backend artifact-keeper#4118) - backend artifact-keeper#4118 gave conda its own handler key, served by the conda-native handler, so the code comments that still called conda a PyPI alias are rewritten. The comment explaining why conda is left out of scan-on-proxy is also corrected: conda repositories are served by the /conda channel router, which never reads the scan_on_proxy flag, so the toggle is still hidden for conda and conda_native (unchanged). conda_native joins conda in the list of formats whose proxy downloads are counted, because the same /conda router serves both formats and records every proxied download. A conda_native Remote now shows a real zero download count instead of "Not tracked". Upgrade note: disabling the PyPI format handler no longer disables conda. The backend checks the format handler row when a repository is created, and conda now has its own conda row on the Format Handlers admin page. To stop new conda repositories being created, disable that row.
  • The Package Age Policy is offered on Remote repositories again from backend 1.11.0 (backend artifact-keeper#4264, web #863) - backend 1.10.0 refused quarantine_enabled: true on Remote and Virtual repositories, so #863 made the panel read-only there. Backend artifact-keeper#4264 gives proxied content a releasable, release-date-aware hold and accepts the policy on Remote repositories again; Virtual repositories are still refused because they cache nothing themselves. When /health reports 1.11.0 or later, a Remote repository's Settings tab shows the editable age-policy form, and a Virtual repository shows the form disabled with a note pointing at its member Remote repositories. A Virtual repository that still has a policy enabled from before the restriction keeps its Disable Age Policy action. Against an older backend, or when the version is unknown, both types keep #863's read-only panel. Held proxied artifacts are released through POST /api/v1/quarantine/proxy-cache/{repo_key}/release, which this change does not add to the UI.
  • Curation: conda_native publisher trust, and a note on rules saved before validation (backend artifact-keeper#4253, #4248) - conda_native repositories are now listed as publisher-trust formats, because the backend evaluates them as conda's publisher family since #4253. A publisher-trust rule marked Invalid now also says it was saved before the backend started rejecting invalid rule values (#4248, backend 1.11.0), and that editing it fixes it.

Fixed

  • The conda setup guide no longer tells conda to put the token in a /t/ URL segment that conda cuts in half (#908) - the Conda and Mamba / Micromamba tabs configured the channel as https://<host>/conda/t/YOUR_TOKEN/<key>, the anaconda.org token form the backend serves. An Artifact Keeper token is <8 hex>_<32 hex>, and conda's token parser (split_anaconda_token, regex /t/([a-zA-Z0-9-]*)) stops at the underscore, so conda 26.x re-rooted the channel and requested /t/<8 hex>/conda_<32 hex>/<key>/<subdir>/repodata.json, which 404s; mamba and micromamba send the path unchanged and worked, which hid the problem. Both tabs now embed the credentials as Basic auth, https://YOUR_USERNAME:YOUR_TOKEN@<host>/conda/<key>, which conda, mamba and micromamba all send as an Authorization: Basic header and which the backend's format routes accept with an API token as the password, the same form the docs site's CLI reference and the pip tabs use. The /t/ form is not offered as an alternative. The note says to percent-encode @ or : in the username and to drop the credentials for a public channel. The upload tab (Bearer token over curl) is unchanged.
  • The console's severity counts agree everywhere, and say what they count (backend artifact-keeper#4338) - the Overview dashboard, the Security dashboard tiles, its per-repository table and the artifact view showed different critical counts for the same data. The backend now computes them all from one definition (each artifact's latest completed scan per scanner type, one vulnerability per advisory at its highest reported severity, open ones in the severity counts, acknowledged ones separately). The tiles read "Open Critical"/"Open High", the Overview's breakdown reads "Open CVEs by Severity", and each carries that definition as its explanation (src/lib/severity-counts.ts). The artifact view's severity bar counted every CVE-history entry, fixed and acknowledged ones included; it now counts open entries only. Closes artifact-keeper#4338.
  • Scans that cataloged nothing read "Not cataloged", never clean (#920, backend artifact-keeper#4154) - a scan that could not read a package's contents (no components cataloged although the format expects some) completes with zero findings, and the web showed it exactly like a clean scan: a green "No vulnerabilities detected" shield on the artifact's Security tab, a "Clean" badge in Security → Scans, and zero findings in the artifact's scan list. Backend 1.11.0 now reports scan_completeness (complete, partial, not_cataloged) with a scan_completeness_reason on every scan and has_uncataloged_scan on the repository score. A not-cataloged scan now shows a Not cataloged badge (with the reason on hover) and "not assessed" instead of a findings count in the artifact's scan list and in Security → Scans; the scan's detail page carries a Not cataloged warning; the artifact's Security tab withholds the green all-clear while the latest scan of any scanner type is not cataloged and says why; and the Security dashboard marks a repository's grade "not cataloged" when has_uncataloged_scan is set. An older not-cataloged scan superseded by a complete one of the same type no longer withholds the all-clear. Older backends send none of these fields and nothing changes. partial scans are not marked yet.
  • Virtual repositories show their members' size instead of 0 (#958, backend artifact-keeper#4423) - backend 1.11.0 stopped reporting a virtual repository's members' bytes as its own storage_used_bytes (every total over repositories counted them twice) and moved them to member_storage_used_bytes, so a virtual repository now reports 0 and the repository list, header and dashboard would have shown it as empty. Where a virtual repository's size is shown (repository list row, repository header, dashboard repository table) it now reads " in members" from the new field, and Settings → Storage adds "its members hold " after the repository's own usage. Totals across repositories keep summing storage_used_bytes only. Older backends do not send the field and the old figure is shown unchanged. member_storage_used_bytes is not in the generated SDK yet and is read through the adapter.
  • The repositories split view fits a 1440 px window (#963) - with a repository selected, the Repositories page grew to about 1960 px at a 1440 px viewport, so the header search, the Create button and the right side of the detail pane were off screen behind a horizontal scroll. Three causes stacked: the app shell's content column and main were flex items with the default min-width: auto, so one wide child widened the whole column; the detail pane's ScrollArea wraps its content in a display: table box that grows to its widest descendant; and the repository tab strip (up to eleven tabs) never wrapped. The content column and main now take min-w-0, the detail pane bounds its content to the panel, and the tab strip wraps onto a second row in the narrow pane. Wide tables inside keep their own horizontal scroll. Checked at 1440 px: the page's scroll width now equals the viewport.
  • The Replication dashboard's refresh button re-reads the Subscriptions and Topology tabs (#841) - after saving a replication mode on the Subscriptions tab, the header's refresh icon fetched only the peer list (GET /api/v1/peers), so the subscriptions table kept what it had loaded first and only a browser reload showed the saved state. Refresh now invalidates the peer list, every peer's subscriptions and every peer's connections, drops mode choices that were picked but not saved so the server's values show, and spins while any of them is loading.
  • The dashboard's Repositories tile opens the repositories page (#822) - the tile in Statistics lifted and showed a pointer on hover like a link, but its click handler was an empty placeholder, so nothing happened. It is now a real link to Repositories, reachable with the keyboard, through a new href option on the shared stat card. The other tiles are not interactive and are unchanged.
  • A release image reports the version of the tag it was built from (#842) - the sidebar of an image built from tag 1.8.0 read 1.7.0, because the label came from package.json, which had not been bumped. Since #784 the release workflows refuse a tag that disagrees with package.json, so this should not recur, but the label still trusted package.json over the tag. A build from a release tag (APP_VERSION=vX.Y.Z) now shows the tag's version and, if package.json disagrees, says so in the tooltip. Non-release builds are unchanged (short commit hash, or the ref when no commit is known).
  • Repository list: the type filter reads in full, staging repositories get their own colour, and artifact paths keep their distinguishing tail (#957) - the type filter was 100 px wide and rendered "All typ"; it now sizes to its label. Staging repositories shared virtual's purple marker in the list and had no colour in the repository header; they now have their own. In a repository's flat Artifacts list the Path column was cut at 200 px from the end, hiding the version and build part that tells rows apart (-1.0-1.el10.noarch.rpm, a conda build string); it is now middle-elided at up to 320 px with the last 18 characters pinned and the full path on hover. The OCI tag-list items of #957 are not part of this change, and visibility is left to #840.
  • The admin Users page listed service accounts as people, offering password resets against identities that have no password (#825, backend artifact-keeper#3634) - service accounts are rows in the users table, so GET /api/v1/users returned them and the Users page rendered them inline with the full set of person-only actions: Edit, Reset password, Force password change, and the admin/active toggles. None of those mean anything for a svc-* identity, which authenticates with API tokens and is created with no usable password; all of them are already offered in their applicable form on the Service Accounts page. The user count was inflated by the same rows -- an instance with 30 people and 12 CI accounts reported 42 users. The page now asks the server for people only, and the total it shows counts people. This one could not be fixed in the browser: the listing is server-side paginated (#564), so dropping service accounts from a fetched page would have left total and total_pages counting rows the page no longer showed -- a short page and, at the boundary, a phantom trailing one. The filter had to be applied by the query that also produces the count, which is what artifact-keeper#3634 added. The filter is opt-in at the API layer: the audit actor filter still lists service accounts (filtering by a CI actor is usually the point), and so does the group member picker (a group grant is a valid way to give a service account access). is_service_account is not in the generated SDK yet, so adminApi.listUsersPage sends it through a narrowed cast.
  • Web image runs under an arbitrary OpenShift UID (#805) - OpenShift's default restricted-v2 SCC ignores the image's USER and runs the container as a random UID in GID 0. .next/cache, the only path the standalone server writes at runtime, was owned 1001:0 with mode 0755, so that UID hit EACCES writing the image-optimization cache. It is now chmod g=rwX, and the Dockerfile test guards the numeric non-root USER, the group-writable cache and the UBI base.
  • Repository tree view now shows every file, loading folders on demand (#850, #955) - the Tree view used to build its folders client-side from one page of the artifact list, which the backend caps at 100 items, so large folders came back truncated. It now reads GET /api/v1/tree one level at a time (root on open, each folder when expanded), so every file appears whatever the folder size. A search typed while in Tree view now shows the ordinary paginated server-side results table, not a tree built from one capped page. Trade-offs: top-level folders no longer open automatically (each expand costs one request); file rows no longer show size or download count, and the header drops its total file count (the tree endpoint does not return download counts); and a folder's listing loads in one response, since the backend does not paginate it, so a folder with tens of thousands of files renders them all without virtualization.
  • Permissions page no longer offers service accounts as User principals (#823, backend artifact-keeper#3634) - the User principal picker listed service accounts, because they are rows in the users table, and granting to one failed with 400 principal_id … does not exist as a user: the API accepts a service account only under the Service Account principal type. The picker now asks the server for people only (GET /api/v1/users?is_service_account=false, backend 1.9.0+; older backends ignore the parameter) under its own query key, so the audit page's actor filter keeps listing service accounts, and service accounts remain selectable under the Service Account principal type. It also requests a 100-row page instead of the backend's default 20, so users beyond the first 20 are now reachable. Known limit: 100 is the backend's maximum page size, so on instances with more than 100 people, those past the first 100 still cannot be picked; a search-driven picker is tracked separately.
  • Completing a chunked upload onto an immutable version no longer reports a checksum mismatch (#894, backend artifact-keeper#4015) - since the backend began refusing chunked completions that would overwrite an immutable version, POST /api/v1/uploads/{id}/complete returns 409 for two reasons, but the web mapped every 409 to "File checksum does not match", so a 100 MB+ upload to an already-released path sent users hunting for a corrupted file. The checksum wording is now kept only when the body says checksum mismatch; any other 409 shows the backend's own message ("Artifact version already exists and is immutable"), or Failed to finalize upload (HTTP 409 Conflict) when the body has none. Small (single-request) uploads already showed the backend message and are unchanged.
  • Dialogs no longer break when their content is long: the token table stays inside the modal, the Create Token footer stays reachable, and the permissions Repository dropdown stops overflowing (#900) - three reports, two missing defaults on DialogContent. It set no max-height and no overflow, so a dialog taller than the viewport pushed its own footer below the window edge with nothing to scroll — entering * as a token name pattern and pressing Preview Matched Repos put Cancel and Create Token out of reach entirely, and at 1280x720 the Preview button itself could no longer be clicked. It was also a grid whose single column defaulted to min-width: auto, so a child wider than the dialog grew the row instead of being bounded by it: the Manage Tokens table ran 264px past the dialog's right edge, delete buttons included, even though Table already carries an overflow-x-auto wrapper that could never engage. Both are now defaults on the primitive, so every dialog in the app gets them, and both stay overridable — several dialogs manage their own scrolling with overflow-hidden flex flex-col and an inner scroll area, and they are unchanged. Sixteen call sites had already pasted max-h-[85vh] overflow-y-auto by hand; the dialogs that broke were the ones that had not. Alongside: the Manage Tokens dialog is now wide but capped (w-[min(72rem,calc(100vw-2rem))], the idiom the other wide dialogs use) and long token names truncate with the full name on hover, and in the permissions Edit dialog the Repository select is full width with a truncating value — it is w-fit whitespace-nowrap by default, which a 45-character repository key pushed ~390px past the dialog edge. Regression tests assert geometry rather than class names, because the reports were about boxes: the table's right edge against the dialog's, and the submit button's bottom edge against the viewport's.
  • New conda and conda_native Remotes default to the conda-forge channel instead of the anaconda.org root (#909) - the create dialog prefilled a conda Remote with https://conda.anaconda.org and left conda_native empty. The backend treats the upstream URL as a channel and fetches {upstream}/{subdir}/repodata.json and {upstream}/channeldata.json beneath it, so the bare root returned 404 until a channel was typed in by hand. Both formats now default to https://conda.anaconda.org/conda-forge; use https://conda.anaconda.org/<channel> for any other channel.
  • The Setup Guide shows conda channel setup for conda and conda_native repositories (#908, backend artifact-keeper#4118) - conda repos were shown pip instructions for a /pypi/<key>/simple/ index and conda_native repos fell through to the generic curl snippet, but the backend serves both as conda channels under /conda/<key>/. Both formats now get Conda and Mamba / Micromamba tabs with a .condarc channel entry and install -c commands using the /conda/t/YOUR_TOKEN/<key> token URL the backend accepts, plus an Upload tab with the curl -X PUT command for local and staging repositories (remote and virtual channels reject uploads). conda_native repositories also now appear under the Setup Guide's Ecosystem filter.
  • Curation publisher-trust rules offer only the match modes and actions the backend accepts, add the conda metadata tier, and mark stored rules the backend cannot read (#910, backend artifact-keeper#4134) - the rule form offered signature and namespace match modes and an audit action that the publisher-trust evaluator does not know; the backend stores them without complaint and then flags every PyPI, npm and conda package as misconfigured. The form now offers attestation and metadata (the tier #4134 added so conda publishers can be trusted) and block / allow / flag, with help text saying what each checks: attestation counts only a cryptographically verified provenance attestation, which today means PyPI only (npm attestations go to review; the conda verifier is not wired yet, artifact-keeper#4155), and metadata trusts the publisher name a package declares, not a verified signature. Rules already stored with an unknown value show an Invalid badge and "Unknown: " in the list, and editing one leaves the field empty with a warning instead of quietly picking a valid value. The top-level Action select also offered flag and audit and defaulted to flag, which the allow/block column constraint rejects with a 500; it now offers allow and block, appears for pattern rules only, and typed rules store the value matching their config action.

Security

  • next 16.3.8, sharp 0.35.5, source-map-js 1.2.2, undici 8.12.1, brace-expansion 1.1.21 / 5.0.12 - the 1.11.0 preparation pinned next 16.3.4, which carries a critical remote code execution in next/og ImageResponse (GHSA-vcvr-r3jv-pc5j, fixed in 16.3.6) and a high server-side request forgery in image optimization (GHSA-cjq9-62q9-8jv4, fixed in 16.3.8), plus the moderate cache-poisoning and information-disclosure advisories fixed in 16.3.8. sharp (librsvg, CVE-2026-96889) and source-map-js (event-loop denial of service) are in the production tree; undici and brace-expansion are test and lint tooling only. The web image now ships none of them; npm audit --omit=dev is clean.
  • Personal access tokens no longer offer a repository scope the server ignores (#902, backend artifact-keeper#4219) - the Access Tokens page let you limit a personal token by format, label and name pattern and sent that as repo_selector, but the backend's personal-token endpoint (POST /api/v1/auth/tokens) accepts only name, scopes and expires_in_days and silently dropped it, so the token it minted could reach every repository your account can. The create dialog now has no repository selector and says personal tokens can't be repository-scoped yet, pointing to service accounts (whose tokens do enforce a selector); the request carries only the three fields the backend accepts, which also keeps it valid once artifact-keeper#4219 makes unknown fields a 400. The page's description no longer says personal tokens can be scoped. Action required: any personal token created with a repository scope before this fix is unrestricted despite what was chosen; review your personal tokens and revoke any that were meant to be limited, re-issuing them as service-account tokens if they need a repository scope.

What's Changed

  • fix(users): list people only, not service accounts (#825) by @sblaisot in #826
  • feat(docker): make web image arbitrary-UID compatible for OpenShift by @brandonrc in #806
  • feat(setup): add vscode gateway onboarding for VSCodium, code-server, and VS Code by @cazlo in #794
  • fix: lazy-load artifact tree and preserve search by @Doubleth1nk in #852
  • fix(permissions): stop offering service accounts as User principals (#823) by @sblaisot in #824
  • chore: bump aws-actions/configure-aws-credentials from 6.2.3 to 6.3.0 by @dependabot[bot] in #882
  • chore: bump docker/setup-buildx-action from 4.2.0 to 4.4.1 by @dependabot[bot] in #881
  • chore: bump docker/build-push-action from 7.3.0 to 7.4.0 by @dependabot[bot] in #880
  • chore: bump softprops/action-gh-release from 3.0.2 to 3.0.3 by @dependabot[bot] in #832
  • chore: bump the codeql-action group across 1 directory with 2 updates by @dependabot[bot] in #804
  • chore(deps): batch the nine open dependabot npm bumps by @brandonrc in #891
  • feat(lifecycle): add explicit reusable cleanup policy assignments by @damjanek in #848
  • feat(ci-oidc): admin UI for CI OIDC provider and identity mapping management by @luko0610 in #390
  • fix(uploads): show the backend's reason for a 409 on upload completion by @brandonrc in #895
  • ci(docker): build the web image's amd64 leg on ak-docker-runners by @brandonrc in #896
  • fix(dialog): keep long content inside the dialog and the footer reachable by @vbartusevicius in #901
  • fix(tokens): stop offering a repository scope on personal tokens the backend ignores by @brandonrc in #903
  • feat(repositories): add GitHub, mise and aqua mirror setup by @gilesw in #846
  • feat(tokens): offer the repository selector on personal tokens for backend 1.11.0+ by @brandonrc in #923
  • fix(repositories): default conda Remotes to the conda-forge channel, not the anaconda.org root by @brandonrc in #924
  • fix(setup): real conda channel instructions for conda and conda_native repositories by @brandonrc in #925
  • fix(curation): offer only the rule values the backend accepts, add the conda metadata tier by @brandonrc in #926
  • ci: cancel a superseded PR run when a newer push arrives by @brandonrc in #927
  • chore(conda): track conda_native proxy downloads, fix stale 'PyPI alias' comments by @brandonrc in #928
  • feat(repositories): show each repository's storage backend by @brandonrc in #929
  • feat(artifacts): show an artifact's origin, including 'originally from' for promoted copies by @brandonrc in #930
  • fix(repositories): age policy on Remote repos again for backend 1.11.0 (#4264); fix the E2E spec by @brandonrc in #939
  • fix(curation): list conda_native for publisher trust; say invalid rules predate validation by @brandonrc in #938
  • feat(repositories): three-state visibility control by @mymarche in #840
  • 1.11.0: conda walkthrough screens and milestone fixes by @brandonrc in #965
  • feat(repositories): allowlist editor for conda virtual repositories (#971) by @brandonrc in #972
  • fix(security): one definition of severity counts across tiles, overview, repository and artifact views (#4338) by @brandonrc in #979
  • test(e2e): pull the tarball, not only the packument, in the remote cached-artifacts spec by @brandonrc in #982
  • chore(release): web 1.11.0 prep — CHANGELOG + version bump by @brandonrc in #980
  • chore(deps): next 16.3.8 and the open security bumps before the 1.11.0 tag by @brandonrc in #993
  • fix(setup): conda channels authenticate with Basic auth, not a /t/ token segment (#908) by @brandonrc in #991

New Contributors

Full Changelog: v1.10.1...v1.11.0

Don't miss a new artifact-keeper-web release

NewReleases is sending notifications on new releases.