A beginner‑friendly, 45‑minute clone of Cloudflare’s security‑audit skill using Node.js and SQLite
Welcome to another official UVF IT build guide! In this tutorial you’ll create a tiny security‑audit skill that mimics the core flow of Cloudflare’s original repo, but runs entirely on your laptop with free tools.
We’ll scaffold a Node.js project, store audit metadata in SQLite, expose a tiny REST API, and generate a JSON findings report – all in under an hour.
npm audit on the local package.json; it does not perform network‑wide reconnaissance or multi‑agent orchestration.Create a fresh npm project and install the minimal dependencies. This gives us a clean workspace and pulls in Express for the HTTP layer and better‑sqlite3 for a fast, native SQLite binding.
We’ll also add a tiny start script so you can launch the server with a single command, keeping the workflow simple for beginners.
mkdir security-audit-clone && cd $_
npm init -y
npm install express@4 better-sqlite3@8
# add a start script to package.json (you can edit manually or run the next line)
node -e "let p=require('./package.json');p.scripts.start='node index.js';require('fs').writeFileSync('package.json',JSON.stringify(p, null, 2));"Set up a tiny SQLite file with a findings table to store audit results. Using better‑sqlite3 lets us interact with the DB synchronously, which is perfect for a small prototype.
The module creates the database file on first run and guarantees the table exists, so later code can simply insert rows without extra checks.
db.js
import Database from 'better-sqlite3';
// Open (or create) a local SQLite file named audit.db in the project root
const db = new Database('audit.db');
// Ensure the findings table exists – id, package, severity, description, timestamp
const init = `CREATE TABLE IF NOT EXISTS findings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
package TEXT NOT NULL,
severity TEXT NOT NULL,
description TEXT,
recorded_at TEXT DEFAULT (datetime('now'))
);`;
db.exec(init);
export function addFinding(pkg, severity, description) {
const stmt = db.prepare('INSERT INTO findings (package, severity, description) VALUES (?, ?, ?)');
stmt.run(pkg, severity, description);
}
export function getFindings() {
return db.prepare('SELECT * FROM findings ORDER BY recorded_at DESC').all();
}
export default db;We add an /audit endpoint that the client can call to start a security scan. The handler runs npm audit --json as a child process, picks out the vulnerable dependencies, and writes each finding into the SQLite findings table we created earlier. Storing the data lets us query it later and mimics the original Cloudflare skill’s ledger behavior.
The code uses Node's child_process.exec to capture the audit output, parses the JSON, and then iterates over the vulnerabilities object. For each vulnerable package we insert a row with the package name, severity, and a short description. This keeps the server lightweight and fully asynchronous.
server.js
import express from 'express';
import { exec } from 'child_process';
import db from './db.js';
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/audit', (req, res) => {
exec('npm audit --json', (err, stdout) => {
if (err) return res.status(500).json({error:'audit failed'});
const audit = JSON.parse(stdout);
const vulns = audit.vulnerabilities || {};
const stmt = db.prepare('INSERT INTO findings (package, severity, title) VALUES (?,?,?)');
db.transaction(() => {
for (const pkg in vulns) {
const { severity, title } = vulns[pkg];
stmt.run(pkg, severity, title);
}
})();
res.json({status:'audit completed', inserted:Object.keys(vulns).length});
});
});
app.listen(PORT, () => console.log(`Server listening on ${PORT}`));Now we start the server, hit the new endpoint, and make sure the findings landed in SQLite. The commands below launch the app in the background, call the API with curl, and then query the findings table using the sqlite3 CLI. If everything is wired correctly you’ll see a count greater than zero, confirming that the audit data was persisted.
You can also inspect a few rows directly to see the stored package name, severity, and description – this mirrors the original skill’s ledger view.
# Start the server in the background
node server.js &
SERVER_PID=$!
# Give it a moment to bind
sleep 2
# Trigger the audit endpoint
curl -s http://localhost:3000/audit
# Query SQLite for stored findings
sqlite3 audit.db "SELECT COUNT(*) FROM findings;"
# Optional: view first three rows
sqlite3 audit.db "SELECT package, severity, title FROM findings LIMIT 3;"
# Clean up
kill $SERVER_PIDTo finish the clone we expose a /report endpoint that reads every row from the findings table and writes a findings.json file matching the format of the original Cloudflare skill. The endpoint then returns the same JSON payload so callers can immediately consume the report without reading the file.
The implementation re‑uses the existing db connection, builds an array of objects, writes it with fs.promises.writeFile, and finally sends the data back to the client.
server.js
import fs from 'fs/promises';
// ... previous imports and /audit route stay unchanged
app.get('/report', async (req, res) => {
try {
const rows = db.prepare('SELECT package, severity, title FROM findings').all();
const report = { findings: rows.map(r => ({package:r.package, severity:r.severity, title:r.title})) };
await fs.writeFile('findings.json', JSON.stringify(report, null, 2));
res.json(report);
} catch (e) {
res.status(500).json({error:'report generation failed'});
}
});
app.listen(PORT, () => console.log(`Server listening on ${PORT}`));| Check | Command / Why it matters |
|---|---|
| Project scaffolds without errors | npm install && node -e "import './db.js'" |
/audit endpoint returns a JSON success message | curl -s http://localhost:3000/audit | grep status |
| SQLite ledger contains at least one row after audit | sqlite3 audit.db "SELECT COUNT(*) FROM findings;" |
/report endpoint returns a JSON array of findings | curl -s http://localhost:3000/report | jq '.findings|length' |
Your minimal security‑audit skill is now live. Feel free to add more phases – e.g., write a markdown summary, version the ledger, or plug in a CI step. Happy hunting!