Skip to content

fix(public): stop Cloudflare email obfuscation degrading email CTAs to "[email protected]" - #123

Merged
nish3451 merged 33 commits into
mainfrom
fix/cloudflare-email-obfuscation-ctas
Aug 19, 2026
Merged

fix(public): stop Cloudflare email obfuscation degrading email CTAs to "[email protected]"#123
nish3451 merged 33 commits into
mainfrom
fix/cloudflare-email-obfuscation-ctas

Conversation

@nish3451

@nish3451 nish3451 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

What

The live tinystudio.in serves email CTAs as the "[email protected]" placeholder. Confirmed with curl https://tinystudio.in/: the homepage body contains a __cf_email__ span with data-cfemail and href="/cdn-cgi/l/email-protection#..." — the zone-level Cloudflare Email Address Obfuscation (Scrape Shield) rewrites every literal support@tinystudio.in in served HTML at the edge. The decode script that restores the real email often never runs (privacy blockers block /cdn-cgi/ scripts; mailto: hrefs with ?subject= params are a known corruption case), so the primary CTAs degrade to the placeholder.

Fix

Entity-encode the @ in every served occurrence across the 13 public pages: support@tinystudio.insupport@tinystudio.in (38 occurrences in HTML content, in both mailto: hrefs and visible text).

  • Browsers decode the entity in text and attribute values → CTAs render and mailto-link exactly as before.
  • Cloudflare's obfuscation regex matches only a literal @ in response bytes → nothing left to rewrite, placeholder impossible.
  • Inline <script> content (contact page measurement script) deliberately left plaintext: entities are not decoded inside script elements (would break the built href), and Cloudflare does not rewrite script content.

Guards

  • scripts/test-public-deploy-bundle.mjs — new section H: every one of the 13 served pages must have no plaintext email outside script blocks and must serve the entity-encoded form. Regression-locks the invariant.
  • scripts/prepare-static-site-bundle.mjs — the defensive Cloudflare-artifact reversal now restores the entity-encoded canonical form.
  • scripts/test-public-conversion-signal.mjs — mailto-route assertion inspects a decoded copy (entity form keeps the route intact).
  • scripts/check-public-live-deploy.mjs — new post-deploy live proof: homepage has no __cf_email__, no "[email" placeholder, no plaintext email, and serves support&#64;tinystudio.in. Fail-closed on the next actual deploy.

Verification

  • npm test full blocking chain: all scripts pass locally except check-retention-automation.mjs, which fails identically on a pristine checkout of origin/main — an external-environment mismatch (~/.codex/automations/tinystudio-retention-checkups/automation.toml was edited today and now points its workspace at the autonomous-service worktree); GitHub Actions treats the missing-file case as warn+exit-0, and today's CI runs (e.g. chore/remove-nonfunctional-codeql @ 09:54Z) passed the same chain, so this is pre-existing and unrelated.
  • Modified tests: test-public-deploy-bundle (H section runs, 55+ checks green), test-public-conversion-signal green, node --check on all .mjs green.
  • prepare-static-site-bundle.mjs run end-to-end on a throwaway copy of public/: exits 0, entity form preserved, zero cf artifacts.
  • The live fix becomes visible after the next Pages deploy (release lane is dormant while the Pages token is missing); the new live proof in the deploy lane will verify it.

Summary by CodeRabbit

  • Bug Fixes

    • Improved support email links across public pages by encoding the @ character while preserving their appearance and mailto destinations.
    • Prevented email addresses from being replaced by unwanted obfuscation or placeholder text in live pages.
  • Tests

    • Added validation to confirm email links render correctly and remain free of plaintext or obfuscated output.

…o "[email protected]"

