<?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: sachin k</title>
    <description>The latest articles on DEV Community by sachin k (@sachinpk).</description>
    <link>https://dev.to/sachinpk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4081767%2Fc3bd1fb5-792c-46d7-9bf3-0c74a2511f00.png</url>
      <title>DEV Community: sachin k</title>
      <link>https://dev.to/sachinpk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sachinpk"/>
    <language>en</language>
    <item>
      <title>Smashing a hidden I/O bug: 100 disk reads per run for data that never changed</title>
      <dc:creator>sachin k</dc:creator>
      <pubDate>Mon, 17 Aug 2026 16:48:54 +0000</pubDate>
      <link>https://dev.to/sachinpk/smashing-a-hidden-io-bug-100-disk-reads-per-run-for-data-that-never-changed-2mb0</link>
      <guid>https://dev.to/sachinpk/smashing-a-hidden-io-bug-100-disk-reads-per-run-for-data-that-never-changed-2mb0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Infrasity-Labs/awesome-tech-newsletter" rel="noopener noreferrer"&gt;awesome-tech-newsletter&lt;/a&gt; is an open-source project by Infrasity Labs that auto-discovers tech newsletters across the web. It runs a fleet of Python fetchers — for Substack, Hacker News, Medium, Product Hunt, Beehiiv, Hashnode, and more — that crawl each platform, classify what they find against a shared keyword config, and aggregate everything into a curated, categorized directory in the README.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;I picked this off the project's open issue tracker: &lt;a href="https://github.com/Infrasity-Labs/awesome-tech-newsletter/issues/41" rel="noopener noreferrer"&gt;issue #41&lt;/a&gt;, a performance bug in the Product Hunt fetcher.&lt;/p&gt;

&lt;p&gt;The fetcher pulls 100 recent posts from the Product Hunt GraphQL API, then loops over them to classify each post against a list of keyword queries. The problem was this line, sitting &lt;strong&gt;inside&lt;/strong&gt; the per-post loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post_edge&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...build text_corpus...
&lt;/span&gt;    &lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_search_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;append_newsletter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;-- every iteration
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks like a harmless list rebuild — until you read what &lt;code&gt;get_search_queries()&lt;/code&gt; actually does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_search_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;append_newsletter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;config_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# disk read
&lt;/span&gt;        &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                          &lt;span class="c1"&gt;# JSON parse
&lt;/span&gt;    &lt;span class="c1"&gt;# ...build query list...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It &lt;strong&gt;opens and JSON-parses &lt;code&gt;config.json&lt;/code&gt; from disk on every single call&lt;/strong&gt;. So every fetch run was doing 100 file opens and 100 JSON parses for data that cannot change mid-loop.&lt;/p&gt;

&lt;p&gt;The smoking gun that this was a bug and not a design choice: every other fetcher in the repo (&lt;code&gt;hackernews.py&lt;/code&gt;, &lt;code&gt;substack.py&lt;/code&gt;, &lt;code&gt;medium.py&lt;/code&gt;, and six more) calls &lt;code&gt;get_search_queries()&lt;/code&gt; exactly once, outside its loop. Only &lt;code&gt;producthunt.py&lt;/code&gt; did it per-post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured impact&lt;/strong&gt; (benchmarked against the repo's real &lt;code&gt;config.json&lt;/code&gt; — 9 categories, 86 generated queries — with 100 simulated posts):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric (per fetch run)&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;config.json&lt;/code&gt; file opens&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON parses&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Classification loop time&lt;/td&gt;
&lt;td&gt;5.14 ms&lt;/td&gt;
&lt;td&gt;0.14 ms (~36× faster)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Honest framing: this is a scheduled crawler, so no user was staring at a spinner. But the cost scales with post count × config size, and redundant loop-invariant I/O is exactly the kind of silent tax that compounds as a project grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;PR: &lt;a href="https://github.com/Infrasity-Labs/awesome-tech-newsletter/pull/43" rel="noopener noreferrer"&gt;https://github.com/Infrasity-Labs/awesome-tech-newsletter/pull/43&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;             posts = data.get('data', {}).get('posts', {}).get('edges', [])
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+            # Load search queries once: get_search_queries() reads and parses
+            # config.json from disk on every call, and its result never changes
+            # within a fetch run, so calling it per-post did 100 redundant
+            # file reads + JSON parses.
+            queries = get_search_queries(append_newsletter=False)
+            seen_urls = set()
+
&lt;/span&gt;             for post_edge in posts:
                 node = post_edge.get('node', {})
&lt;span class="p"&gt;@@
-                queries = get_search_queries(append_newsletter=False)
-                for query, cat in queries:
-                    if query in text_corpus:
+                for keyword, cat in queries:
+                    if keyword in text_corpus:
                         is_tech = True
                         category = cat
                         break
@@&lt;/span&gt;
&lt;span class="gd"&gt;-                    if not any(d['url'] == target_url for d in discovered):
&lt;/span&gt;&lt;span class="gi"&gt;+                    if target_url not in seen_urls:
+                        seen_urls.add(target_url)
&lt;/span&gt;                         logger.info("Discovered Product Hunt: %s", target_url)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;Three changes, in decreasing order of importance:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Hoisted the loop-invariant call.&lt;/strong&gt; &lt;code&gt;get_search_queries()&lt;/code&gt; now runs once per fetch, right after the API response is parsed. This turns 100 disk reads + JSON parses into 1. I deliberately did &lt;em&gt;not&lt;/em&gt; add caching inside &lt;code&gt;get_search_queries()&lt;/code&gt; itself — that would change behavior for every fetcher and risk serving stale config in long-lived processes. Fixing it at the single bad call site keeps the change surgical and matches the pattern the other nine fetchers already use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fixed a variable-shadowing landmine.&lt;/strong&gt; The inner loop used &lt;code&gt;for query, cat in queries:&lt;/code&gt; — but &lt;code&gt;query&lt;/code&gt; was already the name of the GraphQL query string defined earlier in the same function. It happened to be harmless today because the GraphQL string isn't reused after the request, but it's the kind of shadowing that turns a future "add retry logic" PR into a mystery bug. Renamed the loop variable to &lt;code&gt;keyword&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Replaced an O(n²) dedup with a set.&lt;/strong&gt; The duplicate-URL check was &lt;code&gt;any(d['url'] == target_url for d in discovered)&lt;/code&gt; — a full list scan for every discovered post. A &lt;code&gt;seen_urls&lt;/code&gt; set makes it O(1) per lookup. Minor at 100 posts, but free to fix while I was in the function.&lt;/p&gt;

&lt;p&gt;Verification: the module compiles clean (&lt;code&gt;python3 -m py_compile&lt;/code&gt;), imports, and runs correctly through its no-token code path. The benchmark hooked &lt;code&gt;builtins.open&lt;/code&gt; to count file accesses, confirming the 100 → 1 drop, and used &lt;code&gt;timeit&lt;/code&gt; over 20 repeats for the timing numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transparency note:&lt;/strong&gt; I found, benchmarked, and fixed this bug working alongside Claude (Anthropic's AI assistant) — it ran the code analysis and benchmarking in a sandboxed environment while I directed the hunt, reviewed the changes, and submitted the PR.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>Cybersecurity from Zero to Hero #1: The CIA Triad, or Why Your Bank App Keeps Logging You Out</title>
      <dc:creator>sachin k</dc:creator>
      <pubDate>Mon, 17 Aug 2026 14:14:50 +0000</pubDate>
      <link>https://dev.to/sachinpk/cybersecurity-from-zero-to-hero-1-the-cia-triad-or-why-your-bank-app-keeps-logging-you-out-225j</link>
      <guid>https://dev.to/sachinpk/cybersecurity-from-zero-to-hero-1-the-cia-triad-or-why-your-bank-app-keeps-logging-you-out-225j</guid>
      <description>&lt;p&gt;Welcome to post one of my new series, Cybersecurity from Zero to Hero. I am learning security from absolute scratch and writing about it as I go, so you get the explanations while the confusion is still fresh in my head. The plan is simple: concepts first, then networking, then Linux and web security, then real tools and practice platforms. No prior knowledge needed. If you are experienced, stick around anyway, because the comment sections are where you get to correct me and teach everyone else.&lt;/p&gt;

&lt;p&gt;One rule before we start, and it applies to this entire series. Every hands on exercise happens only on systems I own or on platforms built for legal practice like TryHackMe and OWASP Juice Shop. Learning security responsibly is part of learning security.&lt;/p&gt;

&lt;p&gt;Today we lay the foundation stone of all security thinking.&lt;/p&gt;

&lt;p&gt;The one framework everything else builds on&lt;/p&gt;

&lt;p&gt;Ask any security professional where to start and you will hear three letters: CIA. Not the agency. It stands for Confidentiality, Integrity and Availability, and it is the lens through which every security decision is made. Firewalls, passwords, backups, encryption, every tool you will ever meet in this field exists to protect one or more of these three properties.&lt;/p&gt;

&lt;p&gt;Here is what surprised me on day one of studying this. Security is not about stopping hackers. It is about protecting these three promises, and hackers are only one of many things that threaten them. A flood in a data center is a security event. So is an intern deleting the wrong folder.&lt;/p&gt;

&lt;p&gt;🔒 &lt;strong&gt;Confidentiality: only the right people can see it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Confidentiality means information is visible only to those authorized to see it. Your medical records, your salary, your private messages.&lt;/p&gt;

&lt;p&gt;Real world example. When your banking app logs you out after five minutes of inactivity, that is confidentiality at work. The bank assumes you might have walked away from your phone in a coffee shop. The aggressive timeout is not bad design. It is a deliberate tradeoff that sacrifices your convenience to protect your account from the stranger at the next table.&lt;/p&gt;

&lt;p&gt;Tools that protect it: encryption, passwords, multi factor authentication, access controls, and even the privacy screen on a laptop.&lt;/p&gt;

&lt;p&gt;The classic violation: a data breach where customer records leak to the public.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Integrity: the data has not been tampered with&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Integrity means information stays accurate and unaltered except by authorized changes. It is the promise that what you read is what was written.&lt;/p&gt;

&lt;p&gt;Real world example. When you transfer 100 dollars, integrity is the guarantee that it does not become 1000 dollars in transit, and that the recipient account number is not silently swapped. Banks obsess over this. An attacker who can change data is often more dangerous than one who can only read it.&lt;/p&gt;

&lt;p&gt;I also ran my first integrity check today, and you can too. When you download software, sites often publish a checksum next to the file. On Mac or Linux:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bash&lt;br&gt;
shasum -a 256 downloaded_file.zip&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
On Windows PowerShell:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;powershell&lt;br&gt;
Get-FileHash downloaded_file.zip&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Compare the output to the published value. If even one character differs, the file was corrupted or tampered with somewhere between their server and your machine. That comparison is integrity verification in its purest form, and later in this series we will build our own hashing tool in Python.&lt;/p&gt;

&lt;p&gt;Tools that protect it: hashing, checksums, digital signatures, version control, audit logs.&lt;/p&gt;

&lt;p&gt;The classic violation: an attacker altering a student database to change grades.&lt;/p&gt;

&lt;p&gt;⚡ &lt;strong&gt;Availability: the system works when you need it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Availability means authorized users can access the data and systems when they need them. Security that makes a system unusable has failed at its job.&lt;/p&gt;

&lt;p&gt;Real world example. When a Distributed Denial of Service attack floods a website with junk traffic until it collapses, nothing was stolen and nothing was altered. Confidentiality and integrity are intact. But the service is down, so it is absolutely a security incident. Backups, failover systems and disaster recovery plans all exist to protect this leg of the triangle.&lt;/p&gt;

&lt;p&gt;Tools that protect it: redundancy, backups, load balancing, DDoS protection, disaster recovery plans.&lt;/p&gt;

&lt;p&gt;The classic violation: ransomware locking a hospital out of its own patient records.&lt;/p&gt;

&lt;p&gt;🔺 The part that made it click for me: the triangle is a tension&lt;/p&gt;

&lt;p&gt;Here is the insight most beginner summaries skip. The three properties pull against each other, and real security work is about balancing them, not maximizing all three.&lt;/p&gt;

&lt;p&gt;Make a system maximally confidential with twelve authentication steps and you have destroyed availability. Nobody can get in, including the people who should. Make it maximally available with no logins at all and you have destroyed confidentiality. That annoying bank timeout is not a bug or lazy engineering. It is a chosen point on the triangle. Once I saw it this way, a dozen daily annoyances suddenly made sense as deliberate design decisions.&lt;/p&gt;

&lt;p&gt;A question for the experienced folks. When you review a system, do you consciously walk the triad, or has it become instinct? And what is your favorite example of a control that helps one leg while hurting another? Drop it in the comments and I will feature the best answers in a future post.&lt;/p&gt;

&lt;p&gt;🤔 What confused me today&lt;/p&gt;

&lt;p&gt;Where does authenticity fit? Some sources add authenticity and non repudiation as extra properties, and some textbooks talk about a hexad instead of a triad. For now I am parking that question. The triad is the standard mental model and the extensions can wait until the fundamentals are solid.&lt;/p&gt;

&lt;p&gt;Is a power outage really a security issue? My instinct said no, that is an IT problem. But under the triad, anything that threatens availability is in scope for security. The field is wider than the hacker movies suggest.&lt;/p&gt;

&lt;p&gt;⏭️ Next in this series&lt;/p&gt;

&lt;p&gt;Post 2 covers threats, vulnerabilities and risk, three words people use interchangeably that mean very different things. Once you can tell them apart, every security article you read becomes twice as clear. Follow the series so you do not miss it.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>security</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
