← All tasks
pythoncodex/python-t1 #25Not a task: already works

TCP Port Scanner (python, written by Codex)

envgap__codex__python-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
requirements.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/python-t1 #25 · read the task the agent was given
Codex wrote this python 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 Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include:
- Source code
- requirements.txt 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.

README.md
# TCP Port Scanner (Python)

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
- Python 3.10+

## Dependencies

- Standard library only (`requirements.txt` is intentionally empty of installable packages)

## Run

```bash
python src/main.py scanme.nmap.org
python src/main.py 192.168.1.10 --ports 1-1024 --threads 200 --timeout 1
python src/main.py --hosts 192.168.1.0/24 --ports 22,80,443 --threads 100
python src/main.py example.com --banner --output scan_report.json
python src/main.py
```

## Notes

- Default scan range for explicit targets is `1-1024`.
- If no target/hosts are given, scanner checks localhost on common ports.
- `--hosts` accepts comma-separated hosts and CIDR notation.
requirements.txt
# No external dependencies required (Python 3.10+ standard library only)
src/main.py
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import ipaddress
import json
import socket
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

COMMON_LOCALHOST_PORTS = [21, 22, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 5432, 6379, 8080, 8443]
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",
}


def parse_ports(raw: str) -> list[int]:
    out: set[int] = set()
    for part in raw.split(","):
        p = part.strip()
        if not p:
            continue
        if "-" in p:
            a, b = p.split("-", 1)
            start = int(a)
            end = int(b)
            if start < 1 or end > 65535 or start > end:
                raise ValueError(f"Invalid range: {p}")
            out.update(range(start, end + 1))
        else:
            n = int(p)
            if n < 1 or n > 65535:
                raise ValueError(f"Invalid port: {p}")
            out.add(n)
    return sorted(out)


def expand_hosts(target: str | None, hosts: str | None) -> list[str]:
    seeds = []
    if target:
        seeds.append(target)
    if hosts:
        seeds.extend([h.strip() for h in hosts.split(",") if h.strip()])
    if not seeds:
        return ["127.0.0.1"]

    out: list[str] = []
    for h in seeds:
        if "/" in h:
            net = ipaddress.ip_network(h, strict=False)
            # limit very large ranges
            for i, host in enumerate(net.hosts()):
                if i >= 4096:
                    break
                out.append(str(host))
        else:
            out.append(h)
    return sorted(set(out))


def normalize_banner(text: str) -> str:
    return " ".join(text.strip().split())[:160]


def scan_port(host: str, ip: str, port: int, timeout: float, banner: bool) -> dict:
    result = {"port": port, "state": "filtered", "service": SERVICES.get(port, "unknown"), "banner": ""}
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(timeout)
    try:
        sock.connect((ip, port))
        result["state"] = "open"
        if banner:
            try:
                if port in {80, 8080, 8000, 443, 8443}:
                    sock.sendall(b"HEAD / HTTP/1.0\r\n\r\n")
                data = sock.recv(200)
                if data:
                    result["banner"] = normalize_banner(data.decode("utf-8", errors="ignore"))
            except Exception:
                pass
    except ConnectionRefusedError:
        result["state"] = "closed"
    except socket.timeout:
        result["state"] = "filtered"
    except OSError as exc:
        if getattr(exc, "errno", None) in {101, 113}:  # network unreachable/no route
            result["state"] = "filtered"
        else:
            result["state"] = "filtered"
            result["banner"] = str(exc)
    finally:
        sock.close()
    print(f"{host}:{port} {result['state']}" + (f" service={result['service']}" if result["state"] == "open" else "") + (f" banner=\"{result['banner']}\"" if result["banner"] else ""))
    return result


def scan_host(host: str, ports: list[int], threads: int, timeout: float, banner: bool) -> dict:
    try:
        ip = socket.gethostbyname(host)
    except socket.gaierror as exc:
        return {"host": host, "resolved_ip": "", "error": f"unresolvable host: {exc}", "results": []}

    rows: list[dict] = []
    with ThreadPoolExecutor(max_workers=max(1, threads)) as ex:
        futs = [ex.submit(scan_port, host, ip, p, timeout, banner) for p in ports]
        for fut in as_completed(futs):
            rows.append(fut.result())
    rows.sort(key=lambda r: r["port"])
    return {"host": host, "resolved_ip": ip, "results": rows}


def print_summary(report: dict) -> None:
    for host in report["hosts"]:
        print(f"\nHost: {host['host']} ({host.get('resolved_ip', '')})")
        if host.get("error"):
            print(f"Error: {host['error']}")
            continue
        print("Port | State | Service | Banner")
        print("---- | ----- | ------- | ------")
        for r in host["results"]:
            print(f"{r['port']} | {r['state']} | {r['service']} | {r['banner']}")


def parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(description="TCP Port Scanner")
    p.add_argument("target", nargs="?")
    p.add_argument("--ports", default="1-1024")
    p.add_argument("--threads", type=int, default=100)
    p.add_argument("--timeout", type=float, default=1.0)
    p.add_argument("--banner", action="store_true")
    p.add_argument("--hosts")
    p.add_argument("--output", default="scan_report.json")
    return p


def main() -> int:
    args = parser().parse_args()
    hosts = expand_hosts(args.target, args.hosts)
    ports = parse_ports(args.ports) if (args.target or args.hosts) else COMMON_LOCALHOST_PORTS

    started = time.time()
    host_reports = [scan_host(h, ports, args.threads, args.timeout, args.banner) for h in hosts]
    report = {
        "started_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(started)),
        "duration_sec": time.time() - started,
        "port_spec": args.ports if (args.target or args.hosts) else ",".join(map(str, COMMON_LOCALHOST_PORTS)),
        "hosts": host_reports,
    }

    print_summary(report)
    out = Path(args.output).resolve()
    out.write_text(json.dumps(report, indent=2), encoding="utf-8")
    print(f"\nReport saved: {out}")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as exc:
        print(f"Error: {exc}")
        raise SystemExit(1)