<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Callie Schneider</title>
    <description>The latest articles on DEV Community by Callie Schneider (@callieschneider).</description>
    <link>https://dev.to/callieschneider</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4011845%2Fa0e0c73d-521f-4523-bd16-acf827e32fa2.png</url>
      <title>DEV Community: Callie Schneider</title>
      <link>https://dev.to/callieschneider</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/callieschneider"/>
    <language>en</language>
    <item>
      <title>Your CORS proxy is an SSRF engine: how to harden a URL fetcher</title>
      <dc:creator>Callie Schneider</dc:creator>
      <pubDate>Thu, 02 Jul 2026 10:56:49 +0000</pubDate>
      <link>https://dev.to/callieschneider/your-cors-proxy-is-an-ssrf-engine-how-to-harden-a-url-fetcher-5b8i</link>
      <guid>https://dev.to/callieschneider/your-cors-proxy-is-an-ssrf-engine-how-to-harden-a-url-fetcher-5b8i</guid>
      <description>&lt;p&gt;&lt;em&gt;I build and run &lt;a href="https://abundanceapis.com" rel="noopener noreferrer"&gt;Abundance APIs&lt;/a&gt;, a catalog of small utility APIs. This is a writeup of the security design behind the CORS proxy — the part I'd want reviewed. Disclosure: the proxy in the examples is mine; free tier, no signup.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A CORS proxy sounds trivial: take a &lt;code&gt;url&lt;/code&gt; param, &lt;code&gt;fetch&lt;/code&gt; it server-side, return the body with &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt;. In reality a naive version is a gift to attackers — it's a Server-Side Request Forgery (SSRF) engine running on your infrastructure, and it will happily fetch &lt;code&gt;http://169.254.169.254/latest/meta-data/&lt;/code&gt; (cloud credentials) or &lt;code&gt;http://10.0.0.5/internal-admin&lt;/code&gt; on your behalf.&lt;/p&gt;

&lt;p&gt;Here's how the proxy is actually hardened. None of these steps are optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Scheme + host allow-listing
&lt;/h2&gt;

&lt;p&gt;Only &lt;code&gt;http&lt;/code&gt;/&lt;code&gt;https&lt;/code&gt;. Reject everything else outright — &lt;code&gt;file://&lt;/code&gt;, &lt;code&gt;gopher://&lt;/code&gt;, &lt;code&gt;ftp://&lt;/code&gt;, &lt;code&gt;data:&lt;/code&gt; are all SSRF vectors or worse. Then resolve the hostname and reject the request if it points anywhere private.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Block every private/reserved IP range
&lt;/h2&gt;

&lt;p&gt;The obvious one is &lt;code&gt;127.0.0.1&lt;/code&gt;, but the real list is long:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loopback (&lt;code&gt;127.0.0.0/8&lt;/code&gt;, &lt;code&gt;::1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Link-local incl. the cloud metadata address &lt;code&gt;169.254.169.254&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;RFC1918 (&lt;code&gt;10/8&lt;/code&gt;, &lt;code&gt;172.16/12&lt;/code&gt;, &lt;code&gt;192.168/16&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CGNAT (&lt;code&gt;100.64/10&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Multicast, IPv6 ULA (&lt;code&gt;fc00::/7&lt;/code&gt;), IPv6 link-local (&lt;code&gt;fe80::/10&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;IPv4-mapped IPv6 (&lt;code&gt;::ffff:10.0.0.1&lt;/code&gt;) — easy to forget, trivially bypasses a naive IPv4-only check&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Defeat DNS rebinding (the step most proxies skip)
&lt;/h2&gt;

&lt;p&gt;Here's the subtle attack. You validate &lt;code&gt;evil.com&lt;/code&gt; → resolves to &lt;code&gt;1.2.3.4&lt;/code&gt; (public, looks fine) → then you call &lt;code&gt;fetch()&lt;/code&gt;, which resolves the hostname &lt;strong&gt;again&lt;/strong&gt;, and this time it returns &lt;code&gt;169.254.169.254&lt;/code&gt;. This is a TOCTOU (time-of-check-to-time-of-use) gap: the DNS answer changed between your check and the actual connection.&lt;/p&gt;

&lt;p&gt;The fix is to resolve &lt;strong&gt;once&lt;/strong&gt;, validate that specific IP, then &lt;strong&gt;pin the connection to it&lt;/strong&gt; so the fetch can't re-resolve to something else. With Node's &lt;code&gt;undici&lt;/code&gt; you do this with a custom &lt;code&gt;lookup&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undici&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dns&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:dns/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolvePublicIp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isPrivateIp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blocked: resolves to a private or reserved IP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Pin the validated IP so the actual socket can't re-resolve elsewhere.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;resolvePublicIp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Re-validate every redirect hop
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;https://evil.com/start&lt;/code&gt; returns &lt;code&gt;302 → http://169.254.169.254/&lt;/code&gt;. If you follow redirects automatically, the library will happily chase it — straight past your original check. So: follow redirects &lt;strong&gt;manually&lt;/strong&gt;, and run the full IP validation again on every &lt;code&gt;Location&lt;/code&gt; before each hop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hops&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_REDIRECTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;resolvePublicIp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// re-check each hop&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manual&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Bound everything
&lt;/h2&gt;

&lt;p&gt;An open fetcher is also a DoS amplifier and a memory bomb. Cap it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeout&lt;/strong&gt; on the upstream request (e.g. 15s)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max response size&lt;/strong&gt; — stream and abort past the cap (e.g. 10 MB), don't buffer unbounded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max redirects&lt;/strong&gt; (e.g. 5)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing it
&lt;/h2&gt;

&lt;p&gt;The bugs here are the ones that pass a happy-path test and fail in the wild. I verify against: loopback, each RFC1918 range, the cloud metadata IP, IPv4-mapped IPv6, and — the important one — &lt;strong&gt;a real hostname that resolves to loopback&lt;/strong&gt; (&lt;code&gt;localtest.me&lt;/code&gt; resolves to &lt;code&gt;127.0.0.1&lt;/code&gt;), which is exactly what catches a validator that checks the &lt;em&gt;string&lt;/em&gt; instead of the &lt;em&gt;resolved IP&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;If you're building anything that fetches a user-supplied URL server-side — a proxy, a webhook tester, a link-preview/OG scraper, an avatar-uploader-by-URL — you're building an SSRF surface whether you meant to or not. Resolve once, validate the IP, pin the connection, re-check every redirect, and bound the whole thing.&lt;/p&gt;

&lt;p&gt;I run this as a hosted CORS proxy + link-metadata/header-inspector API if you'd rather not maintain it yourself — &lt;a href="https://abundanceapis.com" rel="noopener noreferrer"&gt;abundanceapis.com&lt;/a&gt;, with a free in-browser &lt;a href="https://abundanceapis.com/tools/cors-proxy-tester" rel="noopener noreferrer"&gt;tester&lt;/a&gt; (no signup). But mostly: if you've got a URL-fetching endpoint in prod, go check how it handles &lt;code&gt;169.254.169.254&lt;/code&gt; right now.&lt;/p&gt;

</description>
      <category>security</category>
      <category>node</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Email Validation in One API Call: Syntax, MX, Disposable, Typos</title>
      <dc:creator>Callie Schneider</dc:creator>
      <pubDate>Thu, 02 Jul 2026 07:21:06 +0000</pubDate>
      <link>https://dev.to/callieschneider/email-validation-in-one-api-call-syntax-mx-disposable-typos-3c22</link>
      <guid>https://dev.to/callieschneider/email-validation-in-one-api-call-syntax-mx-disposable-typos-3c22</guid>
      <description>&lt;p&gt;&lt;em&gt;Canonical version on &lt;a href="https://abundanceapis.com/guides/free-email-validation-api" rel="noopener noreferrer"&gt;our guides page&lt;/a&gt;. Disclosure: I built the API used in the examples — it has a permanent free tier.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why form regex isn't enough
&lt;/h2&gt;

&lt;p&gt;A regex can tell you &lt;code&gt;jane@gmial.com&lt;/code&gt; &lt;em&gt;looks&lt;/em&gt; like an email. It can't tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the domain has no MX records (mail is undeliverable),&lt;/li&gt;
&lt;li&gt;it's a throwaway from a disposable-mail provider,&lt;/li&gt;
&lt;li&gt;it's a role account (&lt;code&gt;admin@&lt;/code&gt;, &lt;code&gt;billing@&lt;/code&gt;) that never converts,&lt;/li&gt;
&lt;li&gt;the user meant &lt;code&gt;gmail.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dead addresses pollute your list, tank your sender reputation, and waste onboarding emails.&lt;/p&gt;

&lt;h2&gt;
  
  
  One call, five checks
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://email-validator109.p.rapidapi.com/validate?email=jane.doe@gmial.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-RapidAPI-Key: YOUR_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-RapidAPI-Host: email-validator109.p.rapidapi.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://email-validator109.p.rapidapi.com/validate?email=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-RapidAPI-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-RapidAPI-Host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email-validator109.p.rapidapi.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response combines syntax validation, MX-record lookup, disposable-domain detection (checked against a ~121,000-domain list), role-account detection, and a did-you-mean typo suggestion — one JSON object.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with the results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invalid syntax / no MX&lt;/strong&gt; → reject with a clear message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disposable domain&lt;/strong&gt; → your call: block for trials, allow for downloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typo suggestion&lt;/strong&gt; → offer "Did you mean &lt;a href="mailto:jane.doe@gmail.com"&gt;jane.doe@gmail.com&lt;/a&gt;?" — this alone recovers a surprising number of signups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role account&lt;/strong&gt; → flag for sales/marketing lists rather than blocking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing is stored server-side; each request is validated in-memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it free
&lt;/h2&gt;

&lt;p&gt;In-browser checker (no signup): &lt;a href="https://abundanceapis.com/tools/email-validator" rel="noopener noreferrer"&gt;abundanceapis.com/tools/email-validator&lt;/a&gt;. The API's free tier is 1,000 validations/month.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>email</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Fix CORS Errors in JavaScript: The 5-Minute Proxy Solution</title>
      <dc:creator>Callie Schneider</dc:creator>
      <pubDate>Thu, 02 Jul 2026 07:20:30 +0000</pubDate>
      <link>https://dev.to/callieschneider/fix-cors-errors-in-javascript-the-5-minute-proxy-solution-5714</link>
      <guid>https://dev.to/callieschneider/fix-cors-errors-in-javascript-the-5-minute-proxy-solution-5714</guid>
      <description>&lt;p&gt;&lt;em&gt;Canonical version on &lt;a href="https://abundanceapis.com/guides/fix-cors-errors-in-javascript" rel="noopener noreferrer"&gt;our guides page&lt;/a&gt;. Disclosure: I built the proxy used in the examples — it has a permanent free tier.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the browser blocks your request
&lt;/h2&gt;

&lt;p&gt;The same-origin policy restricts scripts to resources from the same scheme, host, and port. When your JavaScript fetches a different origin, the browser requires the response to carry an &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header that permits your page. If the header is missing, the browser discards the response and logs the familiar error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No 'Access-Control-Allow-Origin' header is present on the requested resource&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For non-simple requests the browser also sends an &lt;code&gt;OPTIONS&lt;/code&gt; preflight that the server must answer correctly.&lt;/p&gt;

&lt;p&gt;The catch: &lt;strong&gt;you cannot change a third-party server's headers.&lt;/strong&gt; A server-side proxy that adds the header is the standard fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: route the request through a CORS proxy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://cors-proxy-web-toolbox.p.rapidapi.com/proxy?url=https://api.github.com/users/octocat"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-RapidAPI-Key: YOUR_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-RapidAPI-Host: cors-proxy-web-toolbox.p.rapidapi.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upstream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.github.com/users/octocat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cors-proxy-web-toolbox.p.rapidapi.com/proxy?url=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;upstream&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-RapidAPI-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-RapidAPI-Host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cors-proxy-web-toolbox.p.rapidapi.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "octocat"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy fetches the upstream URL server-side, returns the body unchanged, and adds permissive CORS headers so the browser accepts it. GET and POST are supported, redirects are followed, and requests to private networks and internal IPs are blocked (SSRF protection — the proxy pins DNS-validated public IPs and re-validates every redirect hop).&lt;/p&gt;

&lt;h2&gt;
  
  
  When a proxy is the wrong tool
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If you control the target server, set &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; there instead.&lt;/li&gt;
&lt;li&gt;Never forward end-user credentials or session tokens through any third-party proxy. Keep proxied requests to public data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it without signing up
&lt;/h2&gt;

&lt;p&gt;There's a free in-browser tester at &lt;a href="https://abundanceapis.com/tools/cors-proxy-tester" rel="noopener noreferrer"&gt;abundanceapis.com/tools/cors-proxy-tester&lt;/a&gt; — paste a URL and see the proxied response. The API itself has a free tier (1,000 requests/month).&lt;/p&gt;

&lt;p&gt;Questions about CORS edge cases (preflights, credentials, redirects)? Happy to answer in the comments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>cors</category>
      <category>api</category>
    </item>
  </channel>
</rss>
