<?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: siyadhkc</title>
    <description>The latest articles on DEV Community by siyadhkc (@siyadhkc).</description>
    <link>https://dev.to/siyadhkc</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%2F3868439%2F5c0b2e2e-0a7c-406f-9d99-1d810624972c.png</url>
      <title>DEV Community: siyadhkc</title>
      <link>https://dev.to/siyadhkc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/siyadhkc"/>
    <language>en</language>
    <item>
      <title>I Built My Own AI Research Assistant for Cybersecurity — Here's the Honest Breakdown</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Wed, 17 Jun 2026 15:16:16 +0000</pubDate>
      <link>https://dev.to/siyadhkc/no-api-keys-no-cloud-bills-no-data-leaving-my-machine-heres-exactly-how-and-what-it-actually-4p1i</link>
      <guid>https://dev.to/siyadhkc/no-api-keys-no-cloud-bills-no-data-leaving-my-machine-heres-exactly-how-and-what-it-actually-4p1i</guid>
      <description>&lt;p&gt;Most people use AI the same way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open ChatGPT, Claude, or Gemini.&lt;/li&gt;
&lt;li&gt;Ask a question.&lt;/li&gt;
&lt;li&gt;Get an answer.&lt;/li&gt;
&lt;li&gt;Close the tab.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I did that too—until I started spending more time on cybersecurity research, vulnerability analysis, and application security. At some point, it felt strange researching privacy and security while sending chunks of my work to infrastructure I didn't control.&lt;/p&gt;

&lt;p&gt;So I decided to build my own. &lt;br&gt;
...&lt;br&gt;
No API keys. No subscriptions. No cloud processing. Just a &lt;strong&gt;Ryzen 7 7435HS, 16GB RAM, an RTX 3050&lt;/strong&gt;, and a growing curiosity about what actually happens behind the chatbot interface.&lt;/p&gt;

&lt;p&gt;The goal was simple: create a local AI research assistant that could search my notes, help with security research, and keep everything on my machine. What I didn't expect was that building it would teach me more about AI, LLMs, RAG, agents, and AI security than years of simply using them ever could.&lt;/p&gt;

&lt;p&gt;Here's what I built and what I learned.&lt;/p&gt;


&lt;h2&gt;
  
  
  First: What "Building Your Own AI" Actually Means
&lt;/h2&gt;

&lt;p&gt;Let’s kill the ambiguity before we go any further. There are three distinct tiers to this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Level 1 — Running an existing model locally:&lt;/strong&gt; You download a pre-trained model and run inference on your own hardware. You didn't create the intelligence; you stopped renting it. This is what most developers mean when they say &lt;em&gt;"I built my own AI."&lt;/em&gt; It's also what I did. No shame in it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 2 — Customizing a foundation model:&lt;/strong&gt; You take a pre-trained model and augment it with your own data — notes, research, codebases, documents. This is RAG (Retrieval-Augmented Generation) or fine-tuning territory. Most startups building "AI assistants" operate here. They did not build a model; they adapted one. There's a difference, and the marketing conveniently forgets it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 3 — Training from scratch:&lt;/strong&gt; OpenAI, Anthropic, Google, Meta. Thousands of GPUs, millions of dollars, specialized research teams. Not realistic for individuals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm at &lt;strong&gt;Level 1 moving toward Level 2&lt;/strong&gt;. That's the practical sweet spot for anyone doing real work without a datacenter.&lt;/p&gt;


&lt;h2&gt;
  
  
  How LLMs Actually Work (The Part Nobody Explains Clearly)
&lt;/h2&gt;

&lt;p&gt;LLMs are prediction systems. &lt;strong&gt;They don't think.&lt;/strong&gt; They predict the statistically most likely next token given everything in their context window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input:  "The capital of France is"
Output: "Paris"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do that billions of times across a massive training corpus, and complex, reasoning-like behavior emerges. That's genuinely it. No magic. No ghost in the machine. Just very expensive statistics that happen to be incredibly useful.&lt;/p&gt;

&lt;p&gt;Understanding this matters for security work specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt Injection:&lt;/strong&gt; The model cannot fundamentally distinguish between your system instructions and an attacker's instructions injected through retrieved content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hallucinations:&lt;/strong&gt; The model is predicting probable text, not retrieving ground truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification:&lt;/strong&gt; You should never blindly trust model output on anything critical without verification.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;The core paradox of LLMs:&lt;/strong&gt; The model is confident. The model is sometimes wrong. These two facts coexist comfortably and will continue to cause problems for everyone who forgets them.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Local? The OPSEC Argument
&lt;/h2&gt;

&lt;p&gt;For general-purpose use, cloud AI is fine. For security research, the calculus is completely different. When you use a cloud model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your prompts leave your machine.&lt;/li&gt;
&lt;li&gt;Sensitive context — CVEs you're analyzing, vulnerability writeups, client infrastructure details — passes through third-party infrastructure.&lt;/li&gt;
&lt;li&gt;You have zero visibility into what's logged, retained, or used for training.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're doing bug bounty, AppSec auditing, or anything involving non-public vulnerability data, feeding that into a cloud API is an &lt;strong&gt;OPSEC problem&lt;/strong&gt;. Full stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local solves this.&lt;/strong&gt; Your data stays on your hardware. No terms of service to audit. No compliance risk from third-party data processing. No API costs. It works completely offline, allowing unlimited experimentation without watching a token counter.&lt;/p&gt;

&lt;p&gt;The tradeoff? Setup takes a weekend and your hardware has strict limits. Still worth it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tool: Ollama
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; is the easiest on-ramp to local AI. Think of it as &lt;strong&gt;Docker for language models&lt;/strong&gt; — it handles downloads, quantization, GPU acceleration, and exposes a clean REST API at &lt;code&gt;http://localhost:11434&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation &amp;amp; Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Mac / Linux installation&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://ollama.sh | sh

&lt;span class="c"&gt;# Pull and run a model&lt;/span&gt;
ollama run qwen3:8b

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The model downloads, loads into memory, and the API goes live.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's happening under the hood:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Download Model Weights ──&amp;gt; Load Into RAM/VRAM ──&amp;gt; Tokenize Input ──&amp;gt; Transformer Inference ──&amp;gt; Local REST API

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama is a model manager, a runtime, and an API server all in one. The intelligence is in the model weights; Ollama is just the plumbing that makes those weights usable without a PhD in infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handy Ollama CLI Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama list         &lt;span class="c"&gt;# See installed models&lt;/span&gt;
ollama pull qwen3:8b &lt;span class="c"&gt;# Download a specific model&lt;/span&gt;
ollama &lt;span class="nb"&gt;rm &lt;/span&gt;llama3    &lt;span class="c"&gt;# Remove an unwanted model&lt;/span&gt;
ollama ps           &lt;span class="c"&gt;# See what models are currently loaded in memory&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Picking the Right Model for Your Hardware
&lt;/h2&gt;

&lt;p&gt;Not all models fit on all machines. Here is an honest breakdown of the hardware requirements:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RAM / VRAM&lt;/th&gt;
&lt;th&gt;Recommended Model&lt;/th&gt;
&lt;th&gt;Experience Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;8GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gemma 4 4B or Phi-4 Mini&lt;/td&gt;
&lt;td&gt;Fits cleanly, decent quality, highly efficient&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;16GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Qwen3 8B or DeepSeek R1 Distilled 8B&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;The Sweet Spot.&lt;/strong&gt; Fast and highly capable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;32GB+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DeepSeek R1 14B–32B&lt;/td&gt;
&lt;td&gt;High-level technical reasoning, heavily data-intensive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;My daily driver:&lt;/em&gt; &lt;strong&gt;Qwen3 8B&lt;/strong&gt;. It provides strong technical reasoning, handles code exceptionally well, is Apache 2.0 licensed, and runs cleanly on my laptop without fighting for VRAM.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 2026 Open Model Landscape
&lt;/h2&gt;

&lt;p&gt;The open-model ecosystem moves fast. Here's where things actually stand right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Qwen3 (Alibaba):&lt;/strong&gt; Currently one of the strongest all-around open families. Apache 2.0, excellent multilingual, and top-tier technical performance. The 4B and 8B variants run wonderfully on consumer hardware. This is my default recommendation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek V4 &amp;amp; R1:&lt;/strong&gt; DeepSeek changed the industry when R1 dropped. MIT licensed, it matches frontier models on math and code benchmarks. The distilled 8B and 14B variants bring heavy-duty reasoning right to consumer hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kimi K2.5:&lt;/strong&gt; Excellent tool usage and designed specifically for agentic workflows. Worth watching if you're building automated agents rather than just doing Q&amp;amp;A.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GLM-5.1:&lt;/strong&gt; Focused on advanced reasoning and agentic tasks. Particularly impressive for structured technical work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Llama 4 Scout (Meta):&lt;/strong&gt; The first Mixture of Experts (MoE) architecture in the Llama family, sporting a massive 10-million token context window. However, it requires ~55GB of VRAM, making it impractical for standard laptops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 4 (Google):&lt;/strong&gt; Highly efficient and accessible. This is your best option if you need capable reasoning on strictly constrained hardware resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;The honest takeaway on hardware:&lt;/strong&gt; 8GB of VRAM was borderline a couple of years ago. It's cramped now. &lt;strong&gt;12GB is the modern floor&lt;/strong&gt; for serious local work, while &lt;strong&gt;16GB gives you room to actually experiment.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Problem With Raw Models: They Don't Know Your Stuff
&lt;/h2&gt;

&lt;p&gt;A stock model only knows what it was trained on. It doesn't know your security notes, your project internals, your custom vulnerability writeups, or yesterday's newly disclosed CVEs.&lt;/p&gt;

&lt;p&gt;That gap is the main limitation of Level 1. &lt;strong&gt;The fix is RAG.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  RAG: Teaching Your AI Your Own Knowledge
&lt;/h3&gt;

&lt;p&gt;RAG stands for &lt;strong&gt;Retrieval-Augmented Generation&lt;/strong&gt;. The concept is simpler than the name suggests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Asks Question 
       │
       ▼
Search Vector Database (ChromaDB)
       │
       ▼
Retrieve Relevant Document Chunks
       │
       ▼
Inject Context into System Prompt (Question + Source Chunks)
       │
       ▼
Local LLM Generates Grounded Answer

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Practical Local RAG Implementation in Python
&lt;/h3&gt;

