Dokploy / Dokploy/dokploy

Preview security block fires before the label filter → false-positive alarms on bot PRs

Open
#4,902 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug needs-triage🔍
Dominant language
TypeScript
Stars
37.4k
Forks
3k
Avg merge
1d 3h
Merged PRs (30d)
73

Description

To Reproduce

[!NOTE]
This report was researched and drafted with the help of an AI assistant. The code
references, line numbers, and log evidence have been verified by a human against the
commit noted below.

  1. Create an Application backed by a GitHub provider and enable Preview Deployments.
  2. In Preview Deployment Settings, set Preview Labels to preview and keep
    Require Collaborator Permissions enabled (the default).
  3. Let a bot with no repo write access (e.g. renovate[bot] / dependabot[bot]) open a
    PR. Its labels are e.g. dependencies, renovatenot preview.
  4. Observe the PR: Dokploy posts a 🚨 Preview Deployment Blocked - Security Protection
    comment, and re-posts it on every subsequent PR event (synchronize, etc.).
Current vs. Expected behavior

Current: The collaborator-permission check (and its block comment) runs before the
preview-label filter. A PR that the label filter would exclude anyway still gets the
alarming security comment — even though it was never going to deploy — and the comment is
re-posted on every PR update (no dedup).

Expected: A PR that does not match the configured preview labels should short-circuit
with no deployment and no security comment. The permission check should only apply to
PRs that would actually trigger a preview.

Flow today
flowchart TD
    PR[PR opened by low-perm author<br/>e.g. renovate bot, no 'preview' label] --> Perm{previewRequireCollaboratorPermissions<br/>!= false?}
    Perm -->|yes| Write{hasWriteAccess?}
    Write -->|no| Block[push to blockedApps + continue<br/>never added to secureApps]
    Block --> Comment[🚨 post 'Preview Deployment Blocked' comment]:::bad
    Write -->|yes| Secure[secureApps]
    Perm -->|no| Secure
    Secure --> Label{has required preview label?}
    Label -->|no| Skip[skip - no deploy]
    Label -->|yes| Deploy[deploy preview]

    classDef bad fill:#5a1414,stroke:#c0392b,color:#fff;

The comment fires at the Block node — reached long before the Label check that
would have skipped this PR harmlessly.

Provide environment information
Operating System:
  OS: Debian Bookworm
  Arch: amd64
Dokploy version: 0.29.0
VPS Provider: IONOS
What applications/services are you trying to deploy?
  https://github.com/vorausrobotik/vdoc
Which area(s) are affected? (Select all that apply)

Application

Are you deploying the applications where Dokploy is installed or on a remote server?

Same server where Dokploy is installed

Additional context
Code proof

1. Permission check + block comment run first — before any label logic
apps/dokploy/pages/api/deploy/github.ts#L411-L469

for (const app of apps) {
  if (app.previewRequireCollaboratorPermissions !== false) {
    // ...
    const { hasWriteAccess, permission } =
      await checkUserRepositoryPermissions(githubProvider, owner, repository, prAuthor);
    if (!hasWriteAccess) {
      console.warn(`🚨 SECURITY: Blocked preview deployment for ${app.name} ...`);
      blockedApps.push(app.name);
      continue;                    // L431 — never reaches secureApps.push
    }
    // ...
  }
  secureApps.push(app);            // L450
}

// L460 — posted whenever ANY app is blocked, regardless of labels, once per webhook delivery
if (blockedApps.length > 0) {
  await createSecurityBlockedComment({ owner, repository, prNumber, prAuthor, ... });
}

2. Label filter + deploy happen only AFTER, and only for secureApps
apps/dokploy/pages/api/deploy/github.ts#L471-L534

for (const app of secureApps) {            // blocked apps are structurally absent here
  if (app?.previewLabels?.length > 0) {
    let hasLabel = false;
    for (const label of githubBody?.pull_request?.labels) {
      if (app?.previewLabels?.includes(label.name)) { hasLabel = true; break; }
    }
    if (!hasLabel) continue;               // L482 — label gate, evaluated too late to stop the comment
  }
  // ...createPreviewDeployment / myQueue.add(...)  <- the only deploy path
}

No deploy for a blocked app is possible: it continues at L431/L443 before
secureApps.push (L450), and the deploy loop iterates secureApps only.

3. A [bot] author can never pass — no allowlist
packages/server/src/utils/providers/github.ts#L48-L86

// L59: octokit.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username })
// L67: const allowedPermissions = ["write", "admin", "maintain"];
// L68: const hasWriteAccess = allowedPermissions.includes(permission.permission);
// L74-L82: catch (404 for a non-collaborator / [bot] App identity) -> hasWriteAccess: false

A GitHub App identity such as renovate[bot] resolves to Permission Level: none, so it
is always blocked — there is no author allowlist or exception to opt out.

Real logs
🚨 SECURITY: Blocked preview deployment for preview from unauthorized user renovate[bot] ... Permission: none
✅ Security notification comment created on PR #371: .../pull/371#issuecomment-...476
✅ Security notification comment created on PR #371: .../pull/371#issuecomment-...680
✅ Security notification comment created on PR #371: .../pull/371#issuecomment-...802

Three identical comments on one PR — the block comment is re-posted on every PR event
(synchronize, etc.), with no deduplication. No deploy is ever attempted.

Why this is rarely reported

This isn't Renovate-specific — a Dependabot user with the same setup gets the identical
comment (dependabot[bot] also resolves to Permission: none). It's simply a narrow
config intersection, all of which must hold at once:

  1. Preview Deployments enabled — a niche opt-in; most users deploy a branch, not
    per-PR ephemeral environments.
  2. Dokploy ≥ v0.24.3 — the collaborator gate is new (added July 2025 in #2192 as the
    RCE fix). Before that there was no check and no comment.
  3. A bot authoring the PRsrenovate[bot] / dependabot[bot] are not repo
    collaborators, so getCollaboratorPermissionLevel returns none for both.
  4. The gate left on (default). Note previewRequireCollaboratorPermissions !== false
    means apps predating the field (undefined) also trigger the check — so upgraders are
    affected, not just new setups.

It surfaces more visibly with Renovate because it rebases/updates PRs frequently, and each
event re-posts the comment (the no-dedup issue) — so the noise is easy to notice.

Impact
  • Noise: an alarming security comment on every dependency-bot PR, repeated on every update.
  • The preview label is already a valid gate (only write+ users can apply a label), yet it
    is bypassed by the earlier permission check.
  • No config-only workaround exists except disabling the gate entirely
    (previewRequireCollaboratorPermissions = false), which is undesirable on public repos.
Proposed fix

Evaluate the label filter first; only run the collaborator check for apps whose label
conditions actually match. A PR that fails the label filter should short-circuit with no
comment and no deploy
. (Bonus: dedup / update the security comment instead of re-posting.)

Optional follow-up (feature): add a trusted-author allowlist (e.g. previewAllowedAuthors)
so known automation like renovate[bot] / dependabot[bot] bypasses the collaborator check.

Will you send a PR to fix it?

Yes

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in apps/dokploy/pages/api/deploy/github.ts around lines 411-534, then read packages/server/src/utils/providers/github.ts around lines 48-86. Trace how previewLabels are checked relative to the collaborator-permission check and security comment creation. Done means a PR that fails the configured label filter causes neither a deployment nor a security comment, while matching PRs retain the permission check.

Written by the indexing model from the issue text.

Assessment

Tech stack
github, typescript
Domain
backend, devops
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.