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. Virtualcondaandconda_nativerepositories 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 ofname [version] [subdir,subdir]lines, and an import frompixi.lockorconda-lock.ymlthat 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'sfrom-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 rendersREPOSITORY_ALLOWLIST_CHANGEDas 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).Internalis readable by every signed-in user with no grant and never anonymously. The repository list marksInternalwith a group icon andPrivatewith a padlock (public stays unmarked); the repository detail header and the CVE blast-radius page reportinternalrather than calling it private. With guest access disabled the control stays and onlyPublicis withdrawn; an already-public repository keeps showing its own value, and saving an unrelated change no longer resends it. The client sendsvisibilitywith a matchingis_public, and readsvisibilityfalling back tois_public, so against a backend without artifact-keeper#3813Internaldegrades toPrivaterather 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.jsonmaintainer 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.mdin 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 ofGET /api/v1/formatsis 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,<2admits the pre-release2.0a1and1.0aorders below1.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 acondaorconda_nativestaging repository, or is global (and so also applies to conda), the form now shows short help with1.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 rulepolicy-predicateat 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; theproxyorigin kind carries a note that no production path records it yet (artifact-keeper#4161), so a policy that allows only proxy matches nothing.predicatesis 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 inremoved, and a CEP-6 notice is appended tonotices.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 ofcondaandconda_nativelocal 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 throughapiFetch, since the/condaroutes are not in the OpenAPI spec; the user's session is sent, whichnotices.jsonrequires 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
removedarray and publishes a CEP-6 notice innotices.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. Forcondaandconda_nativehosted and staging repositories, the web reads the channel's/conda/<key>/notices.jsononce, 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 inremoved, 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 throughapiFetch), 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 explicitrequire_verified: falsereads 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 examplepkg: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 throughapiFetchwith 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 responsemessage. 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
condaandconda_nativerepositories 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.sigssidecar 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 earlierstaterecord are read. A Conda package block lists the subdir, build, license,dependsand the uploader (uploaded_by_usernamefrom 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-64andnoarch, orpy310…andpy311…) looked identical in the list, because the platform subdir and the build string only appeared inside the path.condaandconda_nativerepositories now get Subdir and Build columns, read from the package's recordedindex.jsonfields 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_typeandmember_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 (localreads as "hosted") and its display name when it differs from the key. Forcondaandconda_nativevirtual 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 backendmainbuilds 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 requireGET /api/v1/admin/lifecycle/capabilitiesto returnexplicit_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-oidcendpoints. 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
/generalto 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_selectoronPOST /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 onlyname,scopesandexpires_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,azureorgcs), 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 carryorigin, so the dialog fetches the artifact by id (GET /api/v1/artifacts/{id}), skipped for proxy-cached rows, which have no artifact record;originis 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_nativeRemotes 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/condachannel router, which never reads thescan_on_proxyflag, so the toggle is still hidden forcondaandconda_native(unchanged).conda_nativejoinscondain the list of formats whose proxy downloads are counted, because the same/condarouter serves both formats and records every proxied download. Aconda_nativeRemote 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 owncondarow 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: trueon 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/healthreports 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 throughPOST /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_nativerepositories 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 ashttps://<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 anAuthorization: Basicheader 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 ascan_completeness_reasonon every scan andhas_uncataloged_scanon 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" whenhas_uncataloged_scanis 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.partialscans 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 tomember_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 summingstorage_used_bytesonly. Older backends do not send the field and the old figure is shown unchanged.member_storage_used_bytesis 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
mainwere flex items with the defaultmin-width: auto, so one wide child widened the whole column; the detail pane's ScrollArea wraps its content in adisplay: tablebox that grows to its widest descendant; and the repository tab strip (up to eleven tabs) never wrapped. The content column andmainnow takemin-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
hrefoption 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 withpackage.json, so this should not recur, but the label still trustedpackage.jsonover the tag. A build from a release tag (APP_VERSION=vX.Y.Z) now shows the tag's version and, ifpackage.jsondisagrees, 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
userstable, soGET /api/v1/usersreturned 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 asvc-*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 lefttotalandtotal_pagescounting 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_accountis not in the generated SDK yet, soadminApi.listUsersPagesends it through a narrowed cast. - Web image runs under an arbitrary OpenShift UID (#805) - OpenShift's default
restricted-v2SCC ignores the image'sUSERand runs the container as a random UID in GID 0..next/cache, the only path the standalone server writes at runtime, was owned1001:0with mode 0755, so that UID hit EACCES writing the image-optimization cache. It is nowchmod g=rwX, and the Dockerfile test guards the numeric non-rootUSER, 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/treeone 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
userstable, and granting to one failed with400 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}/completereturns 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 sayschecksum mismatch; any other 409 shows the backend's own message ("Artifact version already exists and is immutable"), orFailed 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 nomax-heightand nooverflow, 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 tomin-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 thoughTablealready carries anoverflow-x-autowrapper 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 withoverflow-hidden flex flex-coland an inner scroll area, and they are unchanged. Sixteen call sites had already pastedmax-h-[85vh] overflow-y-autoby 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 isw-fit whitespace-nowrapby 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.organd left conda_native empty. The backend treats the upstream URL as a channel and fetches{upstream}/{subdir}/repodata.jsonand{upstream}/channeldata.jsonbeneath it, so the bare root returned 404 until a channel was typed in by hand. Both formats now default tohttps://conda.anaconda.org/conda-forge; usehttps://conda.anaconda.org/<channel>for any other channel. - The Setup Guide shows conda channel setup for
condaandconda_nativerepositories (#908, backend artifact-keeper#4118) -condarepos were shown pip instructions for a/pypi/<key>/simple/index andconda_nativerepos 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.condarcchannel entry andinstall -ccommands using the/conda/t/YOUR_TOKEN/<key>token URL the backend accepts, plus an Upload tab with thecurl -X PUTcommand for local and staging repositories (remote and virtual channels reject uploads).conda_nativerepositories 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
metadatatier, and mark stored rules the backend cannot read (#910, backend artifact-keeper#4134) - the rule form offeredsignatureandnamespacematch modes and anauditaction 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 offersattestationandmetadata(the tier #4134 added so conda publishers can be trusted) andblock/allow/flag, with help text saying what each checks:attestationcounts 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), andmetadatatrusts 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 offeredflagandauditand defaulted toflag, which theallow/blockcolumn constraint rejects with a 500; it now offersallowandblock, 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/ogImageResponse(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=devis 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 onlyname,scopesandexpires_in_daysand 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
- @Doubleth1nk made their first contribution in #852
- @damjanek made their first contribution in #848
- @luko0610 made their first contribution in #390
- @vbartusevicius made their first contribution in #901
- @gilesw made their first contribution in #846
Full Changelog: v1.10.1...v1.11.0