Skip to content

🚀 Release v0.29.2#4299

Merged
Siumauricio merged 20 commits into
mainfrom
canary
Apr 25, 2026
Merged

🚀 Release v0.29.2#4299
Siumauricio merged 20 commits into
mainfrom
canary

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented Apr 24, 2026

This PR promotes changes from canary to main for version v0.29.2.

🔍 Changes Include:

  • Version bump to v0.29.2
  • All changes from canary branch

✅ Pre-merge Checklist:

  • All tests passing
  • Documentation updated
  • Docker images built and tested

🤖 This PR was automatically generated by GitHub Actions

Greptile Summary

v0.29.2 release promoting canary to main. Key changes include cross-org security hardening across cluster, deployment, backup, schedule, and WebSocket handlers, a logWebhookError helper to stop leaking Drizzle SQL in webhook error responses, a redesigned invitation email template with organization name personalization, and a ssh2 upgrade to 1.16.0.

  • P1 — Double-trigger in sync-version.yml: Adding push: tags: v* alongside release: published causes both events to fire when a release is created with a new tag. The parallel runs both attempt git push to the MCP and CLI repos; the second always fails with a non-fast-forward error, leaving every automated release with a failed workflow run.

Confidence Score: 4/5

Safe to merge for product code; the sync-version workflow will report a failure on every automated release until the double-trigger is fixed.

One P1 finding (workflow double-trigger causing push failures) caps the score at 4. All product-code changes are security improvements or bug fixes with no new regressions identified.

.github/workflows/sync-version.yml — double-trigger risk from simultaneous release and push:tags events.

Reviews (6): Last reviewed commit: "chore: add push trigger for version sync..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

NomisCZ and others added 10 commits April 3, 2026 01:09
The search filter on the Requests tab was incorrectly filtering by
RequestPath instead of RequestHost, causing "filter by name" to match
URL paths rather than hostnames. Updated the placeholder text to
reflect the correct field being searched.

Fixes #4249
…strictions

- Added comprehensive permission checks for creating, updating, and deleting schedules based on user roles (owner/admin) and schedule types (server/dokploy-server).
- Implemented restrictions for cloud users to prevent managing host-level schedules and changing schedule types.
- Improved access control for server-level schedules to ensure users can only manage schedules associated with their organization.
…e-authz

fix(schedule): add authz checks for server and host-level schedules
…nd WebSocket endpoints

Prevents owner/admin users of one organization from accessing servers,
destinations, and Docker Swarm join tokens belonging to other organizations
by validating organizationId on all endpoints that accept serverId or
destinationId as direct input.

- cluster: validate serverId org on getNodes, addWorker, addManager, removeWorker
- deployment: validate serverId org on allByServer
- backup: validate destinationId + serverId org on listBackupFiles
- volume-backups: validate destinationId + serverId org on restoreVolumeBackupWithLogs
- wss: validate server org on docker-container-logs, docker-container-terminal,
  listen-deployment, and terminal WebSocket handlers
- auth: fix TypeScript type for API key metadata parsing
- Implemented checks in the WebSocket server setups for Docker container logs, terminal, and deployment logs to ensure users can only access resources associated with their active organization.
- Enhanced security by closing WebSocket connections if the organization ID does not match the session's active organization ID.
…rg-idor

