<?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>Sun, 17 May 2026 08:10: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[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>