Skip to content

fix: add auth guard to email notify endpoint#116

Open
gusye1234 wants to merge 2 commits into
mainfrom
fix/email-notify-auth
Open

fix: add auth guard to email notify endpoint#116
gusye1234 wants to merge 2 commits into
mainfrom
fix/email-notify-auth

Conversation

@gusye1234
Copy link
Copy Markdown
Contributor

Summary

  • Adds X-Internal-Secret header validation to /api/email/notify for non-service-binding requests
  • In production, Cloudflare Service Binding already provides isolation (internal-only)
  • This fixes the dev-mode fallback path which was completely unauthenticated
  • Both email-worker callers (index.ts and imap-poller-do.ts) now pass the secret on fallback

Test plan

  • Existing route tests pass (updated to use internal URL)
  • Verify email notify works in dev mode with the secret header
  • Verify unauthenticated requests to /api/email/notify return 401
  • Verify production service-binding path still works (no header check for internal URLs)

Closes #106

The /api/email/notify endpoint was accessible without authentication.
In production it's protected by Cloudflare Service Binding (internal
only), but the dev fallback path was completely open.

Add X-Internal-Secret header validation for non-service-binding
requests, using the shared ENCRYPTION_KEY. Update both email-worker
callers to pass the secret when using the fallback URL.

Closes #106
@gusye1234 gusye1234 requested a review from a team as a code owner May 25, 2026 04:35
Skip the X-Internal-Secret check when ENCRYPTION_KEY is not set in the
environment. This allows E2E tests and local dev setups without secrets
to continue working, while production (which always has the key) remains
protected.
Copy link
Copy Markdown
Contributor Author

@gusye1234 gusye1234 left a comment

Choose a reason for hiding this comment

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

Code Review — PR #116: fix: add auth guard to email notify endpoint

What it does

Adds authentication to the /api/email/notify endpoint when accessed via the dev fallback path (non-service-binding requests). Cloudflare Service Binding requests (identified by http://internal URL prefix) are trusted implicitly; all other callers must pass X-Internal-Secret matching ENCRYPTION_KEY.

Code Quality Checklist

Functionality

  • Code does what it's supposed to do
  • Edge cases handled (missing header, mismatched secret, no ENCRYPTION_KEY configured)
  • Error handling appropriate (401 with generic message)
  • No logic errors

Code Quality

  • Readable and well-structured
  • Guard is at the top of the handler, clear short-circuit pattern
  • Both email-worker callers updated consistently (index.ts and imap-poller-do.ts)
  • Test updated to use http://internal URL to simulate service binding

Code Simplification

  • Minimal, focused change
  • No over-engineering

Security

  • Closes a real vulnerability — previously the dev fallback endpoint was unauthenticated
  • No hardcoded secrets
  • Minor: Using ENCRYPTION_KEY as an auth token is pragmatic but dual-purposes the key. If ENCRYPTION_KEY ever gets rotated or leaked, it now also compromises this auth path. A dedicated INTERNAL_API_SECRET would provide better isolation — but for an internal dev fallback path, this is acceptable.
  • Minor: String comparison secret !== cfEnv.ENCRYPTION_KEY is technically vulnerable to timing attacks. For an internal endpoint this is fine, but crypto.timingSafeEqual would be the hardened approach.

Tests Coverage

  • Test updated to pass through the guard (uses http://internal URL)
  • No explicit tests for the 401 rejection path (missing/wrong secret). Would be good to add a test case that calls from a non-internal URL without the header and asserts 401.

Project Syncing

  • Email-worker index.ts and imap-poller-do.ts both send the secret header on fallback
  • Web endpoint checks it consistently

Observations

  1. URL-prefix trust model: Relying on req.url.startsWith("http://internal") to identify service binding requests is correct for Cloudflare Workers — service bindings use the internal hostname. This is a well-established pattern.

  2. Guard skipped when ENCRYPTION_KEY is falsy: The condition cfEnv.ENCRYPTION_KEY means in environments without this env var, the endpoint remains open. This is presumably intentional for local dev, but worth documenting.

Verdict

Clean, targeted security fix. Closes an unauthenticated endpoint vulnerability on the dev fallback path. The two minor notes (dedicated secret, timing-safe compare) are nice-to-haves for a follow-up. Ready to merge.

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.

Security: Unauthenticated Email Notify Endpoint

1 participant