I Built a GitHub Security Agent in 48 Hours Using Coral — Here’s Exactly How
On May 19, 2026, GitHub lost 3,800 internal repositories. The entry point wasn’t a sophisticated zero-day exploit. It was a poisoned VS…
I Built a GitHub Security Agent in 48 Hours Using Coral — Here’s Exactly How

Blog written by Shrujan Kharwadey (Team Sparrow)
On May 19, 2026, GitHub lost 3,800 internal repositories. The entry point wasn’t a sophisticated zero-day exploit. It was a poisoned VS Code extension installed on one developer’s machine. It was live for 18 minutes. That was enough.
When I read that, my first thought wasn’t “that’s scary.” It was: ”I have 31 repos, I use third-party GitHub Actions I’ve never audited, and I have absolutely no idea if I’m exposed right now.”
That’s the problem Barnacle solves. And this is the story of how I built it — from zero to a working web app — using Coral, a tool that gives you one SQL interface over all your APIs.
— -
## What Is Coral and Why Did I Use It?
Most agent workflows access data one API call at a time. You call GitHub for repos. Then call it again for workflows. Then again for issues. That’s 90+ API calls to scan 31 repos — with custom pagination, retry logic, and JSON parsing for each one.
Coral collapses all of that into SQL.
SELECT name, language, updated_at, open_issues_count
FROM github.user_repos
WHERE owner__login = ‘Unearthly-2004’
ORDER BY updated_at DESC
One query. All your repos. Clean tabular rows. No pagination code. No API client setup. Just SQL.
This is what made Barnacle possible in 48 hours instead of 2 weeks.
— -
## The Problem Statement

Every developer has the same blind spot:
- Third-party GitHub Actions running in their repos they’ve never audited
- Stale repos sitting unpatched for months
- Public repos with open issues piling up
- Zero single place to ask: ”Am I exposed right now?”
The GitHub breach happened through the exact first vector — a third-party tool with malicious code. TeamPCP used it to steal credentials in 18 minutes.
Barnacle makes your attack surface visible before someone exploits it.
— -
## The Architecture
Here’s the full picture of how it works:
Browser (Next.js — Neo-brutalist UI)
↓ clicks SCAN MY GITHUB
Next.js API Route (/api/scan)
↓ spawns coral as subprocess
Coral (running in WSL2 Ubuntu)
↓ translates SQL → GitHub REST API
GitHub API
↓ returns data
Coral returns clean rows
↓ risk engine scores each repo
Frontend renders security dashboard
No database. No GitHub token in the browser. Data never leaves your machine. Coral handles everything.

Architecture Diagram
— -
## Part 1: Setting Up the Environment
Step 1 — Install WSL2 on Windows
If you’re on Windows like me, first install WSL2. Open PowerShell as Administrator:
wsl — install -d Ubuntu
This downloads Ubuntu and sets it up. You’ll be asked to create a username and password. Once done, verify it’s running:
wsl — list — verbose
You should see Ubuntu with VERSION 2.

ubuntu versioning
### Step 2 — Install Coral
Open your Ubuntu terminal (search “Ubuntu” in Start Menu) and run:
curl -fsSL [https://withcoral.com/install.sh](https://withcoral.com/install.sh) | bash
Add it to your PATH:
echo ‘export PATH=”/home/$USER/.local/bin:$PATH”’ >> ~/.bashrc
source ~/.bashrc
Verify:
coral — version
# coral 0.2.1+fbe8a36

coral version
### Step 3 — Connect GitHub as a Coral Source
coral source add — interactive github
Coral will ask for your GitHub Personal Access Token. Create one at:
github.com → Settings → Developer settings → Personal access tokens → Tokens (classic)
Scopes needed: repo, read:org, read:user
Once connected, verify it works:
coral sql “SELECT schema_name, COUNT(*) FROM coral.tables GROUP BY schema_name”
You should see github | 362 — meaning 362 GitHub tables are now queryable via SQL.

queried with coral on shell
### Step 4 — Install Node.js in WSL
This is critical — you need Node.js inside WSL, not just on Windows:
curl -fsSL [https://deb.nodesource.com/setup_20.x](https://deb.nodesource.com/setup_20.x) | sudo bash -
sudo apt-get install -y nodejs
node — version # should show v20.x.x
— -
## Part 2: Building Barnacle
Step 5 — Scaffold the Project
mkdir barnacle
cd barnacle
npx create-next-app@14 . — typescript — tailwind — app — no-src-dir — import-alias “@/*”
Install dependencies:
npm install lucide-react
Step 6 — The Coral Query Layer
This is the heart of the project. Create lib/coral.ts:
import { execFileSync } from ‘child_process’
export async function coralQuery(sql: string): Promise<string> {
const result = execFileSync(
‘/home/YOUR_USERNAME/.local/bin/coral’,
[‘sql’, sql],
{
timeout: 30000,
encoding: ‘utf-8’,
env: {
…process.env,
HOME: `/home/YOUR_USERNAME`,
PATH: ‘/home/YOUR_USERNAME/.local/bin:/usr/local/bin:/usr/bin:/bin’,
CORAL_CONFIG_DIR: `/home/YOUR_USERNAME/.config/coral`
}
}
)
return result
}
Replace YOUR_USERNAME with your WSL username.
Why execFileSync and not exec? Because it passes SQL as a direct array argument — no shell interpretation, no quote escaping issues. This was the hardest bug I hit during the hackathon.
Step 7 — The ASCII Table Parser
Coral returns data as ASCII tables. Create lib/parser.ts to convert them to JSON:
export function parseCoralTable(raw: string): Record<string, string>[] {
const lines = raw.trim().split(‘\n’)
const dataLines = lines.filter(l => l.startsWith(‘|’))
if (dataLines.length < 2) return []
const headers = dataLines[0]
.split(‘|’)
.map(h => h.trim())
.filter(Boolean)
return dataLines.slice(1).map(line => {
const values = line.split(‘|’).map(v => v.trim()).filter(Boolean)
return Object.fromEntries(headers.map((h, i) => [h, values[i] ?? ‘’]))
})
}
Step 8 — The SQL Queries
The key insight from Coral’s constraints — learned through trial and error:
// ✅ This works for personal repos
SELECT name FROM github.user_repos WHERE owner__login = ‘you’
// ❌ This does NOT work
SELECT name FROM github.repos WHERE owner = ‘you’
// Error: github.repos requires WHERE team_id = <constant>
Create lib/queries.ts:
const OWNER = ‘YOUR_GITHUB_USERNAME’
export const QUERIES = {
allRepos: () =>
`SELECT name, language, updated_at, open_issues_count, private, archived, fork
FROM github.user_repos WHERE owner__login = ‘${OWNER}’
ORDER BY updated_at DESC`,
workflows: (repo: string) =>
`SELECT id, name, path, state FROM github.workflows
WHERE owner = ‘${OWNER}’ AND repo = ‘${repo}’`,
openIssues: (repo: string) =>
`SELECT number, title, created_at FROM github.issues
WHERE owner = ‘${OWNER}’ AND repo = ‘${repo}’
AND state = ‘open’ LIMIT 5`,
}
Step 9 — The Risk Scoring Engine
Create lib/risk.ts:
export type RiskLevel = ‘CRITICAL’ | ‘HIGH’ | ‘MEDIUM’ | ‘LOW’ | ‘SAFE’
export function scoreRepo(repo: any, workflows: any[]): RiskLevel {
const daysSince = Math.floor(
(Date.now() — new Date(repo.updated_at).getTime()) / 86400000
)
const hasThirdPartyActions = workflows.some(w =>
w.path && ![‘actions/’, ‘github/’].some(t => w.path.includes(t))
)
if (hasThirdPartyActions) return ‘CRITICAL’
if (repo.private === ‘false’ && daysSince > 180) return ‘HIGH’
if (parseInt(repo.open_issues_count) > 10 && daysSince > 90) return ‘MEDIUM’
if (repo.fork === ‘true’) return ‘MEDIUM’
if (repo.archived === ‘true’) return ‘LOW’
return ‘SAFE’
}
The logic mirrors the actual GitHub breach:
- CRITICAL = uses third-party GitHub Actions → same attack vector as the breach
- HIGH = public repo not updated in 6 months → likely unpatched dependencies
- MEDIUM = forked repos or many open issues → upstream could be compromised
Step 10 — The Scan API Route
Create app/api/scan/route.ts:
import { NextResponse } from ‘next/server’
import { coralQuery } from ‘@/lib/coral’
import { parseCoralTable } from ‘@/lib/parser’
import { QUERIES } from ‘@/lib/queries’
import { scoreRepo } from ‘@/lib/risk’
export async function POST() {
try {
// Get all repos via Coral SQL
const reposRaw = await coralQuery(QUERIES.allRepos())
const repos = parseCoralTable(reposRaw)
// Get workflows per repo — batched 5 at a time
const scored = []
for (let i = 0; i < repos.length; i += 5) {
const batch = repos.slice(i, i + 5)
const results = await Promise.allSettled(
batch.map(repo =>
coralQuery(QUERIES.workflows(repo.name))
.then(parseCoralTable)
.catch(() => [])
)
)
for (let j = 0; j < batch.length; j++) {
const workflows = results[j].status === ‘fulfilled’
? results[j].value : []
scored.push({ …batch[j], workflows, risk: scoreRepo(batch[j], workflows) })
}
}
const riskScore = Math.min(100, Math.round(
scored.reduce((sum, r) => sum + ({ CRITICAL:100, HIGH:70, MEDIUM:40, LOW:15, SAFE:0 }[r.risk] ?? 0), 0)
/ scored.length
))
return NextResponse.json({ repos: scored, riskScore, scannedAt: new Date().toISOString() })
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
}
— -
## Part 3: The Neo-Brutalist UI
I wanted Barnacle to feel like a hacker zine — loud, bold, in-your-face. None of the smooth corporate SaaS aesthetic. If GitHub’s breach was a quiet disaster, Barnacle’s findings should scream at you.

Barnacle HomePage
The design system I used: - Font: Space Grotesk 900 weight — nothing lighter
- Borders:
border-4 border-blackon every single element - Shadows: Hard offset, zero blur —
shadow-[8px_8px_0px_0px_#000] - Colors: Cream background, hot red for critical, vivid yellow for safe
- Buttons: Push DOWN on click like a physical switch

Comparitive Check → 2 workflows detected in repository named Evershop
— -
## Part 4: Running It
Always run from inside WSL Ubuntu terminal, not Windows:
cd /mnt/e/your-project-folder
npm run dev
Then open [http://localhost:3000](http://localhost:3000`) in your Windows browser.

Risk Report
— -
## What I Learned
1. Coral’s SQL constraints are real — read them first.
I wasted an hour trying github.repos before discovering it needs team_id. Use github.user_repos for personal accounts. Always run coral.columns before writing a real query.
2. Run everything in the same environment.
The hardest bug was Node.js running on Windows trying to call a Linux binary. The fix: install Node.js in WSL and run npm run dev from the WSL terminal. Everything shares the same filesystem.
3. execFileSync over exec for subprocess calls.
Passing SQL as a shell string argument causes quote escaping hell. execFileSync with an args array bypasses the shell entirely — no escaping, no injection, no headaches.
4. Coral makes cross-source JOINs trivial.
The same setup that queries repos can query issues, workflows, and commits — and JOIN them. What would be 90 API calls becomes 3 SQL queries.
— -
## The Numbers
Barnacle benchmarks against direct GitHub API calls:
| Metric | Direct API | Coral SQL | | — — — — | — — — — — -| — — — — — -| | API calls for 31 repos | 90+ | 3 | | Code to handle pagination | ~150 lines | 0 | | Time to scan | ~45 seconds | ~8 seconds | | Quote escaping bugs | Many | Zero |
— -
## Try It Yourself
The full source is on GitHub: github.com/Unearthly-2004/barnacle
To run it on your own GitHub account:
- Install Coral:
curl -fsSL [https://withcoral.com/install.sh](https://withcoral.com/install.sh) | bash - Connect GitHub:
coral source add — interactive github - Clone the repo and update
OWNERinlib/queries.ts - Install Node.js in WSL and run
npm run dev - Open
[http://localhost:3000](http://localhost:3000`) and click SCAN MY GITHUB

— -
## What’s Next for Barnacle
- OAuth flow — let any developer scan their own GitHub, not just mine
- Scheduled scans — run every 24 hours, alert on new CRITICAL findings
- More sources — add Sentry and Datadog to correlate security events with repo activity
- Write layer — when Coral supports mutations, auto-create GitHub issues for CRITICAL findings
— -
Blog Written By Shrujan Kharwadey (Team Sparrow)
— -
Built by Team Sparrow at the Pirates of the Coral-bean Hackathon 2026. Powered by Coral (https://withcoral.com) — one SQL interface over all your APIs.
— -
💬 Found this useful? Share it with your team. Every developer should know their blast radius.
메타데이터
- post_id
- 5efa0977fb7f
- slug
- i-built-a-github-security-agent-in-48-hours-using-coral-heres-exactly-how-5efa0977fb7f
- url
- https://medium.com/@kharwadeyshrujan/i-built-a-github-security-agent-in-48-hours-using-coral-heres-exactly-how-5efa0977fb7f
- canonical_url
- https://medium.com/@kharwadeyshrujan/i-built-a-github-security-agent-in-48-hours-using-coral-heres-exactly-how-5efa0977fb7f
- author_url
- https://medium.com/@kharwadeyshrujan
- status
- ok
- fetched_at
- 2026-07-14 19:59:56