<?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: Jacob Perks</title>
    <description>The latest articles on DEV Community by Jacob Perks (@thunkle).</description>
    <link>https://dev.to/thunkle</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%2F4048291%2Fb6a78952-91ee-4d48-93b9-1cbf3a456937.jpg</url>
      <title>DEV Community: Jacob Perks</title>
      <link>https://dev.to/thunkle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thunkle"/>
    <language>en</language>
    <item>
      <title>What I scan for in Framer code components</title>
      <dc:creator>Jacob Perks</dc:creator>
      <pubDate>Mon, 27 Jul 2026 15:33:25 +0000</pubDate>
      <link>https://dev.to/thunkle/what-i-scan-for-in-framer-code-components-45la</link>
      <guid>https://dev.to/thunkle/what-i-scan-for-in-framer-code-components-45la</guid>
      <description>&lt;p&gt;Most of a Framer site is not code. The parts that are, are code components, and that is where the risk collects. A component that calls an API needs a key. A component that needs a library sometimes imports it from a URL. A component rendering content from elsewhere has to decide how much it trusts that content.&lt;/p&gt;

&lt;p&gt;I built a free plugin that reads them and reports what it finds. Here is what it looks for, and the parts that were harder than expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the thing that caught me out
&lt;/h2&gt;

&lt;p&gt;Framer has two things called code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code components&lt;/strong&gt;, the &lt;code&gt;.tsx&lt;/code&gt; files under Assets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom code&lt;/strong&gt;, the named scripts under Settings with a placement dropdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same risk. A key in either ships to the browser. &lt;strong&gt;A plugin can only read the first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The API has &lt;code&gt;getCustomCode()&lt;/code&gt;, which sounds like it returns the project's custom code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CustomCodeLocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headStart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headEnd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bodyStart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bodyEnd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CustomCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CustomCodeLocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
    &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns whatever &lt;strong&gt;your own plugin&lt;/strong&gt; put there. I confirmed it by pasting a script into the Code panel of a real project, saving, and printing the counts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;headStart: 0 chars
headEnd:   0 chars      ← script visibly sitting in Settings → Code
bodyStart: 0 chars
bodyEnd:   0 chars

codeFiles: 1
  Test.tsx: 830 chars   ← Assets → Code, readable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The docs say a plugin can detect custom code "set by your plugin", which read carefully is the entire answer. I went through every read method on the API, roughly fifty, and nothing returns user-authored custom code or site settings.&lt;/p&gt;

&lt;p&gt;So the scanner reads what it can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&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;framer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCodeFiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for &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;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&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;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Test.tsx 830&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;subscribeToCodeFiles&lt;/code&gt; and &lt;code&gt;subscribeToRedirects&lt;/code&gt; let the panel re-scan as the user edits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credentials in components
&lt;/h2&gt;

&lt;p&gt;The most common real finding, for an ordinary reason: standing up a server to hold a key is a bigger job than the component that needs it, so the key goes in the file and stays there.&lt;/p&gt;

&lt;p&gt;Fifteen formats, weighted toward what people reach for when a component calls a model: OpenAI, Anthropic, Groq, Replicate, Hugging Face, Perplexity, alongside Stripe, AWS, Supabase, GitHub, SendGrid, Slack, Twilio, Resend and Convex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The check that decides whether anyone trusts the tool.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Supabase issues two keys. Both are JWTs, both are long, and side by side you cannot tell them apart. The &lt;code&gt;anon&lt;/code&gt; key belongs in the browser and is protected by row-level security. The &lt;code&gt;service_role&lt;/code&gt; key bypasses RLS entirely.&lt;/p&gt;

&lt;p&gt;One field in the payload separates them, so decode rather than pattern match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;supabaseJwtRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&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;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;try&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&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;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;p&gt;&lt;code&gt;service_role&lt;/code&gt; is critical. &lt;code&gt;anon&lt;/code&gt; is not reported at all. Same for Firebase browser keys and Stripe publishable keys, which exist to ship in a page.&lt;/p&gt;

&lt;p&gt;A scanner that flags those has not found anything. It has taught its user that its output is noise, and the next thing they skip will be real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modules imported from a URL
&lt;/h2&gt;

