Skill: Network scanning with Node.js net.Socket
## When to use
Best use case
Skill: Network scanning with Node.js net.Socket is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
## When to use
Teams using Skill: Network scanning with Node.js net.Socket should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/network-scanning/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How Skill: Network scanning with Node.js net.Socket Compares
| Feature / Agent | Skill: Network scanning with Node.js net.Socket | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
## When to use
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Skill: Network scanning with Node.js net.Socket
## When to use
Use this skill when implementing the TCP connect scanner engine, handling socket errors and timeouts correctly, building a concurrency pool for parallel probes, validating scan targets against IP range restrictions, or understanding how to interpret port states.
## TCP connect scanning fundamentals
A TCP connect scan works by attempting a full TCP three-way handshake on each target port:
- If the handshake completes: the port is `open`
- If the connection is refused (TCP RST): the port is `closed`
- If the connection times out with no response: the port is `filtered`
This does not require raw socket privileges (no root/sudo needed). It uses the OS's standard TCP stack.
## Node.js net.Socket probe
```typescript
import * as net from 'node:net';
interface ProbeResult {
state: 'open' | 'closed' | 'filtered';
ms: number;
}
function probeTCP(host: string, port: number, timeoutMs: number): Promise<ProbeResult> {
return new Promise((resolve) => {
const socket = new net.Socket();
const start = Date.now();
let settled = false;
const settle = (result: ProbeResult) => {
if (settled) return;
settled = true;
socket.destroy();
resolve(result);
};
socket.setTimeout(timeoutMs);
socket.once('connect', () => {
settle({ state: 'open', ms: Date.now() - start });
});
socket.once('error', (err: NodeJS.ErrnoException) => {
const state = err.code === 'ECONNREFUSED' ? 'closed' : 'filtered';
settle({ state, ms: Date.now() - start });
});
socket.once('timeout', () => {
settle({ state: 'filtered', ms: timeoutMs });
});
socket.connect(port, host);
});
}
```
Key points:
- Always call `socket.destroy()` after a result to release the file descriptor
- Use a `settled` guard to prevent calling `resolve` twice if multiple events fire
- `ECONNREFUSED` is the only reliable indicator of a definite `closed` state
- Other errors (`EHOSTUNREACH`, `ENETUNREACH`, `ENOBUFS`) should be treated as `filtered`
- `socket.setTimeout` sets the inactivity timeout. The `timeout` event fires but does not close the socket automatically - call `socket.destroy()` explicitly.
## Concurrency pool
Running probes sequentially would be far too slow for large port ranges. A simple token-bucket pool:
```typescript
class ConcurrencyPool {
private active = 0;
private queue: Array<() => void> = [];
constructor(private readonly limit: number) {}
async run<T>(task: () => Promise<T>): Promise<T> {
await this.acquire();
try {
return await task();
} finally {
this.release();
}
}
private acquire(): Promise<void> {
if (this.active < this.limit) {
this.active++;
return Promise.resolve();
}
return new Promise<void>((resolve) => {
this.queue.push(resolve);
});
}
private release(): void {
const next = this.queue.shift();
if (next) {
next();
} else {
this.active--;
}
}
}
```
Usage:
```typescript
const pool = new ConcurrencyPool(100);
const ports = parsePortRange('1-1024');
await Promise.all(
ports.map((port) =>
pool.run(() => probeTCP('127.0.0.1', port, 300).then((result) => {
writeResult(scanId, port, result);
}))
)
);
```
## Rate limiter
A token bucket rate limiter prevents overwhelming the local TCP stack:
```typescript
class RateLimiter {
private tokens: number;
private lastRefill: number;
constructor(private readonly ratePerSecond: number) {
this.tokens = ratePerSecond;
this.lastRefill = Date.now();
}
async acquire(): Promise<void> {
this.refill();
if (this.tokens >= 1) {
this.tokens--;
return;
}
const waitMs = Math.ceil((1 - this.tokens) / this.ratePerSecond * 1000);
await new Promise<void>((r) => setTimeout(r, waitMs));
this.tokens--;
}
private refill(): void {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
this.tokens = Math.min(this.ratePerSecond, this.tokens + elapsed * this.ratePerSecond);
this.lastRefill = now;
}
}
```
## IP address validation
Reject any target that is not loopback or RFC-1918 (unless LAN mode is enabled):
```typescript
import * as net from 'node:net';
function isLoopback(ip: string): boolean {
return ip === '127.0.0.1' || ip.startsWith('127.');
}
function isRFC1918(ip: string): boolean {
const parts = ip.split('.').map(Number);
if (parts.length !== 4 || parts.some((p) => isNaN(p) || p < 0 || p > 255)) return false;
return (
parts[0] === 10 ||
(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
(parts[0] === 192 && parts[1] === 168)
);
}
function validateTarget(ip: string, allowLan: boolean): void {
if (!net.isIPv4(ip)) {
throw new Error(`Invalid IPv4 address: ${ip}`);
}
if (isLoopback(ip)) return;
if (allowLan && isRFC1918(ip)) return;
throw new Error(`Target ${ip} is not a loopback or RFC-1918 address. Set PORTSCANNER_ALLOW_LAN=1 to scan LAN hosts.`);
}
```
## Port range parser
```typescript
function parsePortRange(input: string): number[] {
const ports = new Set<number>();
for (const segment of input.split(',')) {
const trimmed = segment.trim();
if (trimmed.includes('-')) {
const [startStr, endStr] = trimmed.split('-');
const start = parseInt(startStr, 10);
const end = parseInt(endStr, 10);
if (isNaN(start) || isNaN(end) || start < 1 || end > 65535 || start > end) {
throw new Error(`Invalid port range: ${trimmed}`);
}
for (let p = start; p <= end; p++) ports.add(p);
} else {
const port = parseInt(trimmed, 10);
if (isNaN(port) || port < 1 || port > 65535) {
throw new Error(`Invalid port: ${trimmed}`);
}
ports.add(port);
}
}
return Array.from(ports).sort((a, b) => a - b);
}
```
## System resource limits
Running hundreds of parallel TCP connections can exhaust OS file descriptor limits.
Check the current limit:
```
ulimit -n
```
The default on many systems is 256 (macOS) or 1024 (Linux). Each open socket consumes one file descriptor. With `PORTSCANNER_CONCURRENCY=100`, the scanner uses up to 100 simultaneous sockets plus overhead for the Node.js process, API server, and database.
If scans fail with `EMFILE` errors, reduce `PORTSCANNER_CONCURRENCY` or increase the fd limit:
```
ulimit -n 4096
```
In production (Docker), set `ulimits.nofile` in the compose file.
## Graceful shutdown
When a scan is cancelled or the process exits, all in-flight sockets must be destroyed:
```typescript
class ScanHandle {
private readonly sockets = new Set<net.Socket>();
private cancelled = false;
trackSocket(socket: net.Socket): void {
this.sockets.add(socket);
socket.once('close', () => this.sockets.delete(socket));
}
cancel(): void {
this.cancelled = true;
for (const socket of this.sockets) {
socket.destroy();
}
}
isCancelled(): boolean {
return this.cancelled;
}
}
```
Check `handle.isCancelled()` before each probe in the pool loop to stop enqueuing new work.
## Common mistakes
### Not destroying the socket on connect
Opening a socket, reading the connect event, then forgetting to call `socket.destroy()` leaks file descriptors. Always destroy after recording the result.
### Relying on ETIMEDOUT from the socket
`net.Socket` does not automatically emit `error` with `ETIMEDOUT` when the socket timeout fires. The `timeout` event fires instead, and the socket remains open. Always handle the `timeout` event separately from `error`.
### Using `child_process.exec` for nmap or nc
Shell execution for scanning is forbidden in this project. All scanning must go through `net.Socket`.
### Logging raw socket errors to the user
Socket errors are part of normal scan operation (ECONNREFUSED is the most common). Do not surface individual socket errors as warnings to the dashboard. Aggregate them as `closed` results.Related Skills
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".
Skill: network-device-mapper
Application-level patterns for the network-device-mapper project.
node-cli
Build a Node.js CLI tool with Commander.js, interactive prompts, colored terminal output, and pnpm packaging. Use when creating a command-line interface for a Node.js project. Triggers include "CLI tool", "command line", "Commander.js", "terminal interface", "interactive prompts", or any task requiring a Node.js CLI.
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
poll-builder
Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.
Skill: personal-finance
## Overview
Skill: csv-import
## Overview