npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

express-sus

v0.0.2

Published

**express-sus** is a flexible Express.js middleware for detecting and preventing suspicious behavior through a dynamic scoring system. Track user actions, assign scores based on suspicious activities, and automatically block requests when thresholds are e

Readme

express-sus (SusFactor)

express-sus is a flexible Express.js middleware for detecting and preventing suspicious behavior through a dynamic scoring system. Track user actions, assign scores based on suspicious activities, and automatically block requests when thresholds are exceeded.

What is express-sus?

express-sus helps you protect your Express.js applications by:

  • Tracking suspicious behavior - Monitor and score potentially harmful actions
  • Flexible rate limiting - Block requests based on customizable score thresholds
  • Multiple boundaries - Set different limits for different parts of your application
  • Persistent storage - Choose between in-memory or Redis-based caching
  • Automatic score decay - Reduce scores over time to allow legitimate users to recover
  • Custom handlers - Define your own logic for blocked requests

Features

  • Score-based blocking - Assign scores to suspicious activities and block when thresholds are exceeded
  • Multiple zones - Create isolated scoring zones for different parts of your application
  • Flexible caching - Built-in support for in-memory and Redis cache providers
  • Automatic score reduction - Configure periodic score decay to forgive past behavior
  • Boundaries - Set multiple score thresholds throughout your application
  • Customizable - Override default behaviors with custom handlers and ID generators
  • Debug headers - Optionally expose scores and request IDs in response headers

Installation

npm install express-sus

For Redis support, also install:

npm install @redis/client

Quick Start

Basic Usage (In-Memory Cache)

const express = require('express');
const SusFactor = require('express-sus');

const app = express();

// Initialize SusFactor with default settings
const sus = new SusFactor({
  maximumScore: 100,
  addScoreToHeaders: true  // For debugging
});

// Add the middleware to your app
app.use(sus.getMiddleware());

// Create a boundary to block high-score requests
app.use(sus.getBoundary(100).getHandler());

app.get('/', (req, res) => {
  // Detect suspicious behavior and add to score
  if (req.query.username?.includes('<script>')) {
    req.sus.main.addToScore(50);  // Potential XSS attempt
  }
  
  res.send('Hello World!');
});

app.listen(3000);

Using Redis Cache

const express = require('express');
const SusFactor = require('express-sus');
const RedisCacheProvider = require('express-sus/lib/RedisCacheProvider');

const app = express();

// Create Redis cache provider
const redisCache = new RedisCacheProvider({
  ttl: 3600  // Scores expire after 1 hour
});

// Initialize SusFactor with Redis
const sus = new SusFactor({
  cacheProvider: redisCache,
  maximumScore: 100,
  scoreDecutionThreshold: 60000,  // Reduce scores every minute
  scoreDecutionAmount: 10         // Reduce by 10 points
});

app.use(sus.getMiddleware());
app.use(sus.getBoundary(100).getHandler());

app.get('/', (req, res) => {
  res.send('Hello World!');
});

async function start() {
  // Connect to Redis
  await redisCache.connect({ url: 'redis://localhost:6379' });
  app.listen(3000, () => {
    console.log('Server running on port 3000');
  });
}

start();

Configuration

SusFactor Options

const sus = new SusFactor({
  // Add current score to response headers (default: false)
  addScoreToHeaders: true,
  
  // Add request ID to response headers (default: false)
  addRequestIdToHeaders: true,
  
  // Maximum score before blocking (default: 100)
  maximumScore: 100,
  
  // Zone name for this instance (default: "main")
  zone: "api",
  
  // Interval in milliseconds for automatic score reduction (default: null)
  scoreDecutionThreshold: 5000,
  
  // Amount to reduce scores by at each interval (default: null)
  scoreDecutionAmount: 50,
  
  // Cache provider instance (default: InMemoryCacheProvider)
  cacheProvider: customCacheProvider,
  
  // Function to generate request IDs (default: IP-based)
  requestIdGenerator: (req) => req.headers['x-custom-id'],
  
  // Custom handler for blocked requests (default: 429 response)
  onBlockedHandler: (req, res) => {
    res.status(403).json({ error: 'Access Denied' });
  }
});

Cache Provider Options

InMemoryCacheProvider (Default)

const InMemoryCacheProvider = require('express-sus/lib/InMemoryCacheProvider');

const cache = new InMemoryCacheProvider({
  zone: "main"  // Zone name (default: "main")
});

RedisCacheProvider

const RedisCacheProvider = require('express-sus/lib/RedisCacheProvider');

const cache = new RedisCacheProvider({
  zone: "main",                    // Zone name (default: "main")
  prefix: "sus-factor",           // Key prefix (default: "sus-factor")
  ttl: 3600                       // Time-to-live in seconds (default: 3600, set to false to disable)
});

// Connect to Redis
await cache.connect({
  url: 'redis://localhost:6379'
});

Working with Scores

After applying the middleware, each request gets a req.sus[zone] object with methods to manage scores:

app.get('/api/data', async (req, res) => {
  // Get current score
  const score = await req.sus.main.getScore();
  
  // Set score to a specific value
  await req.sus.main.setScore(50);
  
  // Add to the score
  await req.sus.main.addToScore(25);
  
  // Reset score to zero
  await req.sus.main.resetScore();
  
  // Check if request should be blocked
  const blocked = await req.sus.main.isBlocked();
  const blockedCustom = await req.sus.main.isBlocked(75); // Custom threshold
  
  // Get the request ID
  const requestId = req.sus.main.id;
  
  res.json({ score, blocked });
});

Boundaries

Boundaries allow you to set different score thresholds at various points in your application:

const sus = new SusFactor({ maximumScore: 100 });

app.use(sus.getMiddleware());

// Global boundary - blocks at score 100
app.use(sus.getBoundary().getHandler());

// Protected routes with stricter boundary
app.use('/admin', sus.getBoundary(50).getHandler());
app.get('/admin/dashboard', (req, res) => {
  res.send('Admin Dashboard');
});

// Public routes with lenient boundary
app.use('/public', sus.getBoundary(200).getHandler());
app.get('/public/info', (req, res) => {
  res.send('Public Info');
});

Advanced Usage

Multiple Zones

Create isolated scoring zones for different parts of your application:

const apiSus = new SusFactor({
  zone: "api",
  maximumScore: 100
});

const authSus = new SusFactor({
  zone: "auth",
  maximumScore: 10  // Stricter for auth
});

app.use(apiSus.getMiddleware());
app.use(authSus.getMiddleware());

app.post('/api/data', async (req, res) => {
  // Track both zones independently
  await req.sus.api.addToScore(5);
  await req.sus.auth.addToScore(1);
  
  res.json({ message: 'Success' });
});

Custom Request ID Generator

By default, request IDs are generated from IP addresses. You can customize this:

const sus = new SusFactor({
  requestIdGenerator: (req) => {
    // Use user ID if authenticated, otherwise IP
    return req.user?.id || req.ip || 'anonymous';
  }
});

Custom Blocked Handler

Define what happens when a request is blocked:

const sus = new SusFactor({
  onBlockedHandler: (req, res, next) => {
    console.log(`Blocked request from ${req.ip}`);
    res.status(403).json({
      error: 'Access Denied',
      message: 'Too many suspicious activities detected',
      retryAfter: 3600
    });
  }
});

Score Patterns for Common Threats

app.post('/api/login', async (req, res) => {
  const { username, password } = req.body;
  
  // SQL injection attempt
  if (/['";]|(\bOR\b)|(\bAND\b)/i.test(username)) {
    await req.sus.main.addToScore(100); // Immediate block
  }
  
  // Invalid credentials
  const user = await authenticate(username, password);
  if (!user) {
    await req.sus.main.addToScore(10); // Progressive blocking
  }
  
  res.json({ success: !!user });
});

app.get('/api/products', async (req, res) => {
  // XSS attempt in query params
  if (/<script|javascript:|onerror=/i.test(req.query.search)) {
    await req.sus.main.addToScore(75);
  }
  
  // Path traversal attempt
  if (/\.\.|\/etc\/|\/proc\//i.test(req.query.file)) {
    await req.sus.main.addToScore(90);
  }
  
  res.json({ products: [] });
});

API Reference

SusFactor Class

Constructor

new SusFactor(options)

Methods

  • getMiddleware() - Returns Express middleware function
  • getBoundary(score) - Creates a new boundary with optional custom score
  • getZone() - Returns the zone name
  • decreaseAllScoresBy(amount) - Manually decrease all scores
  • startScoreDecutionInterval() - Start automatic score reduction
  • stopScoreDecutionInterval() - Stop automatic score reduction

Request Object Extensions

After middleware is applied, req.sus[zone] contains:

  • id - The request ID (string)
  • getScore() - Get current score (async)
  • setScore(score) - Set score to specific value (async)
  • addToScore(delta) - Add/subtract from score (async)
  • resetScore() - Reset score to zero (async)
  • isBlocked(threshold?) - Check if score exceeds threshold (async)

Cache Providers

Both cache providers implement:

  • getScore(key) - Get score for a key
  • setScore(key, score) - Set score for a key
  • addToScore(key, delta) - Add to score
  • resetScore(key) - Reset score to zero
  • addToAllScores(delta) - Add to all scores

Testing

Run the test suite:

npm test

Run linting:

npm run lint

Example Application

See example.js for a complete working example with Redis integration.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

ISC

Links

Use Cases

  • Rate limiting - Prevent API abuse
  • Brute force protection - Slow down login attempts
  • Bot detection - Score and block automated traffic
  • Security monitoring - Track suspicious patterns (XSS, SQL injection attempts)
  • Progressive penalties - Increasingly strict responses to repeat offenders
  • DDoS mitigation - Block high-volume attackers

Best Practices

  1. Start with conservative scores - Don't block too aggressively at first
  2. Monitor false positives - Use addScoreToHeaders during development
  3. Implement score decay - Allow legitimate users to recover from mistakes
  4. Use Redis for production - In-memory cache doesn't persist across restarts
  5. Combine with other security - express-sus complements but doesn't replace authentication
  6. Test your thresholds - Different applications need different sensitivity levels