&lt;p&gt;Here is how you can spin up a local RAG pipeline using LangChain and Ollama.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;chromadb langchain langchain-community langchain-ollama

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OllamaEmbeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ChatOllama&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_community.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_text_splitters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.chains&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_retrieval_chain&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.chains.combine_documents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_stuff_documents_chain&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Your proprietary knowledge base 
&lt;/span&gt;&lt;span class="n"&gt;your_docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BOLA (Broken Object Level Authorization) occurs when an API doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t verify the requesting user has permission to access the specific object. Most common API vulnerability in 2026.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JWT tokens must be verified server-side. Common mistakes: not checking the signature algorithm, skipping expiry validation, or accepting &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; as a valid algorithm.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Django DEBUG=True in production exposes detailed stack traces, environment variables, and raw database queries to anyone who triggers an internal server error.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Split text into digestible chunks
&lt;/span&gt;&lt;span class="n"&gt;text_splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text_splitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;your_docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Initialize local embeddings and store in ChromaDB
&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OllamaEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qwen3:8b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;vector_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vector_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_retriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# 4. Connect to local LLM
&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOllama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qwen3:8b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&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="c1"&gt;# 5. Define the RAG prompt system
&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a security research assistant. Use the following pieces of retrieved context &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to answer the question. If you don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t know the answer, say that you don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t know.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{context}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_messages&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{input}&lt;/span&gt;&lt;span class="sh"&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;# 6. Create and execute the RAG chain
&lt;/span&gt;&lt;span class="n"&gt;question_answer_chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_stuff_documents_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rag_chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_retrieval_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question_answer_chain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rag_chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is BOLA and why is it dangerous?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point this script toward your local Markdown folders, OWASP PDFs, PortSwigger writeups, or disclosed HackerOne reports, and you instantly have a local research assistant that knows your actual data.&lt;/p&gt;




&lt;h2&gt;
  
  
  RAG vs. Fine-Tuning: The Classic Misconception
&lt;/h2&gt;

&lt;p&gt;Beginners almost always assume they need to fine-tune a model to teach it new information. Usually, they are wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────────────────┬───────────────────────────────────────┐
│               USE RAG WHEN            │            USE FINE-TUNING WHEN       │
├───────────────────────────────────────┼───────────────────────────────────────┤
│ • Knowledge changes frequently        │ • You need style/tone behavioral shifts│
│   (new CVEs, fresh writeups)          │ • You need strict output formatting   │
│ • You need explicit source citations  │ • You want deep task specialization   │
│ • You want fast, zero-cost iteration  │ • You have a vast, clean dataset      │
└───────────────────────────────────────┴───────────────────────────────────────┘

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The play:&lt;/strong&gt; Always start with RAG. Fine-tune only if RAG fails to meet your structural formatting needs after extensive testing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Next Evolution: MCP (Model Context Protocol)
&lt;/h2&gt;

&lt;p&gt;RAG gives a model &lt;em&gt;knowledge&lt;/em&gt;. &lt;strong&gt;MCP gives a model &lt;em&gt;tools&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Model Context Protocol allows local LLMs to safely step outside their sandbox and interact natively with systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              ┌──&amp;gt; GitHub Repository
              ├──&amp;gt; Live CVE Databases
User ──&amp;gt; Agent ──&amp;gt; Tools ──&amp;gt; ├──&amp;gt; Burp Suite Reports
              ├──&amp;gt; Local Filesystem
              └──&amp;gt; System Logs

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A chatbot answers questions; an agent completes tasks. Imagine an automated workflow: &lt;em&gt;Find latest Django CVEs&lt;/em&gt; ──&amp;gt; &lt;em&gt;read advisories&lt;/em&gt; ──&amp;gt; &lt;em&gt;compare against requirements.txt&lt;/em&gt; ──&amp;gt; &lt;em&gt;generate a remediation report&lt;/em&gt; ──&amp;gt; &lt;em&gt;open a local GitHub issue.&lt;/em&gt; That's the power of tool integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Security Risks Inside Your Own Local Tools
&lt;/h2&gt;

&lt;p&gt;Running AI locally protects your data from leaving your machine, but &lt;strong&gt;it shifts the application attack surface.&lt;/strong&gt; Prompt injection isn't theoretical—it's cataloged under real CVEs (e.g., CVE-2025-53773 in GitHub Copilot allowing remote code execution).&lt;/p&gt;

&lt;p&gt;When building local RAG and agent architectures, you must defend against:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Indirect Prompt Injection:&lt;/strong&gt; If your RAG system ingests external data (scraped web pages, untrusted security feeds, public PDFs), an attacker can embed malicious instructions inside those sources. When the data is retrieved, it hijacks your model's execution flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Poisoning:&lt;/strong&gt; Malicious or inaccurate documents dropped into your local directory will poison your vector database embeddings, leading to engineered hallucinations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System Prompt Leakage:&lt;/strong&gt; If your application logic depends on sensitive instructions or hardcoded parameters in the backend prompt configuration, assume they can be extracted by a clever prompt payload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blind Trust in Output:&lt;/strong&gt; The model will give completely inaccurate code analysis or flawed exploit interpretations with absolute confidence. Always cross-reference AI-generated analysis with canonical sources like the NVD (National Vulnerability Database).&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;🛡️ &lt;strong&gt;The Defense:&lt;/strong&gt; Apply &lt;strong&gt;least-privilege principles&lt;/strong&gt; to your agent's tools, sandbox execution environments, sanitize inputs, and treat every retrieved document chunk as potentially untrusted adversarial input.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Honest Downsides of Going Local
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Hardware Ceiling:&lt;/strong&gt; 16GB of RAM gets you to an 8B parameter model cleanly. DeepSeek R1 reasoning models at 14B or 32B require 24GB+ VRAM. If you want to run heavy models, you'll eventually need to expand your hardware or securely lease a decentralized bare-metal GPU (like RunPod or Vast.ai).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Capability Gap:&lt;/strong&gt; While the open-source gap closed drastically, frontier cloud models still hold an edge in multi-step complex synthesis across thousands of asynchronous documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering Overhead:&lt;/strong&gt; Building robust, stateful RAG pipelines with proper chunking, accurate embeddings, and tool orchestration takes real development time. Budget a weekend for setup, not an hour.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Matters Beyond Just Privacy
&lt;/h2&gt;

&lt;p&gt;Building a local AI system isn't about outperforming a trillion-dollar tech giant on a standard benchmark.&lt;/p&gt;

&lt;p&gt;You cannot thoroughly audit AI-integrated applications if you treat the model as a black box. You cannot effectively reason about prompt injection vectors in an enterprise system if you have never engineered a document pipeline from scratch.&lt;/p&gt;

&lt;p&gt;A few years ago, understanding the TCP/IP stack separated master engineers from beginners. Today, understanding LLM inference, embedding vectors, context windows, and tool integration protocols is becoming the new dividing line.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To paraphrase Richard Feynman: &lt;em&gt;There is a profound difference between knowing the name of something and knowing the thing itself.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Build the thing. Then break it. Then secure it.&lt;/strong&gt; That's where the real learning starts.&lt;/p&gt;




&lt;h3&gt;
  
  
  Let's Connect!
&lt;/h3&gt;

&lt;p&gt;I write about API security, backend systems, and building tools from scratch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💬 Drop your thoughts or local setups in the comments below!&lt;/li&gt;
&lt;li&gt;🛠️ What models are you running on your local rig?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>appsec</category>
      <category>python</category>
      <category>ollama</category>
    </item>
    <item>
      <title>AI Just Got Scarier and More Powerful — And Nobody Is Talking About the Right Things</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Fri, 12 Jun 2026 10:07:21 +0000</pubDate>
      <link>https://dev.to/siyadhkc/ai-just-got-scarier-and-more-powerful-and-nobody-is-talking-about-the-right-things-3f6</link>
      <guid>https://dev.to/siyadhkc/ai-just-got-scarier-and-more-powerful-and-nobody-is-talking-about-the-right-things-3f6</guid>
      <description>&lt;p&gt;&lt;em&gt;A honest breakdown of where AI actually is in June 2026 — from purely my perspective.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Let me be upfront before you read a single word further.&lt;/p&gt;

&lt;p&gt;I am not an AI promoter.&lt;/p&gt;

&lt;p&gt;I read Books lot of books . Before I wrote a line of code, I spent above one  years physically wiring network infrastructure — routers, switches, CCTV systems, cable runs in hot server rooms. That background makes me instinctively suspicious of anything that promises speed without understanding. And right now, in June 2026, the AI industry is running at full promotional speed while a lot of developers are quietly getting hurt by believing it.&lt;/p&gt;

&lt;p&gt;So here's my actual take. Not a review. Not a tutorial. An honest read on what's happening, what's real, what's dangerous, and what you should actually do.&lt;/p&gt;




&lt;h2&gt;
  
  
  First: What Just Dropped Yesterday
&lt;/h2&gt;

&lt;p&gt;I'm writing this on June 11th, 2026. Recently, Anthropic released Claude Fable 5 — and this one is different enough to talk about seriously.&lt;/p&gt;

&lt;p&gt;Fable 5 is a Mythos-class model. Mythos is Anthropic's highest tier — previously restricted to a small group of security and infrastructure companies under something called Project Glasswing, because the cybersecurity capabilities were considered too powerful for general release. Yesterday they made a safeguarded version of that same class of model publicly available.&lt;/p&gt;

&lt;p&gt;What does Mythos-class mean in practice? In their internal tests, Fable 5 migrated a Ruby codebase with 50 million lines of code in a single day. The same job would have taken an entire engineering team more than two months.&lt;/p&gt;

&lt;p&gt;Let that sit for a moment.&lt;/p&gt;

&lt;p&gt;The model scores more than 10% higher than Claude Opus 4.8 on key benchmarks — and Opus 4.8 was itself released just two weeks ago. The pace of capability improvement is no longer measured in quarters. It's measured in weeks.&lt;/p&gt;

&lt;p&gt;Fable 5 is available now on the API at $10 per million input tokens and $50 per million output tokens. For Pro and paid subscribers, it's free through June 22nd — after that, usage credits will be required. The context window is 1 million tokens with 128,000 maximum output tokens.&lt;/p&gt;

&lt;p&gt;There's also Claude Mythos 5 — the same base model but with some safety classifiers lifted, available only to approved organizations through Project Glasswing. Not for general access.&lt;/p&gt;

