<?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: Aleh Zasypkin</title>
    <description>The latest articles on DEV Community by Aleh Zasypkin (@azasypkin).</description>
    <link>https://dev.to/azasypkin</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1134154%2F52a95fbd-d2aa-489a-90a2-ef6d38b24d0e.jpg</url>
      <title>DEV Community: Aleh Zasypkin</title>
      <link>https://dev.to/azasypkin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/azasypkin"/>
    <language>en</language>
    <item>
      <title>A primer on open-source intelligence for bug bounty hunting in Grafana</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Tue, 11 Jun 2024 10:18:20 +0000</pubDate>
      <link>https://dev.to/azasypkin/a-primer-on-open-source-intelligence-for-bug-bounty-hunting-in-grafana-hn2</link>
      <guid>https://dev.to/azasypkin/a-primer-on-open-source-intelligence-for-bug-bounty-hunting-in-grafana-hn2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ANNOUNCEMENT&lt;/strong&gt; &lt;br&gt;
Before getting to the main topic of this blog post, I’d like to take a moment to share some exciting news (at least for me): &lt;a href="https://secutils.dev"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt;, the product for software engineers and security researchers that I’ve been working on lately, is &lt;strong&gt;now generally available!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Preparing the tool for GA is what has been keeping me busy for the last couple of months. I’d encourage you to quickly skim through the video guides to learn what Secutils.dev is capable of today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://secutils.dev/docs/guides/webhooks"&gt;&lt;strong&gt;Quickly deploy and program webhooks&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://secutils.dev/docs/guides/web_scraping/content"&gt;&lt;strong&gt;Track changes in any part of a web application&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://secutils.dev/docs/guides/digital_certificates/certificate_templates"&gt;&lt;strong&gt;Easily generate development certificates and keys&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://secutils.dev/docs/guides/web_security/csp"&gt;&lt;strong&gt;Slice and dice content security policies (CSP)&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s still early days for Secutils.dev, and if you want to know what's coming, check out the &lt;a href="https://secutils.dev/docs/project/roadmap"&gt;&lt;strong&gt;roadmap&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;Today, I’d like to touch on open-source intelligence, or OSINT. According to &lt;a href="https://en.wikipedia.org/wiki/Open-source_intelligence"&gt;&lt;strong&gt;Wikipedia&lt;/strong&gt;&lt;/a&gt;, open-source intelligence is the collection and analysis of data gathered from open sources (covert sources and publicly available information) to produce actionable intelligence. As you can infer from the definition, OSINT is a vast topic, and the best way to understand such broad topics is through concrete, narrow-scoped practical examples. In this blog post, I’d like to share one of the approaches on how OSINT techniques can be applied to bug bounty hunting for products with publicly hosted code on GitHub, using the awesome open-source project &lt;a href="https://github.com/grafana/grafana"&gt;&lt;strong&gt;Grafana&lt;/strong&gt;&lt;/a&gt; as an example. Read on!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ DISCLAIMER&lt;/strong&gt; &lt;br&gt;
I’m not a security researcher nor a bug bounty hunter myself, but as an application security engineer, I think about these essential participants of the security ecosystem and how they might approach the applications I defend day and night. Therefore, I have some insights to share. Everything in this post is for educational purposes only and is solely targeted at well-intentioned researchers and bug bounty hunters who follow responsible and ethical security issue disclosure rules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Keep your focus narrow
&lt;/h2&gt;

&lt;p&gt;As a security researcher, there are many ways you can approach an application you want to explore for potential security flaws, from trying to use it in esoteric conditions with tricky input data to thoroughly learning every bit and piece of its source code, hoping to find anything that can knock it out. A seasoned researcher knows that these approaches, unfortunately, can be very time-consuming with a bleak chance of success, so the first step is usually to reduce the scope of research to improve the ratio between time spent and the chance of a successful finding.&lt;/p&gt;

&lt;p&gt;If it’s a new project for you, I’d recommend concentrating on understanding the security model used in the application: authentication, authorization, and integration with third-party applications and services. In an ideal scenario, you might find a very rewarding flaw in the security model itself, and in the worst case, you’ll have a better chance to spot when security primitives are used incorrectly in other areas of the application.&lt;/p&gt;

&lt;p&gt;On one hand, in a large and complex application like &lt;a href="https://github.com/grafana/grafana"&gt;&lt;strong&gt;Grafana&lt;/strong&gt;&lt;/a&gt;, the security model is complex to grasp. On the other hand, it’s just as complex for the application developers. To manage this complexity, the application is frequently split into separate well-defined domains with clear owners. If you know these domains and/or owners, you know where to look. GitHub, or the &lt;a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners"&gt;&lt;strong&gt;GitHub &lt;code&gt;CODEOWNERS&lt;/code&gt; file&lt;/strong&gt;&lt;/a&gt; specifically, is your ally here.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub &lt;code&gt;CODEOWNERS&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;Let’s take a look at the excerpt from the &lt;a href="https://github.com/grafana/grafana/blob/52fe19249e0b46c664297bfa631a10bb647d3341/.github/CODEOWNERS"&gt;&lt;strong&gt;&lt;code&gt;CODEOWNERS&lt;/code&gt; file&lt;/strong&gt;&lt;/a&gt; for the &lt;code&gt;grafana/grafana&lt;/code&gt; repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;…
/.changelog-archive @grafana/grafana-release-guild
/CHANGELOG.md @grafana/grafana-release-guild
/CODE_OF_CONDUCT.md @grafana/grafana-community-support
…
/.github/workflows/update-make-docs.yml @grafana/docs-tooling
/.github/workflows/snyk.yml @grafana/security-team  ----&amp;gt; (1) &amp;lt;----
…
&lt;span class="gh"&gt;# Cloud middleware&lt;/span&gt;
/grafana-mixin/ @grafana/grafana-backend-services-squad

&lt;span class="gh"&gt;# Grafana authentication and authorization  ----&amp;gt; (2) &amp;lt;----&lt;/span&gt;
/pkg/login/ @grafana/identity-access-team
/pkg/services/accesscontrol/ @grafana/identity-access-team
/pkg/services/anonymous/ @grafana/identity-access-team
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CODEOWNERS&lt;/code&gt; file format is pretty self-describing - every line contains a path in the source code repository and the corresponding owner, either a specific person or an entire team. (1) and (2) are what should have caught your eye - it’s easy to spot at least two security-oriented teams in the file - &lt;code&gt;@grafana/security-team&lt;/code&gt; and &lt;code&gt;@grafana/identity-access-team&lt;/code&gt;. Great, now we can discern and learn more about the security-related domains these teams own.&lt;/p&gt;

&lt;p&gt;Let’s assume you have a slightly better understanding of Grafana’s security model domains now, but what’s next? You might also say that generally security-related code is the one that’s hardest to break, and I’d agree. But there is one exception - &lt;strong&gt;newly written code&lt;/strong&gt;! Pressing deadlines to deliver a new feature that force engineers to speed up the review process, code written by engineers who are new to the security domain, incomplete security fixes, and so on and so forth - these are some of the many reasons why newly written code is so compelling for our purpose. That’s the weakest point you might want to target, and there are two potential vectors here: completely new security domains, e.g., a new SSO integration, or changes in the existing domains, e.g., a bug fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating change tracking
&lt;/h2&gt;

&lt;p&gt;Of course, you can periodically manually scan the &lt;code&gt;CODEOWNERS&lt;/code&gt; file for newly introduced domains or write a dedicated tool for that, but it’s a very laborious task that makes the approach somewhat unsustainable in the long term, especially if you have multiple applications to work with and multiple angles to look at. That’s where tools like &lt;a href="https://secutils.dev"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt; can be helpful! Let me show you how you can use the &lt;a href="https://secutils.dev/docs/guides/web_scraping/content"&gt;&lt;strong&gt;“Content Tracker”&lt;/strong&gt;&lt;/a&gt; utility to watch the content of the &lt;code&gt;CODEOWNERS&lt;/code&gt; file on a specific schedule. I won’t be covering what this utility is for and how to use it. You can spend a couple of minutes and watch &lt;a href="https://secutils.dev/docs/guides/web_scraping/content"&gt;&lt;strong&gt;a video guide&lt;/strong&gt;&lt;/a&gt;. I’ll just provide tracker settings you can use for your tracker:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Name&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;pre&gt;[OSINT][Grafana] CODEOWNERS&lt;/pre&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;URL&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre&gt;https://secutils-dev.github.io&lt;/pre&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Revisions&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre&gt;
10
&lt;/pre&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Frequency&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre&gt;
Daily
&lt;/pre&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Content extractor&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre&gt;
const teams = [
  '@grafana/security-team', 
  '@grafana/identity-access-team'
];
return import('https://secutils-dev.github.io/secutils-sandbox/content-extractor-scripts/github-codeowner-file.js')
    .then((module) =&amp;gt; module.run(context, 'grafana', 'grafana', teams));
&lt;/pre&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important part here is the &lt;code&gt;Content extractor&lt;/code&gt; script that is injected into a target page. All this script does is load another external module from the &lt;a href="https://github.com/secutils-dev/sandbox"&gt;&lt;strong&gt;&lt;code&gt;secutils-dev/secutils-sandbox&lt;/code&gt; repository&lt;/strong&gt;&lt;/a&gt; and run its &lt;code&gt;run&lt;/code&gt; function. The &lt;code&gt;run&lt;/code&gt; function expects the GitHub repository owner (&lt;code&gt;grafana&lt;/code&gt;), repository name (&lt;code&gt;grafana&lt;/code&gt;), and the teams to look for in a &lt;code&gt;CODEOWNERS&lt;/code&gt; file. I could put all the logic inside the content extractor script itself, but I prefer to keep the main logic in a separate file to make it easier to debug and iterate on it. Let’s take a look at what I have in the &lt;code&gt;github-codeowner-file.js&lt;/code&gt; script (the full source code can be found &lt;a href="https://github.com/secutils-dev/secutils-sandbox/blob/d54f7d135e1c92fb6493e1901965fd1b9b638e86/content-extractor-scripts/src/github-codeowner-file.ts"&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;):&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="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebPageContext&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="s1"&gt;./types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebPageContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;owner&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;repo&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;teams&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="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;codeOwnersUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://raw.githubusercontent.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/main/.github/CODEOWNERS`&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;lines&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeOwnersUrl&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Owners&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="s1"&gt;Path&lt;/span&gt;&lt;span class="dl"&gt;'&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;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&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;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&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="s1"&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;sort&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;owners&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&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="kr"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;markdown-table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markdownTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;align&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="s1"&gt;l&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="s1"&gt;c&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script simply loads the &lt;code&gt;CODEOWNERS&lt;/code&gt; file from the specified repository, parses it, and only retains entries that are owned by the specified teams. The result is returned as a nice markdown table. Here’s how the result looks like in Secutils.dev:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv71iajdnizhatl1fd4q8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv71iajdnizhatl1fd4q8.png" alt="Grafana CODEOWNERS" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So what we have now is a regular job that runs daily, parses the content of the &lt;code&gt;CODEOWNERS&lt;/code&gt; file for the specified repository, extracts the areas that are owned by the specified GitHub teams, and notifies you via email if it detects any changes. Now, as soon as a new security domain is introduced, you can go and take a closer look at it right away, no need to waste time on doing it manually on a regular interval. Nice!&lt;/p&gt;

&lt;p&gt;Okay, but new domains aren’t introduced that often. What about changes in the existing domains? We can do that too. Let’s tweak our content extractor scripts. The easiest way to know if there were any changes in a specific security domain is to take a look at the recent commits for the specified path. To do that, we can use &lt;a href="https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28"&gt;&lt;strong&gt;GitHub’s Get commits API&lt;/strong&gt;&lt;/a&gt;. For public repositories, this API can be used anonymously, but it has a very low request rate limit - just 60 requests per hour, so it’s better to create a &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens"&gt;&lt;strong&gt;GitHub personal access token (PAT)&lt;/strong&gt;&lt;/a&gt; to query this API. Let’s tweak our main content extractor script to provide an access token:&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;const&lt;/span&gt; &lt;span class="nx"&gt;apiToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github_pat_11xxxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// GitHub personal access token&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;teams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@grafana/security-team&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="s1"&gt;@grafana/identity-access-team&lt;/span&gt;&lt;span class="dl"&gt;'&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;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://secutils-dev.github.io/secutils-sandbox/content-extractor-scripts/github-codeowner-file.js&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grafana&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="s1"&gt;grafana&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiToken&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here are the changes we need to make in the dynamically loaded script:&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="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Endpoints&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="s1"&gt;@octokit/types&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebPageContext&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="s1"&gt;./types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebPageContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;owner&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;repo&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;teams&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;apiToken&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="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;codeOwnersUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://raw.githubusercontent.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/main/.github/CODEOWNERS`&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;lines&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeOwnersUrl&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// Use API token if provided to have higher request rate limit.&lt;/span&gt;
  &lt;span class="c1"&gt;// https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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="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;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;apiToken&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-GitHub-Api-Version&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="s1"&gt;2022-11-28&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-GitHub-Api-Version&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="s1"&gt;2022-11-28&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Retrieve the latest commit for the specified path.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getCommitLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;path&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&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;commits&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`https://api.github.com/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/commits?path=&lt;/span&gt;&lt;span class="p"&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;path&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;per_page=1`&lt;/span&gt;&lt;span class="p"&gt;,&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="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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&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="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Endpoints&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET /repos/{owner}/{repo}/commits&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="s1"&gt;response&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="s1"&gt;data&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;commits&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;0&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;N/A (no commits found)&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topCommit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;commits&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commitLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;topCommit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;topCommit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topCommit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&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="s2"&gt; on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topCommit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;topCommit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;commitLabel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;](&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topCommit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html_url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="s2"&gt;`N/A (&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Owners&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="s1"&gt;Path&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="s1"&gt;Last commit&lt;/span&gt;&lt;span class="dl"&gt;'&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;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&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;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&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="s1"&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;sort&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;owners&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&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;getCommitLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="kr"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;markdown-table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markdownTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;align&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="s1"&gt;l&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="s1"&gt;c&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The change here is that we are now adding a third column to our markdown table, which we fill with the latest commit information returned from the &lt;code&gt;getCommitLink&lt;/code&gt; function invoked with the path extracted from the &lt;code&gt;CODEOWNERS&lt;/code&gt; file. Easy! Here’s how the result looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvduhy8nsiiobwas3ipdr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvduhy8nsiiobwas3ipdr.png" alt="Grafana CODEOWNERS with commits" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, when a new commit is detected in any security domain, you’ll get an email notification. Then, you can go to Secutils.dev to see what has changed exactly with the &lt;code&gt;Diff&lt;/code&gt;  feature and click on the commit link to learn more about the specific changes. Great, isn’t it?&lt;/p&gt;

&lt;h2&gt;
  
  
  But wait, there's more
&lt;/h2&gt;

&lt;p&gt;Tracking changes in the &lt;code&gt;CODEOWNERS&lt;/code&gt; file and security-related domains is just the tip of the open-source intelligence iceberg. If you want to fully embrace its principles, you can go a few steps further since the commit authors can give you a better idea about the composition of the security teams in a particular organization, which isn’t publicly available information. What can you do with this new data? Well, quite a lot.&lt;/p&gt;

&lt;p&gt;For example, the majority of security issues are found outside the security domains, so it’s hard to recognize security fixes automatically. But there is a high chance that some members of the security teams can be tagged for review or advice on the pull requests with those fixes. So you might want to take a closer look at the pull requests for the domains unrelated to security if security folks are involved in one way or another, assuming you know who these security folks are 😬.&lt;/p&gt;

&lt;p&gt;The fun part of open-source intelligence is that it doesn’t limit you to information sources as long as they are public. Social networks, such as LinkedIn, are invaluable sources of information for security researchers, and sadly for bad actors as well.&lt;/p&gt;

&lt;p&gt;If you know who the security team members are, you can go a little bit crazy and set up a dedicated tracker for their LinkedIn profiles. When you detect this infamous “I’m happy to announce” message on their profile, go and check that their access was properly revoked, name-bound sub-domain names and S3 buckets are still owned by the organization, and Slack, Zoom, and GitHub profile handles are properly secured. Security team members usually have elevated privileges within organizations, and when they depart, special care should be taken. &lt;strong&gt;If you notice it’s not the case, please disclose it responsibly and ethically!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvsx30m0uc945rpxbnfrb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvsx30m0uc945rpxbnfrb.png" alt="Grafana LinkedIn profiles" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I know it might look a lot like stalking, but it’s not. It’s about understanding the security posture of the organization you’re interested in, and it’s a very important part of the security research process. If you’re not comfortable with this, you can always stick to the technical part of the research, which is also very rewarding.&lt;/p&gt;

&lt;p&gt;In this blog post, I’ve covered just the most basic and obvious ways to apply open-source intelligence for security research, but there are many more. State-backed actors are known to use these techniques combined with other techniques such as social engineering, and it’s important to understand how they work to be able to defend against them. Let me know if it’s something you’d like to learn more about!&lt;/p&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ASK:&lt;/strong&gt; If you found this post helpful or interesting, please consider showing your support by starring &lt;a href="https://github.com/secutils-dev/secutils"&gt;&lt;strong&gt;secutils-dev/secutils&lt;/strong&gt;&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>opensource</category>
      <category>buildinpublic</category>
      <category>security</category>
      <category>osint</category>
    </item>
    <item>
      <title>Cybersecurity basics: security mindset</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Tue, 20 Feb 2024 12:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/cybersecurity-basics-security-mindset-2d71</link>
      <guid>https://dev.to/azasypkin/cybersecurity-basics-security-mindset-2d71</guid>
      <description>&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;Recently, I was invited to give a presentation on cybersecurity to a group of young developers at &lt;a href="https://onja.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Onja&lt;/strong&gt;&lt;/a&gt;, a social enterprise in Madagascar. Since they are at the beginning of their cybersecurity journey, I didn't want to bore them with the hackneyed OWASP Top 10 or overwhelm them with the plethora of security tools developers have to rely on these days to keep software safe and secure. Instead, I wanted to discuss something basic yet foundational for anyone dealing with cybersecurity - the security mindset.&lt;/p&gt;

&lt;p&gt;In my experience, when it comes to security, the right mindset is what transforms an average engineer into a good one. It's not something you can buy or acquire quickly, but it's something everyone can learn over time and benefit from throughout their career. The earlier you realize this, the better. Similar to building personal wealth, the earlier you start learning and investing, the better your life will be.&lt;/p&gt;

&lt;p&gt;Generally speaking, if you're dealing with anything related to security, the right mindset gets you roughly 80% of the job done; the remaining 20% comes from proper tooling, a good team, and other factors. I like to say that developing a security mindset is simple, but not easy.&lt;/p&gt;

&lt;p&gt;This blog post is the presentation turned into a blog post. Read on!&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore unhappy path
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_explore_unhappy_path-c2912cbebc05fb060b862c361602dbc7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_explore_unhappy_path-c2912cbebc05fb060b862c361602dbc7.png" alt="Explore unhappy path"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As software engineers, you've likely heard about the term "happy path" - the ideal scenario in the application flow. If the user follows the path we expect, and everything else in the application works as planned, everyone is happy. Hence, the term happy path. For a software engineer, it's entirely reasonable to think about the happy path first: it helps in understanding what needs to be built. However, when your goal is to assess whether the application is secure enough and understand how malicious actors or hackers could potentially break it, you should take a much closer look at how the application behaves when things go wrong – the so-called unhappy path.&lt;/p&gt;

&lt;p&gt;This approach is somewhat similar to what QA engineers do, but as a software engineer, you go much deeper. Start by examining what happens when a legitimate user does something unexpected, like pressing buttons in the wrong order, repeatedly and quickly refreshing the page, bombarding your server with requests, or uploading a file of an unexpected type or much larger size than expected. Move on to more advanced scenarios, such as what if the external service or database your application communicates with is unavailable or hacked, sending malicious data? Or if the file you're reading from disk is corrupted, or the user content you render in the application contains malicious code?&lt;/p&gt;

&lt;p&gt;Building a habit of thinking about the unhappy path first might not be easy, but ignoring these scenarios often leads to weaknesses in your application that others could exploit to harm your users and your reputation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assume compromise
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_assume_compromise-87137eff7508a2ef1f9464518c3a9aaf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_assume_compromise-87137eff7508a2ef1f9464518c3a9aaf.png" alt="Assume compromise"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More frequently than not, you hear things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Oh, our network is behind a firewall and fully protected within a corporate network, we don’t need to worry about establishing trust between internal services and applications". But then, a testing application is deployed and exposed to the internet by mistake, becoming an open door for intruders to easily access everything within a corporate perimeter.&lt;/li&gt;
&lt;li&gt;Or, "we use Okta to manage our users and their permissions, it takes care of everything, we don’t need to monitor for suspicious activity of employees' accounts." Then suddenly, Okta's customer support system is hacked, and malicious actors can now act on behalf of your own employees.&lt;/li&gt;
&lt;li&gt;Or, "we don’t need to waste time on automated credentials revocation when an employee leaves the company; the admin will do it manually later this week". But then, suddenly, an angry fired employee wipes out your customer data backups and wreaks havoc in the internal infrastructure.&lt;/li&gt;
&lt;li&gt;Or even, "let’s introduce this new file upload feature without any additional configuration switch that could allow us to easily reduce file size limit or disable the feature completely without rebuilding and re-deploying the application entirely". And then, the application is being DDoS’ed with gigantic files, putting your entire infrastructure down or skyrocketing your Cloud storage bills.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If something bad can happen in theory, someday it will happen in practice, and you'd better be prepared for that right from the start. Assuming that all your security measures are compromised will help you build much more resilient and future-proof applications that will save your time, money, and reputation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid insecurity through obscurity
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_avoid_insecurity_through_obscurity-eab63289af0e62feb5c41cbbec8c6ae8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_avoid_insecurity_through_obscurity-eab63289af0e62feb5c41cbbec8c6ae8.png" alt="Avoid insecurity through obscurity"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might have heard the term "Security through obscurity" - in simple terms, it's the reliance on secrecy as the primary method of providing security to a system or component. There's even an illusion that closed-source software is more secure than open-source because attackers cannot understand the application logic and spot flaws in the code. Some even claim that obfuscating code is a strong security measure to protect intellectual property.&lt;/p&gt;

&lt;p&gt;Statements like that aren’t just untrue, but actually very dangerous - if a company believes that secrecy is a sufficient security measure, they won't invest time and money in educating their employees about security and won’t bother thinking about mitigation and response tactics if they come under attack. Lying to yourself is the worst type of lie.&lt;/p&gt;

&lt;p&gt;Attackers are a smart and creative bunch of people. If they have enough motivation, they will figure out everything about your application and infrastructure they need to conduct an attack. There are thousands of tools easily accessible to them that can analyze every bit and piece of your application. Not to mention the emergence of AI that can sometimes collect the puzzle in minutes.&lt;/p&gt;

&lt;p&gt;So, if you're serious about security, it's better to assume that your secrets aren't secrets to a sufficiently motivated party. It’s important to keep your secrets safe, though it shouldn’t be the only or certainly not the main security measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be pragmatic about security
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_be_pragmatic_about_security-54104c292d7caefb5b7ebbeab00f79d1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_be_pragmatic_about_security-54104c292d7caefb5b7ebbeab00f79d1.png" alt="Be pragmatic about security"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is no doubt that keeping an application secure is important for both developers and users. However, keep in mind that the main goal of the software is to deliver value to the user. Nobody would use or pay for an application just because it's super secure. Look at security as something fundamental, always going without saying, like the absence of bugs, reasonable performance, and usability. There are practical limits to how secure an application can be while still delivering value to the user. When working with other engineers and helping them make their software secure, be collaborative and pragmatic. You have a common goal: to deliver top-notch software to your users that is both secure, useful, and user-friendly.&lt;/p&gt;

&lt;p&gt;If you have to make trade-offs in security to improve usability or deliver more value, talk through the risks with the team, document everything, prepare a plan in case anything goes wrong, and move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Always be learning
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_always_be_learning-205faa2b4d0ea7690a7ed433e075b14b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_always_be_learning-205faa2b4d0ea7690a7ed433e075b14b.png" alt="Always be learning"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The security landscape is constantly changing, in fact, it's one of the most dynamic areas of information technology today. Almost every day, we hear about new vulnerabilities, novel approaches to break applications, or trick users. I won't stop repeating that your knowledge is your most important tool, but it becomes dull very quickly if you don't sharpen it regularly enough. Make learning a part of your routine, read books, follow security researchers on their social networks - they do like to share their knowledge, dive deep into security disclosures and statements, grind through exploit proof-of-concepts until you fully understand how it works.&lt;/p&gt;

&lt;p&gt;Always be curious, always be learning, it will pay off. We are all different, find the way to learn that is suitable for you, something that brings you joy and is fun to do, then it will be much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: logs and exceptions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_logs_and_exceptions-1b5f551b6a3c5312a0f799a3af9b30c7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fassets%2Fimages%2F2024-02-20_security_mindset_logs_and_exceptions-1b5f551b6a3c5312a0f799a3af9b30c7.png" alt="Example: logs and exceptions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During their lifetime logs can flow through different environments with different level of security and access, and it’s hard to be 100% sure where your logs will be at any point in the future. It’s not uncommon to ingest your logs into different external cloud-based solutions (e.g. Elasticsearch or Datadog) for monitoring and further analysis. Older logs might be stored for a long term in a complete (e.g. S3).&lt;/p&gt;

&lt;p&gt;When you’re dealing with logs, these are the important questions you should ask yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can logs contain secrets or customer information?

&lt;ul&gt;
&lt;li&gt;Sensitive information isn’t only passwords or secret tokens, but also so-called PII (Personal Identifiable Information) - anything that can be used to identify a specific person - e.g., full name, username, address, phone number, and any other official document number. Even if you rotate leaked passwords in your application, they might still be valid in other places. Unfortunately, people like to reuse passwords.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;If I log exceptions, do I fully trust all the fields that will be logged?

&lt;ul&gt;
&lt;li&gt;Usually, developers pay attention to the data and responses they send to the user, but throwing exceptions is another important program flow that leads to returning some data to the user that might be sensitive too, but it’s often ignored. Exceptions can include file paths, environment variables, request and response headers that might include credentials and cookies.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Who can access logs during their entire lifetime?

&lt;ul&gt;
&lt;li&gt;You should think about where your logs go after they are logged, how they are stored, and who might potentially have access to them. The safest assumption is that logs might eventually be exposed to the public in one way or another, so it’s better to be careful with logging anything that can be potentially sensitive.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Can logs contain user content that can be weaponized?

&lt;ul&gt;
&lt;li&gt;There is an entire class of vulnerabilities that can be exploited through logs, so-called log injections. It can be anything, from making your terminal unusable to remote code execution where the attacker can execute binaries in your environment. It can be quite scary. I'd encourage you to read more about &lt;a href="https://en.wikipedia.org/wiki/Log4Shell" rel="noopener noreferrer"&gt;&lt;strong&gt;Log4Shell&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://owasp.org/www-community/attacks/Log_Injection" rel="noopener noreferrer"&gt;&lt;strong&gt;log injections&lt;/strong&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it! If you found this post helpful or interesting, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin" rel="noopener noreferrer"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/" rel="noopener noreferrer"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>Supercharge your app with user extensions using Deno JavaScript runtime</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Wed, 24 Jan 2024 11:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/supercharge-your-app-with-user-extensions-using-deno-runtime-5hck</link>
      <guid>https://dev.to/azasypkin/supercharge-your-app-with-user-extensions-using-deno-runtime-5hck</guid>
      <description>&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;Today, I'd like to discuss one of the many approaches to implement user extensions in your application, using &lt;a href="https://secutils.dev/docs/guides/webhooks#generate-a-dynamic-response"&gt;&lt;strong&gt;"script" extensions for the webhooks&lt;/strong&gt;&lt;/a&gt; introduced in Secutils.dev in &lt;a href="https://github.com/secutils-dev/secutils/releases/tag/v1.0.0-alpha.5"&gt;&lt;strong&gt;January, 2024 (1.0.0-alpha.5)&lt;/strong&gt;&lt;/a&gt; as an example. In a nutshell, "script" extensions enable users to dynamically process incoming webhook requests and decide on the response on the fly, making simple webhooks akin to tiny applications.&lt;/p&gt;

&lt;p&gt;As a user, have you ever wished for your favorite application to behave a little differently? Sometimes, even a slight change in behavior could make a big difference in the application or tool you rely on. Alternatively, as a developer, have you found yourself in a situation where numerous user feature requests seem almost identical but not quite enough to implement a single feature that satisfies all users without creating a ton of different toggles to customize behavior?&lt;/p&gt;

&lt;p&gt;These are rhetorical questions, as I'm sure that such scenarios have crossed your path at least once. Otherwise, browser extensions, Shopify apps, Notion integrations, Grafana, and WordPress plugins wouldn't be as popular.&lt;/p&gt;

&lt;p&gt;As a solo-developer for &lt;a href="https://secutils.dev"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt;, I operate with very limited resources and cannot accommodate every user's feature request, even if I wish to. On the other hand, prioritizing and developing features based on assumptions and limited upfront user feedback has its own challenges and risks. That's why, right from the start, I've been considering adding some sort of "extension points" into Secutils.dev that would allow users to customize the certain behavior of the utilities according to their needs.&lt;/p&gt;

&lt;p&gt;The core idea is that if a specific modification holds genuine value for the user, they wouldn't mind investing some time in extending the application themselves, provided they have the right tools and documentation. Actually, this serves as one of the most effective forms of validation that the feature is indeed necessary. Over time, validated user extensions make their way into the main application functionality or even community "extensions" marketplaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the extensions "framework"
&lt;/h2&gt;

&lt;p&gt;The idea looks good in theory. Though it might not work for all applications or users, having a mostly developer audience makes things simpler. Developers are accustomed to modifications, plugins, and extensions. More importantly, they have coding skills, making them more comfortable with writing code to extend the applications they use. Moreover, with the emergence of highly capable code-generating language models (LLMs), being a developer might not be a strict requirement for crafting simple extensions in the future.&lt;/p&gt;

&lt;p&gt;If I've convinced you that extending Sectuils.dev with the user code is a good idea, the next thing to consider is the language for this code. There are many great languages, but let's be honest — there's currently one universal "web" language, and that's JavaScript. It's easy to grasp and forgiving of user errors, making it the ideal language for user extensions!&lt;/p&gt;

&lt;p&gt;If your application is written in JavaScript, integrating it with JavaScript extensions is a no-brainer. However, Secutils.dev is entirely written in Rust. How would I even begin? Fortunately, I recently came across an excellent blog post series explaining how to implement your JavaScript runtime in a Rust application with &lt;a href="https://deno.com/"&gt;&lt;strong&gt;Deno&lt;/strong&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://deno.com/blog/roll-your-own-javascript-runtime"&gt;&lt;strong&gt;Roll your own JavaScript runtime, Part 1&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deno.com/blog/roll-your-own-javascript-runtime-pt2"&gt;&lt;strong&gt;Roll your own JavaScript runtime, Part 2&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deno.com/blog/roll-your-own-javascript-runtime-pt3"&gt;&lt;strong&gt;Roll your own JavaScript runtime, Part 3&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Besides offering a JavaScript runtime, Deno also allows me to have complete control over which APIs and capabilities will be available to user JavaScript extensions. Brilliant!&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Deno Core as extensions runtime
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ NOTE:&lt;/strong&gt; I've left out some non-essential details in code examples for brevity, you can find the full source code on the &lt;a href="https://github.com/secutils-dev/secutils/blob/main/src/js_runtime.rs"&gt;&lt;strong&gt;Secutils.dev GitHub repository&lt;/strong&gt;&lt;/a&gt;. I won't be explaining what Deno is and isn't in this blog post. If you're curious, you can find all the necessary information in the &lt;a href="https://deno.com/"&gt;&lt;strong&gt;official Deno documentation&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The absolute minimum you need to embed a Deno JavaScript runtime in a Rust application is the &lt;a href="https://crates.io/crates/deno_core"&gt;&lt;strong&gt;&lt;code&gt;deno_core&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; crate. The basic code to execute your extension, represented as a string with asynchronous JavaScript code, might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;deno_core&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
  &lt;span class="n"&gt;JsRuntime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;serde_v8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;v8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;PollEventLoopOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;RuntimeOptions&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/// Executes a user script and returns the result.&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;execute_script&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'de&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'de&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;js_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;Into&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create a new instance of the JS runtime.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;JsRuntime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;RuntimeOptions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Convert a JS code string to a `ModuleCodeString` and&lt;/span&gt;
    &lt;span class="c1"&gt;// retrieve the result. This snippet assumes that JS code&lt;/span&gt;
    &lt;span class="c1"&gt;// from `js_code` is asynchronous and returns `Promise`.&lt;/span&gt;
    &lt;span class="c1"&gt;// For example something along these lines: &lt;/span&gt;
    &lt;span class="c1"&gt;// r#"(async () =&amp;gt; {{ return 2 + 2; }})();"#&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;script_result_promise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;
        &lt;span class="nf"&gt;.execute_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;anon&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;js_code&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.into&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="c1"&gt;// Now, wait for the promise to resolve.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="nf"&gt;.resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_result_promise&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;script_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;
        &lt;span class="nf"&gt;.with_event_loop_promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="nn"&gt;PollEventLoopOptions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&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="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Deserialize script result from v8 type and return.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="nf"&gt;.handle_scope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;v8&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Local&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;serde_v8&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_v8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local&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;If you're familiar with Rust, the code should be self-explanatory: we take a string with JavaScript code, convert it to a type expected by Deno/V8, instruct the runtime to execute the script, wait for the result promise to resolve, and then extract and return the value.&lt;/p&gt;

&lt;p&gt;It's also possible to supply parameters to the script being executed. There are various ways to do this, but I opted for the script global scope as a method of sharing input parameters with the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;deno_core&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;serde_v8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v8&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Make sure parameters can be serialized to a&lt;/span&gt;
&lt;span class="c1"&gt;// v8 compatible type.&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Debug,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq,&lt;/span&gt; &lt;span class="nd"&gt;Eq,&lt;/span&gt; &lt;span class="nd"&gt;Clone)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ScriptParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;arg_num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arg_str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arg_array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arg_buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Create params.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;script_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ScriptParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;arg_num&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="n"&gt;arg_str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hello, world!"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;arg_array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"one"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"two"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="n"&gt;arg_buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;vec!&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Retrieve script "scope".&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="nf"&gt;.handle_scope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="nf"&gt;.get_current_context&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nn"&gt;v8&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;ContextScope&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Prepare a key to store our params in the global scope.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;params_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;v8&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"param"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Serialize params value to a v8 compatible type.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;params_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;serde_v8&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to_v8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_params&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="c1"&gt;// Set the value in the global scope (`globalThis.param`).&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;
    &lt;span class="nf"&gt;.global&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params_key&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;params_value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dealing with malfunctioning and malicious extensions
&lt;/h2&gt;

&lt;p&gt;A JavaScript extension operating within a full-fledged JavaScript runtime is a powerful tool, and like any powerful tool, it can be quite harmful if not used correctly. When you're building an extension runtime that will run arbitrary user extensions, it's wise to operate under the assumption that it may be misused someday, whether intentionally malicious or not.&lt;/p&gt;

&lt;p&gt;Fortunately, Deno Core already offers certain security assurances by default: user scripts cannot interact with the network and file system (unless you explicitly expose this functionality, and it's possible!). Even though it significantly reduces the potential for abuse or attacks, scripts can still consume all your CPU and memory resources, leading to a denial-of-service (DoS) for your application!&lt;/p&gt;

&lt;p&gt;For instance, imagine a malicious user provides the following JavaScript extension that never completes and occupies your valuable server's resources:&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="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="c1"&gt;// Infinite loop.&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&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;p&gt;Typically, you should define a time limit for executing user extensions to handle long-running scripts (for Secutils.dev, it's set at 30 seconds), after which the "extension process" will be terminated. The code might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;AtomicBool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Define a timeout after which the script will be terminated.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;termination_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Define the "cancellation token" that main thread&lt;/span&gt;
&lt;span class="c1"&gt;// can use to signal to the termination thread that&lt;/span&gt;
&lt;span class="c1"&gt;// script completed and termination isn't needed.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;timeout_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;AtomicBool&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Retrieve v8::Isolate handle.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;isolate_handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="nf"&gt;.v8_isolate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.thread_safe_handle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;timeout_token_clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeout_token&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// If main thread signaled that script completed&lt;/span&gt;
        &lt;span class="c1"&gt;// execution, exit.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;timeout_token_clone&lt;/span&gt;&lt;span class="nf"&gt;.load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Relaxed&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="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Otherwise, terminate execution if time is out, or sleep for max 2 sec.&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;termination_timeout&lt;/span&gt;&lt;span class="nf"&gt;.checked_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="nf"&gt;.elapsed&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;isolate_handle&lt;/span&gt;&lt;span class="nf"&gt;.terminate_execution&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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="c1"&gt;// Execute script...&lt;/span&gt;

&lt;span class="c1"&gt;// If the script completed execution early, tell&lt;/span&gt;
&lt;span class="c1"&gt;// "terminator" thread to exit.&lt;/span&gt;
&lt;span class="n"&gt;timeout_token&lt;/span&gt;&lt;span class="nf"&gt;.swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Relaxed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code revolves around a special "terminator" thread that terminates the script execution when the time is up, and doesn't need additional explanation. The only detail worth mentioning is that I want the "terminator" thread to exit as early as possible if the script completes within the time budget. Hence, I check the status every 2 seconds instead of sleeping for 30 seconds.&lt;/p&gt;

&lt;p&gt;Protecting against memory-hungry scripts in Deno is more challenging. I won't go into details about how it works and instead direct you to the issue in the Deno repository &lt;a href="https://github.com/denoland/deno/issues/6916"&gt;&lt;strong&gt;with all the details&lt;/strong&gt;&lt;/a&gt;. In short, you need to create a JavaScript runtime with a specific heap limit and add a callback that's invoked when the memory limits are approached. This gives you a chance to terminate the execution before Deno/V8 crashes the entire process.&lt;/p&gt;

&lt;p&gt;For example, a script like this would quickly consume all available memory:&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&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;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, World&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Done&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here’s how you can try to mitigate this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;deno_core&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;JsRuntime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RuntimeOptions&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Create a new instance of the JS runtime with&lt;/span&gt;
&lt;span class="c1"&gt;// a 10 megabytes heap limit. &lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;JsRuntime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RuntimeOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;create_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nn"&gt;v8&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Isolate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create_params&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.heap_limits&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="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="nn"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Retrieve v8::Isolate handle and setup a "near_heap_limit" callback.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;isolate_handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="nf"&gt;.v8_isolate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.thread_safe_handle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="nf"&gt;.add_near_heap_limit_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;current_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Terminate execution.&lt;/span&gt;
    &lt;span class="n"&gt;isolate_handle&lt;/span&gt;&lt;span class="nf"&gt;.terminate_execution&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Give the runtime enough heap to terminate&lt;/span&gt;
    &lt;span class="c1"&gt;// without crashing the process.&lt;/span&gt;
    &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current_value&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are good protective measures to have, but unfortunately, they don't provide complete protection. The script can quickly fill up memory, preventing termination from completing, or it might perform some heavy actions to hog your CPU. So, remember to set the CPU and memory limits for the container or Kubernetes pod where you're running your JavaScript runtime!&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring user extensions
&lt;/h2&gt;

&lt;p&gt;If you're running user extensions or code, it's important to monitor them not only to alert you when something suddenly goes awry but also to gain valuable insights into how users extend and use your application.&lt;/p&gt;

&lt;p&gt;As I mentioned in my &lt;a href="https://secutils.dev/docs/blog/usage-analytics-and-monitoring#monitoring"&gt;&lt;strong&gt;"Privacy-friendly usage analytics and monitoring"&lt;/strong&gt;&lt;/a&gt; post, I rely on the &lt;a href="https://www.elastic.co/"&gt;&lt;strong&gt;Elastic Stack&lt;/strong&gt;&lt;/a&gt; to monitor Secutils.dev deployments. I use Filebeat and Metricbeat to collect and ingest application logs and metrics into Elasticsearch, which I can later use in my Kibana dashboards. I've created several visualizations and dashboards to monitor various aspects of my Secutils.dev Kubernetes deployment. Here are a few relevant to the "webhooks" script extensions:&lt;/p&gt;

&lt;h3&gt;
  
  
  Script execution time
&lt;/h3&gt;

&lt;p&gt;Firstly, I monitor how long user scripts take to execute. If a script takes more than 5 milliseconds to complete, it enters a "red zone" that makes me curious about what it does! The highest bar you see in the screenshot below is attributed to a script that renders PNG on the fly!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3xmcm0XU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://secutils.dev/docs/img/blog/2024-01-24_rust_application_with_js_extensions_execution_time.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3xmcm0XU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://secutils.dev/docs/img/blog/2024-01-24_rust_application_with_js_extensions_execution_time.png" alt="Script execution time" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Script terminations and crashes
&lt;/h3&gt;

&lt;p&gt;As I explained earlier in this post, I set limits on how much time (30 seconds) and memory (10 megabytes) a script can consume during execution. If a script exceeds these limits, it gets terminated, and the relevant logs are recorded for later review. This lets me understand the situation and decide what actions to take. If the user's intent is legitimate, I can collaborate with them individually to adjust these limits on a case-by-case basis. However, if the intent is malicious, well, I take some other measures 😬&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ABkgbjDn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://secutils.dev/docs/img/blog/2024-01-24_rust_application_with_js_extensions_terminations.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ABkgbjDn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://secutils.dev/docs/img/blog/2024-01-24_rust_application_with_js_extensions_terminations.png" alt="Script terminations and crashes" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Overall memory consumption
&lt;/h3&gt;

