Preview security block fires before the label filter → false-positive alarms on bot PRs
Nobody has claimed this yet.
- 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.
- Create an Application backed by a GitHub provider and enable Preview Deployments.
- In Preview Deployment Settings, set Preview Labels to
previewand keep
Require Collaborator Permissions enabled (the default). - Let a bot with no repo write access (e.g.
renovate[bot]/dependabot[bot]) open a
PR. Its labels are e.g.dependencies,renovate— notpreview. - 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:
- Preview Deployments enabled — a niche opt-in; most users deploy a branch, not
per-PR ephemeral environments. - 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. - A bot authoring the PRs —
renovate[bot]/dependabot[bot]are not repo
collaborators, sogetCollaboratorPermissionLevelreturnsnonefor both. - 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
previewlabel 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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