&lt;p&gt;The cybersecurity angle matters to me specifically: Fable 5 has safeguards that route high-risk cybersecurity queries to the older Opus 4.8 instead. Anthropic explicitly built in a fallback for misuse. After 1,000+ hours of red teaming, no universal jailbreaks were found. That's not a trivial engineering achievement — and it tells you something about how seriously they're taking the dual-use problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Else Is in the Field Right Now
&lt;/h2&gt;

&lt;p&gt;The model landscape as of June 2026 — no hype, just the state of play:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT-5.5&lt;/strong&gt;&amp;nbsp;(OpenAI, April 2026) — rebuilt architecture from scratch, leads on agentic workflows and multimodal tasks. 1 million token context window. The ecosystem is still the largest of any model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini 3.1 Pro&lt;/strong&gt;&amp;nbsp;(Google) — strongest benchmark on GPQA Diamond reasoning at 94.3%, integrates natively with Google Workspace. If you live in Google's stack, this is frictionless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grok 4&lt;/strong&gt;&amp;nbsp;(xAI) — leads raw SWE-bench coding scores. Best features locked behind the $300/month SuperGrok tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DeepSeek V3.2&lt;/strong&gt;&amp;nbsp;— delivers roughly 90% of the top proprietary model quality at 1/50th the price. The open-source gap has effectively closed for most real-world tasks.&lt;/p&gt;

&lt;p&gt;The honest conclusion: there is no single best model. Anyone still arguing that debate is fighting 2024. The right model depends on your specific task, your stack, and what you can afford. Use Claude for long-context coding and agentic work. Use Gemini if you're embedded in Google Workspace. Use DeepSeek if cost is the constraint and quality is close enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Statement That Broke the Developer Internet
&lt;/h2&gt;

&lt;p&gt;At Davos in January 2026, Dario Amodei — CEO of Anthropic, the man who built Claude — said this to The Economist:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"We might be 6–12 months away from models doing all of what software engineers do end-to-end."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;He backed it with a specific example: engineers inside Anthropic who told him they no longer write any code from scratch.&amp;nbsp;&lt;em&gt;"I just let the model write the code, I edit it,"&lt;/em&gt;&amp;nbsp;is how they described their workflow.&lt;/p&gt;

&lt;p&gt;The internet predictably split into two camps — panic and dismissal. Both camps missed the point.&lt;/p&gt;

&lt;p&gt;He's right that the act of translating a decided solution into working syntax is being automated. He's wrong — or at minimum imprecise — that this is the same thing as software engineering.&lt;/p&gt;

&lt;p&gt;Here's the distinction that matters: writing code was never the hard part of software engineering. It was never where the real cognitive work lived. The hard part has always been the thinking before and after the code — understanding the system, identifying the failure modes, designing the authorization model, knowing what to do at 3am when production fails in a way nobody predicted.&lt;/p&gt;

&lt;p&gt;AI can write the code. It cannot understand your system. That gap is where your value lives. The developers who forget this and outsource their thinking along with their typing are not becoming more productive. They are becoming fragile.&lt;/p&gt;

&lt;p&gt;Klarna's CEO agreed with Amodei — his company's white-collar workforce will shrink by a third by 2030. Zoho's founder went further, publicly advising software developers to consider alternative livelihoods. These are not fringe opinions. They are the operating assumptions of the people running major technology companies right now.&lt;/p&gt;

&lt;p&gt;The question isn't whether AI is changing software development. It obviously is. The question is: what specifically remains irreducibly human — and are you protecting that?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Vibe Coding Data Is In — It's Bad
&lt;/h2&gt;

&lt;p&gt;Here's where I get specific, because this is the part most AI articles skip.&lt;/p&gt;

&lt;p&gt;Andrej Karpathy — one of the most respected AI researchers alive — coined the term "vibe coding" in February 2025. The pitch: describe what you want, accept all AI changes, stop reading diffs, let it handle everything. 4.5 million people watched that original post. A movement was born.&lt;/p&gt;

&lt;p&gt;Eighteen months later, we have the data.&lt;/p&gt;

&lt;p&gt;Veracode tested over 100 large language models across 80 coding tasks. 45% of AI-generated code contains OWASP Top-10 vulnerabilities. Two years of model improvements have not moved that number. Cross-site scripting defenses failed 86% of the time. Log injection, 88%.&lt;/p&gt;

&lt;p&gt;A security firm built 15 identical apps using five popular vibe coding tools. The result: 69 vulnerabilities across those apps. Six were critical.&lt;/p&gt;

&lt;p&gt;One developer ran a 30-day vibe coding experiment — coding speed went up fivefold initially, then the AI introduced passwords stored in plain text and API keys exposed in responses.&lt;/p&gt;

&lt;p&gt;The Aikido Security founder put it most precisely:&amp;nbsp;&lt;em&gt;"Two engineers can now churn out the same amount of insecure, unmaintainable code as 50 engineers."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Speed without comprehension isn't productivity. It's technical debt with a delayed timer.&lt;/p&gt;

&lt;p&gt;This is Taleb's&amp;nbsp;&lt;em&gt;Fooled by Randomness&lt;/em&gt;&amp;nbsp;playing out in real time. The code runs, so it feels correct. The deployment succeeds, so it feels secure. The vulnerability — the injection flaw, the misconfiguration, the hardcoded credential — isn't visible until the breach, which arrives looking like bad luck rather than the direct consequence it was.&lt;/p&gt;

&lt;p&gt;I study this from a security angle. What vibe-coded applications look like from an attacker's perspective is not complicated. They look like an invitation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where AI Is Actually Going — The Honest Version
&lt;/h2&gt;

&lt;p&gt;Dario Amodei has described the trajectory like this: AI systems that are "broadly better than all humans at almost all things" — what he calls "a country of geniuses in a datacenter." He believes this arrives between 2026 and 2027. Anthropic's co-founder Jack Clark extended that slightly, saying by end of 2026 or 2027, AI will be smarter than a Nobel Prize winner across many major disciplines.&lt;/p&gt;

&lt;p&gt;Andrej Karpathy's timeline is five to ten times more pessimistic. He argues useful autonomous agents are a decade out and that fundamental research breakthroughs — not just scaling — are required to get there.&lt;/p&gt;

&lt;p&gt;Both are credible, informed people with access to the same data, arriving at opposite conclusions. That gap is not solvable by reading more takes on Twitter. The honest answer is: nobody knows. What we do know directionally:&lt;/p&gt;

&lt;p&gt;Gartner predicts 40% of enterprise applications will include task-specific AI agents by end of 2026, up from less than 5% in 2025. The agentic AI market is projected to hit $251 billion by 2034.&lt;/p&gt;

&lt;p&gt;Anthropic itself warned — in the same week they released Fable 5 — that AI systems may soon achieve recursive self-improvement: autonomously improving themselves without human intervention. They are simultaneously releasing the most powerful model ever made publicly available and warning that the pace of progress may be outrunning our ability to govern it. That tension is worth sitting with.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Actually Be Good in This Era
&lt;/h2&gt;

&lt;p&gt;This is the part most articles don't write because it requires taking a real position.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protect the 70% that AI cannot replicate.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;System thinking. Threat modeling. Authorization design. Debugging under pressure when production fails. Judgment calls on architecture. Reading the code AI generates — every line, not on vibes. These are not automatable because they require context, stakes, and accountability that exist in your specific environment, not in the training data of any model.&lt;/p&gt;

&lt;p&gt;Feynman distinguished between knowing the name of something and knowing the thing itself. Vibe coders know the name of what they want to build. The thinking — the actual understanding — is what they've offloaded. That's precisely where the vulnerabilities enter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use AI seriously for the 30% it's genuinely good at.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Boilerplate and scaffolding where patterns are well-defined and low-risk. Snippets for problems you already fully understand — if you don't understand the problem, AI-generated code for it is dangerous, not helpful. Reading unfamiliar codebases faster. Generating test cases that surface edge cases you might have missed. First drafts of documentation.&lt;/p&gt;

&lt;p&gt;The precondition for all of this is comprehension first. The AI accelerates work you already understand. It does not replace the understanding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the model outputs like an adversary would.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're a developer integrating any AI into production — Fable 5, GPT-5.5, anything — your threat model now includes prompt injection, indirect prompt injection via poisoned context sources, and agent privilege escalation. These are not theoretical. Agentic coding assistants have been shown to follow embedded instructions in poisoned README files — exfiltrating credentials while the developer watches what looks like normal code being written.&lt;/p&gt;

&lt;p&gt;Thinking like a defender requires first thinking like an attacker. That combination — developer who builds systems, security researcher who breaks them — is the most defensible position in this market right now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't mistake velocity for capability.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The engineers Amodei referenced — those who no longer write code — are editing AI output. Editing requires judgment. Judgment requires deep understanding of what you're editing and why. The moment that understanding atrophies, the editing becomes rubber-stamping. And rubber-stamped AI code at production scale is exactly what the breach statistics are capturing.&lt;/p&gt;

&lt;p&gt;Marcus Aurelius:&amp;nbsp;&lt;em&gt;"Never esteem anything as of advantage to you that will make you break your word or lose your self-respect."&lt;/em&gt;&amp;nbsp;The self-respect version for engineers in 2026 is: never call yourself a developer if you can no longer read and reason about the code you're shipping.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Actual Summary
&lt;/h2&gt;

&lt;p&gt;Claude Fable 5 is a genuine capability leap. The model landscape has never been more competitive or more capable. Amodei's warnings about software engineering disruption are serious and partially correct. The vibe coding security data is as bad as the skeptics predicted. AGI timelines are genuinely uncertain — plan for a wide band, not a specific year.&lt;/p&gt;

&lt;p&gt;What this means for you: the developers who survive and matter in this era will be those who used AI as a force multiplier on deep understanding — not as a substitute for it. The ones who outsourced their thinking along with their typing will find themselves unable to debug, unable to secure, unable to reason about the systems they're responsible for.&lt;/p&gt;

&lt;p&gt;Speed is available to everyone now. Understanding is not.&lt;/p&gt;

&lt;p&gt;That asymmetry is your edge — if you protect it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about API security, Django, and how developers can think more carefully about what they build. I don't post often, but when I do, it's because I have something I actually believe. Follow if that interests you.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why Most Django Boilerplates Are Insecure by Default</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Wed, 03 Jun 2026 16:18:06 +0000</pubDate>
      <link>https://dev.to/siyadhkc/why-most-django-boilerplates-are-insecure-by-default-4nki</link>
      <guid>https://dev.to/siyadhkc/why-most-django-boilerplates-are-insecure-by-default-4nki</guid>
      <description>&lt;p&gt;You found a Django boilerplate on GitHub. It has 2k stars, a clean README, Docker support, and a Makefile. You clone it, rename a few things, push to production.&lt;/p&gt;

