DEV Community

Cover image for How to Build a Node.js Logger That Catches OWASP Top 10 Attacks and Alerts on Slack
Pentest Testing Corp
Pentest Testing Corp

Posted on

How to Build a Node.js Logger That Catches OWASP Top 10 Attacks and Alerts on Slack

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 curl and browser requests

All the code is provided right here, just copy and paste it into your project.

Node.js Logger That Catches OWASP Top 10 Attacks and Alerts on Slack


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


3. Project Setup

Create a new directory:

mkdir slack-attack-logger
cd slack-attack-logger
npm init -y
npm install express dotenv
Enter fullscreen mode Exit fullscreen mode

Create a .env file for your Slack webhook URL (never hard‑code it):

SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../...
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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'));
Enter fullscreen mode Exit fullscreen mode

8. Testing with Malicious Requests

Start the server:

node server.js
Enter fullscreen mode Exit fullscreen mode

Now fire some attacks using curl:

SQL Injection in query string:

curl "http://localhost:3000/search?q=%27%20OR%20%271%27%3D%271"
Enter fullscreen mode Exit fullscreen mode

XSS in POST body:

curl -X POST http://localhost:3000/login \
  -H "Content-Type: application/json" \
  -d '{"username":"<script>alert(1)</script>", "password":"test"}'
Enter fullscreen mode Exit fullscreen mode

Path Traversal:

curl "http://localhost:3000/../../etc/passwd"
Enter fullscreen mode Exit fullscreen mode

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)