TCP Port Scanner (javascript, written by Claude Code)
envgap__claude-code__javascript-t1-25
Written by a coding agent; not on GitHubWritten 2026-02-27
01 / FAILURE SIGNATURE
As the study recorded it
No identifying execution failure has been captured.
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/javascript-t1 #25 · read the task the agent was given
Claude Code wrote this javascript project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: TCP Port Scanner Write a program that scans TCP ports on a target host to determine which ports are open, closed, or filtered, with support for service detection, concurrent scanning, and configurable scan ranges. FUNCTIONAL REQUIREMENTS: - Accept a target hostname or IP address as a command-line argument - Scan a port range specified via --ports flag (e.g., --ports 1-1024, --ports 80,443,8080, or --ports 1-65535); default: well-known ports 1-1024 - Perform TCP connect scanning: attempt a full TCP handshake to determine if a port is open - Support concurrent scanning with configurable thread count via --threads flag (default: 100) to speed up large range scans - Implement configurable connection timeout via --timeout flag (default: 1 second per port) - Detect common services on open ports by matching port numbers to known services (HTTP/80, HTTPS/443, SSH/22, FTP/21, SMTP/25, DNS/53, etc.) using a built-in service name database - Attempt basic banner grabbing on open ports to identify the running service version via --banner flag - Support scanning multiple hosts via --hosts flag (comma-separated or CIDR notation for a subnet) - Print scan results to console in real-time as ports are discovered, with a final summary table showing: port number, state (open/closed/filtered), service name, and banner (if captured) - Save the full scan report as JSON with --output flag (default: scan_report.json) including scan duration, host info, and per-port results - If no target is given, scan localhost (127.0.0.1) on the common ports (21, 22, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 5432, 6379, 8080, 8443) and display the results - Handle errors: unresolvable hostnames, network unreachable, connection refused vs timeout distinction, and permission issues Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include: - Source code - package.json with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
package.json
{
"name": "tcp-port-scanner",
"version": "1.0.0",
"description": "TCP port scanner with concurrent scanning, service detection, banner grabbing, and CIDR support",
"main": "scanner.js",
"scripts": {
"start": "node scanner.js",
"scan": "node scanner.js"
},
"keywords": ["port-scanner", "tcp", "network", "security"],
"license": "MIT",
"dependencies": {
"chalk": "4.1.2",
"cli-table3": "0.6.3"
}
}
README.md
# TCP Port Scanner - JavaScript (Trial 1) A TCP port scanner with concurrent scanning, service detection, banner grabbing, and CIDR subnet support. Uses Node.js built-in net module, chalk for colored output, and cli-table3 for formatted tables. ## Dependencies - **net** (built-in) - TCP socket connections for port scanning - **chalk** (4.1.2) - Terminal string styling with colors - **cli-table3** (0.6.3) - Formatted table output in the terminal ## Installation ```bash npm install ``` ## Usage ```bash # Scan default ports (1-1024) node scanner.js 192.168.1.1 # Scan specific ports node scanner.js 192.168.1.1 -p 22,80,443,8080 # Scan a CIDR subnet node scanner.js 192.168.1.0/24 -p 22,80,443 # JSON output node scanner.js example.com -p 1-1024 --json # Show closed ports node scanner.js 10.0.0.1 -p 1-100 --show-closed ``` ## Features - TCP connect scanning with configurable concurrency - Service detection via banner grabbing and port-based lookup - CIDR subnet expansion for network scanning - Colored terminal output with formatted tables - JSON output mode for scripting and automation
scanner.js
#!/usr/bin/env node
/**
* TCP Port Scanner with concurrent scanning, service detection, banner grabbing,
* and CIDR subnet support. Uses net (built-in), chalk for colors, cli-table3 for tables.
*/
const net = require("net");
const dns = require("dns");
const { promisify } = require("util");
const chalk = require("chalk");
const Table = require("cli-table3");
const dnsResolve = promisify(dns.resolve4);
// Well-known port to service mappings
const WELL_KNOWN_SERVICES = {
21: "FTP",
22: "SSH",
23: "Telnet",
25: "SMTP",
53: "DNS",
80: "HTTP",
110: "POP3",
135: "MSRPC",
139: "NetBIOS-SSN",
143: "IMAP",
443: "HTTPS",
445: "Microsoft-DS",
993: "IMAPS",
995: "POP3S",
1433: "MSSQL",
1521: "Oracle",
3306: "MySQL",
3389: "RDP",
5432: "PostgreSQL",
5900: "VNC",
6379: "Redis",
8080: "HTTP-Proxy",
8443: "HTTPS-Alt",
27017: "MongoDB",
};
/**
* Parse port specification string into an array of port numbers.
*/
function parsePorts(portSpec) {
const ports = new Set();
const parts = portSpec.split(",");
for (const part of parts) {
const trimmed = part.trim();
if (trimmed.includes("-")) {
const [startStr, endStr] = trimmed.split("-", 2);
let start = parseInt(startStr.trim(), 10);
let end = parseInt(endStr.trim(), 10);
if (start > end) [start, end] = [end, start];
for (let p = start; p <= end; p++) {
if (p >= 1 && p <= 65535) ports.add(p);
}
} else {
const p = parseInt(trimmed, 10);
if (p >= 1 && p <= 65535) ports.add(p);
}
}
return Array.from(ports).sort((a, b) => a - b);
}
/**
* Expand CIDR notation to list of IP addresses.
*/
function expandCIDR(cidr) {
const [baseIp, prefixStr] = cidr.split("/");
const prefix = parseInt(prefixStr, 10);
if (prefix < 0 || prefix > 32) {
console.error(chalk.red(`Invalid CIDR prefix: /${prefix}`));
return [];
}
const ipParts = baseIp.split(".").map(Number);
const ipNum =
(ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3];
const mask = prefix === 0 ? 0 : (~0 << (32 - prefix)) >>> 0;
const network = (ipNum & mask) >>> 0;
const broadcast = (network | ~mask) >>> 0;
const hosts = [];
// Skip network and broadcast addresses for subnets > /31
const start = prefix <= 30 ? network + 1 : network;
const end = prefix <= 30 ? broadcast - 1 : broadcast;
for (let ip = start; ip <= end; ip++) {
hosts.push(
`${(ip >>> 24) & 255}.${(ip >>> 16) & 255}.${(ip >>> 8) & 255}.${ip & 255}`
);
}
return hosts;
}
/**
* Parse targets (IPs, hostnames, CIDR).
*/
async function parseTargets(targetSpec) {
const targets = [];
const parts = targetSpec.split(",");
for (const part of parts) {
const trimmed = part.trim();
if (trimmed.includes("/")) {
const hosts = expandCIDR(trimmed);
targets.push(...hosts);
} else if (/^\d+\.\d+\.\d+\.\d+$/.test(trimmed)) {
targets.push(trimmed);
} else {
try {
const addresses = await dnsResolve(trimmed);
if (addresses.length > 0) {
targets.push(addresses[0]);
}
} catch (err) {
console.error(chalk.red(`Cannot resolve hostname: ${trimmed}`));
}
}
}
return targets;
}
/**
* Attempt to grab a banner from an open port.
*/
function grabBanner(ip, port, timeout = 3000) {
return new Promise((resolve) => {
const socket = new net.Socket();
let banner = "";
socket.setTimeout(timeout);
socket.connect(port, ip, () => {
// Send HTTP probe for web ports
if ([80, 8080, 8443, 443].includes(port)) {
socket.write(`HEAD / HTTP/1.0\r\nHost: ${ip}\r\n\r\n`);
}
});
socket.on("data", (data) => {
banner += data.toString("utf-8");
socket.destroy();
});
socket.on("timeout", () => {
socket.destroy();
resolve(banner.trim().substring(0, 200) || null);
});
socket.on("close", () => {
resolve(banner.trim().substring(0, 200) || null);
});
socket.on("error", () => {
resolve(null);
});
});
}
/**
* Detect the service running on a port.
*/
function detectService(port, banner) {
if (banner) {
const lower = banner.toLowerCase();
if (lower.includes("ssh")) return "SSH";
if (lower.includes("http")) return "HTTP";
if (lower.includes("ftp")) return "FTP";
if (lower.includes("smtp")) return "SMTP";
if (lower.includes("mysql")) return "MySQL";
if (lower.includes("postgresql") || lower.includes("postgres"))
return "PostgreSQL";
if (lower.includes("redis")) return "Redis";
}
return WELL_KNOWN_SERVICES[port] || "Unknown";
}
/**
* Scan a single port on a host.
*/
function scanPort(ip, port, timeout) {
return new Promise((resolve) => {
const socket = new net.Socket();
const result = { port, state: "closed", service: "Unknown", banner: null };
socket.setTimeout(timeout);
socket.connect(port, ip, async () => {
result.state = "open";
socket.destroy();
// Try banner grabbing
const banner = await grabBanner(ip, port, timeout);
result.banner = banner;
result.service = detectService(port, banner);
resolve(result);
});
socket.on("timeout", () => {
result.state = "filtered";
socket.destroy();
resolve(result);
});
socket.on("error", (err) => {
if (err.code === "ECONNREFUSED") {
result.state = "closed";
} else if (err.code === "ETIMEDOUT") {
result.state = "filtered";
} else {
result.state = "closed";
}
socket.destroy();
resolve(result);
});
});
}
/**
* Scan all ports on a host with concurrency control.
*/
async function scanHost(ip, ports, concurrency, timeout) {
const results = [];
let completed = 0;
const total = ports.length;
// Process ports in batches for concurrency control
for (let i = 0; i < ports.length; i += concurrency) {
const batch = ports.slice(i, i + concurrency);
const batchResults = await Promise.all(
batch.map((port) => scanPort(ip, port, timeout))
);
results.push(...batchResults);
completed += batch.length;
process.stdout.write(
`\r Scanning ${ip}: ${completed}/${total} ports (${((completed / total) * 100).toFixed(1)}%)`
);
}
process.stdout.write("\n");
return results.sort((a, b) => a.port - b.port);
}
/**
* Display results in a formatted table.
*/
function displayResults(ip, results, showClosed) {
const open = results.filter((r) => r.state === "open");
const filtered = results.filter((r) => r.state === "filtered");
const closed = results.filter((r) => r.state === "closed");
console.log();
console.log(chalk.cyan.bold(`=== Scan Results for ${ip} ===`));
const table = new Table({
head: [
chalk.cyan("Port"),
chalk.cyan("State"),
chalk.cyan("Service"),
chalk.cyan("Banner"),
],
colWidths: [10, 12, 18, 55],
style: { head: [], border: [] },
});
let displayList = [...open, ...filtered];
if (showClosed) displayList.push(...closed);
displayList.sort((a, b) => a.port - b.port);
for (const r of displayList) {
let stateStr;
switch (r.state) {
case "open":
stateStr = chalk.green.bold("open");
break;
case "filtered":
stateStr = chalk.yellow("filtered");
break;
case "closed":
stateStr = chalk.red("closed");
break;
default:
stateStr = r.state;
}
const bannerStr = r.banner
? r.banner.substring(0, 50).replace(/[\r\n]/g, " ")
: "";
table.push([r.port.toString(), stateStr, r.service, bannerStr]);
}
if (displayList.length > 0) {
console.log(table.toString());
} else {
console.log(chalk.yellow("No open or filtered ports found."));
}
console.log(
`\n${chalk.bold("Summary:")} ${open.length} open, ${filtered.length} filtered, ` +
`${closed.length} closed (Total: ${results.length} ports scanned)`
);
}
/**
* Parse command-line arguments.
*/
function parseArgs() {
const args = process.argv.slice(2);
const config = {
target: null,
ports: "1-1024",
workers: 100,
timeout: 2000,
showClosed: false,
json: false,
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case "-p":
case "--ports":
config.ports = args[++i];
break;
case "-w":
case "--workers":
config.workers = parseInt(args[++i], 10);
break;
case "-t":
case "--timeout":
config.timeout = parseInt(args[++i], 10);
break;
case "--show-closed":
config.showClosed = true;
break;
case "--json":
config.json = true;
break;
case "-h":
case "--help":
console.log("Usage: node scanner.js <target> [options]");
console.log();
console.log("Options:");
console.log(" -p, --ports PORTS Port specification (default: 1-1024)");
console.log(" -w, --workers N Concurrent connections (default: 100)");
console.log(" -t, --timeout MS Timeout in ms (default: 2000)");
console.log(" --show-closed Show closed ports");
console.log(" --json JSON output");
console.log(" -h, --help Show help");
process.exit(0);
break;
default:
if (!args[i].startsWith("-")) {
config.target = args[i];
}
}
}
return config;
}
/**
* Main entry point.
*/
async function main() {
const config = parseArgs();
if (!config.target) {
console.error(chalk.red("Error: No target specified."));
console.log("Usage: node scanner.js <target> [options]");
process.exit(1);
}
if (!config.json) {
console.log(chalk.cyan.bold("TCP Port Scanner"));
console.log(chalk.cyan("================"));
}
const targets = await parseTargets(config.target);
const ports = parsePorts(config.ports);
if (targets.length === 0) {
console.error(chalk.red("No valid targets."));
process.exit(1);
}
if (!config.json) {
console.log(`Targets: ${targets.length} host(s)`);
console.log(`Ports: ${ports.length} port(s)`);
console.log(`Workers: ${config.workers}`);
console.log(`Timeout: ${config.timeout} ms`);
console.log();
}
const startTime = Date.now();
const allResults = [];
for (const ip of targets) {
const results = await scanHost(ip, ports, config.workers, config.timeout);
allResults.push({ host: ip, results });
if (!config.json) {
displayResults(ip, results, config.showClosed);
}
}
if (config.json) {
const jsonOutput = allResults.map((hr) => ({
host: hr.host,
ports: hr.results
.filter(
(r) =>
r.state === "open" || r.state === "filtered" || config.showClosed
)
.map((r) => ({
port: r.port,
state: r.state,
service: r.service,
...(r.banner && { banner: r.banner }),
})),
}));
console.log(JSON.stringify(jsonOutput, null, 2));
} else {
const elapsed = Date.now() - startTime;
console.log(chalk.green.bold(`\nTotal scan completed in ${elapsed} ms`));
}
}
main().catch((err) => {
console.error(chalk.red(`Error: ${err.message}`));
process.exit(1);
});