&lt;p&gt;Congratulations. You just shipped someone else's security assumptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Illusion of "Production-Ready"
&lt;/h2&gt;

&lt;p&gt;Every boilerplate claims to be production-ready. The badge is right there in the README. But production-ready for what? A side project? A fintech API? A platform that stores user PII?&lt;/p&gt;

&lt;p&gt;"Production-ready" in most boilerplates means: &lt;em&gt;it runs without errors&lt;/em&gt;. That's it. The security part is usually left as a comment that says &lt;code&gt;# TODO: change this before deploy&lt;/code&gt; — which, statistically, nobody changes.&lt;/p&gt;

&lt;p&gt;This isn't an attack on boilerplate authors. They're solving a real problem: Django's initial setup is tedious, and abstracting that away has genuine value. The problem is that the &lt;em&gt;defaults they choose&lt;/em&gt; get inherited silently by every project that forks them.&lt;/p&gt;

&lt;p&gt;And defaults are everything in security.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Ships in Most Boilerplates
&lt;/h2&gt;

&lt;p&gt;Let me walk through what you'll typically find if you dig past the README.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;DEBUG = True&lt;/code&gt; Tied to an Environment Variable That Defaults to True
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;DEBUG&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;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEBUG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the canonical mistake. The intent is good — make DEBUG configurable. But the &lt;em&gt;default&lt;/em&gt; is True. So if you deploy and forget to set the environment variable, Django will cheerfully expose your full stack trace, local variables, installed apps, and settings to anyone who hits a 404.&lt;/p&gt;

&lt;p&gt;I've seen this in repos with thousands of stars. The author probably never intended it to ship this way. But it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix is trivial:&lt;/strong&gt;&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="n"&gt;DEBUG&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;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEBUG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;False&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One word. The default should always be the safe option.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;code&gt;SECRET_KEY&lt;/code&gt; Hardcoded or Weakly Generated
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django-insecure-abc123xyz-changeme&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Django's &lt;code&gt;startproject&lt;/code&gt; actually warns you with the &lt;code&gt;django-insecure-&lt;/code&gt; prefix now. But plenty of boilerplates generated before that convention still have static keys checked into version control.&lt;/p&gt;

&lt;p&gt;Even the ones that generate it dynamically sometimes do it wrong:&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="c1"&gt;# generates a NEW key on every restart
&lt;/span&gt;&lt;span class="n"&gt;SECRET_KEY&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;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get_random_secret_key&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means every server restart invalidates all existing sessions and CSRF tokens. That's not a security fix — that's a different bug wearing a security costume.&lt;/p&gt;

&lt;p&gt;The secret key needs to be generated once, stored as an environment variable, and never committed to Git. Full stop.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. &lt;code&gt;ALLOWED_HOSTS = ["*"]&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ALLOWED_HOSTS&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;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again — the default is the dangerous value. &lt;code&gt;ALLOWED_HOSTS = ["*"]&lt;/code&gt; disables Django's Host header validation entirely, opening the door for host header injection attacks. This isn't theoretical. Attackers use it to poison password reset emails with their own domain.&lt;/p&gt;

&lt;p&gt;The boilerplate author probably put &lt;code&gt;"*"&lt;/code&gt; there so you can &lt;code&gt;docker-compose up&lt;/code&gt; without configuring anything. Convenience for the developer. Risk for the user.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. CORS Configured Wide Open
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# django-cors-headers
&lt;/span&gt;&lt;span class="n"&gt;CORS_ALLOW_ALL_ORIGINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CORS exists to protect your users' browsers. When you set this to True, you're telling every origin on the internet that it can make credentialed requests to your API. Combined with &lt;code&gt;SESSION_COOKIE_SAMESITE = None&lt;/code&gt; (which some boilerplates also set), you've recreated the conditions for CSRF attacks in a Single Page App context.&lt;/p&gt;

&lt;p&gt;The reasoning is always the same: &lt;em&gt;"I'll change this when I know my frontend's domain."&lt;/em&gt; But that moment never comes cleanly in the chaos of early development, and it ships.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. No Security Headers
&lt;/h3&gt;

&lt;p&gt;Django doesn't set security headers by default. You need &lt;code&gt;django.middleware.security.SecurityMiddleware&lt;/code&gt; in &lt;code&gt;MIDDLEWARE&lt;/code&gt;, and then you need to actually configure it:&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="n"&gt;SECURE_BROWSER_XSS_FILTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# deprecated but harmless
&lt;/span&gt;&lt;span class="n"&gt;SECURE_CONTENT_TYPE_NOSNIFF&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;X_FRAME_OPTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DENY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SECURE_HSTS_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31536000&lt;/span&gt;
&lt;span class="n"&gt;SECURE_HSTS_INCLUDE_SUBDOMAINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;SECURE_HSTS_PRELOAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;SECURE_SSL_REDIRECT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most boilerplates have &lt;code&gt;SecurityMiddleware&lt;/code&gt; in the list. Few have these settings configured. HSTS in particular is almost always missing — which means every user's first request is over HTTP until they get redirected.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Session and Cookie Settings Left at Defaults
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What most boilerplates ship
&lt;/span&gt;&lt;span class="n"&gt;SESSION_COOKIE_HTTPONLY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;   &lt;span class="c1"&gt;# ✅ Django default, usually fine
&lt;/span&gt;&lt;span class="n"&gt;SESSION_COOKIE_SECURE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;    &lt;span class="c1"&gt;# ❌ Default — cookies sent over HTTP
&lt;/span&gt;&lt;span class="n"&gt;CSRF_COOKIE_SECURE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;       &lt;span class="c1"&gt;# ❌ Default — CSRF token sent over HTTP
&lt;/span&gt;&lt;span class="n"&gt;SESSION_COOKIE_SAMESITE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lax&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# ✅ Usually fine, but often overridden
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SESSION_COOKIE_SECURE = True&lt;/code&gt; means the browser will only send the session cookie over HTTPS. Leaving it False means someone on the same network can intercept it over HTTP. This is a solved problem — but it requires explicitly setting it, and boilerplates rarely do.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Django Admin Sitting on &lt;code&gt;/admin/&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;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;admin/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is Django's own default, so I can't blame boilerplate authors entirely. But nobody changes it either. The admin panel at &lt;code&gt;/admin/&lt;/code&gt; is the first thing automated scanners probe. Moving it to something non-guessable costs you nothing and removes you from a significant percentage of automated attacks.&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="nf"&gt;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;your-own-path-here/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why This Keeps Happening
&lt;/h2&gt;

&lt;p&gt;The ecosystem isn't malicious. It's optimizing for the wrong metric.&lt;/p&gt;

&lt;p&gt;Boilerplates get stars based on how quickly they get you from zero to running. Security settings that would &lt;em&gt;break&lt;/em&gt; a local setup — like &lt;code&gt;SECURE_SSL_REDIRECT = True&lt;/code&gt; without an SSL cert — get disabled so the DX is smooth. The secure version is the harder version to demo.&lt;/p&gt;

&lt;p&gt;Django itself deserves some of this too. The framework ships with safe defaults in some areas (CSRF protection is on by default, passwords are hashed), but makes you opt-in to the rest. That opt-in surface is where boilerplates consistently fail to follow through.&lt;/p&gt;

&lt;p&gt;And then there's us — developers who clone first, configure later, and ship before "later" arrives.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Secure Boilerplate Default Looks Like
&lt;/h2&gt;

&lt;p&gt;Split your settings. This is non-negotiable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;settings/
    base.py      # shared across all environments
    local.py     # DEBUG=True, no SSL, relaxed CORS
    production.py  # strict everything
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;production.py&lt;/code&gt;, the defaults should be secure. If something is missing — a &lt;code&gt;SECRET_KEY&lt;/code&gt;, a &lt;code&gt;DATABASE_URL&lt;/code&gt; — the app should refuse to start, not silently fall back to an insecure value.&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="c1"&gt;# production.py
&lt;/span&gt;&lt;span class="n"&gt;SECRET_KEY&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;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# KeyError if missing — intentional
&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;ALLOWED_HOSTS&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;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;SECURE_SSL_REDIRECT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;SECURE_HSTS_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31536000&lt;/span&gt;
&lt;span class="n"&gt;SECURE_HSTS_INCLUDE_SUBDOMAINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;SESSION_COOKIE_SECURE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;CSRF_COOKIE_SECURE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;CORS_ALLOWED_ORIGINS&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;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CORS_ALLOWED_ORIGINS&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;os.environ["KEY"]&lt;/code&gt; without &lt;code&gt;.get()&lt;/code&gt;. If the variable isn't set, it raises a &lt;code&gt;KeyError&lt;/code&gt; and the app doesn't start. That's correct behavior. Failing loudly in production is safer than running silently insecure.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Checklist Before You Ship
&lt;/h2&gt;

&lt;p&gt;If you're using any boilerplate — including one you wrote yourself six months ago — run through this before you deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;DEBUG = False&lt;/code&gt; in production, with no unsafe default fallback&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;SECRET_KEY&lt;/code&gt; sourced from environment, never in version control&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;ALLOWED_HOSTS&lt;/code&gt; explicitly set, no wildcards&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;CORS_ALLOW_ALL_ORIGINS = False&lt;/code&gt;, origins explicitly whitelisted&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;SESSION_COOKIE_SECURE = True&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;CSRF_COOKIE_SECURE = True&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;SECURE_SSL_REDIRECT = True&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;SECURE_HSTS_SECONDS&lt;/code&gt; set&lt;/li&gt;
&lt;li&gt;[ ] Django admin URL is not &lt;code&gt;/admin/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;django.middleware.security.SecurityMiddleware&lt;/code&gt; is first in &lt;code&gt;MIDDLEWARE&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run &lt;code&gt;python manage.py check --deploy&lt;/code&gt; too. Django ships a deployment checker that will call out many of these issues. Most developers don't know it exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Takeaway
&lt;/h2&gt;

&lt;p&gt;Boilerplates are a starting point, not a finished product. The problem is the word "boilerplate" implies it's something you don't need to think about — just add your code on top. But the security layer is exactly the part that requires thinking.&lt;/p&gt;

&lt;p&gt;The ecosystem needs better defaults. Boilerplate authors should make the &lt;em&gt;safe&lt;/em&gt; behavior the path of least resistance, not the other way around. Django could be more opinionated about production configuration. And we — as developers shipping things to real users — need to stop treating security settings as a post-launch concern.&lt;/p&gt;

&lt;p&gt;It never becomes post-launch. It becomes a breach report.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you found this useful, I write about Django security, API security, and backend architecture on Dev.to. The next piece will be on OWASP API Top 10 vulnerabilities in real Django REST Framework code — the ones that slip through code review.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built a Pre-Commit Secret Scanner Because GitHub's Is Too Late</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Sun, 24 May 2026 15:31:20 +0000</pubDate>
      <link>https://dev.to/siyadhkc/i-built-a-pre-commit-secret-scanner-because-githubs-is-too-late-1eo1</link>
      <guid>https://dev.to/siyadhkc/i-built-a-pre-commit-secret-scanner-because-githubs-is-too-late-1eo1</guid>
      <description>&lt;p&gt;Let me start with a confession.&lt;/p&gt;

&lt;p&gt;I have accidentally committed a &lt;code&gt;.env&lt;/code&gt; file. Not to a private internal repo with no consequences. To a public GitHub repo. With a real Stripe test key in it.&lt;/p&gt;

&lt;p&gt;I caught it within minutes, deleted the file, rotated the key, pushed a new commit. Classic developer scramble. Nothing happened — at least as far as I know.&lt;/p&gt;

&lt;p&gt;But that "as far as I know" lived in my head for a while.&lt;/p&gt;

&lt;p&gt;That experience is what eventually led me to build &lt;strong&gt;env-guard&lt;/strong&gt; — a Python CLI tool that scans your project for secrets and installs as a git pre-commit hook, blocking the commit entirely if it finds something suspicious. The secret never leaves your machine. No scramble. No rotation. No anxiety.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with GitHub Secret Scanning (it's a timing problem)
&lt;/h2&gt;

&lt;p&gt;GitHub has a feature called Secret Scanning. It's legitimately good — it covers dozens of credential formats across major providers and will notify you when it finds something. I'm not here to trash it.&lt;/p&gt;

&lt;p&gt;But it has one fundamental flaw that no amount of good engineering can fix:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It runs after you push.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think about what that means. By the time GitHub scans your code and sends you an alert, your secret has already:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Left your machine&lt;/li&gt;
&lt;li&gt;Traveled over the internet&lt;/li&gt;
&lt;li&gt;Landed on GitHub's servers&lt;/li&gt;
&lt;li&gt;Been indexed, if the repo is public&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Automated bots scrape public GitHub repos constantly. We're talking seconds after a push, not minutes. GitHub will alert you, but the key is already out there. You're now rotating credentials, auditing API usage logs, and hoping whoever grabbed it hasn't done anything with it yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Without env-guard:
  code → commit → push → GitHub scans → alert → key already exposed ❌

With env-guard:
  code → commit blocked → fix locally → push clean code ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of GitHub Secret Scanning as your last line of defense. env-guard is your first.&lt;/p&gt;

&lt;p&gt;The secret never enters git history at all. There's nothing to rotate, nothing to audit, and nothing to lose sleep over.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who this is actually for
&lt;/h2&gt;

&lt;p&gt;If you work with any of the following on a regular basis, env-guard is for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API keys&lt;/strong&gt; — OpenAI, Stripe, Twilio, SendGrid, any SaaS you integrate with&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud credentials&lt;/strong&gt; — AWS access keys, GCP service accounts, anything that costs money if misused&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database connection strings&lt;/strong&gt; — postgres://, mongodb://, redis:// — all of these contain passwords&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private keys&lt;/strong&gt; — RSA, EC, OpenSSH — the kind that give access to your servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically: if you work on real software that talks to real services, you have secrets somewhere in your local environment. And humans make mistakes. Even careful ones.&lt;/p&gt;

&lt;p&gt;I've seen this happen to senior engineers. It's not a skill issue. It's a missing safety net issue.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it detects
&lt;/h2&gt;

&lt;p&gt;env-guard ships with &lt;strong&gt;54 detection rules&lt;/strong&gt; covering every major category of credential you'd encounter in a typical backend or fullstack project:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;Access Key ID, Secret Access Key, Session Token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;API Key, OAuth Client Secret, Service Account JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;Personal Access Token, OAuth Token, App Token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;Live and Test Secret Keys, Publishable Keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI / Anthropic&lt;/td&gt;
&lt;td&gt;API Keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack&lt;/td&gt;
&lt;td&gt;Bot Token, User Token, Webhook URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Twilio&lt;/td&gt;
&lt;td&gt;Account SID, Auth Token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SendGrid&lt;/td&gt;
&lt;td&gt;API Key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Databases&lt;/td&gt;
&lt;td&gt;PostgreSQL, MySQL, MongoDB, Redis connection strings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private Keys&lt;/td&gt;
&lt;td&gt;RSA, EC, PGP, OpenSSH private key blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Django&lt;/td&gt;
&lt;td&gt;SECRET_KEY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;td&gt;Password assignments, token assignments, API key assignments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;And more&lt;/td&gt;
&lt;td&gt;Razorpay, NPM, PyPI, Heroku, Netlify, Cloudinary, and others&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each rule carries a severity level — HIGH, MEDIUM, or LOW — so you're not staring at an undifferentiated wall of warnings. A live Stripe secret key is HIGH. A generic-looking token assignment might be MEDIUM. The output is human-readable and actionable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;env-guard scan &lt;span class="nb"&gt;.&lt;/span&gt;

env-guard scan report
Path    : &lt;span class="nb"&gt;.&lt;/span&gt;
Scanned : 42 files
Found   : 1 potential secret&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;

  ✖ HIGH  Stripe Live Secret Key
  File : config/settings.py:12
  Code : STRIPE_KEY &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sk_live_abc123..."&lt;/span&gt;

──────────────────────────────────────────────────
  1 HIGH
──────────────────────────────────────────────────

Scan failed — secrets detected. Do not commit.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;File. Line number. What was found. Severity. No ambiguity, no guesswork — you know exactly where to go and what to fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started in 60 seconds
&lt;/h2&gt;

&lt;p&gt;Install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;env-guard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requires Python 3.8+. Run a scan on your current project right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;env-guard scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go ahead. Run it on a real project you're working on. You might be surprised what's sitting there.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pre-commit hook — the part that actually changes your habits
&lt;/h2&gt;

&lt;p&gt;Ad-hoc scanning is useful for audits, but the real shift happens when env-guard becomes invisible. When you don't have to think about running it. When it just works in the background every single time you commit.&lt;/p&gt;

&lt;p&gt;That's what &lt;code&gt;install-hook&lt;/code&gt; does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;env-guard install-hook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that once inside your repo. From that point forward, every &lt;code&gt;git commit&lt;/code&gt; triggers an automatic scan before anything gets written to git history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;env-guard: scanning for secrets...
env-guard: commit blocked. Remove secrets before committing.
env-guard: to skip this check (NOT recommended): git commit --no-verify
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If nothing is found, the commit proceeds normally — you won't even notice it ran. If something is found, the commit is hard-blocked with a clear message telling you exactly where the problem is.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--no-verify&lt;/code&gt; escape hatch exists because I didn't want to be heavy-handed. Sometimes you're committing a test fixture that contains a fake-looking credential. The escape hatch is there — but it requires a conscious decision, and that friction is intentional.&lt;/p&gt;

&lt;p&gt;To remove the hook if you ever need to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;env-guard uninstall-hook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Handling false positives
&lt;/h2&gt;

&lt;p&gt;Every scanner has them. A SHA-256 hash that structurally resembles an API key. A documentation example with a placeholder that matches a pattern. A test fixture with a fake credential for unit tests.&lt;/p&gt;

&lt;p&gt;env-guard handles this with a &lt;code&gt;.envguardignore&lt;/code&gt; file in your project root — same concept as &lt;code&gt;.gitignore&lt;/code&gt;, one pattern per line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ignore specific files
&lt;/span&gt;&lt;span class="err"&gt;tests/fixtures/sample.env&lt;/span&gt;

&lt;span class="c"&gt;# Ignore by extension
&lt;/span&gt;&lt;span class="err"&gt;*.log&lt;/span&gt;

&lt;span class="c"&gt;# Ignore entire directories
&lt;/span&gt;&lt;span class="err"&gt;docs/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because it's a committed file, your whole team gets the same exclusions automatically. No per-developer configuration needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  CLI flags for different workflows
&lt;/h2&gt;

&lt;p&gt;A few flags that cover the common situations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Only surface the critical findings&lt;/span&gt;
env-guard scan &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--severity&lt;/span&gt; HIGH

&lt;span class="c"&gt;# Machine-readable output for scripting&lt;/span&gt;
env-guard scan &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json

&lt;span class="c"&gt;# Scan without failing the process (reporting mode)&lt;/span&gt;
env-guard scan &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--no-fail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--no-fail&lt;/code&gt; flag is worth calling out specifically. If you're adding env-guard to an existing large codebase, you probably don't want to immediately block everything and deal with 50 findings on day one. Run with &lt;code&gt;--no-fail&lt;/code&gt; first, triage what's real vs. false positive, set up your &lt;code&gt;.envguardignore&lt;/code&gt;, then remove the flag when you're confident. Gradual adoption is a completely valid path.&lt;/p&gt;




&lt;h2&gt;
  
  
  For teams: CI/CD as a second checkpoint
&lt;/h2&gt;

&lt;p&gt;Pre-commit hooks protect your own machine. But what about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;External contributors who clone the repo and don't run &lt;code&gt;install-hook&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;New team members who haven't set up their environment yet&lt;/li&gt;
&lt;li&gt;Automated processes that generate config files with credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For that, env-guard works in CI pipelines too. Here's a GitHub Actions workflow you can drop into any repo:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret Scan&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.11'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install env-guard&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;env-guard scan .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the same local protection applied to every PR, from every contributor, on every push. env-guard locally + this workflow in CI = defense in depth that actually makes sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  env-guard vs GitHub Secret Scanning — use both
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;env-guard&lt;/th&gt;
&lt;th&gt;GitHub Secret Scanning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When it runs&lt;/td&gt;
&lt;td&gt;Before commit, on your machine&lt;/td&gt;
&lt;td&gt;After push, on GitHub's servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocks the secret&lt;/td&gt;
&lt;td&gt;Yes — commit is blocked&lt;/td&gt;
&lt;td&gt;No — secret is already pushed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works offline&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom ignore rules&lt;/td&gt;
&lt;td&gt;Yes, &lt;code&gt;.envguardignore&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (public repos)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires GitHub&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These aren't competing tools. They protect different parts of the pipeline. Run env-guard locally to stop secrets before they ever leave your machine. Keep GitHub Secret Scanning on as a fallback for anything that slips through — external contributors, edge cases, human error. Use both.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing I keep thinking about
&lt;/h2&gt;

&lt;p&gt;There's a concept in security called "shift left" — the idea that you should catch problems as early in the development process as possible, because the earlier you catch something, the cheaper it is to fix and the less damage it causes.&lt;/p&gt;

&lt;p&gt;A leaked credential caught before the commit? Zero cost. Rotate nothing. Tell no one. Move on.&lt;/p&gt;

&lt;p&gt;A leaked credential caught after a push to a public repo? Rotate the key, audit usage logs, send an incident report if it's a team environment, and spend a few days with that low-grade anxiety of "did anything actually get accessed?"&lt;/p&gt;

&lt;p&gt;env-guard is a shift-left tool. The entire design philosophy is: don't give the secret a chance to travel. Keep it local. Catch it at the source.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;env-guard
env-guard scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want the hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;env-guard install-hook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source on GitHub: &lt;a href="https://github.com/siyadhkc/env-guard" rel="noopener noreferrer"&gt;github.com/siyadhkc/env-guard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it catches something on the first scan — good. That's the point. And if you hit a false positive that's annoying, open an issue. I want the pattern library to be genuinely useful, not just technically comprehensive.&lt;/p&gt;




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

&lt;p&gt;A few things on the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pre-push hook option&lt;/strong&gt; — a second checkpoint right before the push, for teams who want both&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More detection rules&lt;/strong&gt; — 54 is solid but the ecosystem keeps growing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project-level config file&lt;/strong&gt; — custom severity thresholds, rule toggles, team-specific settings beyond just ignores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this is useful to you, star the repo and watch for updates. And if you've ever had a bad day because of an exposed credential — you're not alone, and this is for you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Python. MIT licensed. Contributions welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>devto</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I built a multi-tenant food delivery platform alone. Here's what nobody tells you about that.</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Sun, 17 May 2026 16:40:10 +0000</pubDate>
      <link>https://dev.to/siyadhkc/i-built-a-multi-tenant-food-delivery-platform-alone-heres-what-nobody-tells-you-about-that-104c</link>
      <guid>https://dev.to/siyadhkc/i-built-a-multi-tenant-food-delivery-platform-alone-heres-what-nobody-tells-you-about-that-104c</guid>
      <description>&lt;p&gt;Four user roles. One payment gateway that lied to me. Three rewrites of the same feature. And a GPS bug that nearly flooded my database. This is the real story behind Savor.&lt;/p&gt;




&lt;h2&gt;
  
  
  I was tired of building things that felt fake
&lt;/h2&gt;

&lt;p&gt;At some point every developer hits a wall with tutorial projects. You've built the weather app. You've done the blog CRUD. You've cloned Twitter in a weekend. And none of it actually &lt;em&gt;teaches&lt;/em&gt; you what it feels like when four different types of users are all touching the same data at the same time, and you have to keep their worlds completely separate.&lt;/p&gt;

&lt;p&gt;I wanted something that would break me a little. Something with real business logic, real money, real state synchronization problems. Food delivery checked every single box.&lt;/p&gt;

&lt;p&gt;Customers order food. Restaurants fulfill it. Delivery agents pick it up. Admins oversee everything. Four completely different actors, all interacting with overlapping data, all in real time. That's not a tutorial problem — that's a systems problem. I called it &lt;strong&gt;Savor&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Building this was the first time I genuinely didn't know if something would work until I ran it. That was terrifying and that was the point."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  These aren't exciting choices. They're correct ones.
&lt;/h2&gt;

&lt;p&gt;Django REST Framework on the backend. React 19 + Vite on the frontend. PostgreSQL. I didn't pick these to be cool. I picked them because the problem needed a mature ORM, ACID-compliant transactions, and a frontend that wouldn't fight me on state management.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tech&lt;/th&gt;
&lt;th&gt;Why it mattered&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Django 5.1 + DRF&lt;/td&gt;
&lt;td&gt;Atomic transactions, battle-tested ORM, signals for cross-model side effects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React 19 + Vite&lt;/td&gt;
&lt;td&gt;Fast HMR, modern concurrent features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;SimpleJWT + rotation&lt;/td&gt;
&lt;td&gt;Silent 60-min access token rotation, 7-day refresh — no mid-session logouts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;ACID compliance. Nested financial data cannot partially save.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;Razorpay&lt;/td&gt;
&lt;td&gt;Localized processing, webhook verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Media&lt;/td&gt;
&lt;td&gt;Cloudinary CDN&lt;/td&gt;
&lt;td&gt;Edge delivery. Serving images from Django would be career-ending performance-wise.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS 4.0&lt;/td&gt;
&lt;td&gt;Zero runtime. Design tokens kept four portals visually coherent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animations&lt;/td&gt;
&lt;td&gt;Framer Motion&lt;/td&gt;
&lt;td&gt;Micro-interactions. A premium feel isn't just design — it's movement.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mapping&lt;/td&gt;
&lt;td&gt;Leaflet.js&lt;/td&gt;
&lt;td&gt;Live GPS plotting on the customer tracking view&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The one decision I'd revisit: React Context API over Zustand. Context was fine for the scope but I started feeling prop drilling pain by week four. If you're building something this complex, just start with Zustand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Each portal was its own distinct problem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The customer app&lt;/strong&gt; was where I started because it's the most tangible — you can see it, click it, feel it. The discovery architecture has two layers: global cuisines (Arabian, South Indian, Desserts) and restaurant-internal categories (Must Try, Specials). Getting that hierarchy right in the serializers took four iterations. The cart was where I first hit real business logic — you can't add items from two different restaurants to the same cart. I built persistent local state that checks restaurant ID on every cart mutation. Sounds simple. It wasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The restaurant partner portal&lt;/strong&gt; was the one I underestimated the most. I assumed it'd be a dashboard with some CRUD. What I got was the most complex portal in the system. Restaurant owners are power users. The order lifecycle — Pending → Accept → Preparing → Ready for Delivery — cascades into downstream state changes across three other portals simultaneously. I used Django signals for this. Required very careful thinking about idempotency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The delivery agent interface&lt;/strong&gt; is the one I'm most proud of technically. Geo-fencing logic is implemented at the queryset manager level, not the view level. A Kochi agent cannot see a Mumbai dispatch — and this isn't a frontend filter someone can bypass with a crafted request. The data simply doesn't exist in the response. Getting the earnings calculation to be atomic and accurate on the &lt;code&gt;Delivered&lt;/code&gt; status transition took three rewrites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The admin console&lt;/strong&gt; could have been an afterthought. I didn't let it be. With 3,000+ menu items seeded from the Kerala restaurant dataset, server-side pagination was non-negotiable. I implemented cursor-based pagination — offset pagination on PostgreSQL becomes unusably slow past 1,000 rows.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faci92qhdruwgwaytbbid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faci92qhdruwgwaytbbid.png" alt="website" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Live demo: &lt;a href="https://food-delivery-seven-bice.vercel.app" rel="noopener noreferrer"&gt;food-delivery-seven-bice.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The struggles nobody writes about in project writeups
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;01 — Multi-tenancy took three complete rewrites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first version filtered at the serializer level. That works until a restaurant owner in tenant A briefly sees an order from tenant B during a race condition. The second version moved filters to the view layer — better, still bypassable. The third version put tenant scoping into the queryset manager. That's where it needed to be from the start. Two full days of debugging a bug that shouldn't have existed if I'd thought it through properly the first time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;02 — Razorpay webhook verification in development is a trap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Webhooks need a publicly accessible endpoint. In development, you don't have one. I spent a full day debugging a signature verification failure that turned out to be a timezone mismatch in how I was constructing the HMAC payload. I ended up building a local webhook simulator specifically for dev testing, stripped out before production. This is not in any Razorpay tutorial I found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;03 — GPS telemetry nearly destroyed my database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Early testing: delivery agents broadcast coordinates on every geolocation API event. The browser's &lt;code&gt;watchPosition&lt;/code&gt; fires multiple times per second. At 10 concurrent test agents, that was hundreds of database writes per minute for a field that only needs to update every few seconds. I added a frontend debounce and a backend timestamp guard that rejects updates more frequent than 3 seconds. The database survived. My nerves less so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;04 — CSS across four portals with four different visual languages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The customer app is warm and premium. The delivery interface is utilitarian and fast. The admin console is dense and data-heavy. The partner portal is somewhere between all three. Tailwind's design token system helped, but by week three the tokens were drifting in ways that required a full audit to fix. CSS architecture is genuinely hard at scale and nobody talks about the multi-portal version of this problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;05 — The cart constraint bug that appeared in production and nowhere else&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cart state was using localStorage with a restaurant ID key. In production, the Vercel edge occasionally delivered a cached app version mid-session with a stale cart key. The constraint check passed because the key comparison was against an old restaurant ID. Never reproduced locally. Fixed by adding a session-scoped cart version hash alongside the restaurant ID. The worst kind of bug: requires production traffic to surface.&lt;/p&gt;




&lt;h2&gt;
  
  
  The one decision that saved the whole system
&lt;/h2&gt;

&lt;p&gt;Early on I had a choice: enforce multi-tenancy at the UI layer or the data layer. The UI layer is easier — you just filter what you show. The data layer is harder — you modify how querysets are constructed at the model level so filtered-out data is never even fetched.&lt;/p&gt;

&lt;p&gt;I chose the data layer. Every restaurant-scoped query goes through a custom queryset manager that injects the tenant ID before the SQL hits PostgreSQL. Bypassing tenant isolation requires compromising the Django application layer itself — not just crafting a request with different parameters.&lt;/p&gt;

&lt;p&gt;For financial data, I used &lt;code&gt;@transaction.atomic&lt;/code&gt; everywhere that touched nested profile updates. A restaurant's payout configuration and platform commission rate update in a single atomic operation. Either both succeed or neither does. This is not optional when you're dealing with money.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The architecture that survives isn't the one that looks cleanest in a diagram. It's the one that fails gracefully when things go wrong — and they will go wrong."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  You only understand failure modes by creating them
&lt;/h2&gt;

&lt;p&gt;You can read about multi-tenancy. You can read about JWT rotation. You can understand atomic transactions in the abstract. But there's a kind of understanding you only get from tracing a data leakage bug through four layers of abstraction at 11pm, finding the one queryset that forgot to filter by &lt;code&gt;restaurant_id&lt;/code&gt;, and sitting with the fact that your first two architectural decisions were wrong.&lt;/p&gt;

&lt;p&gt;Building Savor taught me that system design isn't about choosing the right technologies. It's about understanding the failure modes of every interface between them. The Razorpay integration fails if my webhook handler is slow. GPS tracking floods my database if the frontend isn't debounced. Multi-tenant isolation breaks if I filter at the wrong layer. None of these are things you learn by reading. You learn them by breaking them, badly, and having to fix it.&lt;/p&gt;

&lt;p&gt;If you're in the middle of a solo project right now and it feels like the complexity is winning — it probably is, temporarily. That's what week three feels like. The tangle is the work. Push through it.&lt;/p&gt;




&lt;p&gt;Live demo: &lt;a href="https://food-delivery-seven-bice.vercel.app" rel="noopener noreferrer"&gt;food-delivery-seven-bice.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Backend API: &lt;a href="https://food-delivery-backend-c4pe.onrender.com/api" rel="noopener noreferrer"&gt;food-delivery-backend-c4pe.onrender.com/api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Github: &lt;a href="https://github.com/siyadhkc/savor" rel="noopener noreferrer"&gt;food-delivery-github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Postman collection at &lt;code&gt;backend/postman.json&lt;/code&gt; — all auth rotation, order status, and multi-tenant profile endpoints pre-configured.&lt;/p&gt;




</description>
      <category>django</category>
      <category>react</category>
      <category>fullstack</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built a JSON CLI tool with Bun and TypeScript — here's what actually happened</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Mon, 11 May 2026 01:31:57 +0000</pubDate>
      <link>https://dev.to/siyadhkc/i-built-a-json-cli-tool-with-bun-and-typescript-heres-what-actually-happened-28ek</link>
      <guid>https://dev.to/siyadhkc/i-built-a-json-cli-tool-with-bun-and-typescript-heres-what-actually-happened-28ek</guid>
      <description>&lt;p&gt;I wanted a tool that could flatten JSON, filter it, colorize output, and fetch from a URL — all from&lt;br&gt;
the terminal. Something between &lt;code&gt;jq&lt;/code&gt; and &lt;code&gt;gron&lt;/code&gt; but with a cleaner, more intuitive API. So I built&lt;br&gt;
&lt;code&gt;jray&lt;/code&gt;. This is the honest post-mortem of how it went.&lt;/p&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/siyadhkc/jray" rel="noopener noreferrer"&gt;github.com/siyadhkc/jray&lt;/a&gt;&lt;br&gt;
Website:&lt;a href="https://siyadhkc.github.io/Jray" rel="noopener noreferrer"&gt;siyadhkc.github.io/Jray&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Every time I'm debugging an API response or poking around some nested config file, I reach for &lt;code&gt;jq&lt;/code&gt;.&lt;br&gt;
And every time, I spend the first five minutes googling the syntax again. &lt;code&gt;jq&lt;/code&gt; is powerful — genuinely&lt;br&gt;
— but it's its own language, and unless you're writing complex transformations daily, that overhead&lt;br&gt;
adds up.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gron&lt;/code&gt; is the other tool I kept coming back to. It flattens JSON into greppable dot-notation, which&lt;br&gt;
is exactly what you want when you're trying to find where a deeply nested key lives. But &lt;code&gt;gron&lt;/code&gt; is&lt;br&gt;
read-only. You can't filter and get structured output back. You can't fetch from a URL. The color&lt;br&gt;
situation is nonexistent.&lt;/p&gt;

&lt;p&gt;So I scoped out what I actually needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flatten JSON to dot-notation paths (&lt;code&gt;a.b.c = value&lt;/code&gt;) so I can see structure at a glance&lt;/li&gt;
&lt;li&gt;Filter by key pattern without learning a query language&lt;/li&gt;
&lt;li&gt;Fetch directly from a URL so I don't have to &lt;code&gt;curl | jray&lt;/code&gt; every time&lt;/li&gt;
&lt;li&gt;Color output by default on TTY&lt;/li&gt;
&lt;li&gt;Fast enough that it doesn't feel like overhead when I'm in the middle of something&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bun was the obvious runtime choice. Fast cold start, native TypeScript support, a built-in test&lt;br&gt;
runner, and &lt;code&gt;bun build --compile&lt;/code&gt; that bundles everything into a single self-contained binary. No&lt;br&gt;
Node.js version headaches, no separate install step for the runtime.&lt;/p&gt;


&lt;h2&gt;
  
  
  How it's structured
&lt;/h2&gt;

&lt;p&gt;I kept the architecture intentionally flat. No frameworks, minimal dependencies. The core logic is&lt;br&gt;
split into focused modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flatten.ts&lt;/code&gt; — recursive flatten and unflatten. Takes a nested object and produces a map of
dot-notation paths to primitive values. Unflatten goes the other direction.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;filter.ts&lt;/code&gt; — glob-style pattern matching on the flattened keys. &lt;code&gt;user.*&lt;/code&gt; gives you everything
under &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;**.id&lt;/code&gt; matches any &lt;code&gt;id&lt;/code&gt; at any depth.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;color.ts&lt;/code&gt; — ANSI color output. Keys in one color, values type-aware (strings, numbers, booleans
each get their own).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fetch.ts&lt;/code&gt; — thin wrapper around &lt;code&gt;Bun.fetch&lt;/code&gt; that handles the request, checks content type, and
pipes the response into the flatten pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cli.ts&lt;/code&gt; — argument parsing and wiring. Reads from stdin, a file path, or a URL depending on what
flags you pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing compiles down to a single binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun build ./src/cli.ts &lt;span class="nt"&gt;--compile&lt;/span&gt; &lt;span class="nt"&gt;--outfile&lt;/span&gt; jray
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No runtime dependency on Bun after that. You ship the binary, it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problems I actually ran into
&lt;/h2&gt;

&lt;h3&gt;
  
  
  npm name conflict
&lt;/h3&gt;

&lt;p&gt;First thing I did after writing the initial working version was try to publish as &lt;code&gt;jray&lt;/code&gt;. Already&lt;br&gt;
taken — and not squatted. An actual published package with actual users. So I had to switch to a&lt;br&gt;
scoped package: &lt;code&gt;@siyadkc/jray&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That sounds like a small change but it cascades. The README, the install command in all the&lt;br&gt;
examples, the binary name in &lt;code&gt;package.json&lt;/code&gt;, the GitHub Actions publish step, the Dev.to article&lt;br&gt;
draft I'd already half-written — all of it needed updating. It also changes how users install it&lt;br&gt;
slightly, since scoped packages require the &lt;code&gt;@&lt;/code&gt; prefix. Not a dealbreaker, but check npm before you&lt;br&gt;
name your CLI.&lt;/p&gt;
&lt;h3&gt;
  
  
  Package size bloat
&lt;/h3&gt;

&lt;p&gt;My first publish was heavier than it needed to be. Bun's compiled binary includes the runtime,&lt;br&gt;
which is expected and fine. But the npm package itself had no &lt;code&gt;files&lt;/code&gt; field, so it was packaging&lt;br&gt;
everything — source maps, test files, the &lt;code&gt;node_modules&lt;/code&gt; from dev, local configs. None of that&lt;br&gt;
belongs in a published package.&lt;/p&gt;

&lt;p&gt;Fix was straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dist/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"README.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single field cut the published package weight significantly. Always define &lt;code&gt;files&lt;/code&gt;. The npm&lt;br&gt;
registry doesn't know what's dev and what's production unless you tell it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Windows compatibility
&lt;/h3&gt;

&lt;p&gt;I had a &lt;code&gt;postinstall&lt;/code&gt; script that ran &lt;code&gt;chmod +x&lt;/code&gt; on the binary. Works perfectly on Linux and macOS.&lt;br&gt;
On Windows it throws and the install fails. I wrapped it in a try-catch and updated the docs to note&lt;br&gt;
that Windows users should either run the binary path directly or use WSL. Not the cleanest solution,&lt;br&gt;
but honest about the platform situation. Full Windows support is on the roadmap.&lt;/p&gt;
&lt;h3&gt;
  
  
  GitHub Actions for CI and publishing
&lt;/h3&gt;

&lt;p&gt;The pipeline itself wasn't complicated — install Bun, run tests, publish to npm on a version tag.&lt;br&gt;
But there's one specific thing that tripped me up: not using &lt;code&gt;--frozen-lockfile&lt;/code&gt; in CI.&lt;/p&gt;

&lt;p&gt;Without it, &lt;code&gt;bun install&lt;/code&gt; in CI can silently update the lockfile, which means your CI might be&lt;br&gt;
testing against different dependency versions than what you developed against. Always use:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oven-sh/setup-bun@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bun-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bun install --frozen-lockfile&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bun test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publishing to npm from Actions requires setting &lt;code&gt;NPM_TOKEN&lt;/code&gt; as a repository secret and passing it&lt;br&gt;
through to the publish step. Once that's wired, releases are a tag push away.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
bun add &lt;span class="nt"&gt;-g&lt;/span&gt; @siyadkc/jray

&lt;span class="c"&gt;# Flatten JSON from stdin&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"user":{"name":"Eliot","age":24}}'&lt;/span&gt; | jray
&lt;span class="c"&gt;# user.name = "Eliot"&lt;/span&gt;
&lt;span class="c"&gt;# user.age = 24&lt;/span&gt;

&lt;span class="c"&gt;# Flatten a local file&lt;/span&gt;
jray data.json

&lt;span class="c"&gt;# Filter by key pattern&lt;/span&gt;
jray &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"user.*"&lt;/span&gt; data.json
&lt;span class="c"&gt;# user.name = "Eliot"&lt;/span&gt;
&lt;span class="c"&gt;# user.age = 24&lt;/span&gt;

&lt;span class="c"&gt;# Fetch from a URL and flatten&lt;/span&gt;
jray &lt;span class="nt"&gt;--fetch&lt;/span&gt; https://api.example.com/users/1

&lt;span class="c"&gt;# Disable color (for piping into other tools)&lt;/span&gt;
jray &lt;span class="nt"&gt;--no-color&lt;/span&gt; data.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filter uses glob-style matching against the full dot-path of each key. So &lt;code&gt;user.*&lt;/code&gt; matches one&lt;br&gt;
level under &lt;code&gt;user&lt;/code&gt;, while &lt;code&gt;**.id&lt;/code&gt; would match any &lt;code&gt;id&lt;/code&gt; at any depth in the document. It's&lt;br&gt;
intentionally simpler than jq's filter syntax — the goal is something you can remember without&lt;br&gt;
looking it up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it sits vs the competition
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gron&lt;/code&gt; is the closest thing and the most direct comparison. It also flattens to dot-notation and&lt;br&gt;
it's what made me realize there was a gap to fill. But &lt;code&gt;gron&lt;/code&gt; has no color output, no URL fetching,&lt;br&gt;
and no NDJSON streaming support. &lt;code&gt;jray&lt;/code&gt; has all three.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; is in a different category. It's a transformation tool — you write expressions, you reshape&lt;br&gt;
data, you do complex operations. &lt;code&gt;jray&lt;/code&gt; is an inspection tool. You throw data at it and you&lt;br&gt;
understand the structure immediately. They're complementary more than they're competing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fx&lt;/code&gt; is another one that comes up. Interactive, powerful, but it's a TUI. If you're in a script or&lt;br&gt;
piping between tools, you don't want a TUI. &lt;code&gt;jray&lt;/code&gt; is designed to be pipeline-friendly from the&lt;br&gt;
start.&lt;/p&gt;

&lt;p&gt;The longer-term vision is broader than just JSON. Supporting YAML and TOML with the same flatten&lt;br&gt;
and filter interface makes it a universal structured-data CLI. There's also TOON (Token Oriented&lt;br&gt;
Object Notation), which is an emerging format that's genuinely useful for LLM developer workflows&lt;br&gt;
since it's optimized for tokenization. That's a real differentiator if the format gets traction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The launch
&lt;/h2&gt;

&lt;p&gt;Published to npm, wrote this article, posted on X . Also shared in the Bun&lt;br&gt;
Discord since the community there is small enough that people actually read it.&lt;/p&gt;

&lt;p&gt;The GitHub Pages site is live. It's getting redesigned right now — moving toward a flatter, more&lt;br&gt;
minimal aesthetic with an interactive playground section where you can paste JSON directly in the&lt;br&gt;
browser and see the flatten and filter output in real time. No install required to try it. That&lt;br&gt;
single feature will probably do more for adoption than any amount of posting.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;I'd define the &lt;code&gt;files&lt;/code&gt; field before the first publish, not after. I'd also check npm for name&lt;br&gt;
conflicts before I was emotionally attached to the name. And I'd set up the CI pipeline before&lt;br&gt;
the first push to &lt;code&gt;main&lt;/code&gt; so I wasn't debugging GitHub Actions at the same time I was trying to&lt;br&gt;
ship.&lt;/p&gt;

&lt;p&gt;The architecture decisions held up fine. Bun was the right call. The modular structure meant I&lt;br&gt;
could add &lt;code&gt;fetch.ts&lt;/code&gt; and &lt;code&gt;color.ts&lt;/code&gt; without touching the core flatten logic. That's about as much&lt;br&gt;
as you can ask from a first project's structure.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun add &lt;span class="nt"&gt;-g&lt;/span&gt; @siyadkc/jray
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/siyadhkc/jray" rel="noopener noreferrer"&gt;github.com/siyadhkc/jray&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're working on something similar with Bun, or you have opinions on the filter API design,&lt;br&gt;
drop a comment. Always curious how other people are solving the same problems.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is my first article writing on this platform so if i made any narration mistake or anything feel free to tell me, i will improve my writing skill and the way of telling this&lt;br&gt;
-- let's get connected --&lt;/em&gt; &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>From Hardware to Hacking: How Understanding Systems Changed My Approach to Security</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Wed, 15 Apr 2026 15:56:13 +0000</pubDate>
      <link>https://dev.to/siyadhkc/from-hardware-to-hacking-how-understanding-systems-changed-my-approach-to-security-4dh4</link>
      <guid>https://dev.to/siyadhkc/from-hardware-to-hacking-how-understanding-systems-changed-my-approach-to-security-4dh4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I didn’t start my journey in cybersecurity. I started with hardware and networking, learning how systems actually function at a low level — how machines communicate, how configurations impact behavior, and how failures happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift to Development