&lt;p&gt;As I integrate the Deno JavaScript runtime into a Secutils.dev API server application, I want to monitor the overall memory consumption of the API server. As shown in the following screenshot, the API server's memory consumption remains consistently low most of the time, especially when compared to the memory required by the &lt;a href="https://secutils.dev/docs/guides/web_scraping/content"&gt;&lt;strong&gt;Web Scraper&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IMa-7pr1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://secutils.dev/docs/img/blog/2024-01-24_rust_application_with_js_extensions_memory.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IMa-7pr1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://secutils.dev/docs/img/blog/2024-01-24_rust_application_with_js_extensions_memory.png" alt="Script terminations and crashes" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All in all, I'm pleased with how it turned out and how straightforward it was to work with Deno Core. The "script" extensions have proven to be a nice way to turn static responders into tiny applications that users can tailor to their needs without my involvement. I'm planning to make use of the Deno JavaScript runtime in other parts of Secutils.dev where I want to provide users with more flexibility. Stay tuned!&lt;/p&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ASK:&lt;/strong&gt; If you found this post helpful or interesting, please consider showing your support by starring &lt;a href="https://github.com/secutils-dev/secutils"&gt;&lt;strong&gt;secutils-dev/secutils&lt;/strong&gt;&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>opensource</category>
      <category>rust</category>
      <category>deno</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>How to track anything on the internet or use Playwright for fun and profit</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Tue, 16 Jan 2024 11:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/how-to-track-anything-on-the-internet-or-use-playwright-for-fun-and-profit-31a9</link>
      <guid>https://dev.to/azasypkin/how-to-track-anything-on-the-internet-or-use-playwright-for-fun-and-profit-31a9</guid>
      <description>&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;After a refreshing winter-time blogging-break, I'd like to resume introducing new features of &lt;a href="https://secutils.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt; through practical use cases. Ever wondered how to easily track something on the internet that does not offer subscribing to updates natively? If so, let me introduce you a recently released &lt;a href="https://secutils.dev/docs/guides/web_scraping/content" rel="noopener noreferrer"&gt;&lt;strong&gt;web content tracking utility&lt;/strong&gt;&lt;/a&gt; that made its debut in &lt;a href="https://github.com/secutils-dev/secutils/releases/tag/v1.0.0-alpha.4" rel="noopener noreferrer"&gt;&lt;strong&gt;December 2023 (v1.0.0-alpha.4)&lt;/strong&gt;&lt;/a&gt;. I'll explain how I use it for various tasks, well beyond its primary security focus. Additionally, I'll cover how it's made in case you're interested in developing a similar tool yourself. Read on!&lt;/p&gt;

&lt;p&gt;If you've read my previous blog posts or ever experimented with Secutils.dev, you might be familiar with the &lt;a href="https://secutils.dev/docs/guides/web_scraping/resources" rel="noopener noreferrer"&gt;&lt;strong&gt;web resources tracking utility&lt;/strong&gt;&lt;/a&gt;. This utility allows you to monitor changes in web page resources, specifically JavaScript and CSS. While it has a somewhat narrow security-focused purpose — detecting broken or tampered web application deployments — it may not be the type of tool you use daily. Nevertheless, it serves as a good example of what you can build with modern browser automation tools like &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Playwright&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Puppeteer&lt;/strong&gt;&lt;/a&gt;. If you're interested in digging deeper into this specific utility, refer to the following blog post series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://secutils.dev/docs/blog/detecting-changes-in-js-css-part-1" rel="noopener noreferrer"&gt;&lt;strong&gt;Detecting changes in JavaScript and CSS isn't an easy task, Part 1&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://secutils.dev/docs/blog/detecting-changes-in-js-css-part-2" rel="noopener noreferrer"&gt;&lt;strong&gt;Detecting changes in JavaScript and CSS isn't an easy task, Part 2&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://secutils.dev/docs/blog/detecting-changes-in-js-css-part-3" rel="noopener noreferrer"&gt;&lt;strong&gt;Detecting changes in JavaScript and CSS isn't an easy task, Part 3&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back in July 2023, while working on the web page resources tracker utility and integrating it with other Secutils.dev components, I realized that the combination of a browser, scheduler, and notifications offers far more interesting applications beyond web page resources tracking. Imagine this scenario: there's specific content on the internet that interests you, and you want to stay informed about any updates to that content, whether it's a new blog post from your favorite author, changes to a particular web page, or a hot discussion on Hacker News.&lt;/p&gt;

&lt;p&gt;Typically, you would subscribe to email or push notifications through a subscription form, and in many cases, that suffices. But what if the website or application in question doesn't provide a way to subscribe to the updates you need? What if you're only interested in specific changes, or perhaps you want to adjust the frequency of notifications?&lt;/p&gt;

&lt;p&gt;This is where the trio of browser, scheduler, and notifications can be extremely valuable. You can instruct the scheduler to periodically check the content you're interested in, use a browser automation tool to extract the relevant part of the content, and then rely on notifications to alert you to any changes. Essentially, this is what the web page content tracker utility does. In general, if you can manually obtain the information you need through a browser, it can be automated as well.&lt;/p&gt;

&lt;p&gt;Originally, I developed the web page content trackers utility to address a very specific security-focused requirement in my day job — I needed to monitor security headers and a few other properties of the production &lt;a href="https://www.elastic.co/kibana" rel="noopener noreferrer"&gt;&lt;strong&gt;Cloud Kibana&lt;/strong&gt;&lt;/a&gt; deployment. However, since its release, I've found myself leveraging this the content trackers for a lot of use cases that extend well beyond security:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In one of my other projects, &lt;a href="https://azbyte.xyz" rel="noopener noreferrer"&gt;&lt;strong&gt;AZbyte | ETF&lt;/strong&gt;&lt;/a&gt;, I require up-to-date information about exchange-traded funds (ETFs) from various fund providers (iShares, Vanguard, etc.). Content trackers come in handy to monitor their websites for new funds, as these providers don't offer a way to subscribe to such updates.&lt;/li&gt;
&lt;li&gt;For my day job, I track web page metadata of my development &lt;a href="https://docs.elastic.co/serverless" rel="noopener noreferrer"&gt;&lt;strong&gt;“serverless” Elastic projects&lt;/strong&gt;&lt;/a&gt;. This helps me know when they are automatically upgraded to a new version since there's currently no straightforward way to receive notifications about this.&lt;/li&gt;
&lt;li&gt;I use content trackers to keep an eye on "Pricing", "Terms", and "Privacy Policy" pages of some of the tools and services I use. This should help me to catch any unexpected changes early on, especially with services like Notion, Oracle Cloud, and Cloudflare.&lt;/li&gt;
&lt;li&gt;There are several trackers dedicated to "What's New" pages that only notify me when updates include specific keywords, and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, the use cases are virtually limitless. In fact, I've accumulated so many web page content trackers that I now need a way to organize them effectively. I'm considering picking up either the &lt;a href="https://github.com/secutils-dev/secutils/issues/43" rel="noopener noreferrer"&gt;&lt;strong&gt;“Add support for user data tags”&lt;/strong&gt;&lt;/a&gt; or &lt;a href="https://github.com/secutils-dev/secutils/issues/12" rel="noopener noreferrer"&gt;&lt;strong&gt;“Allow grouping user content into separate projects”&lt;/strong&gt;&lt;/a&gt; enhancement during the ongoing “feature sprint” to address this.&lt;/p&gt;

&lt;p&gt;Now, let's take a closer look at one of my simplest personal web page content trackers!&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Trending GitHub repositories
&lt;/h2&gt;

&lt;p&gt;I enjoy discovering new open source projects for inspiration, and the &lt;a href="https://github.com/trending" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub trending page&lt;/strong&gt;&lt;/a&gt; serves as an excellent resource for that. However, as far as I'm aware, there's no straightforward way to receive notifications when a new project rises to the top of the trending repositories, unless I use GitHub APIs directly. To workaround this, I set up a web page content tracker with the following settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2024-01-16_web_page_content_tracker.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2024-01-16_web_page_content_tracker.png" alt="Web Page Content Tracker for GitHub Trending page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All fields should be self-explanatory: I instruct the tracker to check for updates at &lt;code&gt;https://github.com/trending&lt;/code&gt; daily, retaining only the last three revisions. I provide the tracker with a piece of JavaScript code (&lt;code&gt;Content extractor&lt;/code&gt;) to execute on the target web page, extracting the relevant content. If this content differs from the previously extracted content, the tracker sends an email notification. Additionally, if the content extraction attempt fails, the tracker will retry a few more times at 2-hour intervals. If none of the attempts succeed, the tracker notifies about the failure.&lt;/p&gt;

&lt;p&gt;The most important part here is the &lt;code&gt;Content extractor&lt;/code&gt;, a simple JavaScript script executed within the context of the target web page to extract the actual content:&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="c1"&gt;// Get top link at the "trending" page.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h2 a.Link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Cleanup the repository name.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topLinkName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;topLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&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="s1"&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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="p"&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;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; / &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Craft a Markdown-styled content with a link.&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Top repository is **[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topLinkName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;](&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)**`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the script, relying on opaque web page-specific CSS selectors, might appear fragile, these selectors don't change frequently in practice. Moreover, the tracker will notify me if this code begins to fail, allowing me to make necessary adjustments.&lt;/p&gt;

&lt;p&gt;One doesn't need to be proficient in JavaScript to write such simple scripts — ChatGPT and similar tools can generate something like this easily nowadays. I'm seriously thinking about launching a dedicated paid service centered around this functionality. Users could simply hover over the content they want to track, and the AI would handle the rest! Wouldn't that be awesome? 😃&lt;/p&gt;

&lt;p&gt;The script can return Markdown-styled content, making it easier for users to consume. Here's how it looks in Secutils.dev UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2024-01-16_web_page_content_tracker_ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2024-01-16_web_page_content_tracker_ui.png" alt="Web Page Content Tracker UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Markdown and a bit of creativity, one can create a nice personalized version of the tracked content!&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;I won't dive into the UI, HTTP APIs, or storage layer used for this functionality, as it's all standard tech. I'd better focus on the content extraction part, the core of this functionality.&lt;/p&gt;

&lt;p&gt;To begin, all functionality related to browser automation and web scraping lives in a dedicated service — &lt;a href="https://github.com/secutils-dev/secutils-web-scraper" rel="noopener noreferrer"&gt;&lt;strong&gt;Web Scraper&lt;/strong&gt;&lt;/a&gt;. The primary rationale is that dealing with browsers and arbitrary user scripts is tricky from a security standpoint, and it's always a good idea to isolate such functionality as much as possible. You can read more about the security aspects of web scraping in the &lt;a href="https://secutils.dev/docs/blog/running-web-scraping-service-securely" rel="noopener noreferrer"&gt;&lt;strong&gt;"Running web scraping service securely"&lt;/strong&gt;&lt;/a&gt; post.&lt;/p&gt;

&lt;p&gt;As the post title suggests, at the heart of Web Scraper lies &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Playwright&lt;/strong&gt;&lt;/a&gt; with an additional HTTP API layer on top. Playwright is an exceptional tool that manages all interactions with the headless browser and abstracts away a considerable amount of complexity. Let me show you how I use Playwright to extract content from web pages:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ NOTE:&lt;/strong&gt; I've omitted some non-essential details for brevity, you can find the full source code at the &lt;a href="https://github.com/secutils-dev/secutils-web-scraper/" rel="noopener noreferrer"&gt;&lt;strong&gt;Web Scraper GitHub repository&lt;/strong&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;browserToRun&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;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;chromiumSandbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&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="s1"&gt;--disable-web-security&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the snippet above, we run Chromium in headless mode and enable the &lt;a href="https://playwright.dev/docs/api/class-browsertype#browser-type-launch-option-chromium-sandbox" rel="noopener noreferrer"&gt;&lt;strong&gt;security sandbox&lt;/strong&gt;&lt;/a&gt;, which is disabled by default, but I highly &lt;a href="https://secutils.dev/docs/blog/running-web-scraping-service-securely#browser-sandbox" rel="noopener noreferrer"&gt;&lt;strong&gt;recommend enabling it&lt;/strong&gt;&lt;/a&gt;. I also set the &lt;code&gt;--disable-web-security&lt;/code&gt; flag to bypass any CORS restrictions. This is important if you want to allow injected scripts to make XHR requests to other domains/origins. It can be handy if the user's custom script is just a light "shell" that asynchronously loads the JavaScript code to execute from another place (refer to &lt;a href="https://secutils.dev/docs/guides/web_scraping/content#use-external-content-extractor-script" rel="noopener noreferrer"&gt;&lt;strong&gt;examples in documentation&lt;/strong&gt;&lt;/a&gt; for more details).&lt;/p&gt;

&lt;p&gt;Remember that running the browser might consume a significant amount of resources, so you might want to consider shutting it down after some idle timeout or maybe even scale your service to zero completely. Secutils.dev Web Scraper runs the browser on demand and shuts it down after 10 minutes if it's not used by default.&lt;/p&gt;

&lt;p&gt;The next step is to decide what input parameters the content scraper should support. The most obvious candidates are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;[Required]&lt;/strong&gt; URL to track the content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[Required]&lt;/strong&gt; Previously extracted content. In some cases, you might want to compare the previous and new content and only save a new version if a special condition is met.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[Required]&lt;/strong&gt; Content extractor JavaScript script. This script is injected into the target page and executed once the page is loaded. Since the script is executed in the most up-to-date version of the browser, it can use the latest JavaScript features and Web APIs! The script can return anything as long as it's possible to serialize it as a JSON string and store it in a database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[Optional]&lt;/strong&gt; A list of custom HTTP headers to send to the target web page. This may be required if the page you're tracking requires authentication (e.g., via &lt;code&gt;Cookie&lt;/code&gt; or &lt;code&gt;Authorization&lt;/code&gt; HTTP headers) or some sort of consent (e.g., Cookie consent) before rendering the content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[Optional]&lt;/strong&gt; A delay or CSS selector to wait for before the tracker tries to extract content. This is a must for some very dynamic and heavy modern applications that can lazily load the content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all these parameters, the code to scrape content might look like this (simplified and with additional comments):&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="c1"&gt;// Create a new browsing context and pass custom HTTP headers.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;extraHTTPHeaders&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="c1"&gt;// Create a new page within this context.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Inject a custom script, if provided.&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;scripts&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;extractContent&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="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInitScript&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`self.__secutils = { async extractContent(context) { &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extractContent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="c1"&gt;// Navigate to a custom page and retain full `Response`&lt;/span&gt;
&lt;span class="c1"&gt;// to return detailed error messages.&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;response&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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="nx"&gt;timeout&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;err&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client-error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&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;…&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="c1"&gt;// Wait for a custom CSS selector, if provided.&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;waitSelector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waitSelector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;timeout&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;err&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client-error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&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;…&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="c1"&gt;// Finally, extract web page content.&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;extractedContent&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Use `jsonStableStringify` to make sure the same result&lt;/span&gt;
  &lt;span class="c1"&gt;// always serializes to the same JSON string.&lt;/span&gt;
  &lt;span class="nx"&gt;extractedContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;jsonStableStringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;extractContent&lt;/span&gt;
      &lt;span class="c1"&gt;// If content exraction script is provided, execute it.&lt;/span&gt;
      &lt;span class="c1"&gt;// See definiton of `extractContent` function below. &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;extractContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;previousContent&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="c1"&gt;// Otherwise, treat the full web page HTML as content.&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jsBeautify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html_beautify&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client-error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&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;…&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebPageContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Evaluate custom script in the page context.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;context&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extractContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__secutils&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;extractContent&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;extractContent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;extractContent&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&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;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;…&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;extractContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Deserialize previous content, if available.&lt;/span&gt;
      &lt;span class="na"&gt;previousContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousContent&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; 
        &lt;span class="p"&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="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousContent&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="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&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;As you can see, Playwright is a very powerful tool, and working with it is straightforward. I omitted a few pieces that rely on the Chrome DevTools Protocol (e.g., collecting all external requests with responses and clearing browser cache) that aren't strictly relevant for this post, but you can check out the &lt;a href="https://github.com/secutils-dev/secutils-web-scraper/" rel="noopener noreferrer"&gt;&lt;strong&gt;Web Scraper GitHub repository&lt;/strong&gt;&lt;/a&gt; to see the full source code if you're curious.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Web page content trackers are already quite functional, but I have a &lt;a href="https://github.com/secutils-dev/secutils/issues?q=is%3Aopen+is%3Aissue+label%3A%22Component%3A+Utility%3A+Web+Scraping%22" rel="noopener noreferrer"&gt;&lt;strong&gt;number of ideas&lt;/strong&gt;&lt;/a&gt; on how to make them even more useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add ability to capture web page screenshots and performing visual diffs (&lt;a href="https://github.com/secutils-dev/secutils/issues/33" rel="noopener noreferrer"&gt;&lt;strong&gt;secutils#33&lt;/strong&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Allow tracking the content of web pages protected by WAF and CAPTCHA (&lt;a href="https://github.com/secutils-dev/secutils/issues/34" rel="noopener noreferrer"&gt;&lt;strong&gt;secutils#34&lt;/strong&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add support for auto-generated content extraction scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ASK:&lt;/strong&gt; If you found this post helpful or interesting, please consider showing your support by starring &lt;a href="https://github.com/secutils-dev/secutils" rel="noopener noreferrer"&gt;&lt;strong&gt;secutils-dev/secutils&lt;/strong&gt;&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin" rel="noopener noreferrer"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/" rel="noopener noreferrer"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>opensource</category>
      <category>playwright</category>
      <category>security</category>
    </item>
    <item>
      <title>Explore web applications through their content security policy (CSP)</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Tue, 28 Nov 2023 12:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/explore-web-applications-through-their-content-security-policy-csp-401d</link>
      <guid>https://dev.to/azasypkin/explore-web-applications-through-their-content-security-policy-csp-401d</guid>
      <description>&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;I've finally wrapped up the feature development and fixes planned for the &lt;a href="https://github.com/orgs/secutils-dev/projects/1/views/1" rel="noopener noreferrer"&gt;&lt;strong&gt;"Q4 2023 – Oct-Dec" milestone&lt;/strong&gt;&lt;/a&gt; of &lt;a href="https://secutils.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt;, a month earlier than expected! It feels good to be getting better at estimating my own work 🙂 I still need to update documentation and create a few demo videos for the new functionality, but that should be the easy part. Hopefully, I can release a new version in a week or so.&lt;/p&gt;

&lt;p&gt;Like anything we invest our time and energy in, I want to raise awareness about the work I've done, gauge interest, and hopefully receive constructive feedback. I'm not a fan of blunt self-promotion, so I'm going to try something different - demonstrating new features in action. Sometimes I'll show their business value, and other times it'll just be for fun and entertainment. In this post, I'll demonstrate how to use the new &lt;a href="https://github.com/secutils-dev/secutils/issues/16" rel="noopener noreferrer"&gt;&lt;strong&gt;“Import content security policy”&lt;/strong&gt;&lt;/a&gt; feature to learn a bit more about the websites you use every day. Let's dive in!&lt;/p&gt;

&lt;p&gt;Alright, let's explore a few popular websites and uncover any new insights from the CSP they employ. To import the policy, head to &lt;a href="https://secutils.dev/ws/web_security__csp__policies" rel="noopener noreferrer"&gt;&lt;strong&gt;Web Security → CSP → Policies&lt;/strong&gt;&lt;/a&gt; and click on the &lt;code&gt;Import policy&lt;/code&gt; button. Within the import modal, enter an arbitrary name, the webpage URL, and select the source from which to import the policy: &lt;code&gt;HTTP header (enforcing)&lt;/code&gt;, &lt;code&gt;HTTP header (report only)&lt;/code&gt;, or &lt;code&gt;HTML meta tag&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_dialog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_dialog.png" alt="Import CSP dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Google
&lt;/h2&gt;

&lt;p&gt;By default, &lt;a href="https://secutils.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt; allows importing CSP from the &lt;code&gt;Content-Security-Policy&lt;/code&gt; HTTP header, matching the &lt;code&gt;HTTP header (enforcing)&lt;/code&gt; policy source in the import dialog. However, attempting to import CSP from this source results in an error. Surprisingly, &lt;a href="https://google.com" rel="noopener noreferrer"&gt;&lt;strong&gt;google.com&lt;/strong&gt;&lt;/a&gt; doesn't transmit this header. Instead, it delivers CSP via the &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; HTTP header! It actually makes sense when you think about it: as the most visited website on the planet, they wouldn't want to accidentally disrupt anything with CSP changes, especially considering the need to support all web browsers. Instead, opting for a robust monitoring solution around CSP violation reporting seems more plausible. They might even set up alerts for suspicious activities that could indicate attempts to harm users through their website. This is, of course, speculative, but it seems to align logically.&lt;/p&gt;

&lt;p&gt;Now, let's examine what &lt;a href="https://google.com" rel="noopener noreferrer"&gt;&lt;strong&gt;google.com&lt;/strong&gt;&lt;/a&gt; delivers in the &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; HTTP header:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_google.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_google.png" alt="google.com content security policy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The CSP used by Google appears quite reasonable to me, given their stature. Although having two unsafe directives isn't ideal, I trust Google's Security team extensively evaluated potential attack vectors and deemed the risk close to negligible. The inclusion of &lt;a href="https://www.w3.org/TR/CSP/#violation-sample" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;code&gt;report-sample&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; can be incredibly useful when monitoring CSP violations since having a sample makes it much easier to understand how the policy was violated exactly.&lt;/p&gt;

&lt;p&gt;Interestingly, CSP violations are directed to &lt;code&gt;https://csp.withgoogle.com/csp/gws/other-hp&lt;/code&gt;. I hadn't encountered this website before! I encourage you to visit &lt;a href="https://csp.withgoogle.com" rel="noopener noreferrer"&gt;&lt;strong&gt;csp.withgoogle.com&lt;/strong&gt;&lt;/a&gt; to explore this valuable resource dedicated to CSP and its necessity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bing
&lt;/h2&gt;

&lt;p&gt;Alright, let's take a look at Google's competitor - &lt;a href="https://bing.com" rel="noopener noreferrer"&gt;&lt;strong&gt;bing.com&lt;/strong&gt;&lt;/a&gt;. And here's the first surprise - Bing doesn't utilize CSP at all! Who would've thought?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_bing.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_bing.png" alt="bing.com content security policy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can understand why certain websites resort to using unsafe CSP directive values, or why some opt for monitoring rather than strict enforcement. However, I can't seem to find any justification for Bing's complete absence of CSP! If anyone has a hint as to why, I'd love to hear it.&lt;/p&gt;

&lt;h2&gt;
  
  
  DuckDuckGo
&lt;/h2&gt;

&lt;p&gt;Okay, let's explore something more exotic - &lt;a href="https://duckduckgo.com" rel="noopener noreferrer"&gt;&lt;strong&gt;duckduckgo.com&lt;/strong&gt;&lt;/a&gt;. And would you look at that!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_duckduckgo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_duckduckgo.png" alt="duckduckgo.com content security policy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Firstly, unlike Google, DuckDuckGo enforces CSP via the policy delivered within the &lt;code&gt;Content-Security-Policy&lt;/code&gt; HTTP header. Secondly, DuckDuckGo's CSP employs a strict &lt;code&gt;none&lt;/code&gt; default source, which is always a good practice.&lt;/p&gt;

&lt;p&gt;Another notable aspect is that DuckDuckGo's CSP includes sources in the &lt;code&gt;*.onion&lt;/code&gt; top-level domain. This might be because they maintain the same policy regardless of how the website is accessed. That seems logical.&lt;/p&gt;

&lt;p&gt;However, it's disappointing that DuckDuckGo doesn't appear interested in analyzing CSP violations as they haven't set up any reporting. It's a letdown, but on the flip side, it would only make sense if they had the desire and resources to actively utilize this data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: ChatGPT
&lt;/h2&gt;

&lt;p&gt;Of course, how can a blog post these days avoid mentioning AI? Let's peek at the CSP employed by ChatGPT. Can we? Apparently not! ChatGPT utilizes Cloudflare application protection, which blocks the &lt;a href="https://secutils.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt; HTTP client from executing a simple &lt;code&gt;HEAD&lt;/code&gt; request to retrieve the policy. What a shame!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_chatgpt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_chatgpt.png" alt="ChatGPT Cloudflare WAF"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's something I might address in the future. For now, let me turn this setback into an opportunity to demonstrate another method of importing content security policy to &lt;a href="https://secutils.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt; - via serialized policy text. This process is a tad more complex as it involves manually capturing the policy using the browser's developer tools and then pasting it into the &lt;code&gt;Serialized policy&lt;/code&gt; tab within the import dialog. Here's the CSP manually imported from ChatGPT:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_chatgpt_policy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_chatgpt_policy.png" alt="ChatGPT content security policy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply by examining a website's CSP, you can learn a lot about its construction and reliance on third-party solutions. It's a complex beast with many moving parts. Nothing particularly alarming here though. It's a pretty standard set for a modern application.&lt;/p&gt;

&lt;p&gt;I also noticed that OpenAI collects CSP violation reports using the &lt;a href="https://docs.datadoghq.com/integrations/content_security_policy_logs" rel="noopener noreferrer"&gt;&lt;strong&gt;Datadog Content Security Policy integration&lt;/strong&gt;&lt;/a&gt;. However, having &lt;a href="https://www.w3.org/TR/CSP/#directive-script-src" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;code&gt;unsafe-eval&lt;/code&gt; and &lt;code&gt;unsafe-inline&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; in the scripts directive, especially for an application like ChatGPT, does raise concerns. Hopefully, there's a compelling reason behind this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_chatgpt_reporting.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-11-28_import_policy_chatgpt_reporting.png" alt="ChatGPT content security policy reporting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ultimately, whether you're curious about how an application operates or conducting initial reconnaissance, overlooking CSP would be a mistake. Sometimes, it's like a treasure trove, revealing the inner workings and structure of an application.&lt;/p&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ASK:&lt;/strong&gt; If you found this post helpful or interesting, please consider showing your support by starring &lt;a href="https://github.com/secutils-dev/secutils" rel="noopener noreferrer"&gt;&lt;strong&gt;secutils-dev/secutils&lt;/strong&gt;&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin" rel="noopener noreferrer"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/" rel="noopener noreferrer"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>opensource</category>
      <category>security</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Two simple rules for better and more secure code</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Tue, 07 Nov 2023 12:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/two-simple-rules-for-better-and-more-secure-code-166j</link>
      <guid>https://dev.to/azasypkin/two-simple-rules-for-better-and-more-secure-code-166j</guid>
      <description>&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;In one of my previous posts, &lt;a href="https://secutils.dev/docs/blog/best-application-security-tool-is-education"&gt;&lt;strong&gt;"The best application security tool is education"&lt;/strong&gt;&lt;/a&gt;, I discussed why educating yourself or your engineers about security can yield the highest return on investment, especially if you have a limited budget. However, I understand that learning or teaching security is not as straightforward as it sounds. Every organization has its unique characteristics, and every engineer has their own distinct qualities. Moreover, internalizing secure coding practices is a time-consuming process. If you're just starting on this journey, I'm here to share two very simple rules that are easy to remember and have the potential to significantly enhance the security of the code you or your colleagues write. So, let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't log what you don't know
&lt;/h2&gt;

&lt;p&gt;If you're working on a non-trivial application, chances are you're logging various pieces of information. Logs are essential for understanding your software and are indispensable during debugging. In fact, the more application and operational information you log, the better prepared you are to debug any issues that your application might encounter.&lt;/p&gt;

&lt;p&gt;However, the rule I want you to keep in mind is this: every time you consider logging something, ask yourself these two critical questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Is the data I'm logging sensitive?&lt;/strong&gt; For instance, does it include credentials, request or response headers, or any information that could potentially identify my users, like client IPs or user names? In short, anything that falls under the category of &lt;a href="https://www.investopedia.com/terms/p/personally-identifiable-information-pii.asp"&gt;&lt;strong&gt;Personal Identifiable Information (PII)&lt;/strong&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If I'm logging a complex data structure, do I fully understand and trust all the fields that will be logged?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Asking these questions becomes especially crucial when you're dealing with data that originates from users or external services you don't have full control over. This is important because the data you log might eventually end up elsewhere, such as in Elasticsearch, backed up in the cloud, or stored locally for an unknown period. It's not uncommon for these secondary locations to be less secure than the environment where the logs were initially captured. This increases the risk, sometimes significantly, that the logs might be accessed by individuals who weren't meant to see them, even months after the logs were recorded. This is a potential problem, not to mention the possibility of leaks and hacks, which can also occur.&lt;/p&gt;

&lt;p&gt;Leaking sensitive information through logs is one problem, but keep in mind that mishandled logs can also be weaponized, as explained in the &lt;a href="https://owasp.org/www-community/attacks/Log_Injection"&gt;&lt;strong&gt;Log injection article from OWASP&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In summary, please avoid blindly logging everything and, instead, select carefully based on what is safe and genuinely necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't expose raw errors
&lt;/h2&gt;

&lt;p&gt;I have to admit that I see it far more frequently than I would like: an engineer carefully crafts a successful response, selects only the necessary data for return, and wisely sanitizes untrusted data. However, at the same time, they completely neglect the errors their code generates and throws.&lt;/p&gt;

&lt;p&gt;Errors returned to users or consumers of APIs can sometimes contain as much, if not more, sensitive data compared to a successful response. Stack traces, file system paths, URLs with embedded credentials, internal addresses, and environment variables with secrets are just a few examples of what might be included in these errors, inadvertently leaking from your software.&lt;/p&gt;

&lt;p&gt;The rule I want to emphasize is simple: strive to handle errors internally, and log the relevant error details when necessary. But, when it comes to an error that should be visible to your users or API consumers, craft a custom, safe, and actionable error message instead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ NOTE:&lt;/strong&gt; The two rules I've discussed may seem simple and obvious, but you'd be surprised at how often they are neglected, leading to serious security incidents that can cost organizations thousands or even tens of thousands of dollars in remediation and associated expenses. Forewarned is forearmed!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it! If you found this post helpful or interesting, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>programming</category>
      <category>productivity</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Q4 2023 iteration: tracking arbitrary web content, user-specific webhook subdomains, inherited CSP, and more</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Tue, 31 Oct 2023 12:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/q4-2023-iteration-tracking-arbitrary-web-content-user-specific-webhook-subdomains-inherited-csp-and-more-4l4a</link>
      <guid>https://dev.to/azasypkin/q4-2023-iteration-tracking-arbitrary-web-content-user-specific-webhook-subdomains-inherited-csp-and-more-4l4a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The original post was published on my blog on &lt;strong&gt;October 10, 2023&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;Last week, I kicked off the new &lt;a href="https://github.com/orgs/secutils-dev/projects/1/views/1"&gt;&lt;strong&gt;"Q4 2023 – Oct-Dec"&lt;/strong&gt;&lt;/a&gt; development and research iteration for &lt;a href="https://secutils.dev"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt;, the open-source toolbox designed for developing and testing secure applications. In this post, I'll take you through the significant features and changes that will be the focus of my work in the coming weeks and months: tracking arbitrary web content, user-specific webhook subdomains, inherited CSP, and more. Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking arbitrary web content
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tracking issue: &lt;a href="https://github.com/secutils-dev/secutils/issues/28"&gt;#secutils/28&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://secutils.dev/docs/blog/alpha3-release"&gt;&lt;strong&gt;previous update&lt;/strong&gt;&lt;/a&gt;, I mentioned that &lt;a href="https://secutils.dev/docs/guides/web_scraping/resources"&gt;&lt;strong&gt;Web Page Resources trackers&lt;/strong&gt;&lt;/a&gt; now allow you to specify custom JavaScript scripts to filter and map resources. As I implemented this feature, I had a slightly broader vision in mind: extending this functionality to address use cases beyond web page resource tracking. One such case that has been on my mind for some time is the ability to track changes in any arbitrary web page content or behavior.&lt;/p&gt;

&lt;p&gt;Consider this scenario: You're interested in specific content on a web page, such as a list, table, or even navigation bar items, but the page doesn't offer a subscription or notification features. Or maybe you'd rather not create a dedicated account for it. In such cases, the typical approach involves periodically visiting the page and manually scanning for updates — a process that's neither convenient nor efficient. Fortunately, many tasks like this can be automated.&lt;/p&gt;

&lt;p&gt;This is where the new functionality in Secutils.dev comes into play. It will allow you to track arbitrary web content as long as you can extract the relevant data using a custom JavaScript script injected to a target web page. While it may not immediately seem related to security, it has its own utility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security researchers can stay informed about new sections or internal links on websites they're analyzing. &lt;/li&gt;
&lt;li&gt;Developers can receive alerts if their production web pages render or expose unexpected content. &lt;/li&gt;
&lt;li&gt;Anyone needing to monitor updates across various unrelated sources, from public repository commits to recent disclosures on random websites, can now consolidate their tracking efforts in a single location.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  User-specific webhook subdomains
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tracking issue: &lt;a href="https://github.com/secutils-dev/secutils/issues/22"&gt;#secutils/22&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Previously, you could only specify a unique name for the &lt;a href="https://secutils.dev/docs/guides/webhooks"&gt;&lt;strong&gt;auto-responder webhook&lt;/strong&gt;&lt;/a&gt;. For example, if you named your responder as &lt;code&gt;my-page.html&lt;/code&gt;, you'd get the following webhook endpoint URL: &lt;code&gt;https://secutils.dev/api/webhooks/{your_unique_user_handle}/my-page.html&lt;/code&gt;. That's perfectly fine for simple use cases where you can directly feed your full webhook URL to a system or service you're interacting with.&lt;/p&gt;

&lt;p&gt;However, some services might already assume a certain URL structure and won't allow you to specify the full URL directly. In such cases, you're usually limited to providing only a host name. For example, when testing an interaction between Service A and another well-known Service B, such as Elasticsearch or Supabase, Service A might request only the host name and credentials for Service B, as it's well aware of all Service B's REST APIs. In this scenario, you'd either use dedicated mocking tools (e.g. WireMock) or set up a testing Elasticsearch or Supabase cluster yourself. The latter case can be quite involved, and you'll need to go through various steps to understand exactly what data Service A is sending to Service B.&lt;/p&gt;

&lt;p&gt;To address cases like this, Secutils.dev will offer an alternative way to configure and invoke responder webhooks. Each user will be allocated a dedicated webhook subdomain with the ability to configure any path, including the root one (&lt;code&gt;/&lt;/code&gt;): &lt;code&gt;https://{your_unique_user_handle}.webhooks.secutils.dev/my-page.html&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ NOTE:&lt;/strong&gt; It's worth emphasizing that Secutils.dev is not aiming to compete with fantastic mocking tools like WireMock or MockServer, and realistically, it can't. These tools have strong communities and excel at what they do! The goal behind the webhook utility is to complement the Secutils.dev toolbox with yet another commonly used utility in various short-lived security-related scenarios that's incredibly easy and cost-effective to set up and dispose of when no longer needed. A scenario akin to choosing between a universal lightweight Swiss Army knife and highly specialized Wüsthof Chef's knives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Inherited content security policies (CSP)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tracking issue: &lt;a href="https://github.com/secutils-dev/secutils/issues/16"&gt;#secutils/16&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Currently, Secutils.dev enables you to construct content security policies (CSP) templates through a guided UI. Once you've created a policy template, you can generate a policy ready for use on your website. This approach works well when you have a clear goal and can create a new policy template from scratch.&lt;/p&gt;

&lt;p&gt;However, there are times when you'd prefer to base your policy on or "inherit" it from an existing policy. It would be beneficial if, when you create a new policy template in Secutils.dev, you have the option to either specify an existing policy directly or simply point to an arbitrary web page from which to fetch the policy. These changes will enhance the flexibility and usability of Secutils.dev when it comes to managing content security policies.&lt;/p&gt;

&lt;p&gt;This is a feature I'll be working on in the upcoming weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing &amp;amp; collaboration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tracking issue: &lt;a href="https://github.com/secutils-dev/secutils/issues/24"&gt;#secutils/24&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As mentioned in my previous post, I’m planning to gradually expand the list of user-generated content that can be publicly shared via Secutils.dev. In this iteration, my goal is to enable the sharing of user certificate templates. This means that anyone with the unique link will not only be able to view the certificate template but also generate a new key/certificate pair. This feature will enhance collaboration and convenience within the Secutils.dev community. Stay tuned for more updates!&lt;/p&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;p&gt;If you found this post helpful or interesting, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>opensource</category>
      <category>microsaas</category>
      <category>news</category>
    </item>
    <item>
      <title>Announcing 1.0.0-alpha.3 release: more powerful resource tracking, notifications and content sharing</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Tue, 24 Oct 2023 10:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/announcing-100-alpha3-release-more-powerful-resource-tracking-notifications-and-content-sharing-35ca</link>
      <guid>https://dev.to/azasypkin/announcing-100-alpha3-release-more-powerful-resource-tracking-notifications-and-content-sharing-35ca</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The original post was published on my blog on &lt;strong&gt;October 4, 2023&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;Earlier this week, I wrapped up the &lt;a href="https://github.com/orgs/secutils-dev/projects/1/views/1" rel="noopener noreferrer"&gt;&lt;strong&gt;"Q3 2023 – Jul-Sep"&lt;/strong&gt;&lt;/a&gt; iteration and cut a new &lt;a href="https://github.com/secutils-dev/secutils/releases/tag/v1.0.0-alpha.3" rel="noopener noreferrer"&gt;&lt;strong&gt;1.0.0-alpha.3 release&lt;/strong&gt;&lt;/a&gt; of &lt;a href="https://secutils.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt;. In this post, I would like to quickly walk you through the major changes since &lt;a href="https://github.com/secutils-dev/secutils/releases/tag/v1.0.0-alpha.2" rel="noopener noreferrer"&gt;&lt;strong&gt;1.0.0-alpha.2&lt;/strong&gt;&lt;/a&gt;: notifications, more powerful web page resource tracker, sharing capabilities and more. Let’s dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduled resources checks
&lt;/h2&gt;

&lt;p&gt;If you’ve read my previous posts or tried Secutils.dev &lt;a href="https://secutils.dev/docs/guides/web_scraping/resources" rel="noopener noreferrer"&gt;&lt;strong&gt;web page resources tracker&lt;/strong&gt;&lt;/a&gt; functionality, you might recall that users were required to manually trigger resource checks. With this release, you have an option to schedule automatic resources checks to be performed hourly, daily, weekly, or monthly! When you configure the web page resource tracker, you define how many resource revisions Secutils.dev should store so that you can view the diff between two consecutive revisions. Once the limit is reached, the next revision will displace the oldest one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-10-04_scheduled_resource_checks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-10-04_scheduled_resource_checks.png" alt="Scheduled resources checks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Email notifications for changed resources
&lt;/h2&gt;

&lt;p&gt;Since previously you were supposed to manually trigger immediate resource checks, it wouldn't make much sense to send you any additional notifications about detected changes. You'll be presented with the check result in the UI as soon as the check is complete without losing context. However, the automatic scheduled resource checks change the control flow, where Secutils.dev should perform the check regularly and notify you if it detects any changes. In the latest release, you can opt in to email notifications, and Secutils.dev will email you if it detects any changes in resources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-10-04_email_notifications.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-10-04_email_notifications.png" alt="Email notifications for changed resources"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom resources filtering and mapping
&lt;/h2&gt;

&lt;p&gt;Modern web pages can contain numerous resources, and tracking changes for all of them may not be always necessary. Additionally, certain resources, like those injected by web page analytics solutions, can change with every page load, potentially leading to excessive notifications. In such cases, you'll likely want to filter out irrelevant resources or focus on specific ones.&lt;/p&gt;

&lt;p&gt;In more advanced scenarios, you might be interested in only a portion of a web page resource. For instance, there could be scripts bundling multiple third-party libraries, with changes in some libraries being more important than others. It would be convenient to have the ability to trim or "map" these resources into more meaningful resources.&lt;/p&gt;

&lt;p&gt;I explored various approaches to address these use cases in the simplest way possible, but there were always complex edge cases that required a change in direction. However, considering that the primary audience for Secutils.dev is software developers, I decided that introducing some complexity could offer much greater flexibility.&lt;/p&gt;

&lt;p&gt;As mentioned in &lt;a href="https://secutils.dev/docs/blog/detecting-changes-in-js-css-part-1#challenge-2-dynamically-loaded-resources" rel="noopener noreferrer"&gt;&lt;strong&gt;this post&lt;/strong&gt;&lt;/a&gt;, I use Playwright (with Chromium) to extract web page resources. While this choice adds complexity to implementation, security, and deployment, it grants quite a bit of flexibility. With Playwright, I can access, intercept, or modify virtually everything on the tracked web page. Notably, Playwright allows me to inject custom JavaScript scripts into a web page. Rather than inventing my own syntax/parser for custom user resource filters and mapping rules, I can provide users with the full power of &lt;em&gt;modern&lt;/em&gt; JavaScript executed within the latest available browser. The only constraint is that users must adhere to the input and output interfaces expected by Secutils.dev.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-10-04_custom_resources_filtering.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-10-04_custom_resources_filtering.png" alt="Custom resources filtering and mapping"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The potential applications of this approach are vast. I'm already planning to extend it to cover more utilities and use cases, such as tracking changes in page content, not just resources. Imagine a change tracker for virtually anything on the web!&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing &amp;amp; collaboration
&lt;/h2&gt;

&lt;p&gt;In today's world, it's challenging to envision a software engineer or security researcher working entirely in isolation. As software systems grow in size and complexity, collaboration becomes essential. That's one of the reasons why collaboration software is on the rise, with built-in collaboration features becoming increasingly common.&lt;/p&gt;

&lt;p&gt;While it may be too early to implement full-fledged two-way collaboration functionality in Secutils.dev, I recognize that the absence of such features could limit the tool's adoption. Therefore, I'm planning to gradually introduce collaboration-related features in each iteration, starting with the "one-way" sharing functionality released in &lt;a href="https://github.com/secutils-dev/secutils/releases/tag/v1.0.0-alpha.3" rel="noopener noreferrer"&gt;&lt;strong&gt;1.0.0-alpha.3 release&lt;/strong&gt;&lt;/a&gt;. With this release, you can share created content security policies with anyone on the internet, even if they don't have a Secutils.dev account.&lt;/p&gt;

&lt;p&gt;In the future, I intend to expand this sharing functionality to include digital certificate templates and tracked web page resources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-10-04_sharing.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsecutils.dev%2Fdocs%2Fimg%2Fblog%2F2023-10-04_sharing.png" alt="Sharing &amp;amp; collaboration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other enhancements and bug fixes
&lt;/h2&gt;

&lt;p&gt;In addition to the major features mentioned above, this release also includes several smaller enhancements. These include extending the digital certificate editor to allow users to configure private key size (for RSA and DSA) and elliptic curve name (for ECDSA).&lt;/p&gt;

&lt;p&gt;As previously mentioned, while the resource tracker functionality has become more powerful, it also comes with increased security risks. Therefore, I've made security enhancements for Docker images for all Secutils.dev components, and the Web Scraper component itself. I've covered this in more detail in my &lt;a href="https://secutils.dev/docs/blog/running-web-scraping-service-securely" rel="noopener noreferrer"&gt;&lt;strong&gt;Running web scraping service securely&lt;/strong&gt;&lt;/a&gt; post.&lt;/p&gt;

&lt;p&gt;You can find the full change log here: &lt;a href="https://secutils.dev/docs/project/changelog/#100-alpha3" rel="noopener noreferrer"&gt;&lt;strong&gt;changelog#1.0.0-alpha.3&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next few days, I'll be prioritizing work for the upcoming "Q4 2023 – Oct-Dec" iteration. In my next post, I'll provide more details on what I'll be focusing on during this period.&lt;/p&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ASK:&lt;/strong&gt; If you found this post helpful or interesting, please consider showing your support by starring &lt;a href="https://github.com/secutils-dev/secutils" rel="noopener noreferrer"&gt;&lt;strong&gt;secutils-dev/secutils&lt;/strong&gt;&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin" rel="noopener noreferrer"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/" rel="noopener noreferrer"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for being a part of the community!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>opensource</category>
      <category>microsaas</category>
      <category>news</category>
    </item>
    <item>
      <title>Running web scraping service securely</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Thu, 19 Oct 2023 10:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/running-web-scraping-service-securely-5ap</link>
      <guid>https://dev.to/azasypkin/running-web-scraping-service-securely-5ap</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The original post was published on my blog on &lt;strong&gt;September 12, 2023&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://secutils.dev/docs/blog/q3-2023-update-notifications"&gt;&lt;strong&gt;In my previous post&lt;/strong&gt;&lt;/a&gt;, I shared the update regarding the upcoming "Q3 2023 - Jul-Sep" milestone. While I briefly covered how I implemented the notifications subsystem in &lt;a href="https://secutils.dev"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt;, there are a few other important changes I've been working on for this milestone. One of these changes is related to the fact that I’m preparing to allow Secutils.dev users to inject custom JavaScript scripts into the web pages they track resources for (yay 🎉). As a result, I've spent some time hardening the Web Scraper environment's security and wanted to share what you should keep in mind if you’re building a service that needs to scrape arbitrary web pages.&lt;/p&gt;

&lt;p&gt;When it comes to web page resource scraping, Secutils.dev relies on a separate component - &lt;a href="https://github.com/secutils-dev/secutils-web-scraper"&gt;&lt;strong&gt;secutils-dev/secutils-web-scraper&lt;/strong&gt;&lt;/a&gt;. I've built it on top of &lt;a href="https://playwright.dev/"&gt;&lt;strong&gt;Playwright&lt;/strong&gt;&lt;/a&gt; since I need to handle both resources that are statically defined in the HTML and those that are loaded dynamically. Leveraging Playwright, backed by a real browser, instead of parsing the static HTML opens up a ton of opportunities to turn a simple web resource scraper into a much more intelligent tool capable of handling all sorts of use cases: recording and replaying HARs, imitating user activity, and more.&lt;/p&gt;

&lt;p&gt;As you might have guessed, running a full-blown browser within your infrastructure that users can point literally anywhere can be quite dangerous if not done right, so security should be a top-of-mind concern here. Let me walk you through the most obvious security concerns one should address before exposing a service like that to the users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input validation
&lt;/h2&gt;

&lt;p&gt;The first line of defense, and the most basic one, is to limit where users can point their browsers through input argument validation. If you know that only certain resources are supposed to be scraped by users, ensure you properly validate the provided URLs and allow only the expected subset. As a bare minimum, you should validate the arguments on the server/API side, but also consider doing it on the client side if possible, as it would significantly improve the user experience of your service and serve your users better. However, &lt;strong&gt;never-ever&lt;/strong&gt; rely solely on client-side validation - client-side validation is for your users' convenience and is not a security measure, as it can be easily bypassed by directly accessing your APIs.&lt;/p&gt;

&lt;p&gt;Although the resource tracker functionality of Secutils.dev is designed to allow users to scrape virtually any web page on the internet, I still make efforts to validate the provided URL and restrict it as much as possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tracker&lt;/span&gt;&lt;span class="py"&gt;.url&lt;/span&gt;&lt;span class="nf"&gt;.scheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"http"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tracker&lt;/span&gt;&lt;span class="py"&gt;.url&lt;/span&gt;&lt;span class="nf"&gt;.scheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"https"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;bail!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tracker URL scheme must be either http or https"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Checks if the specific hostname is a domain and public (not pointing to the local network).&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;is_public_host_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracker&lt;/span&gt;&lt;span class="py"&gt;.url&lt;/span&gt;&lt;span class="nf"&gt;.domain&lt;/span&gt;&lt;span class="p"&gt;()&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;is_public_host_name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;bail!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tracker URL must have a valid public reachable domain name"&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;
  
  
  Resource isolation
&lt;/h2&gt;

&lt;p&gt;Running an entire browser is a resource-intensive operation, even if it’s &lt;a href="https://en.wikipedia.org/wiki/Headless_browser"&gt;&lt;strong&gt;a headless one&lt;/strong&gt;&lt;/a&gt;. It’s likely that the component responsible for running and dealing with the browser isn’t the only part of your service. It's probably not as critical as components dealing with authentication or database access, for example. You certainly don’t want your entire service to go down just because a resource-intensive web page consumed all the available resources on the host.&lt;/p&gt;

&lt;p&gt;To address this, consider running the component that spawns the browser within a separate container. This approach not only better protects your business-critical functionality but also allows you to scale up or down your browser-specific service independently.&lt;/p&gt;

&lt;p&gt;Additionally, try to explicitly limit the resources available to the container using techniques like &lt;a href="https://en.wikipedia.org/wiki/Cgroups"&gt;&lt;strong&gt;control groups&lt;/strong&gt;&lt;/a&gt; or similar features that suit your environment. For instance, if you’re running your container in Kubernetes, you can &lt;a href="https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"&gt;&lt;strong&gt;limit resources&lt;/strong&gt;&lt;/a&gt; available to that container using configurations such as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-scraper&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:20-alpine3.18&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;250m"&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1Gi"&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Privilege management
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege"&gt;&lt;strong&gt;The principle of least privilege&lt;/strong&gt;&lt;/a&gt; is particularly crucial when dealing with complex software like a web browser. Running a browser as the root user is inviting trouble, and it's something you should avoid. For instance, if you're using Node.js to automate a headless browser with tools like Puppeteer or Playwright, make sure to run it as a &lt;a href="https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#non-root-user"&gt;&lt;strong&gt;non-root user&lt;/strong&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine3.18&lt;/span&gt;
...
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "node", "src/index.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're running your container in Kubernetes and relying on a non-root user, you can safely &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container"&gt;&lt;strong&gt;drop all capabilities&lt;/strong&gt;&lt;/a&gt; for that container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;ALL&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can take additional steps by setting the &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-seccomp-profile-for-a-container"&gt;&lt;strong&gt;appropriate seccomp profile&lt;/strong&gt;&lt;/a&gt; for the Node.js container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;seccompProfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Localhost&lt;/span&gt;
    &lt;span class="c1"&gt;## Taken from https://github.com/microsoft/playwright/tree/main/utils/docker&lt;/span&gt;
    &lt;span class="na"&gt;localhostProfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secutils-web-scraper-seccomp-profile.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These measures ensure that your browser runs with the least privileges necessary, reducing potential security risks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser sandbox
&lt;/h2&gt;

&lt;p&gt;If you've followed the recommendation from the previous section and are running your browser process as a non-root user, there's no reason not to enable a &lt;a href="https://chromium.googlesource.com/chromium/src/+/lkgr/docs/linux/sandboxing.md#linux-sandboxing"&gt;&lt;strong&gt;sandbox for your browser&lt;/strong&gt;&lt;/a&gt;. For example, if you're using Playwright with Chromium, you can enable the sandbox like this:&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;chromium&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="s1"&gt;playwright&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;browserToRun&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;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;chromiumSandbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Enabling the sandbox adds an extra layer of security to your browser operations, visit &lt;strong&gt;&lt;a href="https://no-sandbox.io/"&gt;no-sandbox.io&lt;/a&gt;&lt;/strong&gt; to learn about the potential risks of disabling the Chromium/Chrome sandbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network policies
&lt;/h2&gt;

&lt;p&gt;Even if your input validation code appears reliable today and you have a solid test coverage, bugs can occur at any time. If you have the opportunity to implement multiple layers of defense, make use of as many layers as your financial and resource constraints allow. Implementing proper network policies for the container running the browser based on user-provided URLs is one of such layers. At the very least, you should safeguard your internal infrastructure by allowing access only to globally reachable addresses while excluding local host resources and internal network resources. For example, in the case of IPv4, you can exclude private IP ranges like &lt;code&gt;10.0.0.0/8&lt;/code&gt;, &lt;code&gt;172.16.0.0/12&lt;/code&gt;, and &lt;code&gt;192.168.0.0/16&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Kubernetes, you can achieve this using &lt;a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/"&gt;&lt;strong&gt;&lt;code&gt;NetworkPolicy&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;. Here's an example of how to set up a simple policy to forbid access to non-global IP addresses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NetworkPolicy&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secutils-web-scraper-network-policy&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secutils&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Ingress&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Egress&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secutils-web-scraper&lt;/span&gt;
  &lt;span class="c1"&gt;# Allow incoming traffic only from Secutils.dev API pods.&lt;/span&gt;
  &lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;namespaceSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;kubernetes.io/metadata.name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secutils&lt;/span&gt;
        &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secutils-api&lt;/span&gt;
  &lt;span class="c1"&gt;# Allow outgoing traffic only to DNS server and public internet.&lt;/span&gt;
  &lt;span class="na"&gt;egress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;namespaceSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;kubernetes.io/metadata.name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kube-system&lt;/span&gt;
        &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;k8s-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;coredns&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UDP&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;53&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;53&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ipBlock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cidr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0/0&lt;/span&gt;
          &lt;span class="na"&gt;except&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.0/8&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;172.16.0.0/20&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.0.0/16&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring
&lt;/h2&gt;

&lt;p&gt;So, you've implemented robust input validation, ensured container security, isolated resource-heavy workloads, and restricted network access with network policies. Is that enough for peace of mind? Well, it might be, but then again, it might not. Threat actors and their tactics evolve daily, and what appears secure today might not be tomorrow. In our imperfect world, bugs, misconfigurations, and other errors happen regularly. Stay vigilant, keep an eye out, and maintain constant monitoring of your deployments.&lt;/p&gt;

&lt;p&gt;Fortunately, there are &lt;a href="https://secutils.dev/docs/blog/usage-analytics-and-monitoring#monitoring"&gt;&lt;strong&gt;numerous tools&lt;/strong&gt;&lt;/a&gt; available for monitoring and alerting, ranging from free to paid, simple to sophisticated, self-hosted to fully managed. There's no excuse not to utilize them. Monitor resource usage and set alerts for unexpected spikes, watch for brute-force and DDoS attempts, and pay attention to unexpected errors and service crashes. If your service or product is publicly accessible, I guarantee, monitoring data will reveal a lot of unexpected stuff about what's happening while you're catching some sleep 🙂&lt;/p&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ASK:&lt;/strong&gt; If you found this post helpful or interesting, please consider showing your support by starring &lt;a href="https://github.com/secutils-dev/secutils"&gt;&lt;strong&gt;secutils-dev/secutils&lt;/strong&gt;&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for being a part of the community!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>security</category>
      <category>opensource</category>
      <category>kubernetes</category>
      <category>playwright</category>
    </item>
    <item>
      <title>Q3 2023 update - Notifications</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Tue, 17 Oct 2023 10:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/q3-2023-update-notifications-27jd</link>
      <guid>https://dev.to/azasypkin/q3-2023-update-notifications-27jd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The original post was published on my blog on &lt;strong&gt;September 5, 2023&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;With just one month remaining in the "Q3 2023 - Jul-Sep" milestone (this is how I structure &lt;a href="https://github.com/orgs/secutils-dev/projects/1/views/1"&gt;&lt;strong&gt;my roadmap&lt;/strong&gt;&lt;/a&gt;), I wanted to provide a quick progress update. A significant deliverable for this milestone includes adding support for email notifications and other transactional emails.&lt;/p&gt;

&lt;p&gt;Notifications, in general, and email notifications, specifically, are integral to any product that involves any monitoring or tracking activities. &lt;a href="https://secutils.dev"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt; already includes, and will continue to expand, features that require the ability to send notifications. Two notable examples include sending notifications for changes detected by the web page resources trackers and changes detected in the tracked content security policies (CSP).&lt;/p&gt;

&lt;p&gt;Of course, I don't have infinite resources or time to architect different notification solutions for various use cases. Therefore, the notifications subsystem must be flexible and robust enough to cover all of today's needs and those that might arise in the near future. This includes seemingly unrelated use cases, such as new account activation emails, password reset emails, and even contact form messages. If you take a moment to think about it, you'll realize that these are essentially just different types of notifications.&lt;/p&gt;

&lt;p&gt;For now, a fairly simple &lt;a href="https://github.com/secutils-dev/secutils/blob/2f6c10bc5c47e0ef217fbd7874dd41ceda41ba8e/src/notifications/notification.rs"&gt;&lt;strong&gt;Notification definition&lt;/strong&gt;&lt;/a&gt; (shown below) covers all my immediate needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;/// Defines a notification.&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone,&lt;/span&gt; &lt;span class="nd"&gt;Eq,&lt;/span&gt; &lt;span class="nd"&gt;PartialEq)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Notification&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/// Unique id of the notification.&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NotificationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="cd"&gt;/// The destination of the notification (e.g. registered user, email or server log).&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NotificationDestination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="cd"&gt;/// The content of the notification (e.g. simple text or HTML email).&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NotificationContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="cd"&gt;/// The time at which the notification is scheduled to be sent, in UTC.&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;scheduled_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OffsetDateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;// Example #1: Resources tracker detected changes in the web page reso&lt;/span&gt;
&lt;span class="nn"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nn"&gt;NotificationDestination&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nn"&gt;NotificationContent&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Web page resources tracker {} ({}) detected changes in resources."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tracker&lt;/span&gt;&lt;span class="py"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tracker&lt;/span&gt;&lt;span class="py"&gt;.url&lt;/span&gt;
    &lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nn"&gt;OffsetDateTime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now_utc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;// Example #2: New account activation email&lt;/span&gt;
&lt;span class="nn"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nn"&gt;NotificationDestination&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"12@34.56"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="nn"&gt;NotificationContent&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;NotificationEmailContent&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Activate you Secutils.dev account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// Plain text fallback for simple email clients.    &lt;/span&gt;
        &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"To activate your Secutils.dev account…"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;// HTML email for advanced email clients.&lt;/span&gt;
        &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;r#"
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Activate your Secutils.dev account&amp;lt;/title&amp;gt;…&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Activate your Secutils.dev account&amp;lt;/h1&amp;gt;…&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;"#&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nn"&gt;OffsetDateTime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now_utc&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;To compose and send emails, I rely on an incredible open-source Rust library called &lt;a href="https://github.com/lettre/lettre"&gt;&lt;strong&gt;Lettre&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sending notifications over the network can be quite resource-intensive, and I don't want to let it affect the user experience or block the primary functionality of Secutils.dev. To deal with this, I've implemented a system where notifications are sent in a separate thread. Besides, Secutils.dev doesn't send notifications immediately. Instead, it &lt;a href="https://github.com/secutils-dev/secutils/blob/2f6c10bc5c47e0ef217fbd7874dd41ceda41ba8e/src/notifications/api_ext.rs#L36-L46"&gt;&lt;strong&gt;schedules them for batch delivery&lt;/strong&gt;&lt;/a&gt; at regular intervals. Scheduling notifications is very lightweight, essentially as cheap as inserting a single row into a SQLite database.&lt;/p&gt;

&lt;p&gt;As I previously covered in my &lt;a href="https://secutils.dev/docs/blog/scheduler-component"&gt;&lt;strong&gt;“Building a scheduler for a Rust application”&lt;/strong&gt;&lt;/a&gt; post, I rely on &lt;a href="https://github.com/mvniekerk/tokio-cron-scheduler"&gt;&lt;strong&gt;Tokio Cron Scheduler&lt;/strong&gt;&lt;/a&gt; for various routine background tasks, and &lt;a href="https://github.com/secutils-dev/secutils/blob/2f6c10bc5c47e0ef217fbd7874dd41ceda41ba8e/src/scheduler/scheduler_jobs/notifications_send_job.rs"&gt;&lt;strong&gt;one such task&lt;/strong&gt;&lt;/a&gt; runs every 30 seconds (although the default value, it's configurable). This task checks if there are any pending notifications ready for dispatch. Fortunately, the extra 30-second delay doesn't significantly impact my use cases, but it allows me to manage the load more effectively. This approach works equally well for both near-real-time notifications and those scheduled for future delivery.&lt;/p&gt;

&lt;p&gt;The notification sending job also tracks the time taken to send each batch of notifications, recording this data in the server log. This information is then captured by &lt;a href="https://secutils.dev/docs/blog/usage-analytics-and-monitoring#monitoring"&gt;&lt;strong&gt;my monitoring setup&lt;/strong&gt;&lt;/a&gt;. This monitoring approach helps me determine when it's necessary to fine-tune or scale up my notifications setup.&lt;/p&gt;

&lt;p&gt;Speaking of scalability, the separation between the code responsible for preparing notifications and the code handling their actual transmission gives me the flexibility to scale my notification system easily. I can achieve this by deploying one or more dedicated Secutils.dev server instances solely tasked with serving pending notifications. These dedicated instances may have entirely different hardware profiles optimized just for this purpose.&lt;/p&gt;

&lt;p&gt;Today, Secutils.dev exclusively supports two types of notification destinations: email and the server log. The rationale for requiring email notifications is self-evident. However, it's worth shedding light on the server log as a notification destination. Utilizing the server log as a notification destination is not only very handy for debugging purposes but also serves as an integration channel between Secutils.dev and the Elastic Stack monitoring system. All server logs are ingested into the Elasticsearch instance I've deployed for monitoring. I've crafted a dedicated dashboard and several visualizations within Kibana to specifically monitor server log notifications, leveraging the &lt;code&gt;[Notification]&lt;/code&gt; log record prefix. This allows me to track and visualize any imaginable custom application metric.&lt;/p&gt;

&lt;p&gt;As it's set up right now, the notifications system can be expanded to include more destinations like Slack, Telegram, browser push notifications, or any other third-party webhook with minimal tweaks. And speaking of the future, once Secutils.dev makes it out of beta, I'll be handling more sophisticated transactional or marketing emails, and planning to add &lt;a href="https://loops.so"&gt;&lt;strong&gt;Loops&lt;/strong&gt;&lt;/a&gt; as the notification destination.  Loops is a nice service positioned as "Email for modern SaaS". They also offer a pretty generous free plan, check it out!&lt;/p&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ASK:&lt;/strong&gt; If you found this post helpful or interesting, please consider showing your support by starring &lt;a href="https://github.com/secutils-dev/secutils"&gt;&lt;strong&gt;secutils-dev/secutils&lt;/strong&gt;&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for being a part of the community!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>opensource</category>
      <category>microsaas</category>
      <category>rust</category>
    </item>
    <item>
      <title>The best application security tool is education</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Tue, 10 Oct 2023 10:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/the-best-application-security-tool-is-education-2c8l</link>
      <guid>https://dev.to/azasypkin/the-best-application-security-tool-is-education-2c8l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The original post was published on my blog on &lt;strong&gt;August 29, 2023&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ NOTE:&lt;/strong&gt; Although not directly related to this topic, I encourage you to take a look at the latest &lt;a href="https://www.whitehouse.gov/wp-content/uploads/2023/07/NCWES-2023.07.31.pdf"&gt;&lt;strong&gt;US national cyber security workforce and education strategy from July 31, 2023&lt;/strong&gt;&lt;/a&gt;. The thumbnail picture for this post is taken from there. It's an interesting read!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As you might have guessed, I spend a lot of time thinking about application security - almost every day, in fact. At my day job, I'm constantly pondering how to enhance &lt;a href="https://github.com/elastic/kibana"&gt;&lt;strong&gt;Kibana's&lt;/strong&gt;&lt;/a&gt; security in a scalable manner without overburdening my already hardworking team. Outside of work, I'm equally dedicated to making &lt;a href="https://secutils.dev"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt; even more valuable to fellow engineers looking for better security tools.&lt;/p&gt;

&lt;p&gt;While I'd love to tell you there's a magic tool or a combination of tools that can make your application completely secure, I don't believe it's quite that simple - at least not yet. If you're working within tight budget constraints, resist the urge to spend it all on solutions like Veracode, Snyk, Secutils.dev, or any other security tool. Also, don't obsess over supply chain security and penetration testing just yet. Instead, focus your initial investment on something absolutely critical - educating your engineers about security. You'll reap the rewards, and so will your team. Only once you have a solid educational program or processes in place should you consider investing in additional security-oriented tools.&lt;/p&gt;

&lt;p&gt;I do appreciate various application security tools. Many of them are great and significantly simplify my life as a security engineer. However, none of these tools can prevent developers from inadvertently exposing sensitive information in logs, accidentally sending error stack traces in API responses, caching authorization results in memory, hardcoding S3 bucket names and URLs for third-party services, or neglecting to invalidate access tokens when they are no longer necessary.&lt;/p&gt;

&lt;p&gt;This &lt;strong&gt;isn't&lt;/strong&gt; a fault of the developers though. They might not even be aware that what they're doing could be exploited by a malicious actor with sufficient motivation. Engineers are a creative group, and they can unintentionally craft a vulnerable piece of code in a way that prevents detection by any code or security scanner. While it's clear to engineers that code should be performant (just think of all these technical interview questions and tasks related to code performance, complexity, and efficiency), it's unfortunately less evident that code should also be as secure, if not more so.&lt;/p&gt;

&lt;p&gt;Many of my colleagues and I have experimented with various approaches to enhance the security situation: conducting code security reviews, writing documentation outlining security best practices, advocating for more secure programmatic APIs, and incorporating tools like Snyk and CodeQL. These initiatives certainly help, but they don't always scale efficiently unless engineers begin to consider security with the same seriousness as they do performance and maintainability.&lt;/p&gt;

&lt;p&gt;Historically, security was often an afterthought for the majority of software engineers. This was primarily because security breaches and data leaks were not as frequent, dramatic, or devastating as they are today. As more aspects of our lives become digital, and this shift continues to accelerate, along with the slow but &lt;a href="https://www.whitehouse.gov/wp-content/uploads/2023/07/National-Cybersecurity-Strategy-Implementation-Plan-WH.gov_.pdf"&gt;&lt;strong&gt;steady evolution of government regulations&lt;/strong&gt;&lt;/a&gt;, I believe, we should change this perception through education. This education should extend to specific teams, organizations, and entire company, and the investment will undoubtedly pay off over time.&lt;/p&gt;

&lt;p&gt;If you really care about security, make software security trainings &lt;strong&gt;mandatory&lt;/strong&gt; and &lt;strong&gt;recurring&lt;/strong&gt;, just like other compulsory training sessions related to topics like equal treatment and work ethic, which are common today. All these training sessions are important and can literally impact people's lives. Also, actively ask for a feedback and continually update the training content. Admittedly, it's easier said than done, but...&lt;/p&gt;

&lt;p&gt;When you onboard a new engineer, consider sending them something more meaningful than just another useless branded mug – perhaps a book on application security or a prepaid voucher for a security training course. This gesture will send a clear message from day one that security is an important concern for your company, not merely a nice-to-have. And I &lt;strong&gt;beg&lt;/strong&gt; you, if you offer security training or host internal security-focused hackathons, avoid making them optional or scheduling them after working hours. Otherwise, they won't be taken as seriously – I know I wouldn't.&lt;/p&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ASK:&lt;/strong&gt; If you found this post helpful or interesting, please consider showing your support by starring &lt;a href="https://github.com/secutils-dev/secutils"&gt;&lt;strong&gt;secutils-dev/secutils&lt;/strong&gt;&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for being a part of the community!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>learning</category>
      <category>security</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Useful newsletters and podcasts for indie web developers</title>
      <dc:creator>Aleh Zasypkin</dc:creator>
      <pubDate>Thu, 05 Oct 2023 10:00:00 +0000</pubDate>
      <link>https://dev.to/azasypkin/useful-newsletters-and-podcasts-for-indie-web-developers-4fo4</link>
      <guid>https://dev.to/azasypkin/useful-newsletters-and-podcasts-for-indie-web-developers-4fo4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The original post was published on my blog on &lt;strong&gt;August 22, 2023&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello!&lt;/p&gt;

&lt;p&gt;I'm sharing a quick post today to highlight a few newsletters and podcasts that I, as an indie developer, find useful. Hopefully, they'll be of use to you too. This post is deliberately brief because I firmly believe that the most effective way to learn something new is to start doing it. Hands-on experience is and always was the master key to personal growth. There's no real shortcut, you can't absorb it all from reading a blog post or tuning into a podcast.&lt;/p&gt;

&lt;p&gt;However, that doesn't mean I've sworn off reading, watching, and listening to learn new things or get inspired. I do partake, but I keep it to a bare minimum. In general, I try hard to focus on "creating" over "consuming." Now, let's get to it:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://javascriptweekly.com/"&gt;&lt;strong&gt;JavaScript Weekly&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://nodeweekly.com/"&gt;&lt;strong&gt;Node Weekly&lt;/strong&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;These newsletters are among the best sources to stay up-to-date with the latest happenings in JavaScript, web development, and Node.js. They conveniently categorize content into sections like new releases, articles &amp;amp; tutorials, and code &amp;amp; tools. Since I use JavaScript/TypeScript and Node.js extensively, both in my &lt;a href="https://github.com/elastic/kibana"&gt;&lt;strong&gt;day job&lt;/strong&gt;&lt;/a&gt; and for &lt;a href="https://github.com/secutils-dev"&gt;&lt;strong&gt;Secutils.dev&lt;/strong&gt;&lt;/a&gt;, I have to stay informed about developments in these essential tools. Usually, I quickly scan through the newsletter and focus only on the items that grab my attention — it doesn't consume much time but keeps me well-informed.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://this-week-in-rust.org/"&gt;&lt;strong&gt;This Week in Rust&lt;/strong&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This newsletter is like JavaScript Weekly but for Rust! It provides updates from the Rust community, highlights updates from the vital Rust projects, shares new ideas and thoughts, offers guides, updates from the Rust core teams, and, sometimes, introduces brand new Rust crates. Rust is my passion and the primary programming language I use for the &lt;a href="https://github.com/secutils-dev/secutils"&gt;&lt;strong&gt;Secutils.dev Server&lt;/strong&gt;&lt;/a&gt;. Every time I read this newsletter, I discover something new and exciting about Rust!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://softwareleadweekly.com/"&gt;&lt;strong&gt;Software Lead Weekly&lt;/strong&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This is a curated and condensed newsletter that focuses on people, culture, and leadership in the tech industry. The content selected for this newsletter often resonates with me and even adds a touch of humor. Human beings are complex, and investing in understanding them better always yields significant returns. I hope you too can find valuable insights in this newsletter.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.indiehackers.com/newsletter"&gt;&lt;strong&gt;Indie Hackers newsletter&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://www.indiehackers.com/podcasts"&gt;&lt;strong&gt;podcast&lt;/strong&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;These resources are invaluable for anyone interested in early-stage entrepreneurship. They provide real-life experiences from people worldwide who have ventured to build something, whether successfully or not. This contrasts with the abundance of self-proclaimed experts, thought leaders, and book authors who often lack substantial hands-on experience. Personally, I find greater value in reading about the personal experiences of individuals from various backgrounds who attempted to create small products, even if they didn't succeed, than in consuming the "synthetic" thoughts of bestselling authors (👋 Simon Sinek &amp;amp; Friends).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.youtube.com/@ycombinator"&gt;&lt;strong&gt;Y Combinator YouTube channel&lt;/strong&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Whether you're in the process of building a tech product or just contemplating it, I highly recommend exploring this channel, even if you have no intention of applying to YC. The team offers a wealth of valuable insights, founder anecdotes, and startup guidance that can be challenging to come by elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: &lt;a href="https://theideafarm.com/"&gt;&lt;strong&gt;The idea farm newsletter&lt;/strong&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;While it's undeniable that "hard", "soft", and entrepreneurial skills are very important for those looking for independence and self-sufficiency, there are other universal skills that shouldn't be overlooked. As I mentioned in my &lt;a href="https://secutils.dev/docs/blog/project-finances"&gt;&lt;strong&gt;"Personal Finances and Indie-Project Budget"&lt;/strong&gt;&lt;/a&gt; post, I'm a strong advocate for financial literacy and maintaining healthy personal finances. I'm constantly in learning mode in this area, and seeking ways to preserve and invest the money I have.&lt;/p&gt;

&lt;p&gt;That's why I really like The Idea Farm newsletter, which provides free access to quite unique investment research and ideas. Many of these align neatly with my personal investment philosophy, making it quite an enjoyable read!&lt;/p&gt;

&lt;p&gt;That wraps up today's post, thanks for taking the time to read it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ ASK:&lt;/strong&gt; If you found this post helpful or interesting, please consider showing your support by starring &lt;a href="https://github.com/secutils-dev/secutils"&gt;&lt;strong&gt;secutils-dev/secutils&lt;/strong&gt;&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;Also, feel free to follow me on &lt;a href="https://twitter.com/aleh_zasypkin"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://infosec.exchange/@azasypkin"&gt;&lt;strong&gt;Mastodon&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://www.linkedin.com/in/azasypkin/"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for being a part of the community!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>podcast</category>
      <category>learning</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
