All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
release/1.1.xmaintenance branch +:1.1-devDocker tag rule (#331) - mirrorsartifact-keeper#890; pushes torelease/1.1.xnow publishghcr.io/artifact-keeper/artifact-keeper-web:1.1-devso the v1.1.x release-gate can test a true v1.1.x web/backend pair.
- Type-safe API layer — extend #206 hardening to sso (final batch) (#359 batch 9) - replaced all 30
as nevercasts insrc/lib/api/sso.tswith adapter functions andassertDataguards. 7 read adapters (SsoProvider / OidcConfig / LdapConfig / SamlConfig / LdapTestResult / TokenPair) and 6 write adapters covering the OIDC/LDAP/SAML create+update request shapes. Provider type narrowed vianarrowEnumto the localoidc | ldap | samlunion. The SDK declares attribute_mapping values asunknownwhile the local types declare them as string; the adapter coerces non-strings defensively.ldapLoginruntime-narrows the SDK'sunknown200 response to extract the access/refresh token pair. Closes #359 in full. - Type-safe API layer — extend #206 hardening to security (#359 batch 8) - replaced all 25
as nevercasts insrc/lib/api/security.tswith adapter functions andassertDataguards. 9 read adapters (Dashboard / Score / Scan / ScanList / Finding / FindingList / Policy / ScanConfig / RepoSecurity / TriggerScanResponse) and 4 write adapters (TriggerRequest / CreatePolicyRequest / UpdatePolicyRequest / UpsertScanConfigRequest). The Score adapter synthesizestotal_findingsfrom severity counts since the SDK ScoreResponse doesn't expose it directly. SDK PolicyResponse has additional fields the local ScanPolicy doesn't model (max_artifact_age_days,min_staging_hours,require_signature) which the adapter intentionally drops — those are consumed by the lifecycle module, not security. - Type-safe API layer — extend #206 hardening to promotion (#359 batch 7) - replaced 9 of 10
as nevercasts insrc/lib/api/promotion.tswith adapter functions,assertDataguards, andnarrowEnumforseverity(critical/high/medium/low/info) andPromotionHistoryStatus(promoted/rejected/pending_approval). Oneas unknown asretained inline forpolicy_result(the SDK exposes the field as an opaque key/value bag, the local type declares a typedPolicyEvaluationResultthat consumers only access lazily — bridge documented). Also exportsadaptArtifact/adaptArtifactListfromartifacts.tsandadaptRepository/adaptRepositoryListfromrepositories.tsso promotion can reuse them rather than re-implementing. - Type-safe API layer — extend #206 hardening to dependency-track (#359 batch 6) - replaced all 12
as nevercasts insrc/lib/api/dependency-track.tswith adapter functions andassertDataguards. The SDK declares every metric counter onDtProjectMetrics/DtPortfolioMetricsas optional; the local types declare them as required: number. Adapters coerce undefined → 0 so an empty backend response renders numeric zeros in the metrics card instead of "undefined". Nested adapters forDtFinding(component / vulnerability / analysis / attribution / cwe / license) preserve existing render behavior. - Type-safe API layer — extend #206 hardening to sbom (#359 batch 5) - replaced all 21
as nevercasts insrc/lib/api/sbom.tswith adapter functions,assertDataguards, and exportednarrowCveStatus/narrowPolicyActionhelpers for callers that want a typed status. Multiple SDK shape mismatches are now explicit and documented:LicenseCheckResultis synthesized (SDK returnsviolations: string[]with noaction; adapter coerces to{license, reason}rows and derivesaction: "block"|"allow"fromcompliant);getByArtifactno longer accepts aformatquery param (the SDK has no query and the backend ignored it pre-#359). No app consumer surfaces these endpoints today, so the synthesis is best-effort and documented inline. Other endpoints (generate/list/get/getComponents/convert/getCveHistory/updateCveStatus/getCveTrends/list-get-upsert-deletePolicy) round-trip pages unchanged. - Type-safe API layer — extend #206 hardening to replication (#359 batch 4) - replaced all 11
as nevercasts insrc/lib/api/replication.tswith adapter functions,assertDataguards, andnarrowEnumfor thePeerStatusunion. Dropped three dead fields fromPeerInstance(api_key/sync_filter/updated_at) and one fromPeerConnection(source_peer_id) — all four were declared on the local types but never populated by the SDK and never read by any consumer (verified via grep). The peers list and connections table render unchanged. - Type-safe API layer — extend #206 hardening to telemetry (#359 batch 3) - replaced all 9
as nevercasts insrc/lib/api/telemetry.tswith adapter functions,assertDataguards, and explicit body forwarding. CrashReport's optional+nullable fields (stack_trace,os_info,uptime_seconds,submitted_at,submission_error) now normalize undefined → null. Pages that consume this API are unchanged. - Type-safe API layer — extend #206 hardening to webhooks + analytics (#359 batch 2) - replaced all 9
as nevercasts insrc/lib/api/webhooks.tsand all 11 insrc/lib/api/analytics.tswith adapter functions,assertDataguards, andnarrowEnumfor theWebhookEventstring-to-union narrowing. Webhook events that the web doesn't model yet now fall back toartifact_uploadedwith a console warning instead of crashing render code expecting a known event. Pages that consume these APIs are unchanged. - Type-safe API layer — extend #206 hardening to monitoring + lifecycle (#359 batch 1) - replaced all
as nevercasts insrc/lib/api/monitoring.tsandsrc/lib/api/lifecycle.tswith adapter functions andassertDataguards. Adapters normalize the SDK's?: string | null(optional + nullable) shape to the local types': string | null(required + nullable) shape so callers see a stable contract. Twoas unknown ascasts remain inlifecycle.tsand are commented inline: the SDK incorrectly typescreateLifecyclePolicy/updateLifecyclePolicybodies as the security-policy request shape rather than the lifecycle request shape — to be removed when the generator is rebuilt against the corrected OpenAPI spec. Pages that consume these APIs are unchanged. - Admin Settings page now issues one HTTP call instead of three (#349) - the page used to call
/api/v1/admin/settingsthree times via separateuseQueryhooks (one each forpassword-policy,storage-settings,smtp-config). Replaced with a singleadmin-settingsquery backed by newsettingsApi.getAllSettings()and theuseAdminSettings()hook. The SMTP tab consumes the same hook so react-query dedups it. Public per-getter API (getPasswordPolicy/getStorageSettings/getSmtpConfig) preserved for non-page consumers (e.g. the inlinePasswordPolicyHint). Cuts settings page network round trips by 67%. Behavioral note: with one shared query, a malformed slice of the response (e.g. bad SMTP fields) now fails the whole bundle — Storage and Password Policy rows show "Unavailable" alongside the SMTP error, even though their fields parsed fine. Pre-PR these would have parsed independently. The trade-off is acceptable because all three slices come from the same endpoint and a malformed bundle is almost always a backend-wide problem; per-slice fault isolation is filed as a follow-up. toUserMessagetruncates user-untrusted error text at 240 chars (#356) - prevents a 50KB stack trace or HTML 500 page from rendering as a wall of text in a toast. Truncated output is suffixed with… [truncated, <n> more chars]so it's clear the message was clipped. Author-controlled fallback strings are not truncated.toUserMessageprefixes fallback with HTTP status code (#355) - when an error carries an HTTP status (.status/.statusCode/.body.status) but the body has no useful message, the fallback now reads "(HTTP 409) Failed to create permission" instead of just "Failed to create permission" so a 409 Conflict differentiates from a 500 Internal Server Error in toast text. Backend-provided messages stay unchanged (no double-decoration). Closes the deferred half of #207.- Extract
mutationErrorToasthelper to deduplicate ~125 mutationonErrorcallsites (#354) - the patternonError: (err) => toast.error(toUserMessage(err, "Failed to <action>"))was repeated across most pages; collapsed toonError: mutationErrorToast("Failed to <action>")in 36 files (-145 LOC). Centralizes future tweaks (HTTP status prefix, truncation, telemetry) to one place. User-visible toast strings are unchanged. - Type-safe API layer — replace double-casts with adapters and zod (#206) - removed all
as unknown as Tandas nevercasts in 15 ofsrc/lib/api/*.tsfiles. Each SDK call now goes through an adapter function or a Set-backed narrowing helper that warns on unknown enum values.assertData(new infetch.ts) rejects empty body responses with a contextual error.settings.tsuses zod.safeParse()at the trust boundary forgetPasswordPolicy/getSmtpConfig. PublicxxxApireturn types unchanged so consumer code is untouched.
- Extend SSE EVENT_TYPE_MAP to webhook/artifact/scan/backup/plugin events (#213) - the per-domain map only covered 7 domains (users, groups, repositories, service accounts, permissions, quality gates, dashboard). When backend events fired for the missing domains over SSE, the UI didn't refetch stale data — operators had to hard-refresh. Adds 5 QUERY_KEYS (
WEBHOOKS,WEBHOOK_DELIVERIES,BACKUPS,SECURITY,PLUGINS), 4 INVALIDATION_GROUPS (webhooks,backups,security,plugins), and 19 new event-type entries (webhook.{created,updated,deleted,delivery},artifact.{uploaded,deleted},scan.{started,completed,failed},finding.{acknowledged,acknowledgment_revoked},backup.{created,completed,failed,restored},plugin.{installed,uninstalled,enabled,disabled}). Map size grew 20 → 39. - Setup Guide: sanitize repo keys for Gradle/SBT property names + clearer SSR placeholder (#362, partial) - the Gradle credentials snippet emitted property names like
my-jvm-repoUsernamefor hyphenated repo keys; technically legal ingradle.propertiesbut looks broken to readers expecting identifier rules. Added arepoKeyToGradleIdhelper that camelCases kebab/dot/underscore separators and strips remaining non-alphanumerics. URLs and<id>slots keep the raw key — only property names sanitize. Also replaced the SSR fallbackhttps://artifacts.example.comwith__REPLACE_WITH_REGISTRY_URL__so prerendered HTML doesn't ship with a real-looking domain a user could accidentally copy. Remainingrepo_type(proxy/virtual hides publish steps) andis_public(anonymous mode) fixes deferred to follow-up. - Per-artifact Security tab now surfaces native scan_findings (#368) - the Security tab on the repository view (
security-tab-content.tsx) used to show only SBOM CVE history and Dependency-Track findings, never the nativescan_findingstable. A user who triggered a scan viaPOST /api/v1/security/scanfor a specific artifact had no way to see the resulting findings on the artifact's own page — they had to navigate to/security/scansand find the right scan ID by name+timestamp. NewArtifactScansSectioncomponent lists recent scan_results rows for the artifact (status / type / counts / completed_at) with a "View findings" link to the per-scan page. Sourced fromsecurityApi.listArtifactScans(artifact.id)which already existed but had no consumer. getInstallCommandreturns Gradle/SBT-native snippets instead of Maven XML (#361) - the JVM case inpackage-utils.tsreturned the same<dependency>XML for all three ofmaven/gradle/sbt. Users browsing a Gradle-named repo saw Maven XML in the package detail / copy-snippet UI — same bug class #333 fixed on the Setup Guide page. Nowgradlereturnsimplementation 'GROUP:name:version'andsbtreturnslibraryDependencies += "GROUP" % "name" % "version". Maven output is unchanged.- Surface load failures in
getPasswordPolicyandgetSmtpConfiginstead of silently falling back to defaults (#347) - both getters previously caught any SDK error or schema mismatch and returned baked-in defaults, so a backend outage rendered as plausible-looking placeholder values on the admin Settings page (same failure mode as #334). Now the getters throw on SDK error / unparseable response, and the page renders explicit "Unavailable" states (Password Policy row + SMTP tab error alert) so an operator can tell something's actually wrong. formatBytesreturns "--" for NaN/Infinity/negative input (#348) - previously these inputs produced nonsense strings like "NaN undefined" or "Infinity undefined" visible on the admin Settings → Storage tab. Now returns the same--sentinel already used elsewhere in the package/search rendering paths. Also clamps the unit index for >TB values so multi-PB byte counts render as " TB" rather than indexing past the units table.- SSO login button reads "Sign in with SSO" instead of generic provider names like "default" (#351) - when an admin's SSO provider is named
default/primary/main/sso(or empty/whitespace), the button now falls back to a protocol-aware label (Sign in with SSO (OIDC)/(SAML)) so users see what they're actually clicking. Real provider names like "Corp SSO" are preserved unchanged. - Login page hides username/password fields when only redirect SSO is configured (#350) - previously the form rendered even when the only available auth method was OIDC/SAML, leaving the fields with no consumer. The form now hides when SSO providers exist and no LDAP provider is configured. Setup mode and the
?fallback=localquery param keep the form available for first-time setup and operator recovery. A loading skeleton during the SSO providers fetch prevents the form from briefly flashing visible. Heuristic stopgap until the backend exposes a publiclocal_auth_enabledflag. - Migration Add Connection now lets users pick the source repository manager type (#319) - the form previously had no Source Type field, so the backend silently defaulted every connection to Artifactory. Adds a Source Type Select with Artifactory + Nexus options (the two values the SDK currently accepts), threaded through types, the API adapter, the form state, and the create-connection mutation body. Default remains Artifactory to preserve prior behavior.
- Setup Guide now shows correct client snippets for Gradle and SBT repos (#333) - JVM-format repos (maven / gradle / sbt) previously rendered only Maven
pom.xml/settings.xml. The dialog now offers Maven, Gradle (Groovy), Gradle (Kotlin), and SBT tabs with the correct credential and dependency snippets per client. Default tab tracks the repo's declared format so a Gradle repo opens on Gradle (Groovy). - Mutation errors now surface backend details instead of generic placeholders (#207) - audited every TanStack Query
useMutationand replaced opaqueonError: () => toast.error("Failed to ...")callbacks withtoUserMessage(err, fallback)-driven toasts. 91 callsites across 27 files. Also addsonErrorto 8 previously-silent mutations (security/policies/scans + repo-selector preview), and disambiguates the SSO toggle toasts per provider (OIDC/LDAP/SAML).toUserMessagenow also reads FastAPI-style.detailfields so plugin-install errors (and any other FastAPI-shaped backend error) surface their server-side message.
- Aria attribute coverage on admin pages (#208) - replaced
titlewitharia-labelon icon-only buttons (lifecycle, monitoring, quality-gates, sso, telemetry, groups, security/scans, file-viewer); paired form inputs with labels viahtmlFor/id; added accessible names toSwitchcomponents. Per-row table action buttons (SSO providers, quality gates, lifecycle policies, telemetry crash reports, users, monitoring suppress) now interpolate the row's identifying name into the aria-label so screen readers can disambiguate. Newly accessible-namedRefreshbuttons on approvals, security, and migration pages.
- Pin third-party GitHub Actions to commit SHAs (#205) - every third-party
uses:line incodeql.yml,dependency-review.yml,docker-publish.yml, andstale.ymlis now pinned to a specific commit SHA (with a version comment) so an upstream tag rewrite cannot silently swap action code.ci.ymlwas already pinned and is the model. The same-org reusable workflowartifact-keeper/artifact-keeper-test/.github/workflows/release-gate.yml@main(docker-publish.yml line 191) is intentionally tracked onmain— same-org workflows inherit the org's branch-protection trust boundary, and pinning a reusable workflow to a SHA is operationally heavier. Dependabot is configured forgithub-actions, so bumps continue to flow through review.
- v1.1.8 web image is permanently unavailable (#320) - the web release process stopped at v1.1.3 while the backend continued through v1.1.8. There is no v1.1.8 source ref to rebuild from; backfilling would falsify provenance. See docs/release-history/v1.1.8-web-postmortem.md. Recurrence is prevented by
artifact-keeper#882(image-publish gate).
First stable release of Artifact Keeper Web. Platform parity with artifact-keeper 1.1.0 backend. Consolidates 1.1.0-rc.5 through 1.1.0-rc.9 and post-RC work.
- Chunked upload for multi-GB artifacts (#218) - hashing, pause/resume/cancel controls, retry-per-chunk, speed/ETA readout; automatically engages for files >=100MB when uploading into a repository
- Repository-scoped access tokens (#294) - limit tokens by format filters, name pattern, and labels; token create dialog grows a repo selector when enabled
- Repository Settings tab on the detail view (#298) - inline edit of repository metadata without leaving the page
- Notification configuration tab on repositories (#293) - per-repo webhook and email notification targets
- SMTP configuration in admin settings (#299) - configure outbound mail server from the UI
- Webhook payload template selector (#295) - choose a predefined or custom payload template when creating a webhook
- Quarantine status on artifacts (#292) - list and detail views show quarantine state and banner
- Auth source badge on admin users list and edit dialog (#291) - shows which identity provider a user came from (local, LDAP, SAML, OIDC)
- Account lockout status on failed login (#284) - login page surfaces remaining attempts and lockout expiry
- Password expiry warning banner and force-change flow (#286) - warn before expiry and block access after, forcing a change
- Global error and root error boundary pages (#290) - Next.js
error.tsxandglobal-error.tsxwith telemetry and retry UX - Admin permissions UI (#186 by @TechEnchante) - manage principal / target / action permissions with repository selection
- Staging repository creation (#142) - create staging repos from the UI
- Artifact content viewer with syntax highlighting (#154) - browse file contents inline via Shiki
- Git commit hash in sidebar and settings (#153) - shows the running build hash for support and reproducibility
- Upstream auth fields on remote repo create/edit (#181) - set proxy credentials and tokens when configuring remote repositories
- Storage quota field on repository create/edit (#184) - per-repository size limits
- Default upstream URL suggestion by format (#185) - prefill proxy URL based on selected package format
- Admin token management for other users (#191) - admins can create, list, and revoke tokens on behalf of users
- Playwright E2E suite expansion (#76, #119, #121, #151) - 250+ interaction tests with RBAC role coverage, visual regression, and CI sharding
- Vitest unit test suite with V8 coverage (#69, #70, #71, #112, #113) - coverage gate integrated into CI
- Tutorial video pipeline (#79) - YouTube-ready tutorial generation with Amazon Polly voiceover
- SDK bump to
@artifact-keeper/sdk1.1.4 (#297, #233, #231) - track the generated OpenAPI client through the 1.1.0-rc.5 → 1.1.0 → 1.1.4 progression - Major dependency upgrades: Next.js 16.2.x, React 19.2.x, Tailwind CSS 4.2.x, shadcn/ui on Radix UI, TanStack Query 5.99.x, react-hook-form 7.72.x, framer-motion 12.38.x, vitest 4.1.x, shiki 4.0.x, lucide-react 1.8.x
- CI hardening - SonarCloud scan gated on
SONAR_TOKENavailability (#94); pre-release tags excluded from Docker Hub:latest(#223); duplication and new-code coverage gates added with visible per-step output (#313)
- Access token create dialog overflowed viewport in Playwright (#312) - dialog now capped at
90vhwith inner scroll, matching the pattern used by quality-gates, webhooks, and settings-sso dialogs - E2E selectors collided with new "Name Pattern" label (#301) - anchored
getByLabel(/^name$/i)on the access token dialog - SSO callback did not refresh auth context after token exchange (#276 by @nikitatsym) - callback now calls
refreshUser()before redirecting, so the sidebar reflects the authenticated user without reload - CSP tightened,
Math.randomreplaced, SSO errors sanitized (#217) - reduce XSS surface and information disclosure - Proxy body size limit raised for large artifact uploads (#285) - Next.js proxy middleware body limit increased
- CVE findings displayed GHSA instead of CVE identifier (#280) - resolve advisory IDs for display
- Scan status showed incorrect state when scan failed to execute (#288)
- Password reuse rejection message (#296) - surface the backend's policy message on change-password
- API keys and access tokens not showing after creation (#106)
- Download URL pattern mismatch with backend route (#115)
- Staging repo filtering used wrong type param (#138)
- Docker login
/v2not reaching middleware (#108) - middleware matcher extended - SSO callback route (#201) -
/auth/callbackrewrite routes to the SSO page - Virtual repo create field mapping (#187) - include
member_repos, fix members list - Non-admin users saw admin scope checkbox (#57)
- BACKEND_URL ignored at runtime in standalone Docker (#56, #58)
- Duplicate create buttons in Playwright strict mode (#66)
- Flaky E2E tests for security scans and access tokens (#119)
- Forced password change in E2E setup (#202)
- Release gate ran before image build - Docker publish now builds first, runs the compatibility gate after as an advisory check
- Code duplication gate result was invisible (#313) - step now prints percentage and clone list to stdout and fails fast on parser errors
- URL validation in package metadata and CSP header (#92) - validate URLs rendered from package metadata to prevent stored XSS; add
Content-Security-Policyheader - Instance URL SSRF hardening - reject private IP ranges and IPv6 loopback variants; remove legacy token storage from
localStorage - CSP tightening, Math.random replacement, SSO error sanitization (#217)
- @TechEnchante (#186)
- @nikitatsym (#276)
- @mergify[bot] (#232)
- Access Tokens page and Service Accounts UI (#62) - dedicated page for managing access tokens with service account support, moved from profile tabs to sidebar navigation
- Repo selector for service account token scoping (#64) - UI to restrict service account tokens to specific repositories
- Incus/LXC format (#63) - web UI support for browsing and managing Incus container images
- Live data refresh with SSE (#77) - real-time cache invalidation via server-sent events, TanStack Query cache tuning, and cross-page data coordination
- Plugin install dialog (#75) - wire up plugin installation flow to backend APIs
- Vitest unit test suite (#69, #70, #71) - unit tests for SDK client, auth API, and URL validation with V8 coverage reporting and CI integration
- Playwright E2E test suite (#76) - 250+ interaction tests with RBAC role coverage, visual regression, and CI sharding support
- Tutorial video pipeline (#79) - post-processing pipeline for generating YouTube-ready tutorial videos with Amazon Polly voiceover
- Duplicate create buttons (#66) - removed duplicated button elements that caused Playwright strict mode failures
- Plugins page description (#73) - updated page copy to match actual plugin capabilities
- E2E seed data API paths (#91) - corrected API endpoint paths and configuration in test seed data
- Instance URL validation hardened - prevent SSRF via instance URL by validating against private IP ranges, removing legacy token storage from localStorage
- IPv6 loopback check - fix URL validation to correctly identify IPv6 loopback addresses
- CI SonarCloud conditional (#94) - skip SonarCloud scan when
SONAR_TOKENis unavailable (forks, external PRs)
- URL validation in package metadata and CSP header (#92) - validate URLs rendered from package metadata to prevent stored XSS, add Content-Security-Policy header
- SonarCloud scanning added to CI (#72)
- Mergify auto-merge configuration (#67)
- Dependency upgrades: @tailwindcss/postcss 4.2.0, tailwind-merge 3.5.0, framer-motion 12.34.3, react-hook-form 7.71.2, react-resizable-panels v4, lucide-react, tailwindcss
BACKEND_URLignored at runtime in standalone Docker (#56, #58) — replaced build-timerewrites()with a Next.js middleware that readsBACKEND_URLon each request, so containers can be configured without rebuilding- Non-admin users saw admin scope checkbox (#57) — the "Admin" scope option is now hidden in both API Keys and Access Tokens forms for non-admin users
- Token CRUD E2E tests (#57) — Playwright tests for
POST /api/v1/auth/tokens(create),DELETE /api/v1/auth/tokens/:id(revoke), and empty-name validation
- Extracted
TokenCreateFormcomponent to eliminate duplicated form blocks in the profile page (#57) - Removed
ARG BACKEND_URLfrom Dockerfile build stage; default is now a runtimeENV(#58)
- SBOM UI for viewing, generating, and license compliance analysis
- TOTP two-factor authentication UI
- Instance online/offline status dots in instance switcher
- First-boot setup experience in web UI
- MIT License
- Use native arm64 runners for Docker builds (performance improvement)
- Add error handling to repository mutations for demo mode feedback
- Update demo auto-login password to match demo instance
- Clean up lint errors and unused imports
- Allow docker command to wrap in first-time setup banner
- Prevent docker exec command overflow on mobile screens
- Setup Guide page with repo-specific instructions and format filter
- Search artifacts inside repositories, not just repo names
- Redesigned repository browser with master-detail split-pane layout
- Multi-platform Docker builds (amd64 + arm64)
- Align packages and builds pages with actual backend API
- Remove standalone artifacts page, redirect to repositories
- Make Setup Guide page accessible without authentication
- Pass BACKEND_URL at build time for Next.js rewrites
- Redirect to / instead of /login on logout
- Widen setup dialog and wrap long URLs in code blocks
- Hide package detail panel when no packages exist
- Disable Next.js dev indicators in production
- Remove setState in useEffect and unused variable warnings
- Fetch artifact-matched repos from other pages, sort them first
- Stop 401 refresh loop when logged out
- Resolve lint errors blocking CI Docker image publish