On building a blue team with AI: Scan, Sort, Send

This is a coffee shop in Asakusa, Tokyo, that I visited in winter 2025. The Pegasus on the sign reminds me of Saint Seiya. A guardian. Seemed fitting for these notes on building our own defenses.
A security report about our website landed in my inbox. It got me thinking about how we were checking for these things in the first place, and what happens between one report and the next.
AI makes attacking cheaper. New CVEs show up every week. Our code can stay exactly the same and something we depend on can become a security concern. To me, having a blue team for defense and a red team for offense is a must now. Waiting until we have more time doesn’t make much sense.
The same AI helps us build the defenses too. I built the first version of our setup in about a day with Claude Code. I think that’s the part worth sharing: getting started was much more accessible than I expected.
The security tools themselves already exist. Mature, open source tools, each with a fairly clear job. AI helped me put them together into a working setup. The scanners do the checking; Claude Code helped with the integration. I still had to decide what needed checking and what should happen to the results.
This post covers the repository side of BLUE: our code, dependencies, and infrastructure configuration. Checking our public-facing systems needs additional tools.
SSS: Scan, Sort, Send

I like having a simple way to think about a system. For this one, it’s three steps:
- 🔍 Scan — check repositories, applications, websites, and infrastructure.
- 🗂️ Sort — one place for the findings, no duplicates, kept up to date.
- 📣 Send — get the important findings to real people.
Outside security, my wife and I also have AAA — Ashadiya Aksara Ayodya, the company behind our side hustle, Aksara. I rarely get SSS or AAA cards, so I decided to build a company and a framework around those letters instead. 😄
The last two matter as much as the first. A scanner can give us a report, but somebody still needs to know which findings are current and what needs their attention. I wanted the whole flow working, not just a collection of scanner outputs.
🔍 Scan: what I picked, and why
All five scanning tools are open source. I picked them for different parts of the problem.
🔑 gitleaks: secrets in git. The important bit is the full git history. Deleting a key from the current code doesn’t remove it from an earlier commit. It’s fast, and it produces SARIF, a common format for reporting findings. For this check, looking only at what’s in the repository today would miss the point.
🧩 Semgrep: risky code patterns. I like readable rules. I want to understand what a scanner is looking for, rather than just accept its verdict. Semgrep supports Go and TypeScript, which is most of our stack, and the open source version runs offline. Our code stays on our server for this scan. Those were practical reasons to choose it, without needing a different tool for each language.
🐹 govulncheck: vulnerable Go functions we actually call. A dependency finding tells us a library has a problem. I also want to know whether our code calls the affected function. govulncheck, maintained by the Go team, gives us that reachable-function view. It helps us focus on reachable vulnerabilities when reviewing dependency risk.
📦 Syft: what’s inside the application. It builds an SBOM, or Software Bill of Materials. Basically, a list of libraries and versions. The useful part comes after generating it: we can recheck that inventory daily for fresh CVEs, without rescanning the source. Nobody needs to change the code for a dependency to become relevant to a new vulnerability report.
🏗️ Trivy config: infrastructure code. Terraform, Kubernetes, Dockerfiles. Open security groups, public buckets, containers running as root. These belong in the checks too. Trivy’s config scanning covers those file types in one tool and produces SARIF output, so infrastructure findings can join the rest of the results.
🔗 SARIF: a common language for findings
I’ve mentioned SARIF twice above. It stands for Static Analysis Results Interchange Format. It’s a standard JSON format for static analysis results. A report can carry the rule that triggered, an explanation, and the file and line involved. Compatible tools get a shared structure to read.
Here’s a small, fictional example, with a made-up scanner, rule, and file:
1{
2 "version": "2.1.0",
3 "runs": [
4 {
5 "tool": {
6 "driver": {
7 "name": "example-scanner",
8 "rules": [{ "id": "EXAMPLE001" }]
9 }
10 },
11 "results": [
12 {
13 "ruleId": "EXAMPLE001",
14 "level": "warning",
15 "message": { "text": "Review this configuration setting." },
16 "locations": [
17 {
18 "physicalLocation": {
19 "artifactLocation": { "uri": "example/config.yaml" },
20 "region": { "startLine": 12 }
21 }
22 }
23 ]
24 }
25 ]
26 }
27 ]
28}
Here, ruleId identifies the check, message explains it, and locations points to line 12 of example/config.yaml. A compatible tool can read those fields without scraping a terminal log. Same information, less translation work.
Why I care: I want the scanners to feed the next step without needing a different report reader for every tool. I’d rather spend time fixing things than teaching a dashboard five dialects of JSON. SARIF output helps reduce that tool-specific plumbing between Scan and Sort, and makes the reports reusable across systems that support it.
Support still matters at both ends. SARIF carries the results; deduplication and ticket workflows belong to the tools consuming them. It also has a different job from an SBOM: SARIF reports findings; an SBOM inventories components. Gitleaks and Trivy’s SARIF output help with the findings handoff, while Syft supplies the library and version inventory.
🗂️ Sort: adopt, not build
I was tempted to build our own dashboard. But SecObserve already does what we need, and adopting it meant I could spend the effort on getting the flow working.
It’s open source. It collects findings in one place, removes duplicates, keeps them current, gives us a security gate per product, and comes with Jira integration. That’s quite a lot of work I don’t need to rebuild.
It also rechecks our SBOMs daily against OSV. This completes the Syft part: generate the inventory, then keep checking it against what we know about vulnerabilities. The nightly source scans and the daily inventory checks have different jobs.
📣 Send: put findings where people work
Every High or Critical finding becomes a Jira ticket automatically. When the finding is fixed, the ticket closes automatically too. I don’t want a dashboard saying one thing while the ticket still says another.
Alerts go to Team Chat. A leaked secret gets an @all. Finding a key in history is useful only if we actually get it rotated. This is one notification I don’t want quietly waiting for the next sprint.
For now, nothing blocks merges. I want signal before friction. We need useful findings reaching the team before deciding what should interrupt their work.
💥 The first big run
The scans run nightly at midnight on a self-hosted GitHub Actions runner: changed repositories nightly, all repositories once weekly. The SBOM rechecks happen daily. We also keep encrypted off-server backups.
The first big run crashed the PoC server. Apparently, it also needed protection from my security checks. Since I’m a cheapskate myself, I decided to Limits first, then scale. Every tool now has a memory cap and a time limit. Building the first version in a day was useful. The server still needed enough memory to run it.
For me, the useful part starts when a finding reaches someone who can fix it.
🌐 Next, the public-facing side. In the next post, I’ll cover monitoring our public-facing systems and the checks behind reports like the one that landed in my inbox. That’s part of blue-team monitoring too.
🔴 After that comes RED: Strix, the AI pentest agent, with an abliterated AI model.
References:
- SecObserve — collects and manages the findings.
- OSV — vulnerability database for the daily SBOM rechecks.
- Strix — the AI pentest agent for the later RED post.