<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
  <title>Nirjar Goswami — Articles</title>
  <link>https://nirjar.me/articles</link>
  <atom:link href="https://nirjar.me/rss.xml" rel="self" type="application/rss+xml" />
  <description>Writing about things I’ve experienced and worked on, not just ideas I’ve read about.</description>
  <language>en-us</language>
  <lastBuildDate>Fri, 18 Sep 2026 12:30:10 GMT</lastBuildDate>
  <generator>nirjar.me prerender</generator>
  <item>
    <title>Context engineering: why AGENTS.md beats a better model</title>
    <link>https://nirjar.me/articles/context-engineering-why-agents-md-beats-a-better-model</link>
    <guid isPermaLink="true">https://nirjar.me/articles/context-engineering-why-agents-md-beats-a-better-model</guid>
    <description>Why repository-level rules and project context can matter more for AI-generated code quality than simply switching to a stronger model.</description>
    <content:encoded>&lt;h2&gt;Why rules matter more than the model&lt;/h2&gt;&lt;p&gt;I keep seeing the same debate play out: which model writes the best code. Claude versus GPT versus whatever shipped this week. People benchmark them on leaderboards, argue about which one &amp;quot;feels smarter,&amp;quot; and treat the model choice like it&amp;apos;s the deciding factor.&lt;/p&gt;&lt;p&gt;It isn&amp;apos;t, and I want to be specific about what I mean before this turns into another &amp;quot;prompt better&amp;quot; post.&lt;/p&gt;&lt;p&gt;I&amp;apos;m not talking about which model makes better decisions. A stronger model will still reason through an ambiguous problem better than a weaker one, and no amount of documentation fixes that gap. I&amp;apos;m talking about code quality: how easy it is to understand, maintain, reuse, and keep consistent. Most of the time, when a small problem comes up, you won&amp;apos;t always hand that task to AI. Sometimes you&amp;apos;ll handle it yourself. And with today&amp;apos;s tools to make the codebase more understandable and reusable, why not keep the codebase consistent?&lt;/p&gt;&lt;p&gt;I&amp;apos;ve seen AI write separate and multiple coding patterns for different things across a backend or website simply because it didn&amp;apos;t have enough context. In my own repos, I&amp;apos;ve repeatedly gotten cleaner, more reusable, easier-to-maintain, and more understandable code from a mid-tier model with a real rules file than from a frontier model working from a blank context.&lt;/p&gt;&lt;h2&gt;The thing almost nobody sets up&lt;/h2&gt;&lt;p&gt;Most AI coding agents support a repo-level instructions file. Claude Code reads CLAUDE.md. Other agents read AGENTS.md or their own variant. The idea is simple: instead of repeating your conventions in every prompt, you write them once and the agent reads them automatically at the start of a session.&lt;/p&gt;&lt;p&gt;Plenty of teams still aren&amp;apos;t doing this. They&amp;apos;ll pay for the most expensive model tier available and then hand it a blank context window every single time, as if the agent is supposed to guess their branch naming scheme or figure out on its own that a particular directory is legacy code nobody wants touched.&lt;/p&gt;&lt;p&gt;A rules file isn&amp;apos;t optional polish. It&amp;apos;s the difference between an agent that writes code matching your project and one that writes code matching whatever the internet trained it on.&lt;/p&gt;&lt;h2&gt;What actually goes in one&lt;/h2&gt;&lt;p&gt;This isn&amp;apos;t a generic checklist you copy-paste into every repo. That&amp;apos;s the mistake I see most often, someone finds a template on GitHub, drops it in unchanged, and wonders why the output still feels off. The file has to reflect the actual codebase in front of it. That said, here&amp;apos;s roughly what I cover, grouped by what each part is actually doing.&lt;/p&gt;&lt;h2&gt;The baseline&lt;/h2&gt;&lt;p&gt;The facts an agent can&amp;apos;t infer from the code alone, no matter how well it reads.&lt;/p&gt;&lt;p&gt;- Stack and versions. Exact framework versions, not &amp;quot;React.&amp;quot; I&amp;apos;ve had agents suggest patterns from three major versions ago because nothing told them otherwise.&lt;/p&gt;&lt;p&gt;- File and folder conventions. Where new components live, how modules are named, what the test file naming pattern is. Agents will invent their own scheme if you don&amp;apos;t give them one, and it usually won&amp;apos;t match yours.&lt;/p&gt;&lt;p&gt;- Existing utilities and abstractions. A running list of &amp;quot;we already have a function for this, don&amp;apos;t write a new one.&amp;quot; This is probably the single highest-value section, and the one people skip most.&lt;/p&gt;&lt;h2&gt;The guardrails&lt;/h2&gt;&lt;p&gt;The parts that stop an agent from doing technically-reasonable things you don&amp;apos;t actually want.&lt;/p&gt;&lt;p&gt;- What not to touch. Legacy modules, generated files, anything mid-migration. This one saves more time than almost anything else on the list.&lt;/p&gt;&lt;p&gt;- Security constraints. Anything touching auth, secrets, or user data gets flagged explicitly, because this is the category where I want zero improvisation.&lt;/p&gt;&lt;p&gt;- Scope discipline. What counts as in scope for a given change and what doesn&amp;apos;t. Without this, an agent will &amp;quot;improve&amp;quot; formatting in a file it was never asked to touch, and your diff triples in size for no reason.&lt;/p&gt;&lt;p&gt;- Solution sizing. A cap on how big a fix should be relative to the problem. Left alone, a model will reach for a new abstraction layer or config system when a two-line change would do.&lt;/p&gt;&lt;h2&gt;The process&lt;/h2&gt;&lt;p&gt;How work gets done, not just what it should look like when it&amp;apos;s finished.&lt;/p&gt;&lt;p&gt;- Testing requirements. Whether tests are mandatory for new code, which framework, and what coverage actually matters versus what&amp;apos;s just noise.&lt;/p&gt;&lt;p&gt;- Error handling patterns. How your codebase actually handles failures, not the generic try/catch the model defaults to.&lt;/p&gt;&lt;p&gt;- Commit and PR conventions. Message format, branch naming, whether commits should be atomic or squashed.&lt;/p&gt;&lt;p&gt;- A required sequence. Understand, plan, implement, verify, review, spelled out explicitly rather than assumed. Left to itself, an agent will implement first and verify never, or call something done without running the tests it claims to have run.&lt;/p&gt;&lt;p&gt;None of this is exotic. It&amp;apos;s the stuff a competent new hire would ask about in their first week. The difference is the agent won&amp;apos;t ask. It&amp;apos;ll just guess, and the guess will look plausible enough that you might not catch it in review.&lt;/p&gt;&lt;h2&gt;Domain rules aren&amp;apos;t style rules&lt;/h2&gt;&lt;p&gt;Everything above keeps the code looking consistent. It doesn&amp;apos;t keep it correct, and those are different problems.&lt;/p&gt;&lt;p&gt;In one of my forensic-tooling projects, closed cases can never transition back to open. Case IDs are immutable once assigned. Every mutation has to travel through the application service rather than touching the ORM directly from a CLI layer, no shortcuts. None of that is a formatting preference. It&amp;apos;s a business rule the codebase depends on, and it&amp;apos;s exactly the kind of thing an agent will violate without hesitation if you don&amp;apos;t spell it out, because &amp;quot;add a quick fix that reopens a case&amp;quot; reads as a completely reasonable request until you know it isn&amp;apos;t.&lt;/p&gt;&lt;p&gt;If a project has hard invariants like this, they get their own section, separate from general style. Burying &amp;quot;cases can never reopen&amp;quot; in the same list as &amp;quot;we use camelCase&amp;quot; means it gets read with the same weight as a formatting preference, and it&amp;apos;s not one. It&amp;apos;s the one rule in the whole file you genuinely can&amp;apos;t afford to have ignored.&lt;/p&gt;&lt;h2&gt;Different models, different files&lt;/h2&gt;&lt;p&gt;One thing I&amp;apos;ve had to adjust: the same rules file doesn&amp;apos;t perform identically across models. Some agents follow terse, bulleted instructions well. Others need more explicit examples of the pattern you want, a before-and-after, not just a description of it. &lt;/p&gt;&lt;p&gt;On one project, &amp;quot;reuse the existing service layer&amp;quot; was enough for one agent to consistently find and extend the right service. Another agent read the same line and still wrote a parallel implementation, and only stopped once I added a concrete example showing which service to reuse and which pattern not to reintroduce. I&amp;apos;ve ended up keeping model-specific variants for a couple of projects rather than one universal file, because instructions tuned for one agent produced noticeably worse adherence on another. It&amp;apos;s annoying to maintain, but it&amp;apos;s less annoying than fixing the output afterward.&lt;/p&gt;&lt;h2&gt;The part people skip: making the codebase legible to a new agent&lt;/h2&gt;&lt;p&gt;Rules files handle behavior. They don&amp;apos;t handle context, and a repo of any real size has a lot of it that isn&amp;apos;t visible from the code alone. Why a particular service is structured the way it is. Which parts are being actively migrated. What broke last time someone touched the payment logic without reading the three-year-old comment explaining why.&lt;/p&gt;&lt;p&gt;### Building the index&lt;/p&gt;&lt;p&gt;I keep this in a separate notes system, Obsidian in my case, though Notion works the same way. Not as a replacement for code comments, but as an index: what each major component does, why certain decisions were made, what&amp;apos;s currently in flux. When I bring a new AI agent into an unfamiliar part of the codebase, pointing it at that index gets it oriented faster than letting it infer the architecture from file names and hope for the best.&lt;/p&gt;&lt;p&gt;Think about onboarding a new engineer. You wouldn&amp;apos;t hand them the repo and nothing else. You&amp;apos;d walk them through the architecture, or at least point them at a doc that does. AI agents need the same thing, and most people don&amp;apos;t bother because it feels excessive to write documentation for something that isn&amp;apos;t human. It isn&amp;apos;t excessive. It&amp;apos;s the same onboarding problem, just with a different kind of new hire.&lt;/p&gt;&lt;h2&gt;Where this actually lands&lt;/h2&gt;&lt;p&gt;None of this makes a weak model outreason a strong one. It won&amp;apos;t fix bad architectural decisions or catch a subtle logic error the model wasn&amp;apos;t capable of spotting in the first place. What it does is close the gap on everything downstream of the decision: the actual code that gets written, how well it fits what&amp;apos;s already there, how much cleanup you&amp;apos;re doing afterward.&lt;/p&gt;&lt;p&gt;A better model gives you better reasoning. Better repository context gives that reasoning somewhere useful to land.&lt;/p&gt;</content:encoded>
    <category>Artificial Intelligence</category>
    <pubDate>Wed, 16 Sep 2026 12:00:00 GMT</pubDate>
  </item>
  <item>
    <title>AI agents have permissions now — but who&apos;s in control?</title>
    <link>https://nirjar.me/articles/ai-agents-have-permissions-now-but-whos-in-control</link>
    <guid isPermaLink="true">https://nirjar.me/articles/ai-agents-have-permissions-now-but-whos-in-control</guid>
    <description>A chatbot gives you bad information. An agent takes bad actions. Why agent security is an authorization problem, not a model-quality problem — and what to actually do about it.</description>
    <content:encoded>&lt;h2&gt;A chatbot can lie to you. An agent can act on it.&lt;/h2&gt;&lt;p&gt;A chatbot gives you bad information. You read it, you&amp;apos;re annoyed, you move on.&lt;/p&gt;&lt;p&gt;An agent takes a bad action. It merges the PR. It deletes the table. It sends the wire.&lt;/p&gt;&lt;p&gt;That&amp;apos;s the whole gap this post is about. Everyone&amp;apos;s already built around the fact that LLMs get things wrong — review steps, guardrails, fallback logic. Nobody&amp;apos;s finished figuring out what happens once that same, still-fallible system has a login.&lt;/p&gt;&lt;p&gt;Because it already does. Somewhere in your stack right now, there&amp;apos;s probably an agent holding a GitHub token, an AWS role, read access to a database, maybe a Slack bot token or an inbox it can send from. Some of them touch Kubernetes. A few touch production directly.&lt;/p&gt;&lt;p&gt;I work in identity and access management, so I&amp;apos;ll say my bias up front: most of the industry is treating this as a model-quality problem when it&amp;apos;s really an authorization problem. You don&amp;apos;t fix bad access control by making the thing asking for access smarter. You fix it by not giving it access it doesn&amp;apos;t need.&lt;/p&gt;&lt;h2&gt;The ladder nobody drew on purpose&lt;/h2&gt;&lt;p&gt;Nobody designed this progression on purpose. Each step just looked like a small, reasonable move up from the last one.&lt;/p&gt;&lt;p&gt;A chatbot answers questions and does nothing. An AI assistant drafts things and still does nothing on its own. An AI agent takes actions and needs tool access. An agent with tools calls APIs, reads and writes real systems. An agent with identity authenticates as something and holds credentials. An agent with production access can change the state of the business.&lt;/p&gt;&lt;p&gt;`Let it draft the PR` becomes `let it open the PR`. That becomes `let it merge small PRs`. Eighteen months later, nobody quite remembers agreeing to any of it, but there&amp;apos;s a bot account with write access to main and a Slack channel full of its commits.&lt;/p&gt;&lt;p&gt;Access always drifts this way, human or not. It gets granted under pressure and rarely revoked, because nobody wants to be the one who breaks the demo by tightening the scope.&lt;/p&gt;&lt;p&gt;Agents just move through that drift faster. Provisioning one is a config change, not a ticket to IT.&lt;/p&gt;&lt;h2&gt;The token question that doesn&amp;apos;t have a clean answer&lt;/h2&gt;&lt;p&gt;Here&amp;apos;s the one that actually gets to me: if an agent is holding an OAuth token, whose identity is that token?&lt;/p&gt;&lt;p&gt;Not really the user&amp;apos;s. The user isn&amp;apos;t the one calling the API at 3 a.m. while the agent chews through a backlog.&lt;/p&gt;&lt;p&gt;Not really the agent&amp;apos;s either, since `the agent` isn&amp;apos;t a stable thing. It&amp;apos;s a process that spins up, does something, and disappears — sometimes running a different model version or a different prompt than the one that fetched the token in the first place.&lt;/p&gt;&lt;p&gt;Traditional IAM has a clean answer for the human case: the token represents a person, that person is accountable, you can ask them what happened. Service accounts stretched that model already, but they still represented one fixed, well-understood job. An agent breaks it further, because it reasons — it decides at runtime which tool to call and why. What it does is a function of the prompt, the context, and whatever it decided made sense in the moment, not a role you can point to on a diagram.&lt;/p&gt;&lt;p&gt;A 2026 Cloud Security Alliance survey found that 68% of organizations can&amp;apos;t reliably tell an AI agent&amp;apos;s activity apart from a human&amp;apos;s in their own logs. That&amp;apos;s not a future risk. That&amp;apos;s the current state of visibility at most companies running agents today.&lt;/p&gt;&lt;h2&gt;So who&amp;apos;s responsible when it breaks something&lt;/h2&gt;&lt;p&gt;This is the question every security team gets cornered into answering — usually right after an incident, the worst possible time to be figuring out policy for the first time.&lt;/p&gt;&lt;p&gt;Is it the developer who wrote the system prompt? The team that granted the IAM role? The vendor whose model ran `DROP TABLE` because it misread the instruction? The company that deployed it with no human approval gate on destructive actions?&lt;/p&gt;&lt;p&gt;My honest answer: the org that granted the access is accountable, full stop. Same as you&amp;apos;re accountable for a junior engineer&amp;apos;s mistakes if you hand them prod credentials on day one with no review process. `The AI did it` isn&amp;apos;t a liability shield — it&amp;apos;s an admission that nobody scoped the blast radius before flipping it on.&lt;/p&gt;&lt;p&gt;The fix isn&amp;apos;t a better model. It&amp;apos;s the boring IAM work most teams were already planning to get to eventually.&lt;/p&gt;&lt;h2&gt;Should an agent be allowed to create infrastructure?&lt;/h2&gt;&lt;p&gt;I go back and forth on this one more than anything else here.&lt;/p&gt;&lt;p&gt;The case for yes: agents are genuinely good at infrastructure-as-code. Terraform, CloudFormation — structured, pattern-heavy work is exactly where LLMs are strong, and letting an agent stand up a dev environment on request is a real productivity win.&lt;/p&gt;&lt;p&gt;The case for no, at least not yet: infrastructure creation can quietly cost you money, open a network path, or leave a resource nobody remembers to tear down — and it can do all three without tripping anything that looks like an incident. A confused agent with `terraform apply` access isn&amp;apos;t just a data risk. It&amp;apos;s a standing invitation to run up your cloud bill or open a door you find in an audit eighteen months later.&lt;/p&gt;&lt;p&gt;Where I&amp;apos;ve landed, for anything touching a real environment: agents generate the plan. A human approves the apply. Not because the code is usually wrong — half the time it&amp;apos;s fine. Because `fine most of the time` is a bad bar for anything with a real cost and a real blast radius attached to it.&lt;/p&gt;&lt;h2&gt;Permanent credentials are the mistake we already know not to make&lt;/h2&gt;&lt;p&gt;We learned this with humans a decade ago. Now everyone&amp;apos;s relearning it with agents.&lt;/p&gt;&lt;p&gt;A recent industry report on non-human identity management put the share of non-human credentials that never get rotated on any recommended schedule at 71%. Only about 20% of organizations have a real process for offboarding or revoking API keys once they&amp;apos;re no longer needed.&lt;/p&gt;&lt;p&gt;Estimates of the non-human-to-human identity ratio vary by source — CyberArk has cited 80:1, the Cloud Security Alliance a more conservative 45:1. Every count agrees on the direction. It&amp;apos;s growing, and agents are the fastest-growing slice of it.&lt;/p&gt;&lt;p&gt;I think about this constantly on AegisMesh, the IAM platform I&amp;apos;ve been building. The whole deny-first design exists because a static, long-lived credential is a liability from the moment it&amp;apos;s created — the only question is when it gets used against you, not if.&lt;/p&gt;&lt;p&gt;For anything with real-world write access, an agent shouldn&amp;apos;t hold a permanent secret. Short-lived, scoped, automatically expiring credentials, reissued per task, are what actually survive a compromised prompt or a misbehaving tool call. There are narrower cases — a sandboxed agent with no real write access — where a longer-lived credential is a defensible trade-off. But as a default, permanent credentials are the same mistake we already made with service accounts, just moving faster this time.&lt;/p&gt;&lt;h2&gt;What this looks like when it goes wrong&lt;/h2&gt;&lt;p&gt;All of this is easy to wave off as theoretical until you walk through one concrete path. Say a developer types something completely ordinary into a deployment agent: `Deploy the latest version to production.`&lt;/p&gt;&lt;p&gt;Under the hood that turns into a chain: the agent reads the GitHub repo, generates the Terraform or deployment config, calls the AWS API, assumes an IAM role — and production changes.&lt;/p&gt;&lt;p&gt;Every arrow in that chain is a place this can go wrong. None of them require anything as dramatic as a hack.&lt;/p&gt;&lt;p&gt;What if the repository contains a prompt injection? A README comment, a commit message, a config file the agent reads for context — any of it can carry text crafted to look like an instruction. The agent isn&amp;apos;t separating `content I&amp;apos;m reading` from `commands I should follow` as cleanly as we&amp;apos;d like. If that hidden text says to also update the payments webhook URL, a capable agent may just do it, as part of the job it thinks it was asked to do.&lt;/p&gt;&lt;p&gt;What if the agent interprets `latest version` incorrectly? Latest merged commit, latest tagged release, latest commit on a long-lived release branch — a human on the deploy team catches that ambiguity instantly. An agent that guesses wrong doesn&amp;apos;t throw an error. It deploys, confidently, and the first sign of trouble is a production incident.&lt;/p&gt;&lt;p&gt;What if the deployment tool has broader permissions than the agent actually needs? This is the one that turns a small mistake into a big one. If the IAM role behind that Terraform call has write access to the whole account instead of one service, every failure above inherits that same wide blast radius. The agent didn&amp;apos;t need admin access to do its job. It had admin access because scoping the role precisely was more work than attaching AdministratorAccess and moving on — which is how most human IAM messes start too.&lt;/p&gt;&lt;p&gt;None of these require the agent to be hacked. Each one is the agent doing exactly what it was built to do, with one link in the chain slightly wrong. There&amp;apos;s often no exploit to point to afterward. Just a permission that was wider than it needed to be, and an action taken faster than anyone could review it.&lt;/p&gt;&lt;h2&gt;The insider threat framing is right, and it&amp;apos;s worse than it sounds&lt;/h2&gt;&lt;p&gt;CyberArk&amp;apos;s VP of cyber research, Lavi Lazarovitz, has described AI agents as a new class of digital coworker — one that reasons and acts autonomously instead of following a fixed script. Security researchers writing about agent identity have started calling this exactly what it is: a new insider threat.&lt;/p&gt;&lt;p&gt;That&amp;apos;s the right frame. An insider threat is dangerous specifically because it already has legitimate access. The damage doesn&amp;apos;t come from breaking in — it comes from what a trusted identity does once it&amp;apos;s inside. That&amp;apos;s the shape of an overprivileged agent: no breach required, just a normal, authenticated action that happened to be the wrong one, at machine speed.&lt;/p&gt;&lt;p&gt;And it&amp;apos;s not staying single-agent. As multi-agent setups become normal — a coding agent handing off to a deployment agent, a support agent pulling context from a data agent — you get a new failure mode: agents manipulating or exploiting other agents. Prompt injection isn&amp;apos;t just a way to fool a person&amp;apos;s chatbot anymore. It&amp;apos;s a way to pivot from one compromised agent into whatever the next one in the chain is trusted to touch. Lateral movement, wearing an AI costume. Most agent architectures have zero segmentation between agents the way we&amp;apos;d expect between network zones.&lt;/p&gt;&lt;h2&gt;What this actually means if you&amp;apos;re building with agents right now&lt;/h2&gt;&lt;p&gt;Not a grand theory. Just what I&amp;apos;d actually do.&lt;/p&gt;&lt;p&gt;Give every agent a named human owner. If nobody can explain why an agent has an identity, it shouldn&amp;apos;t have one.&lt;/p&gt;&lt;p&gt;Scope permissions to the task, not the team. `Reads the customer table` isn&amp;apos;t a scope. `Reads order status for the ticket it&amp;apos;s currently handling` is closer.&lt;/p&gt;&lt;p&gt;Default to no permanent credentials for anything with real write access. If your framework doesn&amp;apos;t support short-lived tokens per task, that&amp;apos;s a gap to close before you scale usage.&lt;/p&gt;&lt;p&gt;Put a human approval gate on anything destructive or irreversible — prod deploys, infra creation, moving money, deleting data.&lt;/p&gt;&lt;p&gt;Log agent actions as agent actions. Don&amp;apos;t let them blend into human audit trails where nobody can tell the difference later.&lt;/p&gt;&lt;p&gt;Treat agent-to-agent calls as a trust boundary, not an internal detail. You wouldn&amp;apos;t let one microservice call another with no auth check. Don&amp;apos;t let one agent hand off to another with no check on what it&amp;apos;s allowed to ask for.&lt;/p&gt;&lt;p&gt;None of this is exotic. It&amp;apos;s the same deny-first, least-privilege thinking IAM has preached for years, applied to an identity type that reasons about what it wants before it asks for it.&lt;/p&gt;&lt;p&gt;The chatbot era asked whether you could trust what it says. The agent era is asking whether you can trust what it&amp;apos;s allowed to do. That&amp;apos;s the harder question, and it&amp;apos;s the one that actually matters now.&lt;/p&gt;</content:encoded>
    <category>Artificial Intelligence</category>
    <pubDate>Tue, 15 Sep 2026 12:00:00 GMT</pubDate>
  </item>
  <item>
    <title>Why AI can&apos;t just rewrite Windows</title>
    <link>https://nirjar.me/articles/why-ai-cant-just-rewrite-windows</link>
    <guid isPermaLink="true">https://nirjar.me/articles/why-ai-cant-just-rewrite-windows</guid>
    <description>Why rewriting Windows with AI is not primarily a code-generation problem, but a problem of scale, complexity, backward compatibility, and decades of accumulated dependencies.</description>
    <content:encoded>&lt;h2&gt;The scale is hard to actually picture&lt;/h2&gt;&lt;p&gt;4,000 engineers. 1,760 daily builds across 440 branches. A git repository so large that Microsoft had to invent a custom file system just to manage it because standard Git couldn&amp;apos;t handle it.&lt;/p&gt;&lt;p&gt;50 million lines of code. 3.5 million files. Close to 300 GB. Built, broken, patched, and shipped for 41 years without stopping, since Windows 1.0 in 1985.&lt;/p&gt;&lt;p&gt;I work on things that feel complex to me. AegisMesh, the IAM platform I&amp;apos;ve been building, has RBAC, a full DevSecOps pipeline, observability. It still feels manageable because I can hold most of it in my head on a decent day.&lt;/p&gt;&lt;p&gt;Nobody holds Windows in their head. I don&amp;apos;t think any single team does either.&lt;/p&gt;&lt;h2&gt;The problem isn&amp;apos;t the code. It&amp;apos;s everything the code is touching.&lt;/h2&gt;&lt;p&gt;Complexity in software doesn&amp;apos;t scale the way people expect.&lt;/p&gt;&lt;p&gt;5 components means 10 possible interactions. 100 components is nearly 5,000. Windows, with millions of components, has interactions that are for practical purposes impossible to fully account for.&lt;/p&gt;&lt;p&gt;Fred Brooks put it directly in No Silver Bullet (1986): &amp;quot;The elements interact with each other in some nonlinear fashion, and the complexity of the whole increases much more than linearly.&amp;quot;&lt;/p&gt;&lt;p&gt;And you can&amp;apos;t test your way out of it either. Some bugs only appear at production scale, with real users doing things nobody anticipated. No test suite can replicate 41 years of that.&lt;/p&gt;&lt;h2&gt;AI can&amp;apos;t even read Windows, let alone rewrite it&lt;/h2&gt;&lt;p&gt;Models work within a context window. Working memory, basically. Code averages about 18 tokens per line. 50 million lines means roughly 900 million tokens.&lt;/p&gt;&lt;p&gt;Gemini 3 Pro, currently the largest available context window, handles around 10 million tokens. Claude Opus 4.6 handles around 1 million. That makes the Windows codebase 90x to 900x larger than what any AI can process at once.&lt;/p&gt;&lt;p&gt;Any AI working on Windows is always working blind to most of it. It sees one street, not the city. It can write something that looks completely correct locally and silently break something three subsystems away.&lt;/p&gt;&lt;p&gt;I hit a smaller version of this while building DeployLens. An AI tool suggested a change that made total sense in isolation. It broke a GitHub Actions integration because the AI had no idea that dependency existed. On a 900 million token codebase, that problem doesn&amp;apos;t just scale, it multiplies in ways that are genuinely hard to reason about.&lt;/p&gt;&lt;h2&gt;AI already struggles at large codebases&lt;/h2&gt;&lt;p&gt;The numbers on this aren&amp;apos;t great. According to a Veracode study, 45% of AI-generated code has security vulnerabilities. XSS failure rate in AI-generated code sits at 86%. In large, mature codebases, fewer than 44% of AI suggestions even get accepted. A METR study found that experienced developers are actually 19% slower on real tasks in large codebases when using AI tools.&lt;/p&gt;&lt;p&gt;AI can&amp;apos;t reliably understand dependency graphs, build systems, or architectural conventions. It moves a file and breaks every import pointing to the old location. Now imagine that at 50 million lines of scale.&lt;/p&gt;&lt;h2&gt;Backward compatibility is the thing nobody wants to deal with&lt;/h2&gt;&lt;p&gt;Say AI wrote perfect code. Every function, every module, exactly right.&lt;/p&gt;&lt;p&gt;Still not done.&lt;/p&gt;&lt;p&gt;Control Panel still exists in Windows because drivers from 2012 call its functions. Windows has to run software from decades ago, drivers from manufacturers that don&amp;apos;t exist anymore, enterprise apps embedded in hospital systems, bank infrastructure, and government workflows that nobody fully understands anymore.&lt;/p&gt;&lt;p&gt;When Microsoft ships an update, they&amp;apos;re also quietly guaranteeing that every DLL, every registry interaction, every weird edge case from 1998 still behaves the same way. Some behaviors exist specifically because an app from 2003 depended on a bug. Fixing the bug breaks the app. So the bug stays. That&amp;apos;s not a failure, that&amp;apos;s the deal made with the ecosystem over 40 years.&lt;/p&gt;&lt;p&gt;An AI rewriting from scratch can&amp;apos;t know any of that unless someone wrote it down. Most of it wasn&amp;apos;t.&lt;/p&gt;&lt;h2&gt;&amp;quot;Just run thousands of agents&amp;quot; doesn&amp;apos;t fix it&lt;/h2&gt;&lt;p&gt;Multi-agent failure rates range from 41% to 87% in practice. Coordination breakdowns alone account for 37% of all failures. Anthropic found that putting Claude in a multi-agent setup dropped performance by 35%. At 1 step, you&amp;apos;re looking at around 95% success rate. At 10 steps, 60%. At 20 steps, 36%.&lt;/p&gt;&lt;p&gt;This is Brooks&amp;apos;s Law reborn. Adding more people to a late software project makes it later. Adding more agents to a complex codebase makes it worse. At some point your job becomes building and managing the infrastructure that coordinates the agents, not writing the actual code.&lt;/p&gt;&lt;h2&gt;Microsoft already tried&lt;/h2&gt;&lt;p&gt;Galen Hunt, a Microsoft Distinguished Engineer, said publicly: &amp;quot;My goal is to eliminate every line of C and C++ from Microsoft by 2030. One engineer, one month, one million lines of code.&amp;quot;&lt;/p&gt;&lt;p&gt;After community pushback, Microsoft clarified this was a research project, not a product roadmap. They explicitly denied plans to rewrite Windows 11 using AI.&lt;/p&gt;&lt;p&gt;Their actual progress on the .NET Runtime, a much simpler codebase than the Windows kernel: 878 Copilot PRs over 10 months, 535 merged, 126,000 lines touched. Windows has 50 million. At that rate, it would take over 330 years to rewrite Windows. And again, this was the easy part.&lt;/p&gt;&lt;h2&gt;Where this is going&lt;/h2&gt;&lt;p&gt;AI tools make me faster at the boring parts, which frees me up for the parts that actually need thinking. That&amp;apos;s the real value, augmentation, not replacement.&lt;/p&gt;&lt;p&gt;But &amp;quot;just rewrite it&amp;quot; gets code generation and software engineering mixed up. Windows isn&amp;apos;t slow to fix because the code is badly written. It&amp;apos;s slow because the problem itself is irreducibly complex. 50 million lines of code. 41 years of decisions. Billions of devices. Millions of third-party applications. An entire global economy running on it.&lt;/p&gt;&lt;p&gt;AI is powerful. But this isn&amp;apos;t a problem you can brute-force with a bigger model.&lt;/p&gt;</content:encoded>
    <category>Artificial Intelligence</category>
    <pubDate>Thu, 04 Jun 2026 12:00:00 GMT</pubDate>
  </item>
  <item>
    <title>Making technical debt visible with SonarQube</title>
    <link>https://nirjar.me/articles/making-technical-debt-visible-with-sonarqube</link>
    <guid isPermaLink="true">https://nirjar.me/articles/making-technical-debt-visible-with-sonarqube</guid>
    <description>A 30-day cleanup of AegisMesh using SonarQube, turning 872 initially invisible bugs, vulnerabilities, hotspots, code smells, and duplication into measurable technical debt and a quality-gated workflow.</description>
    <content:encoded>&lt;h2&gt;Why bother&lt;/h2&gt;&lt;p&gt;Before SonarQube, most checks were reactive — linting, manual review, runtime debugging. That approach misses a lot: security hotspots, duplicated logic, unstable React hooks, unsafe async handling, cognitive complexity creeping up unnoticed.&lt;/p&gt;&lt;p&gt;The goal wasn&amp;apos;t perfect code. It was measurable code.&lt;/p&gt;&lt;h2&gt;What the first scan found&lt;/h2&gt;&lt;p&gt;The first scan was uncomfortable. Issues that had existed for weeks, invisible until now.&lt;/p&gt;&lt;p&gt;872 total issues on day one. 215 bugs, 76 vulnerabilities, 8 security hotspots, 90+ code smells, and ~9.7% duplicated code. Quality Gates: 0% passing.&lt;/p&gt;&lt;p&gt;None of it was catastrophic in isolation. The problem was accumulation.&lt;/p&gt;&lt;h2&gt;Frontend problems&lt;/h2&gt;&lt;p&gt;The React frontend had issues that worked fine locally but were risky at scale — stale useEffect dependencies, unsafe state updates, duplicated validation logic, inconsistent null handling.&lt;/p&gt;&lt;p&gt;One component had a cognitive complexity score of 24, three duplicated validation blocks, and five hook dependency warnings. After refactoring: complexity dropped to 11, duplication gone, render stability improved.&lt;/p&gt;&lt;h2&gt;Backend security findings&lt;/h2&gt;&lt;p&gt;The backend scan was more interesting. Input validation was inconsistent between routes — some payloads validated, others weren&amp;apos;t. Not an immediate exploit, but an obvious gap.&lt;/p&gt;&lt;p&gt;A few validators had regex patterns that could cause catastrophic backtracking under crafted payloads. Rewrote them with bounded patterns. JWT handling had duplicated token verification logic scattered across middleware — centralizing it cut auth-related duplication by 38%. A handful of debug logs were also leaking partial tokens and internal error structures, flagged as hotspots and cleaned up.&lt;/p&gt;&lt;p&gt;None of these were dramatic. That&amp;apos;s kind of the point — small, risky patterns that compound over time.&lt;/p&gt;&lt;h2&gt;30 days of cleanup&lt;/h2&gt;&lt;p&gt;Started at 872. Closed out at 479 — security at 21, reliability at 192, maintainability at 266, duplications down to 7.3%, and hotspots reviewed at 0.0%.&lt;/p&gt;&lt;p&gt;Still work left. But the numbers moved, and that&amp;apos;s the point — technical debt went from abstract to visible.&lt;/p&gt;&lt;p&gt;Technical debt went from abstract to visible. That made it fixable.&lt;/p&gt;&lt;h2&gt;Quality gates changed the workflow&lt;/h2&gt;&lt;p&gt;Every pull request now gets scanned automatically. Builds fail if vulnerabilities exceed threshold, duplication increases, coverage drops, or critical issues appear.&lt;/p&gt;&lt;p&gt;The pipeline blocks obvious problems before they need manual review.&lt;/p&gt;&lt;h2&gt;What I&amp;apos;d do differently&lt;/h2&gt;&lt;p&gt;Add SonarQube earlier. Scanning a large existing codebase all at once creates noisy reports and a backlog that&amp;apos;s demoralizing to look at. Starting from the first commit keeps issue counts manageable and the feedback loop tight.&lt;/p&gt;&lt;p&gt;Take security hotspots seriously from day one. Most of them weren&amp;apos;t immediately exploitable — but they all represented unclear intent or risky patterns. Ignoring them early just defers the cleanup.&lt;/p&gt;&lt;p&gt;Watch cognitive complexity. The hardest files to maintain were almost always the ones SonarQube flagged first. That correlation held consistently.&lt;/p&gt;&lt;h2&gt;Final thought&lt;/h2&gt;&lt;p&gt;SonarQube didn&amp;apos;t fix anything on its own. It made problems impossible to ignore — vulnerability counts, duplication percentages, complexity scores, failed quality gates. Once the numbers were visible, the work became obvious.&lt;/p&gt;</content:encoded>
    <category>Practice</category>
    <pubDate>Mon, 25 May 2026 12:00:00 GMT</pubDate>
  </item>
  <item>
    <title>Bastion&apos;s path from Docker to Kubernetes</title>
    <link>https://nirjar.me/articles/bastions-path-from-docker-to-kubernetes</link>
    <guid isPermaLink="true">https://nirjar.me/articles/bastions-path-from-docker-to-kubernetes</guid>
    <description>The practical lessons from containerizing AegisMesh, moving it into Kubernetes, and building a Jenkins pipeline, including startup ordering, Prisma, Node versions, plugins, configuration, and build hygiene.</description>
    <content:encoded>&lt;h2&gt;Starting with Docker&lt;/h2&gt;&lt;p&gt;The backend Dockerfile is three stages because of Prisma. It needs to generate a client at build time and needs a DATABASE_URL for that, even though nothing actually connects. One stage for prod dependencies, one for prisma generate with a placeholder URL, one final stage that combines both.&lt;/p&gt;&lt;p&gt;dumb-init was an afterthought. npm start as PID 1 doesn&amp;apos;t forward signals, so docker stop would hang indefinitely. Annoying to debug, obvious in hindsight.&lt;/p&gt;&lt;p&gt;The frontend was straightforward. Vite builds, Nginx serves. Separate dev Dockerfile that skips the build and runs the dev server directly.&lt;/p&gt;&lt;h2&gt;Startup Order&lt;/h2&gt;&lt;p&gt;The backend kept dying because Postgres wasn&amp;apos;t ready. The container being up and the database being ready aren&amp;apos;t the same thing — took me longer than it should have to work that out. Added a healthcheck so the backend actually waits for connections.&lt;/p&gt;&lt;p&gt;The dev setup had one thing I missed until it broke. The host was overwriting files inside the container with no warning. One line to isolate them.&lt;/p&gt;&lt;h2&gt;Moving to Kubernetes&lt;/h2&gt;&lt;p&gt;Didn&amp;apos;t touch Kubernetes until Docker was stable.&lt;/p&gt;&lt;p&gt;Same problem, different tool. You can run tasks before the main container starts — one waits for the database, one runs migrations. The app doesn&amp;apos;t deal with any of it.&lt;/p&gt;&lt;p&gt;Decide where config lives before writing anything. I changed my mind halfway through and lost time I didn&amp;apos;t need to lose.&lt;/p&gt;&lt;h2&gt;Jenkins&lt;/h2&gt;&lt;p&gt;I thought it would take a few hours. It took most of a week.&lt;/p&gt;&lt;p&gt;First: npm not found. Jenkins had no Node on PATH. Then Prisma rejected the Node version — added a validation stage that fails immediately if it&amp;apos;s not 20.19+, 22.12+, or 24+.&lt;/p&gt;&lt;p&gt;Then lint ran for the first time. 20+ errors. Bad setState in effects, stale references, broken hook dependencies — none of it was new, it had just never been checked. Fixed all of it.&lt;/p&gt;&lt;p&gt;The thing that ate the most time wasn&amp;apos;t any of that. A plugin I didn&amp;apos;t install and didn&amp;apos;t know about was breaking the entire run. Found it by reading console output until one line looked wrong.&lt;/p&gt;&lt;h2&gt;What I&amp;apos;d Do Differently&lt;/h2&gt;&lt;p&gt;Set up .dockerignore before writing anything. I was pulling things into the build context that had no reason to be there. Builds were slower than they should&amp;apos;ve been and I only caught it later.&lt;/p&gt;&lt;p&gt;Decide the ConfigMap and Secret split before touching any manifests. Changing it halfway through means revisiting files you already considered done. Not hard, just annoying.&lt;/p&gt;&lt;p&gt;Give Jenkins environment setup more time than you think it needs. The Jenkinsfile is the easy part. Node not being on PATH, the wrong Node version failing Prisma, a plugin you never installed quietly breaking the entire run — that&amp;apos;s where the time actually goes.&lt;/p&gt;&lt;h2&gt;Final Thoughts&lt;/h2&gt;&lt;p&gt;Docker felt like overhead until it wasn&amp;apos;t. Kubernetes made me think about things Compose lets you ignore. Jenkins was a bad week and I&amp;apos;d do it again.&lt;/p&gt;</content:encoded>
    <category>DevOps</category>
    <pubDate>Thu, 30 Apr 2026 12:00:00 GMT</pubDate>
  </item>
  <item>
    <title>Rethinking my Git workflow</title>
    <link>https://nirjar.me/articles/rethinking-my-git-workflow</link>
    <guid isPermaLink="true">https://nirjar.me/articles/rethinking-my-git-workflow</guid>
    <description>How moving from unmanaged folders and versioned filenames to GitHub, commits, branches, documentation, and repositories changed the way I build and maintain projects.</description>
    <content:encoded>&lt;h2&gt;Before GitHub&lt;/h2&gt;&lt;p&gt;My actual setup at that time was pretty messy: a folder. Inside that folder, files with names like main_final.py, main_final_v2.py, main_final_v2_REAL.py. No backup. No structure.&lt;/p&gt;&lt;p&gt;When something broke — and things broke — I&amp;apos;d either spend an hour undoing changes by memory or just start over.&lt;/p&gt;&lt;p&gt;I didn&amp;apos;t know that was a bad system until I lost work I actually cared about.&lt;/p&gt;&lt;h2&gt;Discovering GitHub&lt;/h2&gt;&lt;p&gt;When I finally used it properly, the first thing I did was create a repo, clone it locally, and just start pushing code.&lt;/p&gt;&lt;p&gt;Every time I finished something — even something small — I&amp;apos;d stage it, commit, push. Open GitHub, see it sitting there. Green squares filling in. It kept me going more than I expected.&lt;/p&gt;&lt;p&gt;The better part was opening a repo weeks later and actually being able to see what happened. Not piece it together. Just read it.&lt;/p&gt;&lt;h2&gt;What Changed&lt;/h2&gt;&lt;p&gt;Version control was the first thing that actually helped. Something breaks, I find the last commit where it didn&amp;apos;t and roll back. That spiral of &amp;quot;what did I even change&amp;quot; doesn&amp;apos;t happen much anymore — and when it does, I can just check.&lt;/p&gt;&lt;p&gt;Commit messages I still get lazy with sometimes. But even a half-decent message is better than nothing when you&amp;apos;re staring at a diff three weeks later with no memory of what you were doing.&lt;/p&gt;&lt;p&gt;Branches I didn&amp;apos;t use properly for a while. Before, trying something new meant duplicating the folder or just making the change and hoping. Now I branch off, try it, merge or delete. I started finishing things I would&amp;apos;ve dropped.&lt;/p&gt;&lt;h2&gt;Accidental Portfolio&lt;/h2&gt;&lt;p&gt;I wasn&amp;apos;t thinking about GitHub as a portfolio when I started. I was just pushing code. But when I started applying for internships, having repos with real commit history and READMEs made a difference I didn&amp;apos;t anticipate. Recruiters could just look. No explaining, no describing — just a link.&lt;/p&gt;&lt;p&gt;Collaboration I haven&amp;apos;t used that much yet. But the few times I have, it beat sending files over chat and hoping nobody touched the wrong thing.&lt;/p&gt;&lt;h2&gt;What I Learned&lt;/h2&gt;&lt;p&gt;Commit messages actually matter. &amp;quot;fix stuff&amp;quot; is fine until you&amp;apos;re debugging at midnight with no memory of what changed. Even &amp;quot;fix auth redirect on logout&amp;quot; takes five seconds and has saved me real time more than once.&lt;/p&gt;&lt;p&gt;README files I kept skipping. Still do sometimes. But I&amp;apos;ve opened old projects with no idea how to run them — including ones I built myself. At that point documentation stops being optional.&lt;/p&gt;&lt;p&gt;Pushing regularly matters more than I thought. One big commit at the end tells you nothing about how the project came together. The history only works if you actually built it along the way.&lt;/p&gt;&lt;h2&gt;Final Thoughts&lt;/h2&gt;&lt;p&gt;I avoided GitHub for a long time because it felt like extra work. It wasn&amp;apos;t. I was just skipping the part where I treated my own projects seriously.&lt;/p&gt;&lt;p&gt;Commit regularly. Write messages that mean something. Keep things in repos.&lt;/p&gt;&lt;p&gt;That&amp;apos;s most of it — the rest you figure out as you go.&lt;/p&gt;</content:encoded>
    <category>Practice</category>
    <pubDate>Thu, 16 Apr 2026 12:00:00 GMT</pubDate>
  </item>
  <item>
    <title>How VaultLock reliably fetches brand logos</title>
    <link>https://nirjar.me/articles/how-vaultlock-reliably-fetches-brand-logos</link>
    <guid isPermaLink="true">https://nirjar.me/articles/how-vaultlock-reliably-fetches-brand-logos</guid>
    <description>How VaultLock moved logo fetching out of the UI and into a dedicated backend flow using input normalization, caching, multiple fallback sources, response validation, and graceful failure handling.</description>
    <content:encoded>&lt;h2&gt;Showing a logo sounds trivial. It isn&amp;apos;t.&lt;/h2&gt;&lt;p&gt;In VaultLock, I needed something that could take &amp;quot;GitHub&amp;quot; or &amp;quot;facebook.com&amp;quot; and return the right icon — without crashing the UI or making external requests. Getting there took more architecture than I expected.&lt;/p&gt;&lt;h2&gt;The wrong first solution&lt;/h2&gt;&lt;p&gt;The first solution was to let QML handle it: fetch the URL, render the icon. This fell apart quickly. Qt&amp;apos;s icon components aren&amp;apos;t consistently available across the stack. Rendering remote images caused glitches and crashes. And if the UI is making external requests based on user input, you&amp;apos;ve handed control of what gets fetched to whoever&amp;apos;s typing.&lt;/p&gt;&lt;p&gt;So I moved everything into the backend.&lt;/p&gt;&lt;h2&gt;Input normalization&lt;/h2&gt;&lt;p&gt;Users don&amp;apos;t enter clean data. They type &amp;quot;Google&amp;quot;, &amp;quot;linkedin.com&amp;quot;, &amp;quot;https://facebook.com&amp;quot;, or whatever they remember.&lt;/p&gt;&lt;p&gt;The first step is normalization — extract a domain if there&amp;apos;s a URL, check a brand dictionary, fall back to guessing name.com. By the time anything downstream runs, the input is a clean domain.&lt;/p&gt;&lt;h2&gt;Caching, then fetching&lt;/h2&gt;&lt;p&gt;Before any network call goes out, the system checks memory, then bundled assets, then disk. Most repeat lookups never hit the network at all. When they do, the request goes to one of three sources: Clearbit, Google&amp;apos;s favicon service, or DuckDuckGo&amp;apos;s favicon service. No user controlled endpoints.&lt;/p&gt;&lt;h2&gt;Validation and storage&lt;/h2&gt;&lt;p&gt;Every response gets validated before it touches the cache: HTTP 200, at least 500 bytes, valid Content-Type, and magic bytes that actually match the claimed format. Anything that fails gets discarded. Filenames are md5 hashes of the domain; writes go to a temp file and get renamed. The plumbing is boring on purpose.&lt;/p&gt;&lt;h2&gt;When fetching fails&lt;/h2&gt;&lt;p&gt;When the UI doesn&amp;apos;t get a file path, it renders a badge — a dark gradient radiused square with the first two letters of the service name, or ?? if there&amp;apos;s nothing to pull from.&lt;/p&gt;&lt;p&gt;On the backend, logo_manager.py adds the domain to failed_domains and skips it going forward. No retries. The badge stays, nothing broken gets shown.&lt;/p&gt;&lt;h2&gt;UI isolation&lt;/h2&gt;&lt;p&gt;VaultLock UI doesn’t fetch, validate, or guess, it never sees any of this logo fetching process.&lt;/p&gt;&lt;p&gt;Thats why VaultLock is reliable.&lt;/p&gt;</content:encoded>
    <category>Security</category>
    <pubDate>Wed, 08 Apr 2026 12:00:00 GMT</pubDate>
  </item>
  <item>
    <title>DeployLens: finding my deployment blind spots</title>
    <link>https://nirjar.me/articles/deploylens-finding-my-deployment-blind-spots</link>
    <guid isPermaLink="true">https://nirjar.me/articles/deploylens-finding-my-deployment-blind-spots</guid>
    <description>What building DeployLens revealed about CI/CD security, excessive pipeline permissions, long-lived AWS credentials, CodeQL findings, and the importance of knowing exactly what version is running in production.</description>
    <content:encoded>&lt;h2&gt;Your pipeline has permissions. Real ones.&lt;/h2&gt;&lt;p&gt;CI/CD tutorials frame it as a speed thing. Push code, tests run, it deploys. What they skip: your pipeline can push container images, update infrastructure, write to S3, call AWS APIs. In most early setups, mine included, those permissions are wide open.&lt;/p&gt;&lt;p&gt;That&amp;apos;s what started bothering me while building DeployLens. I kept noticing how much access my GitHub Actions workflows quietly assumed. Nobody hacked me, nothing broke, but I kept asking: if someone pushed a malicious commit right now, what could the pipeline actually do with its current permissions?&lt;/p&gt;&lt;p&gt;I didn&amp;apos;t love where that question went.&lt;/p&gt;&lt;h2&gt;The secrets problem&lt;/h2&gt;&lt;p&gt;Early on I had AWS credentials in GitHub Actions secrets. AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, injected as env vars. Standard setup, everyone does it.&lt;/p&gt;&lt;p&gt;Then I started reading about what goes wrong. Workflows that run on PRs from forks. Compromised dependencies that exfiltrate env vars. A workflow that accidentally logs env to stdout and now your keys are in a public build log. These aren&amp;apos;t hypothetical.&lt;/p&gt;&lt;p&gt;I switched to OIDC. GitHub Actions can assume an IAM role directly, no stored credentials, short-lived token that expires after the job. Maybe 30 minutes of setup total. I wouldn&amp;apos;t have touched it if DeployLens hadn&amp;apos;t forced me to actually look at how my pipeline was talking to AWS.&lt;/p&gt;&lt;h2&gt;What CodeQL found&lt;/h2&gt;&lt;p&gt;I added CodeQL to AegisMesh mostly because it looked good. Security scanning, SAST, sure.&lt;/p&gt;&lt;p&gt;Then it flagged something. User input passing through without proper sanitization. Not catastrophic, but the kind of pattern that becomes catastrophic when the code around it changes. Fixed it in 10 minutes.&lt;/p&gt;&lt;p&gt;The part I keep thinking about: I wrote that code. I reviewed it. I didn&amp;apos;t see it.&lt;/p&gt;&lt;p&gt;Static analysis catches a specific class of problems that humans miss not because we&amp;apos;re careless but because we read for logic. We&amp;apos;re checking if it does what we want, not whether it could be exploited. CodeQL doesn&amp;apos;t read for intent. It just looks for patterns. That&amp;apos;s exactly why it caught something I didn&amp;apos;t.&lt;/p&gt;&lt;h2&gt;The actual problem is visibility&lt;/h2&gt;&lt;p&gt;Security issues in CI/CD are usually not dramatic. It&amp;apos;s just that nobody knows the real state of what&amp;apos;s running. The SHA that passed CI and the container image actually serving traffic — same thing? Did the last deploy finish? Did it roll back without telling anyone?&lt;/p&gt;&lt;p&gt;If you can&amp;apos;t answer those questions, you can&amp;apos;t answer whether a vulnerable version is deployed right now.&lt;/p&gt;&lt;p&gt;DeployLens matched commit SHAs from GitHub against ECS task definitions on the AWS side. Simple idea, genuinely annoying to implement because AWS doesn&amp;apos;t expose that data in any obvious way. But building it changed how I think about pipelines. Less &amp;quot;automation tube&amp;quot; and more &amp;quot;system with its own state, access, and history that nobody&amp;apos;s watching.&amp;quot;&lt;/p&gt;&lt;h2&gt;What I&amp;apos;d tell myself at the start&lt;/h2&gt;&lt;p&gt;Don&amp;apos;t save security for a cleanup pass. OIDC takes less time to set up than rotating leaked credentials later. Branch protection is five minutes. CodeQL is a checkbox in GitHub&amp;apos;s UI.&lt;/p&gt;&lt;p&gt;The hard part was never the implementation. It was noticing the gap existed. And I only noticed because I was building something that forced me to look.&lt;/p&gt;</content:encoded>
    <category>DevOps</category>
    <pubDate>Wed, 08 Apr 2026 12:00:00 GMT</pubDate>
  </item>
</channel>
</rss>
