🚀 Release v0.29.13#4883
Merged
Merged
Conversation
API key names longer than 32 characters were rejected by better-auth with a 400 that surfaced as an opaque INTERNAL_SERVER_ERROR (the generic "Failed to generate API key" toast). Add a shared name schema (min 1, max 32, matching better-auth's default maximumNameLength) used by both the tRPC input and the client form, and surface the limit on the Name field so users see it before submitting. Fixes #4798
…ength fix: validate API key name length to prevent opaque 500
The randomize/isolated-deployment volume transform split mount strings on ':' and kept only the first two segments, so an access mode like :ro, :z or :Z was silently dropped and read-only mounts became read-write. Keep the full path+mode remainder when rebuilding the mount string. Fixes #4818
…s-mode fix(compose): preserve named-volume access mode when adding suffix
The Server Domain form rejected an empty value ('Invalid domain name'),
making domain assignment a one-way operation. The backend already
removes the Traefik router and clears the host when it receives an
empty host, so only the client-side validation blocked removal.
Allow an empty domain to clear it, and skip the https/letsencrypt
requirements when the domain is being removed.
Fixes #4821
…ctor The repository CommandItem used the repo name as its cmdk value and the check icon compared only names, so two repos with the same name in different owners/orgs showed the selected checkmark and hover on both entries. Key the item by owner/name and include the owner in the selected comparison. GitLab already keyed by URL and is unaffected. Fixes #4793
Updated the PopoverContent components across various provider files to ensure consistent width and padding. Enhanced the display of repository and branch names by adding truncation to prevent overflow in the UI. This change applies to Bitbucket, Gitea, GitHub, and GitLab provider components, as well as their respective compose components.
fix(ui): disambiguate repos with the same name in the repository selector
fix(settings): allow clearing the server domain
fix(ui): typos
Renames the compose "Reload" action to "Rebuild" to accurately reflect its actual behavior. Clicking "Reload" triggers rebuildCompose() which executes a full Docker build (docker compose up -d --build). Also updates dialog text, toast notifications, and tooltips accordingly. Fixes #4666
… application, including common and settings JSON files for Azerbaijani, German, English, Spanish, Persian, French, Indonesian, Italian, Japanese, Korean, Kazakh, Malayalam, Dutch, Norwegian, and Polish. This cleanup helps streamline the localization process by eliminating empty files.
…viders User-controlled git fields (customGitUrl, branch names, repo owner/name, gitlab namespace, SSH hostname) were interpolated unescaped into git clone / ssh-keyscan shell commands run via execAsync / execAsyncRemote, allowing authenticated OS command injection. All such values are now passed through shell-quote before interpolation (defense at the sink, covering every code path including the compose branch bypass). Closes GHSA-qxcw-cx35-2hrw, GHSA-hrfh-82jj-3q46, GHSA-6693-xv3f-69px, GHSA-qwwm-hc7m-7xp9, GHSA-grrj-6xrh-j6vp, GHSA-x2p2-qq8g-2mqq, GHSA-cg8g-x23v-5fw8
fix(security): OS command injection in git clone across all providers
The .one / getRepositories / getBranches / testConnection handlers for github, gitlab, gitea and bitbucket resolved a provider by bare id under protectedProcedure and returned the full row (OAuth tokens, app private keys, webhook secrets) with no organization or entitlement check, letting any authenticated user read another org's git-provider credentials (IDOR). Adds assertGitProviderAccess() in the git-provider service (rejects cross-org with NOT_FOUND and non-entitled same-org with FORBIDDEN, reusing the existing getAccessibleGitProviderIds entitlement logic) and calls it in every read handler before returning secret-bearing data. Closes GHSA-66r5-5m5f-57vg
…nches The getBranches inputs type the provider id as optional, so only fetch and authorize when an id is actually present; the underlying getBranches already returns [] when no id is supplied.
…sclosure fix(security): git provider credential disclosure via cross-org IDOR (.one endpoints)
findServerById eagerly loads the sshKey relation (needed for server-side SSH operations) including the plaintext privateKey. server.one and server.remove returned that record to the client, exposing the private key to any member with server:read regardless of canAccessToSSHKeys. Adds redactServerSshKey() in the server service and applies it to the server.one and server.remove responses. No client feature consumes the private key (the SSH key management UI uses the dedicated sshKey router), and server-side callers keep using findServerById directly, so behaviour is unchanged. Closes GHSA-w9cp-jqfw-4xj9
swarm.getNodes/getNodeInfo/getNodeApps/getAppInfos accepted a caller-supplied
serverId and ran remote Docker Swarm reads against it with no organization
check, exposing another tenant's swarm topology cross-org (getContainerStats
already had the check; the others did not). getNodeInfo additionally
interpolated nodeId unescaped into 'docker node inspect ${nodeId}'.
Adds an org-ownership assertion to the four unscoped handlers and passes nodeId
through shell-quote in getNodeInfo.
Closes GHSA-jj6h-388v-9rwm
fix(security): SSH private key disclosure via server read endpoints
Match the existing getContainerStats style instead of a shared helper.
findApplicationById eagerly loads the github/gitlab/gitea/bitbucket relations (needed server-side to clone) including OAuth tokens, the GitHub App private key and webhook secret. application.one returned them to the client, exposing them to any member with service:read even when hasGitProviderAccess was false. Adds redactApplicationGitSecrets() in the application service (blanks the secret columns, immutably) and applies it to application.one. No client feature reads these secrets (verified in the frontend); server-side clone paths use findApplicationById directly, so behaviour is unchanged. Closes GHSA-hg9j-j5mc-phf5, GHSA-wx75-vxph-2m2f
fix(security): cross-org IDOR + nodeId injection in swarm read endpoints
fix(security): host-schedule owner/admin bypass via applicationId (member → root)
fix(security): missing authorization on GitHub App setup callback (unauth cross-org write)
…Word helper Use shell-quote's quote([...]) directly at each git-provider clone call site instead of the one-line shellWord wrapper, and remove the now-unused helper. Behavior is identical (quote([String(v ?? '')])).
…ders refactor(providers): inline quote() in git clone commands, drop shellWord helper
…mand injection Wrap S3 credential flags (getS3Credentials + destination.testConnection), the listBackupFiles search path, and every restore backupPath/backupFile with shell-quote's quote() so $(), backticks, quotes and spaces in destination fields or backupFile can no longer break out of the rclone shell commands.
Add VOLUME_NAME_REGEX (Docker volume-name format) and enforce it on volumeName in create/update/runManually — a legit volume name never contains shell metacharacters, so this blocks injection across every docker run/rm/rclone sink at once. Escape the user-supplied backupFileName and the mount volumeName in database rebuild with quote().
… invocations quote() the user-derived paths that reach the shell in file mounts (mount.ts, docker getCreateFileCommand), patch repo read (repoPath/filePath), certificate create/remove (certificatePath), and the remote scheduled command (containerId/shellType/command/logPath), so $(), backticks and traversal in these fields can no longer inject commands.
writeTraefikConfigRemote piped the stringified YAML through echo '...' where a single quote in any label/host/serviceName broke out (GHSA-478p). Encode it as base64 like the other writers, and quote() every configPath used in remote rm/cat/redirect commands (including the input.path-derived ones).
fix(security): escape user-controlled values across command-injection sinks (quote sweep)
server.remove deleted a server (and its deployment rows) by caller-supplied serverId without checking it belongs to the active organization, unlike server.one and server.update. An owner/admin of org A could delete org B's server registration. Resolve and compare the server's organizationId before the active-services guard, so cross-org callers are rejected without leaking existence.
…yById remote path
The remote (execAsyncRemote) path built `echo ${password} | docker ${args.join(" ")}`
with the password, registryUrl and username interpolated unescaped, so a password
like `pw; whoami` ran arbitrary commands as root on the target server. Reuse
safeDockerLoginCommand (already used by create/update), which shell-escapes each
field and feeds the password via --password-stdin. The local argv+stdin path was
already safe.
fix(security): command injection in registry.testRegistry / testRegistryById
fix(security): cross-org authorization bypass in server.remove
The organization switcher's DropdownMenuContent inherited its width from the collapsed trigger (~40px) via the base primitive's w-(--radix-dropdown-menu-trigger-width), clamping the menu to the min-w-32 (128px) floor. This cut off the org name and action buttons in icon mode. Set an explicit w-64 so the menu fits its content regardless of the trigger width, matching the pattern already used by the notification dropdown in the same file. Fixes #4840
…width fix(ui): organization menu clipped when sidebar is collapsed
The verify-totp handler checked for the error code INVALID_TWO_FACTOR_AUTHENTICATION, which no longer exists in better-auth 1.6.23 (the two-factor plugin now returns INVALID_CODE). As a result, entering a wrong TOTP code fell through to the generic catch and showed "Error verifying 2FA code / Unknown error" instead of a clear "Invalid verification code" message. Match the current better-auth error code so the specific, actionable message is shown.
fix(2fa): show correct error message for invalid TOTP code
fix: rename compose "Reload" action to "Rebuild"
Updated the SSO configuration to trust verified emails and modified user creation to set emailVerified to true by default. This enhances security and ensures that user email verification is properly handled.
…king fix(auth): enable email verification for SSO and user creation
Organization admins can define their own AI providers (name + API URL) from the AI settings section. When at least one custom provider is defined, it replaces the built-in provider list in the Add AI form, the API URL is auto-filled and locked, and the backend rejects any configuration whose URL is not in the allowed list.
feat(ai): allow organizations to define custom AI provider presets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR promotes changes from
canarytomainfor version v0.29.13.🔍 Changes Include:
✅ Pre-merge Checklist: