Skip to content

fix(webhooks): read the signature header ClickUp and Linear actually send - #1645

Merged
cosmicbboy merged 1 commit into
mainfrom
fix/clickup-linear-signature-headers
Oct 2, 2026
Merged

cosmicbboy merged 1 commit into
mainfrom
fix/clickup-linear-signature-headers

Conversation

@cosmicbboy

Copy link
Copy Markdown
Collaborator

Found while triaging GitHub Copilot's review of unionai/unionai-docs#1673, which flagged the ClickUp header as its three high-severity findings. They're one bug, and it belongs here rather than in the docs. Linear has the same bug; Copilot did not catch that one.

The bug

ClickUpProvider looked for X-Clickup-Signature; ClickUp signs with X-Signature. LinearProvider looked for X-Linear-Signature; Linear signs with Linear-Signature.

Neither header is present on a real delivery, so verify hit its if not signature guard, returned False, and _app.py raised 401 before parse ever ran. With require_signature=True — the default — both integrations rejected every genuine webhook. Not a degraded path: nothing got through.

Verified against the vendor docs (ClickUp, Linear). I checked the other three providers too: GitHub X-Hub-Signature-256 and Slack X-Slack-Signature are correct and untouched, and Jira's X-Webhook-Token is the plugin's own shared-token stand-in because Jira Cloud doesn't sign webhooks at all.

Why CI was green

Each plugin's SAMPLE_DELIVERY signs with the same wrong header its own verify reads, so assert_provider_conforms verified the sample against itself and passed. The round trip is self-consistent no matter what the header is called — it can never catch this class of bug.

The per-plugin _parse helpers also built signature headers that parse ignores entirely. They tested nothing and propagated the wrong name into two more files.

Changes

  • Providers — correct header in the lookup and in the docstring that generates the API reference (clickup/_provider.py, linear/_provider.py).
  • Sample deliveries — _sample_headers emits the real header, so SAMPLE_DELIVERY now mirrors an actual delivery.
  • New test per provider — asserts the literal wire header verifies and the old name does not. This is the one thing the round trip can't check. Confirmed it fails against the pre-fix code.
  • _parse helpers — stop fabricating headers parse doesn't read.
  • Shared conformance helper — _CREDENTIAL_HEADERS gates the hostile-credential check by header name and continues on anything unlisted, so renaming a header silently switched that check off instead of failing. Updated the names and made a no-match assert, so it can't degrade to a no-op the same way again.
  • Docs — plugins/clickup/README.md, plugins/linear/README.md, plugins/README-saas-integrations.md.

Verification

  • All five SaaS plugin suites pass: clickup 6, linear 6, github 45, slack 24, jira 6.
  • Core webhook tests pass: 103 (tests/flyte/extras/webhooks, tests/flyte/app/extras/test_webhook_app.py).
  • Re-introduced the ClickUp bug on both files as a control: the new wire-contract test fails, and so does conformance via the new _CREDENTIAL_HEADERS guard. Two independent nets now catch it.
  • ruff check, ruff format --check, and the pre-commit fmt/mypy/ty hooks pass. No dependency or pyproject.toml changes, so the uv.lock gate is untouched.

Docs follow-up

unionai/unionai-docs#1673 should regenerate the content/api-reference/.../clickup/ pages after this merges (they render the docstrings above) and hand-edit content/integrations/saas-integrations/clickup.md. The Linear pages in that PR need the same correction, which the review didn't flag. The remaining low-severity comments there are docs-wording only and imply no further SDK change.

🤖 Generated with Claude Code

…send

`ClickUpProvider` looked for `X-Clickup-Signature` and `LinearProvider` for
`X-Linear-Signature`. Neither product sends those. ClickUp signs with
`X-Signature` and Linear with `Linear-Signature`, so `verify` found no header,
returned False, and `WebhookAppEnvironment` raised 401 before `parse` ever ran.
With `require_signature=True` — the default — both integrations rejected every
genuine delivery.

The test suite could not see it. Each plugin's `SAMPLE_DELIVERY` signs with the
same wrong header its `verify` reads, so the conformance round trip verified
against itself and passed. The per-plugin `_parse` helpers also built signature
headers that `parse` ignores entirely, which spread the wrong name without
testing anything.

So each provider now gets a test asserting the literal wire header, which is
the one thing the self-consistent round trip cannot check, and `_parse` no
longer fabricates headers it does not use.

`_CREDENTIAL_HEADERS` in the shared conformance helper needed the new names
too. That set gates the hostile-credential check by header name and `continue`s
on anything unlisted, so a rename silently turned the check off for that
provider rather than failing — the same way the round trip hid the bug itself.
It now asserts that it matched a header.

GitHub (`X-Hub-Signature-256`) and Slack (`X-Slack-Signature`) were verified
correct against their docs and are untouched. Jira's `X-Webhook-Token` is the
plugin's own shared-token stand-in, since Jira Cloud does not sign webhooks.

Refs: https://developer.clickup.com/docs/webhooksignature
Refs: https://linear.app/developers/webhooks

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant