Skip to content

fix(zoom): skip participants partitions blocked by Cloudflare WAF - #1933

Merged
mitasovr merged 2 commits into
constructorfabric:mainfrom
mitasovr:fix/zoom-participants-cloudflare-block
Jul 27, 2026
Merged

fix(zoom): skip participants partitions blocked by Cloudflare WAF#1933
mitasovr merged 2 commits into
constructorfabric:mainfrom
mitasovr:fix/zoom-participants-cloudflare-block

Conversation

@mitasovr

Copy link
Copy Markdown
Contributor

What

Adds an HttpResponseFilter (action: SUCCESS, error_message_contains: "Sorry, you have been blocked") to the participants requester of the zoom connector, next to the existing 404-skip filter, so partitions answered by Cloudflare's WAF block page are skipped instead of failing the sync forever.

Issue: #1932

Why

Since 2026-07-23 every zoom sync on dev-vhc dies on participants: Cloudflare in front of api.zoom.us false-positives on the literal meeting uuid 9XG9hLVrTtWhAK2A7TFO6w== and answers HTTP 403 with an HTML "Sorry, you have been blocked" page instead of a Zoom JSON error. Verified unauthenticated from inside the cluster: the block fires in both single- and double-escaped encodings, while other uuids with the same == tail get a normal Zoom 401 JSON — it is the uuid string itself, not quota, encoding, or IP reputation. A meeting uuid is immutable, so the partition can never succeed; and because the parent _meetings cursor persists only via this stream's state (incremental_dependency), one blocked meeting wedges every subsequent sync on the same partition.

Why match the body, not the status code

Real Zoom 403s (missing dashboard_meetings:read:admin scope — a JSON body) must keep failing the sync loudly: silently skipping them would advance the parent cursor over meetings that were never fetched (permanent data loss, green-but-empty syncs). In the declarative CDK, filter conditions (http_codes / predicate / error_message_contains) OR together and predicate only sees the parsed JSON body, so an AND of "403 + Cloudflare body" is not expressible; error_message_contains falls back to the raw body for non-JSON responses, which makes a body-only match on the block-page phrase exactly the discriminator needed. On a skipped partition the JsonDecoder yields {} for the HTML body, so the partition emits 0 records and the stream moves on — same mechanics as the existing 404 skip.

Tests

Two new mock tests in tests/test_participants.py (suite 18/18 green, validate_strict.py passes):

  • a Cloudflare-blocked partition (real poison uuid, trimmed real block page) is skipped, other meetings emit, and parent_state advances past the blocked meeting (it is deliberately the older one);
  • a genuine Zoom 403 (JSON body) still fails the sync.

After merge

Deploy the connector image on dev-vhc, then argo submit --from cronwf/zoom-default-sync -n insight and verify the job is green and the _meetings cursor advances (checklist in #1932).

🤖 Generated with Claude Code

Since 2026-07-23 every zoom sync on dev-vhc dies on the participants
stream: Cloudflare in front of api.zoom.us false-positives on the
literal meeting uuid "9XG9hLVrTtWhAK2A7TFO6w==" and answers HTTP 403
with an HTML "Sorry, you have been blocked" page instead of a Zoom
JSON error. Verified unauthenticated from the cluster: the block fires
in both single- and double-escaped encodings, while other uuids with
the same "==" tail get a normal Zoom 401 JSON - so it is the uuid
string itself, not quota, encoding, or IP reputation. A meeting uuid
is immutable, so the partition can never succeed; and because the
parent _meetings cursor persists only via this stream's state
(incremental_dependency), one blocked meeting wedges every subsequent
sync on the same partition.

Add an HttpResponseFilter (action=SUCCESS) matching the Cloudflare
block page body next to the existing 404-skip filter.
error_message_contains falls back to the raw body for non-JSON
responses, so it matches the Cloudflare HTML page but never a genuine
Zoom 403 (missing dashboard_meetings:read:admin scope - a JSON body),
which keeps failing the sync loudly. Filter conditions OR together in
the CDK and predicate only sees parsed JSON, so a body match without
http_codes is the only way to express this.

Mock tests: a Cloudflare-blocked partition is skipped while other
meetings emit and parent_state advances past the blocked meeting; a
genuine Zoom 403 still fails the sync.

Issue: constructorfabric#1932

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
@mitasovr
mitasovr requested a review from a team as a code owner July 27, 2026 04:36
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Regenerate the connectors-ddl snapshot

This PR changes src/ingestion/**. If your change affects any
bronze / silver / gold schema, regenerate the committed DDL snapshot
and include it in this PR:

cd src/ingestion/scripts/bootstrap-db
set -a; source pins.env; source .env; set +a
./bootstrap-db.sh connectors-config.yaml   # fresh ClickHouse 25.7.5
./dump-ddl.sh                              # writes scripts/connectors-ddl/*.sql

Commit the resulting scripts/connectors-ddl/*.sql diff. If nothing
changed, no snapshot update is needed. (Regeneration is manual for now.)

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@mitasovr, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c1f88db3-3813-4b12-ac05-b38974de3086

📥 Commits

Reviewing files that changed from the base of the PR and between 8e5bb89 and f3d51da.

📒 Files selected for processing (2)
  • src/ingestion/connectors/collaboration/zoom/connector.yaml
  • src/ingestion/connectors/collaboration/zoom/tests/test_participants.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@mitasovr
mitasovr merged commit be96150 into constructorfabric:main Jul 27, 2026
41 checks passed
mitasovr added a commit that referenced this pull request Jul 27, 2026
… manifest (#1936)

#1933 fixed the participants stream (skip partitions 403-blocked by
Cloudflare's WAF) in connector.yaml but left descriptor version at 1.2.0 —
the version already active in Airbyte since Jul 14. Reconcile republishes a
manifest only on descriptor-version drift, so it no-ops and the pre-fix
manifest keeps running (and failing) every nightly sync.

Patch bump per ADR-0015: republish + catalog re-discover, no full-refresh —
the fix only skips poisoned partitions at extract time; bronze→silver
semantics are unchanged.

Refs #1932, #1933.

Signed-off-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
Co-authored-by: Roman Mitasov <Roman.Mitasov@constructor.tech>
Co-authored-by: Claude Fable 5 <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.

zoom: participants sync wedged by Cloudflare WAF false positive on a meeting UUID

3 participants