<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>The Asterisk</title>
        <link>https://paragraph.com/@ivy</link>
        <description>undefined</description>
        <lastBuildDate>Sat, 01 Aug 2026 22:53:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[How To Use Robots To Get Rid Of Spam]]></title>
            <link>https://paragraph.com/@ivy/how-to-use-robots-to-get-rid-of-spam</link>
            <guid>zvFrbzNwck9aPqmv3d80</guid>
            <pubDate>Fri, 28 Nov 2025 01:20:13 GMT</pubDate>
            <description><![CDATA[If you're not a user of archiving services like archive.today to get past paywalls (or even if you are), you might have a lot of spam in your inbox. Eventually, you get fed up with it and book off an afternoon to get rid of all of it in an effort to get back to Inbox Zero. What if you used a robot friend to get it done in an hour instead?Who You Might BeThis article is aimed at non-technical audiences with enough technical seeds so developers new to using AI can also run with it to create the...]]></description>
            <content:encoded><![CDATA[<p>If you're not a user of archiving services like <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://archive.today">archive.today</a> to get past paywalls (or even if you are), you might have a lot of spam in your inbox. Eventually, you get fed up with it and book off an afternoon to get rid of all of it in an effort to get back to <em>Inbox Zero. </em></p><p>What if you used a robot friend to get it done in an hour instead?</p><h2 id="h-who-you-might-be" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Who You Might Be</h2><p>This article is aimed at non-technical audiences with enough technical seeds so developers new to using AI can also run with it to create their own projects. I was prompted to write this by a recent project for a client, a React/Web application that works with Gmail to do automatic categorization (sample code is coming soon!). </p><p><em>Note: AI-assist tools allowed me to complete this project in a weekend, just using their basic functionality of prompted code generation. Think of the process as having a very junior developer at your beck and call, telling them what to do and then reviewing logical units of work to make sure they're correct.</em></p><p><em>You can use a lot of different tools for this: Built in chat windows in </em><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://cursor.com/"><em>Cursor </em></a><em>and </em><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://windsurf.com/"><em>Windsurf</em></a><em>, or completely separate command-line utilities like </em><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.claude.com/product/claude-code"><em>Claude Code </em></a><em>. My preference is Claude Code, but I encourage you to experiment and see which one you like best!</em></p><h2 id="h-some-scaffolding" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Some Scaffolding</h2><p>We're going to skip the scaffolding of a basic React application and using OAuth to log in with Gmail and assume we have a working web app with 3 display areas: Our connected e-mail accounts, some categories we've defined, and the e-mails themselves. </p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2f4a759bf451b731ac6801ca4e7226e815374e0589c6d2a215349fc2caff1a0a.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAUCAIAAABj86gYAAAACXBIWXMAABYlAAAWJQFJUiTwAAADM0lEQVR4nKVVIZikIBi1TZy0dcqEu7LFYjFZLCaLiWSi0CgkEolGoZlIJpLNRCNNmrZt0zaTjfvWN+e5s3fpXvBT+PHxv/f/kM3zzDnP87zcUFVV3/fe++kA/w8cZ7+HYTYLIZRlmR1wuVxijCGEGOM8z2HDvCF8xT54fAJYO89zNs+z3TCOo3POWmuMkVIKIaSUKSXnHGPMGKOUkr/BOVdKreu6LAtjTCn1FEAI8d4/CIQQTdOcTqfr9eq9B/ntdhNCLMsyDENVVVmWnU4nzvn9fsdOhRBpA2azLMvzPMY4TdP9ftdaW2tDCFmMkVLa9z3nvOu6tm3xjl2klKy1GGyahjHWdR2Sa5oGBOfzWQiBPLTWSinGGKV0HMcHQdd1jLHb7VZVVVEUZVlCsaqqUkrGGBAURVFVVVmWXdcZY4qiSCkty5JlmXNOb1BKjeOIzY3j+ClRCIFtkFIyxjjnlFLskVIKgqZp5nkehqEoCilljFFKWdd1Smld1/P5DD+FENbaGKNSqu/7PwSc82EYjDHDb+BdCLGu6zAM7Yau6wghkJEQAvqU0vV6xQiAAM65c+5BQAhhjFlry7L88eNnnuecc2MM9mg2ibqug0SXy6VpGq11WZbIIMsyY4wQAjszxnDOv2TQ9z0hZBiGekNRFCg42Gg2JpRv27Za62mapJRHD0II3nvOudY6xqi1fpZo3LC3rnNumiZIZLcqQl01TQOHGGOoMUgkhFBKiQ1KKUopY+yPRPjGGgiNv/R9jzKt61pKqbWGAsYYxljbtiB4eXnhG9BiQgj84UGwl6n3vizLPM9fX1+VUs45lKnWGu4VRVHX9fV6JYRYayERPICknHP0/LMHjDE0F8ys6xo57RlUVYVPNBrSBT0IxnH03kMf7z3CvhC8v7+nb5BSwgPnHM6v/YkzGGGEEDiHbvDehxC01sMwfHbykWA9YCcwxry9vf2VHkvwsgNr0U/PBH/NwPw/AaX0fr/j7N2RUkKZ6q20n2aXZcFpuq4r5/w4/vHxge55nKY4ZODbEZRSrfW6rtM0wd6nWWvtzrQX+jEArmRHf46Ak7j5vs/ufuJW2eOPwJX5C8l9iXFlj2AGAAAAAElFTkSuQmCC" nextheight="1108" nextwidth="1750" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>For resiliency, we've designed our application with Anthropic as the primary API provider and OpenAI as the <em>fallback</em>: If we can't connect to Anthropic, we'll use OpenAI instead. </p><h2 id="h-robot-friends-start-helping" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Robot Friends Start Helping</h2><p>We let the user define their own <em>categories</em>, a logical object that has a friendly name we define and also a description of what kind of e-mails should go into it. When a new e-mail comes in, we ask our AI partner to read it and place it in the appropriate category, or the 'general' category if none match. </p><p>We also have AI give us quick summaries of each e-mail in our right-most pane: It reads the e-mail, and then generates a short summary for us. In the actual app if we click on the e-mail itself we get the original message text. </p><p>Now things get interesting: We want to make AI Agents that will process unsubscribe requests like a worker processing a job (you might be familiar with this through technologies like <em>RabbitMQ</em>, <em>BullMQ</em> or one of many other asynchronous worker systems). </p><p>These agents have to look through an email and find the unsubscribe link. If the site we're trying to unsubscribe from is really clever, they might only let us do this after ticking some boxes or doing some other complex UI interaction. </p><p>There's also a very specific wrinkle here: Our AI Agents have to 'look' at a web page to see all the controls we might have to interact with and/or success text that indicates we've unsubscribed. That means using a virtual web browser to view and take a screenshot of the unsubscribe page.</p><h2 id="h-some-virtual-browser-wrinkles" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Some Virtual Browser Wrinkles</h2><p>There are software packages that facilitate this like <em>Playwright</em> and <em>Puppeteer</em>, but they come with a catch: They don't work very well with serverless compute. It's highly recommended you look at a containerized backend like Railway instead so you don't run into any issues. </p><p>You <em>can</em> look at a hosted service like <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.browserless.io/">Browserless</a> which lets you connect to a hosted web browser you can do things with, but it can be expensive if you want to connect to more than 1 virtual browser at a time. </p><p>How to create your job system will not be covered in this article, so we'll assume you have a working asynchronous task system that is ready to accept instructions. </p><p>Here's how our robot friend will specifically unsubscribe for us:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/43c74b6e867ecb907e4d0eb3b46e18bd22cb731e71b7a9541ed42cb54f30ee7d.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAANCAIAAABHKvtLAAAACXBIWXMAABYlAAAWJQFJUiTwAAABzElEQVR4nKVTobLkIBBERD+HiloVtQ63Km5VXFxcHDIuDjkOiUSOG4lEIpErI/cXInO12/dSr+rUvW1BDTDVM/Q0Sv4HzLyuq/8GESEIIZyHwzD0fT/PMxGJiIox4iLGKCIhhBgjVhFBHEJgZmQ653DFbxDRsizDMBCRc05ErLVd143jSETM/Cqw7/txHGAXkVJKrRWMKaXjOB6PB9rctm3fd2Qycykl51xrRUPOOSJa17Vpmq7rcKicc9frVWtNRCGEZVmUUlprFBjHses6ay1EQL8xRnTXtq1Squs6JEPAeZ611saYvwWIqGkapZT3fl3XaZq01n3fn1ul1P1+JyJrrXrDWrssSynldrsZY/q+r7VCNxFBx8YYyK5SSvWNUgo0uVwu8zznnCFCrXXbNjTIzN77nLP3PqXknGvbFjHkPWdgjHHOvWbgvQcFCp5EUDmEUEqB6CGE5/N5HAcEOU3xkx1IbzDzy0VI8t5jjwJYTxfFb9vQGycdHoQt/wPkvAr8Gsw8DMPp15wz3o0rxB8VEJF5nkE0TdPX1xd0PkcVQvioQEoJ3nXOWWtP7+E3IPj0BQAzW2uNMfgfGDu8q85p/Jr657RPs4gI4j9MEOTOGoJNyQAAAABJRU5ErkJggg==" nextheight="1196" nextwidth="2884" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>We'll have to generate a <em>prompt</em>  here, or how we're going to specifically tell our robot friend to do this for us. I'm going to leave this as an exercise for you (if you want to skip this part, you can ask Claude or ChatGPT to help you with a pretty good starter prompt).</p><p>Optimizing prompts is a <em>skill  </em>and this is a great project to develop that skill, watching what happens as you change and optimize the prompt to get our robot friend to do what you want. </p><p>For this project I ended up adding a bit of UI sugar in marking e-mail messages that launched a successful unsubscribe operation in green, ones in progress as yellow and failed ones in red. Feel free, though, to use whatever UI pattern you prefer!</p><p>I hope this article has been helpful for you, if only to learn about some things that our robot friends can do for us! <strong>Can you think of other ways they might be able to save us a lot of time? </strong></p><br><br><br>]]></content:encoded>
            <author>ivy@newsletter.paragraph.com (The Asterisk)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/f64e840c2c01b77f66ac7772dfe4af4cf09f10de2e6efdc678c1776f53556f7e.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Next.JS Dynamic Imports]]></title>
            <link>https://paragraph.com/@ivy/nextjs-dynamic-imports</link>
            <guid>lyNIh81Yii06kT9ogtEP</guid>
            <pubDate>Mon, 18 Nov 2024 18:43:32 GMT</pubDate>
            <description><![CDATA[The final code for the project is here, and if you prefer a video walkthrough it's available on my YouTube channel here: The Quest For BeatsI needed to use an external component for a Next.JS project I'm working on: mixmotion-player which is a React front-end for assets stored on Mixcloud. Mixmotion is a client-side only react component, meaning it has some dependencies that make it tricky to integrate with something that does server side rendering like Next. It will depend on globals like wi...]]></description>
            <content:encoded><![CDATA[<p><em>The final code for the project is </em><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/poi-son-ivy/mixmotion-typescript"><em>here</em></a><em>, and if you prefer a video walkthrough it's available on my YouTube channel here: </em></p><div data-type="youtube" videoid="Yjy7NCOhEHg">
      <div class="youtube-player" data-id="Yjy7NCOhEHg" style="background-image: url('https://i.ytimg.com/vi/Yjy7NCOhEHg/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=Yjy7NCOhEHg">
          <img src="https://paragraph.xyz/editor/youtube/play.png" class="play">
        </a>
      </div></div><div class="relative header-and-anchor"><h2 id="h-the-quest-for-beats">The Quest For Beats</h2></div><p>I needed to use an external component for a Next.JS project I'm working on: <em>mixmotion-player</em> which is a React front-end for assets stored on Mixcloud.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/lewhunt/mixmotion">Mixmotion</a> is a client-side only react component, meaning it has some dependencies that make it tricky to integrate with something that does server side rendering like Next. It will depend on globals like <em>window</em> which don't exist on the server.</p><p>Here's how I got it to work, and necessary steps which you will probably have to do if you're integrating a similar React component. </p><p>1) I was unfamiliar with the component, so I created a new blank React project running on Vite. <br>2) I imported Mixmotion into that project using <em>npm install mixmotion-player</em>.<br>3) I used boilerplate code from the Mixmotion GitHub page, and was soon listening to sick beats in my browser. </p><div class="relative header-and-anchor"><h2 id="h-webpack-kills-the-vibe">Webpack Kills The Vibe</h2></div><p>Things were going well! Then I tried integrating it with my existing Next project and ran into issues. </p><p>First, an easily fixable one: Mixmotion prefers React 18.2.0 so I edited <em>package.json </em>to use those React versions.</p><p>Then the hard one: Mixmotion not finding the <em>window</em> global on server-side renders.</p><p>There are a couple different ways to deal with this, I've found using <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://nextjs.org/learn-pages-router/seo/improve/dynamic-import-components"><em>dynamic imports</em></a> to be the cleanest.</p><p>I created a dynamic wrapper for the Mixmotion component:</p><pre data-type="codeBlock" text="import dynamic from &quot;next/dynamic&quot;;

const MixmotionPlayer = dynamic(() => import(&quot;mixmotion-player&quot;).then((mod) => mod.MixmotionPlayer), {
    ssr: false,
});

export default MixmotionPlayer;"><code><span class="hljs-keyword">import</span> dynamic <span class="hljs-keyword">from</span> <span class="hljs-string">"next/dynamic"</span>;

<span class="hljs-keyword">const</span> <span class="hljs-title class_">MixmotionPlayer</span> = <span class="hljs-title function_">dynamic</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">"mixmotion-player"</span>).<span class="hljs-title function_">then</span>(<span class="hljs-function">(<span class="hljs-params">mod</span>) =&gt;</span> mod.<span class="hljs-property">MixmotionPlayer</span>), {
    <span class="hljs-attr">ssr</span>: <span class="hljs-literal">false</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">MixmotionPlayer</span>;</code></pre><p>We're following a fairly standard flow here:</p><p><em>-Importing dynamic from a Next module<br>-Creating a container via MixmotionPlayer for our component<br>-Calling the dynamic function, which in turn imports a </em><strong><em>named export</em></strong><em> (mod stands for module here) <br>-Passing an option of ssr: false which tells Next that this should not be included in server side rendering.</em></p><p>We're all set, right? Not <em>quite</em> yet. </p><p>We've dealt with runtime issues, but we have to keep in mind that by default Next uses webpack to create the production bundle. This will work with <em>next dev</em>, but will throw an error when using <em>next build</em> about webpack being unable to resolve the module. </p><p>When you deploy to a service like Vercel, you'll be taking the bundle generated by <em>next build</em> and not using <em>next dev</em>, so this is an issue. We can fix it by telling Next not to include this module in the bundle. </p><p>You'll need to change your next.config.ts or .js to something like the following (this example uses TypeScript):</p><pre data-type="codeBlock" text="import type { NextConfig } from &quot;next&quot;;

const nextConfig: NextConfig = {
  reactStrictMode: true,

  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback = {
        'mixmotion-player': false,
      };
    }

    return config;
  }

};

