Library / SDK
Ullaakut/nmap avatar
Ullaakut/nmap

Ullaakut/nmap: idiomatic nmap bindings for Go

Idiomatic nmap library for go developers

1,053 stars118 forksGoMIT

At a glance

What is it?
This library gives Go programs a typed way to drive nmap, the network scanner, by shelling out to the binary and parsing its XML output. The API is a scanner built from functional options, with synchronous, asynchronous and progress-reporting runs, and the option helpers cover targets, ports, timing templates, scripts, service detection and firewall bypass rules.
Who is it for?
Adopt this library if you are writing a security audit tool in Go and want nmap's detection and script engine without writing an XML parser, and if you are willing to ship the nmap binary alongside your application.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A wrapper that shells out and parses XML

This library aims at providing idiomatic nmap bindings for Go developers, in order to make it easier to write security audit tools using Golang.

How it works is stated plainly, and it is the single most important fact for anyone evaluating it. The library shells out to the nmap binary using Go's exec package and parses the XML output. That means nmap must be installed and available on your PATH for the library to work, and compatibility is confirmed with the current latest version of nmap, 7.98.

The design consequence is that this is a binding, not a reimplementation. Everything nmap can do through its script engine, its NSE probes and its timing templates is available, because nmap is doing the work. What the library adds is the layer that Go developers actually want: typed options, a scanner object, a context for cancellation and a result structure with hosts, ports, services and statistics.

The alternative, parsing the XML yourself or driving the nmap binary with hand-built argument strings, is exactly the work this package removes.

The project is MIT licensed, and the module path is github.com/Ullaakut/nmap/v4, so the v4 suffix in the import path is not a version of nmap but a version of the Go library, and it matters for the major version rule of Go modules.

Why the author argues for Go here

The README has a section on why use Go for penetration testing, and it is an honest argument rather than a marketing one.

Most pentest tools are currently written using Python and not Go, because it is easy to quickly write scripts, lots of libraries are available, and it is a simple language to use. The author's counterpoint is that for writing robust and reliable applications, Go is the better tool, because it is statically compiled, has a static type system, much better performance, is also a very simple language to use, and goroutines are awesome. The last line is a joke at his own expense, with a note that he might be slightly biased and an invitation to disagree.

The technical substance of the argument is the deployment profile. A scanner tool that has to run on several machines, that has to keep working when a dependency changes, and that gets handed to other people is a different kind of program from a script you run once on your laptop, and static compilation and a static type system are the right answer for that case.

The library is also a good example of what a Go wrapper should look like. The nmap binary is a stable, well documented program with a stable command line, and the wrapper turns that command line into typed options rather than strings.

The scanner API, in the shape you would expect

The synchronous example is the one to start with, because it shows the whole shape of the API: a scanner built from options, a run that takes a context, and a result you walk.

go
scanner, err := nmap.NewScanner(
    nmap.WithTargets("scanme.nmap.org"),
    nmap.WithPorts("80,443,843"),
)
if err != nil {
    log.Fatalf("creating nmap scanner: %v", err)
}

result, err := scanner.Run(ctx)
if err != nil {
    log.Fatalf("running network scan: %v", err)
}

The result is a typed structure with a Hosts slice, each host having Addresses and Ports, and each port having an ID, a Protocol, a State and a Service with a name. There is also a Stats field with a Finished timestamp and an Elapsed duration, and a Warnings method for the non-critical errors nmap reports along the way, which the example logs as warnings rather than treating as failures.

The context is the other thing to notice. The example creates one with a five minute timeout and defers the cancel, and a scanner that runs for minutes in a long-lived service needs a way to stop, so the context is not decoration.

The printed output in the README shows what a result looks like once you format it yourself, with the host address, one line per port and a summary line with the host count and the elapsed time.

Progress reporting, and why the percentage can go down

The progress example adds a callback and a timing template, and the documentation around it is unusually careful.

It relies on terminal escape sequences and only works when the process is attached to a TTY. That restriction is worth understanding, because it means the same binary cannot report progress the same way when it is running as a service, and the option is WithProgress with an interval and a callback that receives a TaskProgress value with a Percent field.

The note about monotonicity is the part that saves you a bug report against your own code. Progress is not guaranteed to increase monotonically, because nmap estimates the time remaining and can revise that estimate, which may cause the reported percentage to go down. The example output in the README shows a Connect Scan climbing from 2.59% to 100.00%, and the estimate of the remaining hosts moving around as it goes.

So a progress bar built on this value has to tolerate going backwards, and the timing template matters. The example uses WithTimingTemplate with nmap.TimingAggressive, which is nmap's own template for how aggressively to speed up a scan as it progresses.

For the asynchronous case, RunAsync returns the standard output stream, the standard error stream and a channel carrying the result, and the example selects over them, so a program can print nmap's own output as it arrives, surface errors as they come and still finish with the parsed result.

Privileges, and the scan type that avoids them

The Privileges section is the one to read before you deploy anything, and it is short.

Some scan types require elevated privileges, for example SYN scans, OS detection, or raw socket usage. If you enable those options, you may need to run your program with sudo or with the appropriate capabilities for your platform.

