<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Nirjar Goswami]]></title><description><![CDATA[Insights from what i have learned and built so far.]]></description><link>https://blog.nirjar.me</link><generator>RSS for Node</generator><lastBuildDate>Tue, 06 Oct 2026 17:37:38 GMT</lastBuildDate><atom:link href="https://blog.nirjar.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why AI can't just rewrite Windows]]></title><description><![CDATA[Every few months, someone says it. Usually in a comment thread, sometimes a non-developer friend. The logic sounds reasonable on the surface: AI can build APIs, write services, generate entire applica]]></description><link>https://blog.nirjar.me/why-ai-can-t-just-rewrite-windows</link><guid isPermaLink="true">https://blog.nirjar.me/why-ai-can-t-just-rewrite-windows</guid><dc:creator><![CDATA[NirjarGoswami]]></dc:creator><pubDate>Thu, 04 Jun 2026 06:39:09 GMT</pubDate><content:encoded><![CDATA[<p>Every few months, someone says it. Usually in a comment thread, sometimes a non-developer friend. The logic sounds reasonable on the surface: AI can build APIs, write services, generate entire applications. So why not Windows?</p>
<p>Because Windows isn't a coding problem. It's a complexity problem. And those are very different things.</p>
<hr />
<h2><strong>The scale is hard to actually picture</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/69d526e45da14bc70e69e463/be0771d7-ddc6-4f9b-b092-66afa6827fa0.webp" alt="" style="display:block;margin:0 auto" />

<p>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't handle it.</p>
<p>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.</p>
<p>I work on things that feel complex to me. <a href="https://github.com/nirjxr26/AegisMesh-IAM">AegisMesh</a>, the IAM platform I'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.</p>
<p>Nobody holds Windows in their head. I don't think any single team does either.</p>
<hr />
<h2><strong>The problem isn't the code. It's everything the code is touching.</strong></h2>
<p>Complexity in software doesn't scale the way people expect.</p>
<p>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.</p>
<p>Fred Brooks put it directly in <em>No Silver Bullet</em> (1986): "The elements interact with each other in some nonlinear fashion, and the complexity of the whole increases much more than linearly."</p>
<p>And you can'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.</p>
<hr />
<h2><strong>AI can't even read Windows, let alone rewrite it</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/69d526e45da14bc70e69e463/82998ff4-dbd9-4aa9-ad03-a4f118969c77.webp" alt="" style="display:block;margin:0 auto" />

<p>Models work within a context window. Working memory, basically. Code averages about 18 tokens per line. 50 million lines means roughly 900 million tokens.</p>
<p>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.</p>
<p>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.</p>
<p>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't just scale, it multiplies in ways that are genuinely hard to reason about.</p>
<hr />
<h2><strong>AI already struggles at large codebases</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/69d526e45da14bc70e69e463/7b5f4088-c328-41c3-852c-abc4d71584cf.webp" alt="" style="display:block;margin:0 auto" />

<p>The numbers on this aren'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.</p>
<p>AI can'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.</p>
<hr />
<h2><strong>Backward compatibility is the thing nobody wants to deal with</strong></h2>
<p>Say AI wrote perfect code. Every function, every module, exactly right.</p>
<p>Still not done.</p>
<p>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't exist anymore, enterprise apps embedded in hospital systems, bank infrastructure, and government workflows that nobody fully understands anymore.</p>
<p>When Microsoft ships an update, they'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's not a failure, that's the deal made with the ecosystem over 40 years.</p>
<p>An AI rewriting from scratch can't know any of that unless someone wrote it down. Most of it wasn't.</p>
<hr />
<h2><strong>"Just run thousands of agents" doesn't fix it</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/69d526e45da14bc70e69e463/068473c8-077c-41f9-bdce-56042bd4d001.webp" alt="" style="display:block;margin:0 auto" />

<p>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're looking at around 95% success rate. At 10 steps, 60%. At 20 steps, 36%.</p>
<p>This is Brooks'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.</p>
<hr />
<h2><strong>Microsoft already tried</strong></h2>
<img alt="" style="display:block;margin:0 auto" />

<p><a href="https://www.linkedin.com/posts/galenh_principal-software-engineer-coreai-microsoft-activity-7407863239289729024-WTzf?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAGOK1LUBycBiOJtYGid75GOM2SOr-NxkL58">Galen Hunt</a>, a Microsoft Distinguished Engineer, said publicly: "My goal is to eliminate every line of C and C++ from Microsoft by 2030. One engineer, one month, one million lines of code."</p>
<p>After community pushback, Microsoft clarified this was a research project, not a product roadmap. They explicitly denied plans to rewrite Windows 11 using AI.</p>
<p>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.</p>
<hr />
<h2><strong>Where this is going</strong></h2>
<p>AI tools make me faster at the boring parts, which frees me up for the parts that actually need thinking. That's the real value, augmentation, not replacement.</p>
<p>But "just rewrite it" gets code generation and software engineering mixed up. Windows isn't slow to fix because the code is badly written. It'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.</p>
<p>AI is powerful. But this isn't a problem you can brute-force with a bigger model.</p>
]]></content:encoded></item><item><title><![CDATA[872 Issues. 30 Days. Here's What SonarQube Found]]></title><description><![CDATA[At some point AegisMesh stopped being a side project. More services, more auth logic, more CI/CD automation — and more places for problems to hide. The codebase was growing but I had no way to tell if]]></description><link>https://blog.nirjar.me/sonarqube</link><guid isPermaLink="true">https://blog.nirjar.me/sonarqube</guid><dc:creator><![CDATA[NirjarGoswami]]></dc:creator><pubDate>Mon, 25 May 2026 09:58:51 GMT</pubDate><content:encoded><![CDATA[<p>At some point <a href="https://github.com/nirjxr26/AegisMesh-IAM">AegisMesh</a> stopped being a side project. More services, more auth logic, more CI/CD automation — and more places for problems to hide. The codebase was growing but I had no way to tell if it was actually getting better.</p>
<p>I needed numbers. Not gut feelings, not "it feels cleaner" — actual metrics. That's where SonarQube came in.</p>
<hr />
<h2><strong>Why bother</strong></h2>
<p>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.</p>
<p>The goal wasn't perfect code. It was measurable code.</p>
<hr />
<h2><strong>What the first scan found</strong></h2>
<p>The first scan was uncomfortable. Issues that had existed for weeks, invisible until now.</p>
<p>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.</p>
<p>None of it was catastrophic in isolation. The problem was accumulation.</p>
<hr />
<h2><strong>Frontend problems</strong></h2>
<p>The React frontend had issues that worked fine locally but were risky at scale — stale <code>useEffect</code> dependencies, unsafe state updates, duplicated validation logic, inconsistent null handling.</p>
<p>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.</p>
<hr />
<h2><strong>Backend security findings</strong></h2>
<p>The backend scan was more interesting. Input validation was inconsistent between routes — some payloads validated, others weren't. Not an immediate exploit, but an obvious gap.</p>
<p>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.</p>
<p>None of these were dramatic. That's kind of the point — small, risky patterns that compound over time.</p>
<hr />
<h2><strong>30 days of cleanup</strong></h2>
<p>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%.</p>
<p>Still work left. But the numbers moved, and that's the point — technical debt went from abstract to visible.</p>
<p>Technical debt went from abstract to visible. That made it fixable.</p>
<hr />
<h2><strong>Quality gates changed the workflow</strong></h2>
<p>Every pull request now gets scanned automatically. Builds fail if vulnerabilities exceed threshold, duplication increases, coverage drops, or critical issues appear.</p>
<p>The pipeline blocks obvious problems before they need manual review.</p>
<hr />
<h2><strong>What I'd do differently</strong></h2>
<p>Add SonarQube earlier. Scanning a large existing codebase all at once creates noisy reports and a backlog that's demoralizing to look at. Starting from the first commit keeps issue counts manageable and the feedback loop tight.</p>
<p>Take security hotspots seriously from day one. Most of them weren't immediately exploitable — but they all represented unclear intent or risky patterns. Ignoring them early just defers the cleanup.</p>
<p>Watch cognitive complexity. The hardest files to maintain were almost always the ones SonarQube flagged first. That correlation held consistently.</p>
<hr />
<h2><strong>Final thought</strong></h2>
<p>SonarQube didn'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.</p>
]]></content:encoded></item><item><title><![CDATA[Containerized AegisMesh with Docker, Kubernetes, and Jenkins]]></title><description><![CDATA[I'd been putting off Docker on AegisMesh for a while. The project had enough going on already — IAM, MFA, OAuth, a policy engine. Adding containers felt like picking up extra work for no immediate rea]]></description><link>https://blog.nirjar.me/containerized-aegismesh</link><guid isPermaLink="true">https://blog.nirjar.me/containerized-aegismesh</guid><dc:creator><![CDATA[NirjarGoswami]]></dc:creator><pubDate>Thu, 30 Apr 2026 06:22:25 GMT</pubDate><content:encoded><![CDATA[<p>I'd been putting off Docker on AegisMesh for a while. The project had enough going on already — IAM, MFA, OAuth, a policy engine. Adding containers felt like picking up extra work for no immediate reason.</p>
<p>Eventually I just did it.</p>
<hr />
<h2><strong>Starting with Docker</strong></h2>
<p>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.</p>
<p>dumb-init was an afterthought. npm start as PID 1 doesn't forward signals, so docker stop would hang indefinitely. Annoying to debug, obvious in hindsight.</p>
<p>The frontend was straightforward. Vite builds, Nginx serves. Separate dev Dockerfile that skips the build and runs the dev server directly.</p>
<hr />
<h2><strong>Startup Order</strong></h2>
<p>The backend kept dying because Postgres wasn't ready. The container being up and the database being ready aren'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.</p>
<p>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.</p>
<hr />
<h2><strong>Moving to Kubernetes</strong></h2>
<p>Didn't touch Kubernetes until Docker was stable.</p>
<p>Same problem, different tool. You can run tasks before the main container starts — one waits for the database, one runs migrations. The app doesn't deal with any of it.</p>
<p>Decide where config lives before writing anything. I changed my mind halfway through and lost time I didn't need to lose.</p>
<hr />
<h2><strong>Jenkins</strong></h2>
<p>I thought it would take a few hours. It took most of a week.</p>
<p>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's not 20.19+, 22.12+, or 24+.</p>
<p>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.</p>
<p>The thing that ate the most time wasn't any of that. A plugin I didn't install and didn't know about was breaking the entire run. Found it by reading console output until one line looked wrong.</p>
<hr />
<h2><strong>What I'd Do Differently</strong></h2>
<p>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've been and I only caught it later.</p>
<p>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.</p>
<p>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's where the time actually goes.</p>
<hr />
<h2><strong>Final Thoughts</strong></h2>
<p>Docker felt like overhead until it wasn't. Kubernetes made me think about things Compose lets you ignore. Jenkins was a bad week and I'd do it again.</p>
]]></content:encoded></item><item><title><![CDATA[How GitHub Changed My Workflow]]></title><description><![CDATA[I thought GitHub was just a place for people to put code so others could download it. Like a fancier zip file host. I'd seen it everywhere, opened it a few times, got confused, and closed the tab.

Be]]></description><link>https://blog.nirjar.me/how-github-changed-my-workflow</link><guid isPermaLink="true">https://blog.nirjar.me/how-github-changed-my-workflow</guid><dc:creator><![CDATA[NirjarGoswami]]></dc:creator><pubDate>Thu, 16 Apr 2026 05:45:37 GMT</pubDate><content:encoded><![CDATA[<p>I thought GitHub was just a place for people to put code so others could download it. Like a fancier zip file host. I'd seen it everywhere, opened it a few times, got confused, and closed the tab.</p>
<hr />
<h2>Before GitHub</h2>
<p>My actual setup at that time was pretty messy: a folder. Inside that folder, files with names like <code>main_</code><a href="http://final.py"><code>final.py</code></a>, <code>main_final_</code><a href="http://v2.py"><code>v2.py</code></a>, <code>main_final_v2_</code><a href="http://REAL.py"><code>REAL.py</code></a>. No backup. No structure.</p>
<p>When something broke — and things broke — I'd either spend an hour undoing changes by memory or just start over.</p>
<p>I didn't know that was a bad system until I lost work I actually cared about.</p>
<hr />
<h2>Discovering GitHub</h2>
<p>When I finally used it properly , the first thing I did was create a repo, clone it locally, and just start pushing code.</p>
<p>Every time I finished something — even something small — I'd stage it, commit, push. Open GitHub, see it sitting there. Green squares filling in. It kept me going more than I expected.</p>
<p>The better part was opening a repo weeks later and actually being able to see what happened. Not piece it together. Just read it.</p>
<hr />
<h2>What Changed</h2>
<p>Version control was the first thing that actually helped. Something breaks, I find the last commit where it didn't and roll back. That spiral of "what did I even change" doesn't happen much anymore — and when it does, I can just check.</p>
<p>Commit messages I still get lazy with sometimes. But even a half-decent message is better than nothing when you're staring at a diff three weeks later with no memory of what you were doing.</p>
<p>Branches I didn'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've dropped.</p>
<hr />
<h2>Accidental Portfolio</h2>
<p>I wasn'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't anticipate. Recruiters could just look. No explaining, no describing — just a link.</p>
<p>Collaboration I haven't used that much yet. But the few times I have, it beat sending files over chat and hoping nobody touched the wrong thing.</p>
<hr />
<h2>What I Learned</h2>
<p>Commit messages actually matter. "fix stuff" is fine until you're debugging at midnight with no memory of what changed. Even "fix auth redirect on logout" takes five seconds and has saved me real time more than once.</p>
<p>README files I kept skipping. Still do sometimes. But I've opened old projects with no idea how to run them — including ones I built myself. At that point documentation stops being optional.</p>
<p>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.</p>
<hr />
<h2>Final Thoughts</h2>
<p>I avoided GitHub for a long time because it felt like extra work. It wasn't. I was just skipping the part where I treated my own projects seriously.</p>
<p>Commit regularly. Write messages that mean something. Keep things in repos.</p>
<p>That's most of it — the rest you figure out as you go.</p>
]]></content:encoded></item><item><title><![CDATA[How VaultLock Reliably Fetches Brand Logos]]></title><description><![CDATA[Showing a logo sounds trivial. It isn't.
In VaultLock, I needed something that could take "GitHub" or "facebook.com" and return the right icon — without crashing the UI or making external requests. Ge]]></description><link>https://blog.nirjar.me/vaultlock-logo-fetching</link><guid isPermaLink="true">https://blog.nirjar.me/vaultlock-logo-fetching</guid><dc:creator><![CDATA[NirjarGoswami]]></dc:creator><pubDate>Wed, 08 Apr 2026 11:53:35 GMT</pubDate><content:encoded><![CDATA[<h2><strong>Showing a logo sounds trivial. It isn't.</strong></h2>
<p>In VaultLock, I needed something that could take "GitHub" or "facebook.com" and return the right icon — without crashing the UI or making external requests. Getting there took more architecture than I expected.</p>
<hr />
<h2><strong>The wrong first solution</strong></h2>
<p>The first solution was to let QML handle it: fetch the URL, render the icon. This fell apart quickly. Qt's icon components aren'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've handed control of what gets fetched to whoever's typing.</p>
<p>So I moved everything into the backend.</p>
<hr />
<h2><strong>Input normalization</strong></h2>
<p>Users don't enter clean data. They type "Google", "linkedin.com", "<a href="https://facebook.com">https://facebook.com</a>", or whatever they remember.</p>
<p>The first step is normalization — extract a domain if there'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.</p>
<hr />
<h2><strong>Caching, then fetching</strong></h2>
<p>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's favicon service, or DuckDuckGo's favicon service. No user controlled endpoints.</p>
<hr />
<h2><strong>Validation and storage</strong></h2>
<p>Every response gets validated before it touches the cache: HTTP 200, at least 500 bytes, valid Content-Type, and <strong>magic bytes</strong> 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.</p>
<hr />
<h2><strong>When fetching fails</strong></h2>
<p>When the UI doesn't get a file path, it renders a badge — a dark gradient radiused square with the first two letters of the service name, or <code>??</code> if there's nothing to pull from.</p>
<p>On the backend, <code>logo_manager.py</code> adds the domain to <code>failed_domains</code> and skips it going forward. No retries. The badge stays, nothing broken gets shown.</p>
<hr />
<h2><strong>UI isolation</strong></h2>
<p>VaultLock UI doesn’t fetch, validate, or guess, it never sees any of this logo fetching process.</p>
<p>Thats why VaultLock is reliable.</p>
]]></content:encoded></item><item><title><![CDATA[Building DeployLens Exposed My Deployment Blind Spots]]></title><description><![CDATA[I started DeployLens because I was frustrated. Not in a visionary way. I was working on AegisMesh, which is one of my another IAM project, pushing changes constantly, and couldn't tell what version wa]]></description><link>https://blog.nirjar.me/ci-cd-blind-spots</link><guid isPermaLink="true">https://blog.nirjar.me/ci-cd-blind-spots</guid><dc:creator><![CDATA[NirjarGoswami]]></dc:creator><pubDate>Tue, 07 Apr 2026 16:16:34 GMT</pubDate><content:encoded><![CDATA[<p>I started DeployLens because I was frustrated. Not in a visionary way. I was working on AegisMesh, which is one of my another IAM project, pushing changes constantly, and couldn't tell what version was running where. GitHub said the workflow passed. AWS said something was running. Same thing? I had no clean answer.</p>
<p>So I built a tool to connect those dots. Somewhere in the middle of building it, I realized I also had no idea how insecure my pipelines were.</p>
<hr />
<h2>Your pipeline has permissions. Real ones.</h2>
<p>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.</p>
<p>That'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?</p>
<p>I didn't love where that question went.</p>
<hr />
<h2>The secrets problem</h2>
<p>Early on I had AWS credentials in GitHub Actions secrets. <code>AWS_ACCESS_KEY_ID</code>, <code>AWS_SECRET_ACCESS_KEY</code>, injected as env vars. Standard setup, everyone does it.</p>
<p>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 <code>env</code> to stdout and now your keys are in a public build log. These aren't hypothetical.</p>
<p>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't have touched it if DeployLens hadn't forced me to actually look at how my pipeline was talking to AWS.</p>
<hr />
<h2>What CodeQL found</h2>
<p>I added CodeQL to AegisMesh mostly because it looked good. Security scanning, SAST, sure.</p>
<p>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.</p>
<p>The part I keep thinking about: I wrote that code. I reviewed it. I didn't see it.</p>
<p>Static analysis catches a specific class of problems that humans miss not because we're careless but because we read for logic. We're checking if it does what we want, not whether it could be exploited. CodeQL doesn't read for intent. It just looks for patterns. That's exactly why it caught something I didn't.</p>
<hr />
<h2>The actual problem is visibility</h2>
<p>Security issues in CI/CD are usually not dramatic. It's just that nobody knows the real state of what'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?</p>
<p>If you can't answer those questions, you can't answer whether a vulnerable version is deployed right now.</p>
<p>DeployLens matched commit SHAs from GitHub against ECS task definitions on the AWS side. Simple idea, genuinely annoying to implement because AWS doesn't expose that data in any obvious way. But building it changed how I think about pipelines. Less "automation tube" and more "system with its own state, access, and history that nobody's watching."</p>
<hr />
<h2>What I'd tell myself at the start</h2>
<p>Don'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's UI.</p>
<p>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.</p>
]]></content:encoded></item></channel></rss>