&lt;/h2&gt;

&lt;p&gt;Later, I moved into development, working with Python and full-stack technologies. Building applications helped me understand how software is structured, but it also revealed something important:&lt;/p&gt;

&lt;p&gt;Most applications are not built with security as a priority.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turning Point
&lt;/h2&gt;

&lt;p&gt;This realization pushed me toward cybersecurity, specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web application security&lt;/li&gt;
&lt;li&gt;API security&lt;/li&gt;
&lt;li&gt;Cloud platforms (AWS, Azure, GCP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of just building systems, I began analyzing how they can be exploited.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Different Way of Thinking
&lt;/h2&gt;

&lt;p&gt;The biggest shift was in mindset:&lt;/p&gt;

&lt;p&gt;From:&lt;br&gt;
“How do I build this system?”&lt;/p&gt;

&lt;p&gt;To:&lt;br&gt;
“How can this system fail or be attacked?”&lt;/p&gt;

&lt;p&gt;This dual perspective — builder and attacker — is critical in modern cybersecurity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;I’ll be sharing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Practical security insights&lt;/li&gt;
&lt;li&gt;Real-world learning experiences&lt;/li&gt;
&lt;li&gt;Breakdowns of vulnerabilities and misconfigurations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re interested in understanding systems deeply — not just building them, but also testing their limits — follow along.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>career</category>
    </item>
    <item>
      <title>I built a modern alternative to gron using Bun and TypeScript</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Sun, 12 Apr 2026 17:16:36 +0000</pubDate>
      <link>https://dev.to/siyadhkc/i-built-a-modern-alternative-to-gron-using-bun-and-typescript-3358</link>
      <guid>https://dev.to/siyadhkc/i-built-a-modern-alternative-to-gron-using-bun-and-typescript-3358</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Working with JSON in the terminal is painful. &lt;code&gt;jq&lt;/code&gt; is powerful &lt;br&gt;
but requires learning a whole query language. &lt;code&gt;grep&lt;/code&gt; works but &lt;br&gt;
matches values too — not just paths.&lt;/p&gt;

&lt;p&gt;For example if I search for "name" in my JSON:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;grep matches &lt;code&gt;json.user.name&lt;/code&gt; ✅ (correct)&lt;/li&gt;
&lt;li&gt;grep also matches &lt;code&gt;json.bio = "my name is Alice"&lt;/code&gt; ❌ (wrong)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built &lt;strong&gt;jray&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What jray does
&lt;/h2&gt;

&lt;p&gt;jray flattens any JSON into one line per value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;jray data.json
json.organization.name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Acme Corporation"&lt;/span&gt;
json.users[0].name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Alice Pemberton"&lt;/span&gt;
json.users[0].role &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;
json.users[0].active &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;json.billing.plan &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"enterprise"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can grep it, awk it, pipe it — using tools you already know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four commands that cover everything
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Flatten with color output&lt;/span&gt;
jray data.json

&lt;span class="c"&gt;# Fetch a URL directly — no curl needed&lt;/span&gt;
jray https://jsonplaceholder.typicode.com/users/1

&lt;span class="c"&gt;# Filter by path only (never matches values)&lt;/span&gt;
jray data.json &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"billing"&lt;/span&gt;

&lt;span class="c"&gt;# Extract any subtree as clean JSON&lt;/span&gt;
jray data.json &lt;span class="nt"&gt;--select&lt;/span&gt; &lt;span class="s2"&gt;"billing"&lt;/span&gt;

&lt;span class="c"&gt;# Print just the values&lt;/span&gt;
jray data.json &lt;span class="nt"&gt;--values&lt;/span&gt;

&lt;span class="c"&gt;# Reconstruct original JSON&lt;/span&gt;
jray data.json | jray &lt;span class="nt"&gt;--ungron&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The comparison with existing tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;jray&lt;/th&gt;
&lt;th&gt;jq&lt;/th&gt;
&lt;th&gt;gron&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flatten to lines&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconstruct JSON&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter by path&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extract subtree&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fetch from URLs&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Color output&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No query language&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript / modern&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I learned building it
&lt;/h2&gt;

&lt;p&gt;This was my first open source CLI tool. Key lessons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Separate your CLI from your logic&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;cli.ts&lt;/code&gt; handles only I/O — reading files, printing output.&lt;br&gt;
&lt;code&gt;flatten.ts&lt;/code&gt;, &lt;code&gt;filter.ts&lt;/code&gt; etc. know nothing about terminals.&lt;br&gt;
This makes everything testable in isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Use &lt;code&gt;unknown&lt;/code&gt; not &lt;code&gt;any&lt;/code&gt; in TypeScript&lt;/strong&gt;&lt;br&gt;
JSON is dynamic but that doesn't mean &lt;code&gt;any&lt;/code&gt;. Using &lt;code&gt;unknown&lt;/code&gt; &lt;br&gt;
forces you to check types before using values — much safer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Recursive functions are perfect for JSON&lt;/strong&gt;&lt;br&gt;
JSON is a tree. Walking a tree with recursion is the &lt;br&gt;
cleanest pattern — each call handles one node and &lt;br&gt;
delegates children back to itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Bun is genuinely fast and simple&lt;/strong&gt;&lt;br&gt;
Native TypeScript, built-in fetch, fast startup. &lt;br&gt;
For a CLI tool, Bun is a great choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The README matters as much as the code&lt;/strong&gt;&lt;br&gt;
Nobody uses a tool they can't understand in 30 seconds.&lt;br&gt;
Writing clear examples with real output is as important &lt;br&gt;
as writing the code itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install and try it
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @siyadkc/jray
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jray https://jsonplaceholder.typicode.com/users/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/siyadhkc/Jray" rel="noopener noreferrer"&gt;github.com/siyadhkc/Jray&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;for more info Website: &lt;a href="https://siyadhkc.github.io/Jray/" rel="noopener noreferrer"&gt;https://siyadhkc.github.io/Jray/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;This is my first open source tool. Feedback on the code &lt;br&gt;
quality is very welcome — especially from experienced &lt;br&gt;
TypeScript developers!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>bunjs</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
  </channel>
</rss>