Fix/ghsa f8wj 5c4w frhg cross org idor
@github-actions github-actions Bot requested a review from Siumauricio as a code owner April 24, 2026 18:53
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Apr 24, 2026
Comment on lines +88 to +91
if (server.organizationId !== session.activeOrganizationId) {
ws.close();
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 pingInterval not cleared on early org-check return

The pingInterval created on line 79 is only cleaned up inside the ws.on("close", ...) handlers registered later (lines 141 and 172), but neither handler is ever registered when the organization check fails and the function returns early. The interval will keep firing every 45 seconds for the lifetime of the process. Because ws.readyState won't equal OPEN after ws.close(), no pings are actually sent, but the interval handle accumulates across all rejected connections.

Suggested change
if (server.organizationId !== session.activeOrganizationId) {
ws.close();
return;
}
if (server.organizationId !== session.activeOrganizationId) {
clearInterval(pingInterval);
ws.close();
return;
}

Siumauricio and others added 4 commits April 24, 2026 12:58
fix: drop .zip deployment - isDate is not a function
- Added `sendInvitationEmail` function to send invitation emails when a new organization is created in the cloud environment.
- Updated email template to enhance the invitation message and included a direct link for users to accept the invitation.
- Refactored email sending logic in the user router to utilize the new invitation email rendering function.
- Improved organization invitation email design for better user experience.
feat: implement invitation email functionality for organization creation
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Apr 25, 2026
Comment on lines 117 to 122
const serviceId =
existingSchedule.applicationId || existingSchedule.composeId;
if (serviceId) {
await checkServicePermissionAndAccess(ctx, serviceId, {
schedule: ["update"],
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 scheduleType change bypasses owner/admin role check in non-cloud mode

When an existing schedule has applicationId set, the serviceId branch is taken and only checkServicePermissionAndAccess runs. In non-cloud mode there is no guard against changing scheduleType, so a non-admin user who holds schedule:update permission on an application can send scheduleType: "server" and an arbitrary serverId in the update payload. updateSchedule(input) persists that change; the schedule then executes against a server (potentially from another org) without the owner/admin role check that the else branch enforces.

The IS_CLOUD type-change guard should also apply in self-hosted mode, or the server org/role checks should be repeated whenever input.scheduleType shifts to "server" or "dokploy-server".

…ror-responses

fix: stop leaking Drizzle SQL queries in webhook error responses (#4276)
fix: filter requests by hostname instead of path
Comment on lines +339 to +343
await sendInvitationEmail({
email,
inviteLink,
organizationName: org?.name || "organization",
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Email failure orphans the invitation record

sendInvitationEmail is called after the invitation has already been committed to the database. If the SMTP call fails (server unreachable, wrong credentials, etc.), the error propagates uncaught and TRPC returns a 500 to the client — but the invitation row is already persisted and the audit() on line 346 is skipped. On retry, the caller hits the "invitation already sent" conflict guard and gets a confusing error, since the invitation is valid in the DB but they never got a confirmation.

Contrast this with the equivalent path in user.ts, where the email send is wrapped in try/catch so a transient mailer failure doesn't fail the whole operation. The email send here should be wrapped similarly:

try {
    await sendInvitationEmail({ email, inviteLink, organizationName: org?.name || "organization" });
} catch (error) {
    console.error("Failed to send invitation email:", error);
}

Comment on lines +151 to +164
if (
existingSchedule.scheduleType === "server" &&
existingSchedule.serverId
) {
const targetServer = await findServerById(existingSchedule.serverId);
if (
targetServer.organizationId !== ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You don't have access to this server.",
});
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security input.serverId not validated in update — cross-org server hijack

The org-membership check on lines 151–164 validates existingSchedule.serverId (the server currently stored), but updateSchedule(input) on line 177 persists the entire input payload — including any new serverId the caller provides. updateScheduleSchema extends createScheduleSchema which includes serverId, and updateSchedule does a plain .set(rest) on all input fields, so a user with update permission on a server-type schedule can silently redirect it to any server in any org simply by supplying a foreign serverId in the request body. Add the same org check against input.serverId before calling updateSchedule:

if (input.serverId && input.serverId !== existingSchedule.serverId) {
    const newServer = await findServerById(input.serverId);
    if (newServer.organizationId !== ctx.session.activeOrganizationId) {
        throw new TRPCError({
            code: "UNAUTHORIZED",
            message: "You don't have access to this server.",
        });
    }
}

@Siumauricio Siumauricio merged commit 222b167 into main Apr 25, 2026
12 checks passed
Comment on lines 3 to +8
on:
release:
types: [published]
push:
tags:
- 'v*'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Double-trigger when release is published with a new tag

When a GitHub Release is created and a new tag is created in the same action (e.g., via the GitHub API or the Release UI with "Create new tag"), GitHub fires both release: published and push: tags: v* in parallel. Both workflow runs will clone the external repos, commit, and attempt to push. The first run's push succeeds; the second run fails with a non-fast-forward error because the remote has diverged. This causes a visible workflow failure on every automated release.

A common fix is to use a concurrency group with cancel-in-progress: true, or to guard with an if condition that distinguishes the two event types:

jobs:
  sync-version:
    concurrency:
      group: sync-version-${{ github.ref }}
      cancel-in-progress: true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated pr release size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants