Skip to content

fix(preview-deployments): Filter preview labels before the collaborator check - #4903

Open
g3n35i5 wants to merge 1 commit into
Dokploy:canaryfrom
g3n35i5:fix/preview-label-filter-before-permission-check
Open

fix(preview-deployments): Filter preview labels before the collaborator check#4903
g3n35i5 wants to merge 1 commit into
Dokploy:canaryfrom
g3n35i5:fix/preview-label-filter-before-permission-check

Conversation

@g3n35i5

@g3n35i5 g3n35i5 commented Jul 24, 2026

Copy link
Copy Markdown

What is this PR about?

For GitHub preview deployments the collaborator-permission check ran before the preview-label filter. As a result a PR that the label filter would exclude anyway (e.g. a renovate[bot] / dependabot[bot] PR without the required label) still hit the permission check, was pushed to blockedApps, and triggered the alarming 🚨 Preview Deployment Blocked security comment — even though it was never going to deploy.

The preview label is itself a valid gate: only collaborators with write access can add labels to a PR. Evaluating it first means a PR that does not match the configured previewLabels short-circuits with no GitHub API call, no deployment, and no security comment.

Changes:

  • Extract the decision logic into apps/dokploy/utils/preview-deployment.ts (pullRequestMatchesPreviewLabels + partitionPreviewApps). The label filter now runs first, so resolveAuthorPermission (the GitHub API call) is only invoked for apps whose labels match the PR. The check still fails closed: any error blocks the app rather than deploying it.
  • Rework apps/dokploy/pages/api/deploy/github.ts to use the helper and drop the now-redundant label check from the deploy loop.
  • Improve traceability: every candidate app emits exactly one greppable decision log — ACCEPTED, BLOCKED, or SKIPPED — carrying the PR number, action, author, and reason. The previously-silent label-mismatch continue is now logged as a routine skip (deliberately not a SECURITY block), and the preview-limit skip is logged too. The SECURITY keyword is retained on security-relevant lines so existing log-based alerting keeps matching.

Example decision logs (one per candidate app, per PR event):

⏭️  Preview SKIPPED for "web" — PR #42 (opened) by dependency-bot[bot] on acme/web-app: PR labels [dependencies, renovate] do not match required [preview]
🚨 SECURITY: Preview BLOCKED for "web" — PR #42 (opened) by dependency-bot[bot] on acme/web-app: author lacks write access (permission: none)
⚠️  SECURITY: Preview ACCEPTED for "web" — PR #42 (opened) by dependency-bot[bot] on acme/web-app: collaborator permission check disabled (any author allowed)
✅ SECURITY: Preview ACCEPTED for "web" — PR #42 (opened) by alice on acme/web-app: author has write access (permission: write)
⏭️  Preview SKIPPED for "web" — PR #42 (opened): preview limit reached (3/3)

Tests:

  • apps/dokploy/__test__/git-provider/preview-deployment-label-order.test.ts (new) unit-tests the label semantics, the label-before-permission ordering, fail-closed behavior, and the traceability log contract.
  • apps/dokploy/__test__/deploy/github-webhook-handler.test.ts gains three end-to-end pull_request cases driving the real handler: a label-mismatched bot PR posts no security comment and does not deploy; a label-matched PR without write access posts the comment; a label-matched PR with write access queues the preview.

Built this branch, deployed the image to a real Dokploy instance, and replayed the failing scenario — voraus-renovate[bot] PRs on vorausrobotik/vdoc without the required preview label — across opened / labeled / unlabeled events on two PRs.

Every event now short-circuits at the label filter: no permission check, no deployment, and no 🚨 Preview Deployment Blocked comment. The decision logs confirm it:

⏭️  Preview SKIPPED for "preview" — PR #371 (unlabeled) by voraus-renovate[bot] on vorausrobotik/vdoc: PR labels [dependencies] do not match required [preview]
⏭️  Preview SKIPPED for "preview" — PR #371 (labeled)   by voraus-renovate[bot] on vorausrobotik/vdoc: PR labels [dependencies, renovate] do not match required [preview]
⏭️  Preview SKIPPED for "preview" — PR #372 (opened)    by voraus-renovate[bot] on vorausrobotik/vdoc: PR labels [dependencies, renovate] do not match required [preview]
⏭️  Preview SKIPPED for "preview" — PR #372 (labeled)   by voraus-renovate[bot] on vorausrobotik/vdoc: PR labels [dependencies, renovate] do not match required [preview]

No security comment was posted on either PR — the false-positive alarm is gone.

Checklist

Before submitting this PR, please make sure that:

  • You created a dedicated branch based on the canary branch.
  • You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
  • You have tested this PR in your local instance. If you have not tested it yet, please do so before submitting. This helps avoid wasting maintainers' time reviewing code that has not been verified by you.

Issues related (if applicable)

Closes #4902

Screenshots (if applicable)

N/A


Note

AI assistance: this change was drafted with Claude Code using the claude-opus-4-8[1m] model (Opus 4.8, 1M-token context).

…or check

For GitHub preview deployments the collaborator-permission check ran
*before* the preview-label filter. As a result a PR that the label filter
would exclude anyway (e.g. a `renovate[bot]` / `dependabot[bot]` PR without
the required label) still hit the permission check, was pushed to
`blockedApps`, and triggered the alarming `🚨 Preview Deployment Blocked`
security comment — even though it was never going to deploy.

The preview label is itself a valid gate: only collaborators with write
access can add labels to a PR. Evaluating it first means a PR that does not
match the configured `previewLabels` short-circuits with no GitHub API call,
no deployment, and no security comment.

Changes:

- Extract the decision logic into `apps/dokploy/utils/preview-deployment.ts`
  (`pullRequestMatchesPreviewLabels` + `partitionPreviewApps`). The label
  filter now runs first, so `resolveAuthorPermission` (the GitHub API call) is
  only invoked for apps whose labels match the PR. The check still fails
  closed: any error blocks the app rather than deploying it.
- Rework `apps/dokploy/pages/api/deploy/github.ts` to use the helper and drop
  the now-redundant label check from the deploy loop.
- Improve traceability: every candidate app emits exactly one greppable
  decision log — `ACCEPTED`, `BLOCKED`, or `SKIPPED` — carrying the PR number,
  action, author, and reason. The previously-silent label-mismatch `continue`
  is now logged as a routine skip (deliberately not a `SECURITY` block), and
  the preview-limit skip is logged too. The `SECURITY` keyword is retained on
  security-relevant lines so existing log-based alerting keeps matching.

Example decision logs (one per candidate app, per PR event):

    ⏭️  Preview SKIPPED for "web" — PR Dokploy#42 (opened) by dependency-bot[bot] on acme/web-app: PR labels [dependencies, renovate] do not match required [preview]
    🚨 SECURITY: Preview BLOCKED for "web" — PR Dokploy#42 (opened) by dependency-bot[bot] on acme/web-app: author lacks write access (permission: none)
    ⚠️  SECURITY: Preview ACCEPTED for "web" — PR Dokploy#42 (opened) by dependency-bot[bot] on acme/web-app: collaborator permission check disabled (any author allowed)
    ✅ SECURITY: Preview ACCEPTED for "web" — PR Dokploy#42 (opened) by alice on acme/web-app: author has write access (permission: write)
    ⏭️  Preview SKIPPED for "web" — PR Dokploy#42 (opened): preview limit reached (3/3)

Tests:

- `apps/dokploy/__test__/git-provider/preview-deployment-label-order.test.ts`
  (new) unit-tests the label semantics, the label-before-permission ordering,
  fail-closed behavior, and the traceability log contract.
- `apps/dokploy/__test__/deploy/github-webhook-handler.test.ts` gains three
  end-to-end `pull_request` cases driving the real handler: a label-mismatched
  bot PR posts no security comment and does not deploy; a label-matched PR
  without write access posts the comment; a label-matched PR with write access
  queues the preview.

Closes Dokploy#4902

---

AI assistance: this change was drafted with Claude Code using the
`claude-opus-4-8[1m]` model (Opus 4.8, 1M-token context). The GitHub issue was
read via the `gh` CLI, the fix and tests were generated iteratively, and every
change was manually reviewed, `tsc`/Biome-checked, and verified against the
passing test suite before committing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@g3n35i5
g3n35i5 requested a review from Siumauricio as a code owner July 24, 2026 08:02
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant