Automated SAST & Secrets Detection
Static Analysis & Gitleaks β Day 2
Audit vulnerable-store-api with Gitleaks for high-entropy tokens and author custom Semgrep AST rules to detect unparameterized SQL injections.
In this lab playbook, you will inspect source code without executing it (Static Application Security Testing). You will use Gitleaks to scan commit history for inadvertently hardcoded API tokens, and write custom Abstract Syntax Tree (AST) rules in Semgrep to catch unparameterized SQL queries in server.js.
The official reference codebase for Day 2 is hosted in the repository. Clone the repository and navigate to day-2-sast-secrets to run the working solution.
git clone https://github.com/letstrnsfrm-ai/security-compliance-roadmap.git
cd security-compliance-roadmap/day-2-sast-secretsInject Hardcoded Secrets & Insecure SQL Queries
Create an Express server file (server.js) simulating vulnerable legacy code containing hardcoded credentials and string-concatenated SQL queries.
const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const app = express();
app.use(express.json());
// β οΈ VULNERABILITY 1: Hardcoded AWS Production Key & JWT Secret
const AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE_SECRET_KEY_PROD";
const JWT_PRIVATE_SECRET = "super-secret-production-signing-key-12345";
const db = new sqlite3.Database(':memory:');
db.serialize(() => {
db.run("CREATE TABLE users (id INT, username TEXT, password TEXT, role TEXT)");
db.run("INSERT INTO users VALUES (1, 'admin', 'SuperAdminSecret!', 'architect')");
db.run("INSERT INTO users VALUES (2, 'john', 'UserPass123', 'reader')");
});
// β οΈ VULNERABILITY 2: Unparameterized SQL Query (Direct String Concatenation)
app.get('/api/users', (req, res) => {
const username = req.query.username || '';
const query = "SELECT id, username, role FROM users WHERE username = '" + username + "'";
db.all(query, [], (err, rows) => {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
});
});
app.listen(3001, () => console.log('Store API running on port 3001'));Audit High-Entropy Secrets with Gitleaks
Run Gitleaks locally to detect leaked credentials across your files and git commit history.
Finding: AKIAIOSFODNN7EXAMPLE_SECRET_KEY_PROD
RuleID: aws-access-token
Secret: AKIAIOSFODNN7EXAMPLE_SECRET_KEY_PROD
File: server.js
Line: 7
Commit: HEAD
Entropy: 4.312910Author Custom AST Rule in Semgrep
Semgrep analyzes code syntax trees (ASTs) rather than raw text regexes. Write a custom Semgrep rule to detect direct SQL string concatenation.
rules:
- id: no-unparameterized-sql
languages: [javascript, typescript]
message: "Detected unparameterized SQL query constructed via string concatenation. Use parameterized queries (?, $1) to prevent SQL Injection."
severity: ERROR
metadata:
cwe: "CWE-89: Improper Neutralization of Special Elements used in an SQL Command"
owasp: "A03:2021 - Injection"
patterns:
- pattern-either:
- pattern: $DB.all($QUERY + $INPUT, ...)
- pattern: $DB.run($QUERY + $INPUT, ...)
- pattern: $DB.all(`... ${$INPUT} ...`, ...)
- pattern: $DB.run(`... ${$INPUT} ...`, ...)Remediate Secrets & Parameterize SQL Queries
Extract secrets into environment variables and refactor the SQLite query to use parameterized query placeholders.
const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const app = express();
app.use(express.json());
// β
SECURE 1: Loaded from Environment Variables with Fallbacks
const AWS_SECRET_KEY = process.env.AWS_SECRET_KEY || '';
const JWT_PRIVATE_SECRET = process.env.JWT_PRIVATE_SECRET || '';
const db = new sqlite3.Database(':memory:');
db.serialize(() => {
db.run("CREATE TABLE users (id INT, username TEXT, password TEXT, role TEXT)");
db.run("INSERT INTO users VALUES (1, 'admin', 'SuperAdminSecret!', 'architect')");
db.run("INSERT INTO users VALUES (2, 'john', 'UserPass123', 'reader')");
});
// β
SECURE 2: Parameterized SQL Query with Prepared Statement Placeholder (?)
app.get('/api/users', (req, res) => {
const username = req.query.username || '';
const query = "SELECT id, username, role FROM users WHERE username = ?";
db.all(query, [username], (err, rows) => {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
});
});
app.listen(3001, () => console.log('Store API running on port 3001'));semgrep scan --config .semgrep/
# Output: 0 findings. Ran 1 rule across 1 file.
gitleaks dir --verbose
# Output: No leaks found.- Understood high-entropy credential leakage risks and automated scanning with Gitleaks.
- Discovered why raw regex patterns produce false positives and why AST-based SAST (Semgrep) provides deterministic findings.
- Created a custom Semgrep security rule (.semgrep/no-unparameterized-sql.yml) mapped to CWE-89.
- Refactored server.js to use environment variables and parameterized SQL placeholders, achieving a zero-finding scan.
π§ Knowledge Check
What makes Abstract Syntax Tree (AST) scanning in Semgrep superior to regular expressions for SQL injection detection?