The tip that follows is the practical part. For unprivileged runs, prefer connect scans, with the -sT flag.

That distinction is the one that decides whether this library is usable in your environment. A connect scan works over ordinary TCP connections and needs no privileges at all, which means it can run as an unprivileged user, in a container without extra capabilities, or on a host where the operator cannot give the process root. A SYN scan is faster and quieter because it never completes the connection, but it needs raw sockets, which means either root or the capability to open them.

The same applies to operating system detection, which sends a series of probes designed to fingerprint the target stack and therefore needs raw packet handling.

So the choice is not only a performance one. It is a decision about what your scanning tool is allowed to do on the machine it runs on, and the library makes that decision available through the same option set as everything else.

The option surface, and the examples as a catalogue

The repository layout is a map of the API, and the file names are the option groups.

There are opt_scan.go, opt_target.go, opt_ports.go, opt_host.go, opt_service.go, opt_os.go, opt_script.go, opt_timing.go, opt_output.go, opt_progress.go, opt_firewall.go and opt_misc.go, which is the whole nmap command line broken into typed pieces, and each of those has a matching test file. Alongside them are nmap.go for the scanner itself, scan_sync.go, scan_async.go and scan_progress.go for the three run modes, xml.go for the output parsing, iflist.go for the interface listing, helpers.go and errors.go, and a pkg/ directory.

The examples directory is the most efficient way to learn the library, because there are sixteen of them and they are named after the task rather than the API: async_scan, basic_scan, count_hosts_by_os, exclude_targets, host_discovery, ipv6_scan, list_interfaces, os_and_service_detection, output_files, progress_scan, script_scan, service_detection, spoof_and_decoys, timing_templates, top_ports and udp_scan.

Reading them tells you what people actually need from a scanner. Counting hosts by operating system, spoofing and decoys, script scanning, output files and the timing templates are the ones that show up in real audit workflows, and each is a short program that starts with NewScanner and an option or two.

The test target runs go test with coverage and the race detector.

Tooling, releases and where the project stands

The build and test tooling is plain Go, and the Makefile has three targets. The format target runs gofmt with the simplify flag over the source. The test target runs go test with coverage and the race detector. The lint target runs golangci-lint with the configuration in .golangci.yml.

The dependency list in the go.mod is short and tells you how the library is tested. hamba/testutils and stretchr/testify are the assertion libraries, mattn/go-isatty is there for the terminal detection that the progress reporting needs, and testcontainers-go is the interesting one, because it means the test suite can run a real nmap inside a container rather than mocking the binary.

That choice explains a lot about the quality of the project. A wrapper that parses XML from a third party binary is only as correct as its parser, and testing it against the real nmap in a container is the only way to know the parser handles the actual output rather than a fixture someone wrote by hand.

The release history shows the project moving quickly in a year. Version 3.1.0 came on 2026-01-04 with AllPorts support, 4.0.0 on 2026-01-31, and 4.0.1 on 2026-07-26, with the last push on 2026-10-15. The repository is not archived, and the project is MIT licensed, with a Go Report Card badge and a coverage badge from Coveralls.

Editorial conclusion

Adopt this library if you are writing a security audit tool in Go and want nmap's detection and script engine without writing an XML parser, and if you are willing to ship the nmap binary alongside your application. Do not adopt it if you cannot install nmap on the target machine, because the library shells out to it and nmap must be on the PATH, and be careful with the scan types you enable, since SYN scans, operating system detection and raw socket use need elevated privileges. Before you start, decide whether your deployment can run unprivileged, because the recommended path for that is connect scans, and check the privilege and capability requirements for the scan types you actually need.

Frequently asked questions

How does the Go nmap library work?

It shells out to the nmap binary using Go's exec package and parses the XML output, so nmap must be installed and available on your PATH. Compatibility is confirmed with nmap 7.98.

Do I need root to use this nmap library?

Not for every scan type. SYN scans, operating system detection and raw socket usage need elevated privileges, so you may need sudo or the appropriate capabilities. For unprivileged runs the README recommends connect scans, with the -sT flag.

How do I run a scan and read the results?

Create a scanner with nmap.NewScanner and options such as WithTargets and WithPorts, then call Run with a context. The result has a Hosts slice with addresses and ports, each port carrying an ID, protocol, state and service, plus stats with the elapsed time and a Warnings method for non-critical nmap errors.

Does the progress reporting always increase?

No. Progress is not guaranteed to increase monotonically, because nmap estimates the time remaining and can revise that estimate, so the reported percentage may go down. It also relies on terminal escape sequences and only works when the process is attached to a TTY.

How is the library tested?

The go.mod pulls in testcontainers-go alongside testify and hamba/testutils, so the test suite can run a real nmap inside a container rather than relying on hand-written fixtures. The Makefile runs the tests with coverage and the race detector, and linting uses golangci-lint with the .golangci.yml configuration.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. Ullaakut/nmap on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ullaakut-nmap.svg)](https://hysenlabs.com/projects/ullaakut-nmap)

Was this useful?