Skip to content

fix(zohomail): validate handshake signature before persisting secret - #616

Open
Rudra2637 wants to merge 1 commit into
corsairdev:mainfrom
Rudra2637:fix/zohomail-webhook-handshake-overwrite
Open

fix(zohomail): validate handshake signature before persisting secret#616
Rudra2637 wants to merge 1 commit into
corsairdev:mainfrom
Rudra2637:fix/zohomail-webhook-handshake-overwrite

Conversation

@Rudra2637

@Rudra2637 Rudra2637 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes the ZohoMail webhook handshake handler logic to prevent unauthorized or bare webhook secret overwrite attempts.

Specifically:

  • Check for any existing stored webhook secret in the database before proceeding.
  • Validate incoming handshake request signatures before calling ctx.keys.set_webhook_signature(...).
  • If an existing secret is found in storage, require a signature on the handshake request and verify it using that existing secret to prove ownership before allowing rotations.
  • If it is a first-time setup (no existing secret found), verify the signature against the new x-hook-secret if present, or persist directly if no signature is provided.

Fixes #583

Checklist

Before submitting your PR, please verify the following:

  • I have run pnpm lint and all checks pass
  • I have run pnpm typecheck and there are no TypeScript errors
  • I have run pnpm build and all packages build successfully
  • I have run pnpm test and all tests pass
  • I have added or updated tests where applicable
  • I have added or updated necessary documentation

Screenshots / Demos (if applicable)

Screenshot 2026-08-06 103059

Additional Notes

No breaking changes or new package dependencies introduced. All modifications are localized to @corsair-dev/zohomail.

Summary by CodeRabbit

  • Bug Fixes
    • Improved webhook handshake security by validating signatures before accepting or replacing secrets.
    • Prevented unsigned or invalidly signed secret updates.
    • Preserved existing secrets when replacement validation fails.
    • Added clearer error handling when secret retrieval or persistence fails.
    • Ensured secrets are not saved after a failed signature validation.

@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
www Skipped Skipped Aug 6, 2026 5:13am

Request Review

@github-actions github-actions Bot added the plugin Changes inside a plugin package label Aug 6, 2026
@Rudra2637 Rudra2637 changed the title fix(zohomail): validate handshake signature before persisting secret0 fix(zohomail): validate handshake signature before persisting secret Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The Zoho Mail handshake now loads the stored webhook secret before validation. It rejects unsigned or invalid secret replacements, persists secrets only after successful validation, and adds integration tests for failed setup and secret rotation.

Changes

Zoho webhook secret validation

Layer / File(s) Summary
Handshake validation and persistence
packages/zohomail/webhooks/challenge.ts
The handler loads the existing secret, handles retrieval failures, validates signatures against the correct secret, rejects unauthorized replacement, and delays persistence until validation succeeds.
Handshake security coverage
packages/zohomail/webhooks.integration.test.ts
Integration tests verify that failed setup does not persist a secret, unsigned and invalid rotations preserve the existing secret, and valid rotation updates it.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Zoho as Zoho webhook request
  participant Handler as Zoho webhook handler
  participant Store as Webhook secret store
  participant Verifier as Signature verifier
  Zoho->>Handler: Send x-hook-secret and optional signature
  Handler->>Store: Load existing secret
  Handler->>Verifier: Verify signature against stored or incoming secret
  Handler->>Store: Persist secret after validation
Loading

Possibly related issues

  • corsairdev/corsair issue 598: Addresses the same webhook signing-secret overwrite class in a different handler.
  • corsairdev/corsair issue 597: Addresses the same webhook secret-overwrite vulnerability in the Asana handler.

Possibly related PRs

Suggested reviewers: yuvrxj-afk

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: validating the Zoho Mail handshake signature before persisting the secret.
Linked Issues check ✅ Passed The changes implement issue #583 by validating signatures before persistence, protecting rotations, preserving first-time setup, and adding required tests.
Out of Scope Changes check ✅ Passed The changes are limited to Zoho Mail handshake logic and integration tests that directly support issue #583.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/zohomail/webhooks/challenge.ts`:
- Around line 30-32: Update the webhook-secret initialization flow around
ctx.keys.get_webhook_signature so first-time setup uses an atomic
create-if-absent or compare-and-set write, preventing concurrent unsigned
requests from overwriting an established secret. Preserve normal reuse of an
existing secret and add coverage for concurrent initialization attempts.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5502f93d-822e-47f4-b2c4-d776abd7584f

📥 Commits

Reviewing files that changed from the base of the PR and between 74ecc92 and d804426.

📒 Files selected for processing (2)
  • packages/zohomail/webhooks.integration.test.ts
  • packages/zohomail/webhooks/challenge.ts