The tinystudio.in zone rewrites literal support@tinystudio.in in served
HTML into __cf_email__ spans (and mailto hrefs into /cdn-cgi/l/
email-protection# links) whose visible text is the "[email protected]"
placeholder; the decode script often does not run (privacy blockers
block /cdn-cgi/ scripts), so the primary email CTAs render as the
placeholder on the live site.

Entity-encode the @ in every served occurrence (support&#64;tinystudio.in):
browsers decode the entity in text and hrefs, so the CTA renders and
links exactly as before, while Cloudflare's obfuscation regex has no
literal @ to match in the response bytes. Inline <script> content is
left untouched (entities are not decoded there, and Cloudflare does not
rewrite script content).

Also: update the bundle-prep email-protection reversal to restore the
entity-encoded canonical form, lock the invariant in with a new
test-public-deploy-bundle section (no plaintext email outside script
blocks in any of the 13 served pages), teach the conversion-signal test
to inspect the mailto route on a decoded copy, and add a live
no-obfuscation proof to the release lane's post-deploy verification.

@greptile-apps greptile-apps 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.

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 35 minutes

Limit details: You’ve used the included review currently available.

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 within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: 95da859b-7a00-4948-92ea-6ce1aafbce2e

📥 Commits

Reviewing files that changed from the base of the PR and between 94e971a and 5a33dfc.

📒 Files selected for processing (5)
  • public/drishti/privacy/index.html
  • public/privacy/index.html
  • public/promptly/privacy/index.html
  • public/terms/index.html
  • scripts/check-public-live-deploy.mjs
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: bb0249ff-5a8e-4327-b28a-eefd46f7d6ad

📥 Commits

Reviewing files that changed from the base of the PR and between 10cd244 and 94e971a.

📒 Files selected for processing (17)
  • public/404.html
  • public/contact/index.html
  • public/drishti/index.html
  • public/drishti/privacy/index.html
  • public/drishti/support/index.html
  • public/index.html
  • public/privacy-choices/index.html
  • public/privacy/index.html
  • public/promptly/index.html
  • public/promptly/privacy/index.html
  • public/promptly/support/index.html
  • public/support/index.html
  • public/terms/index.html
  • scripts/check-public-live-deploy.mjs
  • scripts/prepare-static-site-bundle.mjs
  • scripts/test-public-conversion-signal.mjs
  • scripts/test-public-deploy-bundle.mjs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Public email links now encode @ as &#64;. Static-site preparation preserves this encoding after Cloudflare replacement. Live and bundle checks reject plaintext or placeholder support email output.

Changes

Email address encoding

Layer / File(s) Summary
Encode email addresses in public pages
public/404.html, public/contact/index.html, public/drishti/..., public/index.html, public/privacy-choices/index.html, public/privacy/index.html, public/promptly/..., public/support/index.html, public/terms/index.html
Public mailto targets and visible support email text now use &#64; where applicable.
Preserve encoding during bundle preparation
scripts/prepare-static-site-bundle.mjs
Cloudflare-obfuscated support links are restored with an entity-encoded @.
Validate encoded served markup
scripts/check-public-live-deploy.mjs, scripts/test-public-conversion-signal.mjs, scripts/test-public-deploy-bundle.mjs
Checks require the encoded support address and reject plaintext addresses, placeholders, and Cloudflare obfuscation output.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 94e97

The change prevents Cloudflare from rewriting public email CTAs while preserving their displayed addresses and mailto behavior; no actionable merge-blocking risk remains beyond normal checks and review.


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

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

@greptile-apps greptile-apps 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.

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps 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.

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps 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.

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps 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.

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps 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.

nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

…obfuscation-ctas

# Conflicts:
#	public/drishti/index.html
#	public/promptly/index.html
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@nish3451
nish3451 merged commit 32099a9 into main Aug 19, 2026
2 checks passed
nish3451 added a commit that referenced this pull request Aug 19, 2026
PR #123 landed its own section E (Cloudflare email obfuscation) in
check-public-live-deploy.mjs. Kept both checks and relabelled this branch
footer tap-target section from E to F.
nish3451 added a commit that referenced this pull request Aug 19, 2026
PR #123 entity-encoded the footer mailto (support&#64;) to stop Cloudflare
Email Address Obfuscation rewriting it. Kept that encoding on all 13 pages and
re-applied this PR footer-email class on top, so the underline affordance and
the obfuscation fix both survive.
nish3451 added a commit that referenced this pull request Aug 19, 2026
PR #123 replaced the literal support@tinystudio.in in the public HTML with
support&#64;tinystudio.in so Cloudflare Email Address Obfuscation cannot
rewrite it into a [email protected] placeholder, and its live check now
asserts the plaintext form is absent. This guard still required the plaintext
form, so the two contradicted each other. Updated to match the landed
encoding.
nish3451 added a commit that referenced this pull request Aug 19, 2026
PR #123 replaced the literal support@tinystudio.in in the public HTML with
support&#64;tinystudio.in so Cloudflare Email Address Obfuscation cannot
rewrite it into a [email protected] placeholder, and its live check now
asserts the plaintext form is absent. This guard still required the plaintext
form, so the two contradicted each other. Updated to match the landed
encoding.
nish3451 added a commit that referenced this pull request Aug 19, 2026
PR #123 landed its own section E (Cloudflare email obfuscation) in
check-public-live-deploy.mjs. Kept both live checks and relabelled this
branch section from E to F.
nish3451 added a commit that referenced this pull request Aug 19, 2026
PR #123 landed its own section E (Cloudflare email obfuscation) in
check-public-live-deploy.mjs. Kept both live checks and relabelled this
branch section from E to F.
nish3451 added a commit that referenced this pull request Aug 19, 2026
PR #123 landed its own section E (Cloudflare email obfuscation) in
check-public-live-deploy.mjs. Kept both live checks and relabelled this
branch section from E to F.
nish3451 added a commit that referenced this pull request Aug 19, 2026
check-public-live-deploy.mjs gained sections E (Cloudflare email, PR #123) and
F (JSON-LD coverage, PR #127) on main. Kept both and relabelled this branch
trust-page outline check to section G; also took main count-free proofs
comment wording.
nish3451 added a commit that referenced this pull request Aug 19, 2026
package.json: kept main ci/test chains, re-inserted this PR
test-public-product-lane-consistency.mjs step.

Also entity-encoded the two new "Ask about 0509" mailto links this PR adds to
public/contact/index.html. PR #123 landed test-public-deploy-bundle.mjs, which
fails any plaintext support@ address outside a script block, so the new links
now use support&#64;tinystudio.in like every other address on the page. The
mailto built inside the measurement script block is untouched - it is inside a
script and the guard permits it.
nish3451 added a commit that referenced this pull request Aug 19, 2026
check-public-live-deploy.mjs now carries sections A-G on main (E Cloudflare
email #123, F JSON-LD #127, G footer tap-targets #110). Rather than resolve the
overlapping hunks by hand, this takes main file verbatim and grafts this
branch shared-footer copy proof onto it as section H. Repo check chain green.
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