TCP Port Scanner (javascript, written by Codex)
envgap__codex__javascript-t1-25
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
None
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
codex/javascript-t1 #25 · read the task the agent was given
Codex 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
4 files, exactly as written, before any repair.
package-lock.json
{
"name": "tcp-port-scanner",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tcp-port-scanner",
"version": "1.0.0",
"dependencies": {
"p-limit": "5.0.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/p-limit": {
"version": "5.0.0"
}
}
}
package.json
{
"name": "tcp-port-scanner",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"p-limit": "5.0.0"
}
}
README.md
# TCP Port Scanner (JavaScript) Performs TCP connect scanning for one or many hosts with configurable port ranges, concurrency, timeout, optional banner grabbing, CIDR expansion, real-time output, and JSON reporting. ## Requirements - Ubuntu 22.04 - Node.js 20+ (LTS) ## Dependencies (Pinned) - `p-limit@5.0.0` ## Setup ```bash npm install ``` ## Run ```bash node src/index.js scanme.nmap.org node src/index.js 192.168.1.10 --ports 1-1024 --threads 200 --timeout 1 node src/index.js --hosts 192.168.1.0/24 --ports 22,80,443 --threads 100 node src/index.js example.com --banner --output scan_report.json node src/index.js ``` ## Notes - Default scan range for explicit targets is `1-1024`. - If no target/hosts are provided, scanner runs on `127.0.0.1` common ports. - `--hosts` accepts comma-separated hosts and CIDR notation (IPv4).
src/index.js
import fs from "fs";
import path from "path";
import dns from "dns/promises";
import net from "net";
import pLimit from "p-limit";
const COMMON_LOCALHOST_PORTS = [21, 22, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 5432, 6379, 8080, 8443];
const SERVICES = {
20: "ftp-data", 21: "ftp", 22: "ssh", 23: "telnet", 25: "smtp", 53: "dns", 80: "http", 110: "pop3",
123: "ntp", 143: "imap", 443: "https", 465: "smtps", 587: "submission", 993: "imaps", 995: "pop3s",
1433: "mssql", 1521: "oracle", 3306: "mysql", 3389: "rdp", 5432: "postgresql", 6379: "redis", 8080: "http-alt", 8443: "https-alt",
};
function parseArgs(argv) {
const options = {};
const positional = [];
for (let i = 0; i < argv.length; i += 1) {
const t = argv[i];
if (t.startsWith("--")) {
const key = t.slice(2);
const next = argv[i + 1];
if (next && !next.startsWith("--")) {
options[key] = next;
i += 1;
} else options[key] = true;
} else positional.push(t);
}
return { options, positional };
}
function parsePorts(raw) {
const text = String(raw || "1-1024");
const out = new Set();
for (const part of text.split(",")) {
const p = part.trim();
if (!p) continue;
if (p.includes("-")) {
const [a, b] = p.split("-", 2).map((v) => Number.parseInt(v, 10));
if (!Number.isFinite(a) || !Number.isFinite(b) || a < 1 || b < 1 || a > 65535 || b > 65535 || a > b) {
throw new Error(`Invalid port range: ${p}`);
}
for (let x = a; x <= b; x += 1) out.add(x);
} else {
const n = Number.parseInt(p, 10);
if (!Number.isFinite(n) || n < 1 || n > 65535) throw new Error(`Invalid port: ${p}`);
out.add(n);
}
}
return [...out].sort((a, b) => a - b);
}
function ipToInt(ip) {
return ip.split(".").reduce((acc, seg) => ((acc << 8) + Number(seg)) >>> 0, 0);
}
function intToIp(n) {
return `${(n >>> 24) & 255}.${(n >>> 16) & 255}.${(n >>> 8) & 255}.${n & 255}`;
}
function expandCidr(cidr) {
const [base, maskRaw] = cidr.split("/", 2);
const mask = Number.parseInt(maskRaw, 10);
if (!base || !Number.isFinite(mask) || mask < 0 || mask > 32) throw new Error(`Invalid CIDR: ${cidr}`);
const baseInt = ipToInt(base);
const netmask = mask === 0 ? 0 : ((0xffffffff << (32 - mask)) >>> 0);
const network = baseInt & netmask;
const hostCount = 2 ** (32 - mask);
const hosts = [];
for (let i = 0; i < hostCount; i += 1) {
const ip = intToIp((network + i) >>> 0);
if (mask <= 30 && (i === 0 || i === hostCount - 1)) continue;
hosts.push(ip);
if (hosts.length >= 4096) break;
}
return hosts;
}
function parseHosts(positionalTarget, hostsArg) {
const out = [];
if (positionalTarget) out.push(positionalTarget);
if (hostsArg) out.push(...String(hostsArg).split(",").map((s) => s.trim()).filter(Boolean));
if (out.length === 0) return ["127.0.0.1"];
const expanded = [];
for (const h of out) {
if (h.includes("/")) expanded.push(...expandCidr(h));
else expanded.push(h);
}
return [...new Set(expanded)];
}
function normalizeBanner(text) {
return text.replace(/\s+/g, " ").trim().slice(0, 160);
}
function scanPort(host, port, timeoutMs, banner) {
return new Promise((resolve) => {
const socket = new net.Socket();
let done = false;
const finish = (result) => {
if (done) return;
done = true;
socket.destroy();
resolve(result);
};
socket.setTimeout(timeoutMs);
socket.once("timeout", () => finish({ state: "filtered", banner: "" }));
socket.once("error", (err) => {
if (err && err.code === "ECONNREFUSED") finish({ state: "closed", banner: "" });
else if (err && (err.code === "EHOSTUNREACH" || err.code === "ENETUNREACH" || err.code === "ETIMEDOUT")) finish({ state: "filtered", banner: "" });
else finish({ state: "filtered", banner: err?.message || "" });
});
socket.connect(port, host, () => {
if (!banner) {
finish({ state: "open", banner: "" });
return;
}
let data = "";
socket.setTimeout(Math.min(1000, timeoutMs));
if (port === 80 || port === 8080 || port === 8000 || port === 443 || port === 8443) {
socket.write("HEAD / HTTP/1.0\r\n\r\n");
}
socket.on("data", (chunk) => {
data += chunk.toString("utf8");
if (data.length > 160) data = data.slice(0, 160);
});
setTimeout(() => finish({ state: "open", banner: normalizeBanner(data) }), Math.min(1000, timeoutMs));
});
});
}
async function resolveHost(host) {
try {
const res = await dns.lookup(host);
return res.address;
} catch {
throw new Error(`Unresolvable host: ${host}`);
}
}
function printResult(host, row) {
console.log(`${host}:${row.port} ${row.state}${row.state === "open" ? ` service=${row.service}` : ""}${row.banner ? ` banner="${row.banner}"` : ""}`);
}
function printSummary(report) {
for (const host of report.hosts) {
console.log(`\nHost: ${host.host} (${host.resolvedIp})`);
console.log("Port | State | Service | Banner");
console.log("---- | ----- | ------- | ------");
for (const r of host.results) {
console.log(`${r.port} | ${r.state} | ${r.service} | ${r.banner || ""}`);
}
}
}
async function scanHost(host, ports, options) {
const timeoutMs = Math.max(100, Number(options.timeout ?? 1) * 1000);
const threads = Math.max(1, Number(options.threads ?? 100));
const doBanner = Boolean(options.banner);
const resolved = await resolveHost(host);
const limit = pLimit(threads);
const rows = [];
await Promise.all(ports.map((port) => limit(async () => {
const r = await scanPort(resolved, port, timeoutMs, doBanner);
const row = { port, state: r.state, service: SERVICES[port] || "unknown", banner: r.banner || "" };
rows.push(row);
printResult(host, row);
})));
rows.sort((a, b) => a.port - b.port);
return { host, resolvedIp: resolved, results: rows };
}
async function main() {
const { options, positional } = parseArgs(process.argv.slice(2));
const target = positional[0] || "";
const hosts = parseHosts(target, options.hosts);
const ports = target || options.hosts
? parsePorts(options.ports || "1-1024")
: [...COMMON_LOCALHOST_PORTS];
const started = Date.now();
const hostReports = [];
for (const h of hosts) {
try {
hostReports.push(await scanHost(h, ports, options));
} catch (e) {
hostReports.push({ host: h, resolvedIp: "", error: e instanceof Error ? e.message : String(e), results: [] });
console.error(`Host ${h} failed: ${e instanceof Error ? e.message : String(e)}`);
}
}
const report = {
startedAt: new Date(started).toISOString(),
durationSec: (Date.now() - started) / 1000,
portSpec: (target || options.hosts) ? (options.ports || "1-1024") : COMMON_LOCALHOST_PORTS.join(","),
hosts: hostReports,
};
printSummary(report);
const out = path.resolve(options.output || "scan_report.json");
fs.writeFileSync(out, `${JSON.stringify(report, null, 2)}\n`, "utf8");
console.log(`\nReport saved: ${out}`);
}
main().catch((e) => {
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
process.exit(1);
});