Most developers don’t think about common web attacks until something breaks—or worse, until a breach is already in the news. But what if you could spot SQL injection, cross-site scripting, and path traversal in real time as they hit your API, even in development or staging?
In this tutorial, I’ll walk you through building a lightweight Express middleware that scans incoming requests for patterns from the OWASP Top 10, then pushes a detailed alert to your team’s Slack channel. Think of it as an early‑warning tripwire—it won’t replace a full penetration test or a Web Application Firewall, but it will make you instantly aware when something malicious knocks on your door.
We’ll cover:
- A simple, extendable detection engine for common attack signatures
- Secure Slack webhook integration
- Middleware that logs both the attack and the original request context
- Practical testing with
curland browser requests
All the code is provided right here, just copy and paste it into your project.
1. What Exactly Will We Catch?
We’ll focus on four broad, dangerous families that keep showing up in OWASP’s Top 10:
-
SQL Injection (SQLi) –
' OR 1=1 --,UNION SELECT, etc. -
Cross‑Site Scripting (XSS) –
<script>,onerror=,javascript: -
Path Traversal –
../,..\\,/etc/passwd -
Command Injection –
; ls,| cat, backticks
For demonstration purposes, the detection will be pattern‑based (string matching) rather than AST‑level. That keeps the code clear and easy to modify. Real attackers can obfuscate payloads, so treat this as a low‑friction first filter, not a comprehensive defense.
2. Prerequisites
- Node.js (v18 or later) and npm
- An Express server to instrument
- A Slack workspace where you can create an incoming webhook
3. Project Setup
Create a new directory:
mkdir slack-attack-logger
cd slack-attack-logger
npm init -y
npm install express dotenv
Create a .env file for your Slack webhook URL (never hard‑code it):
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../...
Quick security note: Keep this .env out of version control by adding it to .gitignore.
4. Building the Detection Engine
Create a file named detection.js. This module exports a function that takes a req object and returns an array of detected threats (or an empty array if clean).
// detection.js
const attackPatterns = [
{
name: 'SQL Injection',
patterns: [
/(\bUNION\b.+?\bSELECT\b)/i,
/(\bSELECT\b.+?\bFROM\b)/i,
/(' OR '1'='1)/i,
/(--[^\\n]*$)/im,
/(\/\*.*\*\/)/i, // inline comments
/\bOR\b\s+\d=\d/i
]
},
{
name: 'Cross-Site Scripting (XSS)',
patterns: [
/<script[^>]*>.*?<\/script>/i,
/javascript:/i,
/on\w+\s*=\s*"[^"]*"/i, // onerror, onclick etc.
/<img[^>]+onerror\s*=/i,
/alert\s*\(/i
]
},
{
name: 'Path Traversal',
patterns: [
/\.\.\//,
/\.\.\\/,
/\/etc\/passwd/i,
/\/windows\/win\.ini/i
]
},
{
name: 'Command Injection',
patterns: [
/;\s*(ls|id|cat|rm|pwd)/i,
/\|\s*(ls|id|cat)/i,
/`[^`]+`/,
/\$\([^\)]+\)/,
/&&\s*(curl|wget)/i
]
}
];
function detectThreats(req) {
const sources = [
req.url,
...Object.values(req.body || {}).map(String),
...Object.values(req.query || {}).map(String),
...Object.values(req.params || {}).map(String),
req.headers['user-agent'] || '',
req.headers['referer'] || ''
].join(' ');
const found = [];
for (const category of attackPatterns) {
for (const regex of category.patterns) {
if (regex.test(sources)) {
found.push({
category: category.name,
matchedPattern: regex.source,
snippet: sources.substring(0, 200) // small context
});
break; // one hit per category is enough
}
}
}
return found;
}
module.exports = { detectThreats };
Why we joined URL, body, query, params, and select headers: attackers love to hide payloads anywhere. In a production setup, you’d want to parse application/json, URL‑encoded, multipart (maybe skip file uploads), but for now this covers the most common paths.
5. The Slack Notification Module
Create slackNotifier.js. It formats the detection results into a clean Slack message and sends it to your webhook.
// slackNotifier.js
const https = require('https');
async function sendSlackAlert(threats, req) {
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
if (!webhookUrl) {
console.warn('SLACK_WEBHOOK_URL not set, skipping alert');
return;
}
const blocks = [
{
type: 'header',
text: { type: 'plain_text', text: '🚨 Attack Detected on Dev/Staging API' }
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Method:*\n${req.method}` },
{ type: 'mrkdwn', text: `*URL:*\n${req.originalUrl}` },
{ type: 'mrkdwn', text: `*IP:*\n${req.ip}` }
]
}
];
threats.forEach((t, i) => {
blocks.push({
type: 'section',
text: {
type: 'mrkdwn',
text: `*Threat ${i+1}:* ${t.category}\n*Pattern:* \`${t.matchedPattern}\`\n*Snippet:* \`\`\`${t.snippet}\`\`\``
}
});
});
const message = { text: 'Attack detected', blocks };
return new Promise((resolve, reject) => {
const data = JSON.stringify(message);
const url = new URL(webhookUrl);
const options = {
hostname: url.hostname,
port: 443,
path: url.pathname + url.search,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length
}
};
const reqSlack = https.request(options, (res) => {
res.on('data', () => {}); // consume
res.on('end', resolve);
});
reqSlack.on('error', reject);
reqSlack.write(data);
reqSlack.end();
});
}
module.exports = { sendSlackAlert };
The message uses Slack Block Kit to present a clean, scannable alert that your team can act on immediately. In a real engagement, you might deduplicate alerts to avoid spam.
6. Wiring the Middleware
Now we create the actual Express middleware, attackLoggerMiddleware.js:
// attackLoggerMiddleware.js
const { detectThreats } = require('./detection');
const { sendSlackAlert } = require('./slackNotifier');
function attackLoggerMiddleware(req, res, next) {
const threats = detectThreats(req);
if (threats.length > 0) {
// Log locally as well
console.warn(`[SECURITY] ${threats.length} threat(s) detected from ${req.ip}`);
// Fire Slack notification (don't block the response)
sendSlackAlert(threats, req)
.catch(err => console.error('Slack notification failed', err));
}
// Always proceed – we don't block requests here, just observe.
next();
}
module.exports = { attackLoggerMiddleware };
Notice we call next() immediately. This middleware is observational only; it doesn’t interfere with the normal request flow. The Slack call is non‑blocking, so even if the webhook is slow, your API stays responsive.
7. Putting It All Together in an Express App
Create a simple server.js:
// server.js
require('dotenv').config();
const express = require('express');
const { attackLoggerMiddleware } = require('./attackLoggerMiddleware');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Apply the middleware globally
app.use(attackLoggerMiddleware);
// Example routes
app.get('/search', (req, res) => {
// vulnerable query: /search?q=test
res.json({ result: 'No results for ' + req.query.q });
});
app.post('/login', (req, res) => {
// imagine a SQL-backed login; body.username & body.password
res.send('Logged in (probably)');
});
app.listen(3000, () => console.log('Server listening on port 3000'));
8. Testing with Malicious Requests
Start the server:
node server.js
Now fire some attacks using curl:
SQL Injection in query string:
curl "http://localhost:3000/search?q=%27%20OR%20%271%27%3D%271"
XSS in POST body:
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"username":"<script>alert(1)</script>", "password":"test"}'
Path Traversal:
curl "http://localhost:3000/../../etc/passwd"
Each time, you should see a console warning and, after a second, a message in your configured Slack channel similar to:
🚨 Attack Detected on Dev/Staging API
Method: GET
URL: /search?q=' OR '1'='1
IP: ::1
Threat 1: SQL Injection
Pattern: (' OR '1'='1)/i
Snippet: /search?q=' OR '1'='1 ...
9. What’s Missing? (And Why a Pentest Still Matters)
This logger is a fantastic visibility booster for dev and staging environments, but it has deliberate limitations:
- No payload obfuscation handling – an attacker using double URL‑encoding, mixed case, or comments will sail right through.
- No business‑logic checks – IDOR, privilege escalation, and mass assignment won’t match any of our patterns.
- It’s just a logger – it doesn’t block or neutralize any request.
Real‑world attacks are rarely this straightforward. That’s where a professional penetration test comes in: we manually chain vulnerabilities, bypass filters, and dig into business logic flaws that an automated regex will never find.
👉 If you’d like to see what a real attacker could uncover in your application, our team at Pentest Testing Corp offers deep‑dive assessments tailored to your stack. Explore our services or drop us a message; we’d love to show you the gaps this logger can’t catch.
10. Next Steps
You’ve just built a fully functional attack logger from scratch. Feel free to extend the detection patterns, add deduplication, or even turn it into a simple WAF rule generator. And if you’ve built something similar—or caught an actual zero‑day with a homegrown logger—share your story in the comments!
Happy (and safe) coding!
Pentest Testing Corp helps startups and enterprises find and fix critical vulnerabilities before attackers do. Visit us at pentesttesting.com.

Top comments (0)