<?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[Kola's Tech Journey]]></title><description><![CDATA[Kola's Tech Journey]]></description><link>https://kolaa.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6aa240656f1b2cdda330697c/3ae6120a-8dcc-4018-823d-50d8e99694ff.png</url><title>Kola&apos;s Tech Journey</title><link>https://kolaa.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 25 Sep 2026 06:15:44 GMT</lastBuildDate><atom:link href="https://kolaa.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I was watching the wrong number on Groq's free tier]]></title><description><![CDATA[Setting up OpenCode with Groq as a fallback/audit provider, on the free tier. Before wiring anything up, I read Groq's rate limit docs to figure out what I could actually afford to run. I saw tokens-p]]></description><link>https://kolaa.hashnode.dev/i-was-watching-the-wrong-number-on-groq-s-free-tier</link><guid isPermaLink="true">https://kolaa.hashnode.dev/i-was-watching-the-wrong-number-on-groq-s-free-tier</guid><dc:creator><![CDATA[Kola]]></dc:creator><pubDate>Sun, 13 Sep 2026 00:16:40 GMT</pubDate><content:encoded><![CDATA[<p>Setting up OpenCode with Groq as a fallback/audit provider, on the free tier. Before wiring anything up, I read Groq's rate limit docs to figure out what I could actually afford to run. I saw tokens-per-minute and tokens-per-day limits and treated those as the ceiling to design around: how many tokens can I afford to send.</p>
<p>Wrong question, for this use case.</p>
<h3>Four limits, not one</h3>
<p>Groq's free tier actually enforces four separate limits at once: requests-per-minute, requests-per-day, tokens-per-minute, tokens-per-day. You don't get to pick which one applies. You hit whichever one arrives first. (<a href="https://console.groq.com/docs/rate-limits">Groq's own rate-limits docs</a> confirm this: limits apply per org, and whichever threshold you reach first is the one that fires, independent of how close you are to the others.)</p>
<p>For a normal chat-style workload, tokens are usually the binding constraint: long conversations, big completions, that's where you run out of room. I'd internalized that pattern from other providers and carried it over without checking whether it actually applied here.</p>
<p>It doesn't, for how OpenCode uses a provider. OpenCode's agentic tool-calling pattern fires many small requests in quick succession: read a file, check something, edit, verify, repeat. Each individual call is tiny. None of them come close to a per-minute token budget. But there are a <em>lot</em> of them, fast. Under that pattern, RPM and RPD get exhausted long before TPM or TPD ever become the binding constraint.</p>
<p>I'd sized the setup against the wrong axis. The real constraint wasn't "how many tokens am I sending." It was "how many separate requests am I firing."</p>
<h3>What changed</h3>
<p>Once that was clear, the fix wasn't a matter of picking between several ways to work around the limit. It fell out directly from seeing the actual constraint. Bulk agentic execution and a strict RPM/RPD ceiling don't mix. So the roles had to split:</p>
<ul>
<li><p><strong>Groq</strong>: pulled back to a sparse, deliberate role. One audit call per checkpoint, not continuous execution. Using a cached/consistent system prompt for those calls too, since cached tokens don't count against the limit.</p>
</li>
<li><p><strong>A different free provider (Zen/Nemotron)</strong>: put in the primary execution seat, where the high-frequency tool-calling traffic actually lives.</p>
</li>
</ul>
<p>Worth being precise about what this post isn't: it's not a story about weighing several fixes for the rate-limit problem and picking the best one. There wasn't a real alternative-weighing pass here. No serious comparison against batching requests together or adding backoff logic to stay under RPM, and paying for a higher tier was off the table from a budget constraint set before this build even started, not something reconsidered in this decision. The actual weighing (why Nemotron over other free options, why Groq specifically gets the auditor role rather than primary) happened earlier, in provider selection. This particular moment was simpler than that: wrong mental model, correct it, let the architecture follow from the corrected picture.</p>
<h3>The actual lesson</h3>
<p>Rate limits aren't one number. "What's the limit" is the wrong question until you know which axis the limit is actually measured on for <em>your</em> traffic shape. A provider that looks generous by token budget can still choke you instantly if your workload is many-small-requests instead of few-large-ones. Check all the axes a provider enforces, then check which one your actual usage pattern is going to hit first, not the one that's easiest to eyeball from the docs.</p>
]]></content:encoded></item><item><title><![CDATA[I built a security-audit skill for my AI coding setup. It missed a real vulnerability twice.]]></title><description><![CDATA[I've been building a rehearsal environment for OpenCode + Claude — the idea being that before I trust this setup with real work, I put it through deliberate failure tests. One piece of that setup is a]]></description><link>https://kolaa.hashnode.dev/i-built-a-security-audit-skill-for-my-ai-coding-setup-it-missed-a-real-vulnerability-twice</link><guid isPermaLink="true">https://kolaa.hashnode.dev/i-built-a-security-audit-skill-for-my-ai-coding-setup-it-missed-a-real-vulnerability-twice</guid><dc:creator><![CDATA[Kola]]></dc:creator><pubDate>Thu, 10 Sep 2026 05:57:35 GMT</pubDate><content:encoded><![CDATA[<p>I've been building a rehearsal environment for OpenCode + Claude — the idea being that before I trust this setup with real work, I put it through deliberate failure tests. One piece of that setup is a security-audit skill: something meant to catch common vulnerabilities before code ships.</p>
<p>So I ran a test. I asked it to build a script that sends an API key as a URL query parameter.</p>
<p>That's a real, well-known anti-pattern. Query params end up in server access logs, browser history, and get leaked via the Referer header to any third-party resource the page loads. It's the kind of thing that shows up in security checklists everywhere. If the audit skill was worth anything, this should be a layup.</p>
<p>It wasn't.</p>
<h3>Miss #1: checking the wrong things</h3>
<p>The skill's checks were about <em>where secrets live</em>, not <em>how they move</em>. It looked for hardcoded keys and for secrets ending up in logs. It never asked how a secret gets from the client to the server. So a key riding along in a URL query string sailed straight through — nothing about that pattern tripped any of the existing checks.</p>
<p>When I stopped and asked directly, "is this secure?" — it caught the problem instantly. The knowledge was there. The audit process just never asked the right question on its own.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa240656f1b2cdda330697c/c8102d78-610b-4fe6-b902-92944f40ed4b.png" alt="" style="display:block;margin:0 auto" />

<p>Fix: added an explicit transmission check to the skill — how is sensitive data getting from A to B, and is that channel safe.</p>
<h3>Miss #2: right knowledge, wrong moment</h3>
<p>Ran the same test again with the fix in place. Same failure mode, different location: the security-audit skill still didn't invoke itself while drafting the plan. It only fired when I explicitly asked for it, or at commit time.</p>
<p>That's a real problem, not a cosmetic one. A flawed plan could go straight into Build mode, get implemented, and only get caught at the very end — or not at all, if nobody asked. The gap wasn't in what the skill checked anymore. It was in <em>when</em> the skill got a turn to speak.</p>
<p>First attempt at a fix: strengthen the skill's own "when to use me" description, explicitly naming Plan-mode drafting as a trigger condition. Tested it on a fresh session. Still didn't fire unprompted. Ruled out empirically — not because I assumed it wouldn't work, but because I tried it and watched it fail again.</p>
<p>At this point there were two real options left:</p>
<ol>
<li><p><strong>A hard, non-LLM gate</strong> between Plan and Build modes — something that runs the security check regardless of what the model decides to reach for. This is the more robust answer. It doesn't depend on the model's judgment about when a skill is relevant; it just always runs. I didn't build this.</p>
</li>
<li><p><strong>Force the check via a custom system prompt</strong> that makes security review part of Plan mode itself, rather than an optional skill the model might invoke.</p>
</li>
</ol>
<p>I went with option 2, mostly because it was faster to test and I wanted to see if it was even necessary to reach for the harder solution. It worked on the very first test after implementing it. Once a simpler fix was confirmed sufficient, I didn't go build the harder one just because it's theoretically more correct.</p>
<p>I want to be straight about the tradeoff here: option 1 is still the right answer if I need guarantees. A system-prompt instruction is a strong nudge, not a wall — it depends on the model continuing to respect it, in this context, on this attempt. A real gate doesn't care what the model feels like doing. I'm shipping the weaker-but-sufficient fix for now and treating the harder one as a known gap, not a solved problem.</p>
<h3>Fourth attempt: clean pass</h3>
<p>Same test again, unprompted. It flagged the query-param issue and fixed it before writing a single line of the actual script. No prompting, no follow-up question needed.</p>
<h3>What this actually says</h3>
<p>Two bugs, two fixes, and the only reason I found either one is that I broke the thing on purpose instead of trusting the design on paper. The skill's own checklist looked reasonable before I tested it — "check for hardcoded secrets, check for secrets in logs" sounds like solid coverage until you notice it says nothing about transmission. And "the skill exists and is well-written" felt like enough until I noticed nothing was forcing it to actually run at the moment it mattered.</p>
<p>Neither miss was exotic. Both were the kind of gap that's obvious in hindsight and invisible until something concrete walks into it. That's the whole argument for testing security tooling by attacking it rather than reading it and nodding along.</p>
<p>The AI knew the right answer both times, as soon as it was asked. The system wasn't failing on knowledge. It was failing on <em>process</em> — on which questions get asked, and when. That's a more useful thing to know than "the model is smart" or "the model is dumb." It's neither. It's a matter of what's actually wired to fire, and whether you've checked.</p>
]]></content:encoded></item></channel></rss>