← All tasks
javascriptcodex/javascript-t1 #25Not a task: already works

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);
});