惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
IT之家
IT之家
博客园_首页
人人都是产品经理
人人都是产品经理
博客园 - Franky
I
InfoQ
Recent Announcements
Recent Announcements
P
Proofpoint News Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
GbyAI
GbyAI
大猫的无限游戏
大猫的无限游戏
aimingoo的专栏
aimingoo的专栏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
月光博客
月光博客
Microsoft Security Blog
Microsoft Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
雷峰网
雷峰网
博客园 - 聂微东
N
Netflix TechBlog - Medium
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
D
Docker

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How to Build a Secure Serverless Port Scanner in Node.js ...
Ibrahim Abdulmajid · 2026-06-12 · via DEV Community
Cover image for How to Build a Secure Serverless Port Scanner in Node.js (and Prevent SSRF)

Ibrahim Abdulmajid

Every network engineer and systems developer needs to verify connection ports. Whether you're debugging why a remote database connection is failing, checking if an SSH daemon is running, or auditing active firewall rules, programmatically checking TCP ports is a core developer task.

However, writing a port scanner in Node.js comes with a massive, critical security risk: Server-Side Request Forgery (SSRF).

If you allow users to pass a host parameter directly into a network socket connection, an attacker can input localhost or local IPs (like 127.0.0.1 or 192.168.1.1) to map and scan your own server's internal networks, databases, and microservices.

Here is how to build a high-performance TCP port scanner in Node.js that runs in a serverless environment and is fully hardened against SSRF attacks.


1. The Core Port Scanner logic (TCP & Banner Grabbing)

We use Node's built-in net module to attempt quick TCP socket connections.

If a connection succeeds, we wait up to 250ms to read any greeting bytes sent by the server. This allows our scanner to perform banner grabbing—allowing us to extract software versions (like SSH-2.0-OpenSSH_8.9p1) directly.

import net from 'net';

function checkPort(ipAddress, port, timeout = 1000) {
  return new Promise((resolve) => {
    const socket = new net.Socket();
    let status = 'closed';
    let banner = null;
    let completed = false;

    socket.setTimeout(timeout);

    socket.once('connect', () => {
      status = 'open';
      // Wait briefly for greeting banner (e.g. SSH/SMTP welcome banners)
      socket.setTimeout(250);
    });

    socket.on('data', (data) => {
      banner = data.toString('utf8').trim().substring(0, 128);
      cleanup();
    });

    socket.on('timeout', () => {
      status = status === 'open' ? 'open' : 'filtered';
      cleanup();
    });

    socket.on('error', () => {
      status = 'closed';
      cleanup();
    });

    function cleanup() {
      if (completed) return;
      completed = true;
      try { socket.destroy(); } catch (e) {}
      resolve({ port, status, banner });
    }

    try {
      socket.connect(port, ipAddress);
    } catch (err) {
      status = 'closed';
      cleanup();
    }
  });
}

2. Preventing SSRF: Hardening the DNS Lookup

Before you let Node connect to any hostname (like google.com or an IP address), you must:

  1. Resolve the hostname to an IP address.
  2. Check if the resolved IP belongs to a private, loopback, or link-local subnet.
  3. Terminate the request if it points to a private subnet.

Here is the subnet validation check:
import dns from 'dns';


// Check if IPv4 matches loopback, private subnets (Class A/B/C), or link-local
function isPrivateIPv4(ip) {
  const parts = ip.split('.').map(Number);
  if (parts.length !== 4 || parts.some(isNaN)) return true;

  return (
    parts[0] === 127 || // Loopback (127.0.0.0/8)
    parts[0] === 10 ||  // Private Class A (10.0.0.0/8)
    (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) || // Private Class B
    (parts[0] === 192 && parts[1] === 168) || // Private Class C (192.168.0.0/16)
    (parts[0] === 169 && parts[1] === 254) || // Link-local (169.254.0.0/16)
    parts[0] === 0      // Local broadcast
  );
}

// Check if IPv6 points to loopback, link-local, or unique local ranges
function isPrivateIPv6(ip) {
  const normalized = ip.toLowerCase().trim();
  return (
    normalized === '::1' || 
    normalized === '::' ||
    normalized.startsWith('fe80:') || // Link-local
    normalized.startsWith('fc00:') || // Unique local
    normalized.startsWith('fd00:')
  );
}

Now, integrate this check into your routing logic:


async function secureScanHandler(host, portsToScan = [80, 443]) {
  // 1. Resolve host to target IP
  let targetIp;
  try {
    const lookup = await dns.promises.lookup(host);
    targetIp = lookup.address;
  } catch (err) {
    throw new Error(`Could not resolve host: ${err.message}`);
  }

  // 2. Enforce SSRF blocklist
  const isIPv6 = targetIp.includes(':');
  const isPrivate = isIPv6 ? isPrivateIPv6(targetIp) : isPrivateIPv4(targetIp);

  if (isPrivate) {
    throw new Error('Access denied: Scanning internal network targets is prohibited.');
  }

  // 3. Scan ports in parallel
  const results = await Promise.all(
    portsToScan.map(port => checkPort(targetIp, port))
  );

  return { host, ip: targetIp, results };
}

3. Best Practices for Production Scanners

  1. Scan Limits: Serverless runtimes (like Vercel or AWS Lambda) have strict execution timeouts (typically 10-15s on free plans). Restrict developers to scanning a maximum of 20 ports concurrently to ensure your handler finishes quickly.
  2. Timeout Boundaries: Keep the connection timeout boundary low (e.g., 1000ms). Firewalls that drop packages silently (resulting in a filtered status) will cause sockets to hang until they hit this threshold.

Try it Live

If you don't want to build, host, and maintain your own scraping servers and IP subnets lists, I have deployed a fully hardened version of this tool.

You can try out, test code snippets, and call this service with a free sandbox tier (up to 100 queries a month) directly at the Port Scanner & Network Diagnostics API on RapidAPI.

How are you managing network diagnostic validations in your deployment flows? Let me know in the comments below!