Comment on lines +30 to +32
let existingSecret: string | undefined;
try {
await ctx.keys.set_webhook_signature(hookSecret);
existingSecret = (await ctx.keys.get_webhook_signature()) ?? undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the key-store contract and look for conditional-write support.
ast-grep outline packages/zohomail/webhooks/challenge.ts --items all
rg -n -C 12 '\b(get_webhook_signature|set_webhook_signature)\b' packages
rg -n -C 8 '\b(compareAndSet|compare_and_set|createIfAbsent|create_if_absent|transaction|serializ|mutex|lock|version)\b' packages

Repository: corsairdev/corsair

Length of output: 50375


Authorization Bypass (CWE-367): Time-of-check Time-of-use (TOCTOU) Race Condition

Reachability: External · Exploitability: Moderate

Make first-time webhook-secret setup atomic.

When two requests read an empty secret before either write, an unsigned request can overwrite the legitimate secret. Use a create-if-absent or compare-and-set write and add concurrent setup coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zohomail/webhooks/challenge.ts` around lines 30 - 32, Update the
webhook-secret initialization flow around ctx.keys.get_webhook_signature so
first-time setup uses an atomic create-if-absent or compare-and-set write,
preventing concurrent unsigned requests from overwriting an established secret.
Preserve normal reuse of an existing secret and add coverage for concurrent
initialization attempts.

@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR moves Zoho Mail handshake signature validation before secret persistence and adds coverage for rejected overwrites and authenticated rotation. However, the read-check-write sequence remains non-atomic, allowing concurrent first-time handshakes to bypass the new ownership requirement.

  • Retrieves the currently stored webhook secret before processing a handshake.
  • Requires signatures verified with the stored secret for ordinary replacement attempts.
  • Persists the incoming secret only after validation succeeds.
  • Adds integration tests for failed persistence, bare and invalid overwrite attempts, and authenticated rotation.

Confidence Score: 3/5

The PR should not merge until concurrent first-time handshakes cannot overwrite an established secret without proving ownership.

The new validation blocks sequential unauthorized overwrites, but two overlapping requests can both observe an absent secret and independently persist, allowing an unsigned later write to replace the legitimate value.

Files Needing Attention: packages/zohomail/webhooks/challenge.ts

Security Review

A concurrent unsigned first-time handshake can observe the same absent-secret state as a legitimate setup and overwrite the legitimate secret. How this was verified: The handler’s independent read and write are reachable concurrently, while the key manager serializes only writes and provides no atomic compare-and-set around the ownership decision.

Important Files Changed

Filename Overview
packages/zohomail/webhooks/challenge.ts Reorders validation before persistence and authenticates rotations, but uses a non-atomic read-check-write sequence that permits concurrent unsigned overwrite.
packages/zohomail/webhooks.integration.test.ts Adds useful sequential overwrite and rotation coverage, but does not exercise concurrent first-time handshakes.

Sequence Diagram

sequenceDiagram
  participant L as Legitimate handshake
  participant A as Unsigned attacker handshake
  participant H as Zoho handler
  participant K as Key storage
  L->>H: New secret and valid signature
  A->>H: Attacker secret, no signature
  H->>K: Read existing secret
  K-->>H: None
  H->>K: Read existing secret
  K-->>H: None
  Note over H: Both requests authorize as first-time setup
  L->>K: Persist legitimate secret
  A->>K: Persist attacker secret
  Note over K: Attacker secret overwrites legitimate secret
Loading

Reviews (1): Last reviewed commit: "fix(zohomail): validate handshake signat..." | Re-trigger Greptile

let existingSecret: string | undefined;
try {
await ctx.keys.set_webhook_signature(hookSecret);
existingSecret = (await ctx.keys.get_webhook_signature()) ?? undefined;

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 Non-atomic secret ownership check

If two first-time handshakes for the same tenant overlap, both can observe that no secret exists and proceed without ownership verification, allowing a later unsigned write to replace the legitimate secret and break or take control of subsequent webhook authentication.

How this was verified: The handler performs independent read and write operations, while the key manager serializes only writes and the webhook path provides no per-tenant lock around the ownership decision.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Plugin PR scorecard — packages/zohomail

Check Status Notes
R1 — Scope: plugin files only
R2 — Tests with assertions
R3 — Description complete
R3 — Linked issue / claim ⚠️ No "Fixes #…" or claim link — add one if this PR has a claim or issue
R4 — Demo video / recording

Rules: PLUGIN_PR_RULES.md · re-runs on every push

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Hey @Rudra2637, thanks for the contribution! 🏴‍☠️ Before a maintainer reviews, please fix the items below — the review re-runs automatically on your next push.

Must fix

  • P1 packages/zohomail/webhooks/challenge.ts:32Non-atomic secret ownership check
    If two first-time handshakes for the same tenant overlap, both can observe that no secret exists and proceed without ownership verification, allowing a later unsigned write to replace the legitimate secret and break or take control of subsequent webhook authentication.

How this was verified: The handler performs independent read and write operations, while the key manager serializes only writes and the webhook path provides no per-tenant lock around the ownership decision.

If anything remains after your next push, a bot commit will clean it up; a maintainer always does the final review and merge.

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

Labels

bot:round-1 Review bot posted consolidated findings plugin Changes inside a plugin package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(zohomail): handshake can overwrite webhook signing secret

1 participant