&lt;p&gt;Framer lets a component import straight from a URL. Useful, mostly fine, and it means whoever controls that host controls part of your site. If the file changes tomorrow, your site runs the new version.&lt;/p&gt;

&lt;p&gt;Two cases get flagged: anything over plain &lt;code&gt;http&lt;/code&gt;, and anything from a host that is not a recognised package CDN. esm.sh, jsDelivr, unpkg and Skypack stay quiet since that is normal practice. An unrecognised domain is worth a look, especially on a site someone else built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content turned into code
&lt;/h2&gt;

&lt;p&gt;Three worth knowing about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;, where any part of the HTML coming from a URL parameter or CMS field means someone can run scripts on your site&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eval&lt;/code&gt; and &lt;code&gt;new Function&lt;/code&gt;, which run whatever string they are handed&lt;/li&gt;
&lt;li&gt;reading a key or token out of &lt;code&gt;location.search&lt;/code&gt; or &lt;code&gt;location.hash&lt;/code&gt;, which leaks it into browser history, referrer headers and analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Redirects
&lt;/h2&gt;

&lt;p&gt;Not components, but the other thing nobody re-reads. When a redirect's destination page is deleted, Framer nulls the target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Redirect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;   &lt;span class="c1"&gt;// null = destination page was deleted&lt;/span&gt;
    &lt;span class="nx"&gt;expandToAllLocales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a broken redirect with no indication in the UI. Chains, loops and duplicate sources all fall out of the same array.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug I shipped into my own first draft
&lt;/h2&gt;

&lt;p&gt;Before publishing I ran the patterns against strings that are &lt;strong&gt;not&lt;/strong&gt; secrets. Four of six benign inputs came back flagged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FALSE POSITIVE  git commit SHA in a comment    -&amp;gt; "Twilio API key exposed"
FALSE POSITIVE  cache-busting hash in a URL    -&amp;gt; "Twilio API key exposed"
FALSE POSITIVE  css class name                 -&amp;gt; "OpenAI API key exposed"
FALSE POSITIVE  hyphenated word chain          -&amp;gt; "OpenAI API key exposed"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Twilio key identifier is &lt;code&gt;SK&lt;/code&gt; plus 32 hex characters, which is also every other git hash. And I had allowed hyphens in the OpenAI pattern, so &lt;code&gt;sk-spinner-container-large-variant&lt;/code&gt; qualified.&lt;/p&gt;

&lt;p&gt;Two guards. Require a match to mix character classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;looksHighEntropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for formats shape-identical to ordinary text, require supporting context before reporting at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twilio-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;bSK&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;fA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;requiresContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;twilio&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// no Twilio in the file, no finding&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero false positives afterwards, every real key still caught.&lt;/p&gt;

&lt;p&gt;Not a lesson about regexes. One confident wrong finding costs more trust than ten correct ones earn, and the only way to know which you have written is to test against things that are fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits worth stating out loud
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No custom code.&lt;/strong&gt; A clean scan does not mean a clean site. Check the Settings panel yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No published HTML.&lt;/strong&gt; No API for the rendered site, and you cannot fetch it cross-origin from the plugin iframe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No proof a tracker fired before consent.&lt;/strong&gt; That needs a browser watching the network on the live page. From inside the editor you can only infer, so the tool says "no consent signal found" rather than asserting a violation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The plugin
&lt;/h2&gt;

&lt;p&gt;Free, no account, no tiers: &lt;a href="https://www.framer.com/community/marketplace/plugins/thunkle-security-scanner/" rel="noopener noreferrer"&gt;Thunkle Security Scanner&lt;/a&gt; on the Framer Marketplace.&lt;/p&gt;

&lt;p&gt;Runs entirely in the browser, zero network requests, small static bundle so you can verify that in devtools rather than take my word for it.&lt;/p&gt;

&lt;p&gt;I write more of this at &lt;a href="https://thunkle.ai" rel="noopener noreferrer"&gt;thunkle.ai&lt;/a&gt;, where I audit and fix apps built with AI tools.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>cybersecurity</category>
    </item>
  </channel>
</rss>