export default nextConfig;
"><code><span class="hljs-keyword">import</span> <span class="hljs-title"><span class="hljs-keyword">type</span></span> { <span class="hljs-title">NextConfig</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"next"</span>;

const nextConfig: NextConfig <span class="hljs-operator">=</span> {
  reactStrictMode: <span class="hljs-literal">true</span>,

  webpack: (config, { isServer }) <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>isServer) {
      config.resolve.fallback <span class="hljs-operator">=</span> {
        <span class="hljs-string">'mixmotion-player'</span>: <span class="hljs-literal">false</span>,
      };
    }

    <span class="hljs-keyword">return</span> config;
  }

};

export default nextConfig;
</code></pre><p>Webpack is checking if we're doing a server or client side build, and if it's a client build we're telling Webpack to not try to resolve the <em>mixmotion-player</em> package.</p><p>It might seem odd that we're excluding the <em>client </em>side! After all, how can the client work if this package is excluded? </p><p>It's only being excluded from Next's static analysis at <strong>build time</strong>, not from the project entirely. It will still be dynamically loaded by the client at <strong>runtime. </strong></p><p>Now that <em>next build</em> works, I was able to deploy the project to Vercel via <em>vercel --prod</em>. And voila: Sick beats on Next via mixmotion-player!</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/168912044da9ba5f71345396c293d1b3.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAPCAIAAAAK4lpAAAAACXBIWXMAABYlAAAWJQFJUiTwAAAEuUlEQVR4nE2UXW/bZBzFU5LMcpM4ferEfmzHcWLHcRzHtRO/xHX8NC9ts5KlXbu1GVGXoSjatCsQCFaoNGAXMKHtglXbuKiqCoRWbjcukPgQIPEBisR34GZXKOmGkM71+Z1z/H8cghAWCgVN00xzybYt17VdtzaT6bqG65rLvoWQu7aGer3169f7g8HV4XBnNNodjfbG4/fG4+F4vD8e749Gw8Fgd2vrSqfTcl2npCgsxyaTydB/gFqt6rq25zme5/i+5fs13zd83wgCCyGr1aoj5KCgttlv7w02BoPe9e3u9kxXt9a3ttb7/fWNjXang3zfM80lWS6w7FuAKIqaVq7Vqp7nNPw6CtyVpt1q2a2m1WpaCFW7XX9/f+/w8JOzsx+Gw521tcDzzI2NlXc3Gt3u8ur68mqn3ml7CLm+b9m2oWmlQkFiWQaAhRBNU4KQVdWSaVY8z0KB3Vxx2u0L2e223UTVy93AdesvX/7y+x9/2nbdNO2dnT3DUHq9RrtlNVEVoWoQmMvekm1XDKOsqrIoCm8AAACOY2VZ0nXVtpd83wxQrd2y2q1au2U02yYK9M1+65uHD8/Ojo+Pv83yqVujK0+fPtH14tqqjZARNJa8etl1VNsqGYaiacWiIgkCDyEEAIQIgoCQFoSsokiGodp22V+uoEBHQQUFlaBRWa6XLnf9O3fufPXF3d9+fXJwb/fzg+2Tk2cIuSioNPzKzFqpVYu6XiirkqKIkpTj+QyE9LQBjuMkuchxjCTlVFUyDLlmFR1HqTtFxyk4VqFaFb16ZTK5+dGHu6cnn5399Oj7Zx8fHX25ubXmOMW6O3U3jIKmiSUlV5QFURJ4PsOyDE1TySQRwrBLySRB0xTPc7KcK6uirhcMQzIM0TByup6taHxRZg8ODl+//ufVq5/v3n2/02ncuLF/pbdWNaSZu6zpYkkRClImL2YEIcNxLMvCdDpFEEQoEo0QRCKdTl2UUJS8quY1TSyrfKnEiXk6K6QZZsH3/fPzv87P/z45OT09/fHFi7N+f70gQU2bBiqreVnOSjN3nme4aXyaJMkpYG5uLhabJ8lFlmVEMStJ05pFOaMomXye1rTcrH6uVlN0XW616v1+ZzDYHA53VjuN5sr0cWiapKqioggX8XmeYf8PCIVCGIYBsAAhzfMZScxJUlaSeEliKYq4dq13dPTo9u1bk8nNBw8O7336wWQyevz4616vM5mMnj//7v79A1UtKNP1c2/3YVgWQkiT5GyiUCgUjUYJggAAzBicIGQEgeE4imFSAOBE4hKOz+H4O7FYxPOql7ttmgYpkmA5CkKS56Gi5IvyNFZezPA8dwGgaQoAEI/HpwAAAI7jopjX9crsoiADKYpaJMkEALFEApvHIwDECQK3auZ4fIskF/I5jmEoQciIYvZCPM/wPANhWpalnZ3t2STwDSAcjmAYRpJkOp0CYCGdJtNpAACRSOCxGIbjkWh0DsPC4XBIEHjXtXEcI0mQIhchpFiW5jjm7SxUOp2a3cv0L0SSJIZhUwBN04qimKY5+xgAgIULJYlELDaP4xiOYxh2KRqNXBx0LDafTBKzKCkIKQjpmabuJLkIwEI8HicIwjTNcDj8L2wJCLGmouSAAAAAAElFTkSuQmCC" nextheight="1354" nextwidth="2818" class="image-node embed"><figcaption htmlattributes="[object Object]" class="">Also check out chicbangs / <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://higher.fm">higher.fm</a> on Mixcloud!!</figcaption></figure><p></p>]]></content:encoded>
            <author>ivy@newsletter.paragraph.com (The Asterisk)</author>
            <category>nextjs</category>
            <category>code</category>
            <category>devrel</category>
            <category>react</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e54b86506998b2c6952dcb2b5b0876c3.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Yes, Your Web3 Startup Does Have Time For Security]]></title>
            <link>https://paragraph.com/@ivy/yes-web3-security</link>
            <guid>GzB0ONuuvfnv6TqHEKZo</guid>
            <pubDate>Wed, 29 Nov 2023 23:53:34 GMT</pubDate>
            <description><![CDATA[There's very few people in information security who haven't received the look when some security measure is going to cost something, whether it's pur...]]></description>
            <content:encoded><![CDATA[<p>There's very few people in information security who haven't received <strong><em>the look</em></strong> when some security measure is going to cost something, whether it's pure capital or time. Sometimes it's the '<em>do we really need this?'</em> look, other times it's '<em>well can't this wait until we actually have customers?'</em> </p><p>Having been in information security in various capacities for a while, my skin itches a bit when I see this discourse pop up in web3. Many look upon web3 as the wild west, not having quite attained that arbitrary point of maturity to where its product can be intrinsically trusted. Some of crypto's PR woes stem from this, and unfortunate incidents when opportunism drove decisions more than responsibility did.</p><p>Ironically, the PR issue that plagues web3 has created space for some unhealthy attitudes. After FTX (<em>and many other things</em>), the general public associates crypto with many unfavourable words, 'scam' being one of them. Bloggers have created enough FUD around NFT's that a lot are willing to write any project off as a ponzi. In this kind of environment, it's tempting to view lesser security issues as just <em>moving fast, breaking things</em>. </p><p>Greenfield industries, startups, and other environments where speed is essential do require some reimagining of the kind of mindset that might be expected at <em>Google</em> or <em>Salesforce</em>. There often isn't time or resources for multiple levels of architecture review, steering committees, or formal verification audits. In terms of responsibility, Engineering is responsible to the company for shipping. </p><p>This should, however, not be the end of the security discussion. In my view, if you're going to reap the benefits of building in web3, you have some minimum level of responsibility to the rest of us. My initial impetus for writing this was driven by ChatGPT generating solidity vulnerabilities, but reading some commentary that '<em>web3 startups don't have time for security</em>' is what really made me put fingers to keyboard here. </p><p>The all or nothing attitude really puzzles me. Yes, some of the more elaborate or less time-consuming security products cost money, but there are alternatives. Setting up CI/CD security pipelines is doable in a weekend, and there are low-cost auditors. If you hit up crypto twitter or Farcaster, you'd be very likely to find an outside set of eyes that will have a look at your code for free. </p><p>The dynamics in web2 are certainly different, especially with an established customer base and/or financial systems. If you break that kind of trust with relative normies, who are more concerned about their financial data being secure than some of the loftier aspirations of web3, you probably aren't getting it back. As a result, meticulous testing cycles, staging environments, and approval matrices slow things down.</p><p>Things are different in web3 and startup land in general, and even web3 security auditors will say that some do take worrying about security too far. It's always a tradeoff, and the most securely written solidity will end up never being used if your startup doesn't meet timelines or can't raise. These are nuances that bend and/or break traditional security models, and <em>'we don't have time for security</em>' is the easy way around them.</p><p>There's always some minimum level of effort than can be put in to make things more secure than zero effort would result in. No one gets it perfect, Okta's woes over the last few years have certainly shown that's equally true for web2. Doing nothing, though,<strong> isn't good enough</strong>, as Gordon Ramsey might say. </p><p>There are also many ways of doing something. There is no end of Solidity content including security, which can be adapted for lunch and learns, if your startup engages in them. If you don't have a devops person / don't have the resources to hire one, you can post bounties for specific tasks. If you have social media talent and presence, you can probably content your way to having talented people red-team your contracts. </p><p>With all due respect to the incredible importance of speed and shipping in a space like web3: <strong>Yes, you do have time for security.</strong></p><p></p>]]></content:encoded>
            <author>ivy@newsletter.paragraph.com (The Asterisk)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/55c5ad3f119284aa6977aacb23c1b299.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>