[PR #216] feat(core): add nmap XML import support #4134

Open
opened 2026-08-30 17:56:12 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/reconurge/flowsint/pull/216
Author: @3moorydxb
Created: 8/24/2026
Status: 🔄 Open

Base: main ← Head: feat/nmap-importer


📝 Commits (1)

  • 45c90b2 feat(core): add nmap XML import support

📊 Changes

8 files changed (+953 additions, -11 deletions)

View changed files

📝 flowsint-api/app/api/routes/sketches.py (+5 -3)
📝 flowsint-app/src/components/sketches/import-sheet.tsx (+6 -5)
📝 flowsint-core/pyproject.toml (+1 -0)
📝 flowsint-core/src/flowsint_core/imports/file_parser.py (+6 -3)
➕ flowsint-core/src/flowsint_core/imports/nmap/__init__.py (+5 -0)
➕ flowsint-core/src/flowsint_core/imports/nmap/parse_nmap.py (+282 -0)
➕ flowsint-core/tests/import/nmap/test_nmap_import.py (+637 -0)
📝 uv.lock (+11 -0)

📄 Description

Closes #107.

Note on #184. @rachit367 opened a PR for this in June that I didn't see before starting — I looked at the issue thread rather than its linked PRs, which was my mistake. I've commented there with the differences. I'm happy to close this in favour of #184, or to contribute the tests and the state/banner handling to that branch instead — whichever the maintainers prefer.

Imports an nmap XML report (nmap -oX) that the investigator produced themselves, so a sketch can be enriched from a scan that was already run rather than scanning the target again. It goes through the existing import path — no new active-collection surface.

Mapping

Matches the shape the naabu enricher already produces:

  • each host reported up → Ip
  • each <hostname> → Domain, with REVERSE_RESOLVES_TO for a PTR record and RESOLVES_TO for a name given on the command line
  • each <port> → Port, linked HAS_PORT from its host

Two deliberate differences from naabu

Both are about not overstating what a scan found:

  • Port state is carried through verbatim rather than flattened to open, so filtered stays distinguishable from closed.
  • banner is only set when nmap actually fingerprinted the service. A service name guessed from the port number isn't presented as an identified one, and a TLS-wrapped service reads as ssl/http, the way nmap's own output prints it.

Security

Parsing goes through defusedxml. An uploaded report is untrusted input, and its service banners are whatever the scanned hosts sent back.

Known limitation — port identity is not host-scoped

I want to flag this rather than have it surface in review.

repository.py MERGEs on (nodeLabel, sketch_id), and Port.compute_label builds the label from number + service + protocol with no host in it. So two hosts in one report that both have 80/tcp/http collapse into a single shared Port node, and SET n += $props means the last host in the file wins its state and banner. I reproduced this end to end against the existing MERGE semantics: host A's port 80 open/nginx was overwritten to filtered/null by host B.

This is pre-existing — the naabu enricher uses the identical MERGE. But this PR is what makes it reachable: naabu hardcodes state="open" and runs one IP at a time, so conflicting states weren't previously possible. A /24 scan is the ordinary way people use nmap, and it's the first thing anyone will feed this.

The parser can't fix it. The label is computed by Port.compute_label in flowsint-types, and changing it would change the naabu enricher's node identity too — that's a maintainer's call, not a first-PR call. Happy to follow up with it if you tell me which way you want it to go.

Tests

637 lines covering the mapping, port-state handling, banner suppression, PTR vs command-line hostname edge direction, and malformed/hostile XML.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/reconurge/flowsint/pull/216 **Author:** [@3moorydxb](https://github.com/3moorydxb) **Created:** 8/24/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `feat/nmap-importer` --- ### 📝 Commits (1) - [`45c90b2`](https://github.com/reconurge/flowsint/commit/45c90b2d3af8dc792d9140221de6ddc76f32d358) feat(core): add nmap XML import support ### 📊 Changes **8 files changed** (+953 additions, -11 deletions) <details> <summary>View changed files</summary> 📝 `flowsint-api/app/api/routes/sketches.py` (+5 -3) 📝 `flowsint-app/src/components/sketches/import-sheet.tsx` (+6 -5) 📝 `flowsint-core/pyproject.toml` (+1 -0) 📝 `flowsint-core/src/flowsint_core/imports/file_parser.py` (+6 -3) ➕ `flowsint-core/src/flowsint_core/imports/nmap/__init__.py` (+5 -0) ➕ `flowsint-core/src/flowsint_core/imports/nmap/parse_nmap.py` (+282 -0) ➕ `flowsint-core/tests/import/nmap/test_nmap_import.py` (+637 -0) 📝 `uv.lock` (+11 -0) </details> ### 📄 Description Closes #107. > **Note on #184.** @rachit367 opened a PR for this in June that I didn't see before starting — I looked at the issue thread rather than its linked PRs, which was my mistake. I've commented there with the differences. I'm happy to close this in favour of #184, or to contribute the tests and the state/banner handling to that branch instead — whichever the maintainers prefer. Imports an nmap XML report (`nmap -oX`) that the investigator produced themselves, so a sketch can be enriched from a scan that was already run rather than scanning the target again. It goes through the existing import path — no new active-collection surface. ## Mapping Matches the shape the naabu enricher already produces: - each host reported up → `Ip` - each `<hostname>` → `Domain`, with `REVERSE_RESOLVES_TO` for a PTR record and `RESOLVES_TO` for a name given on the command line - each `<port>` → `Port`, linked `HAS_PORT` from its host ## Two deliberate differences from naabu Both are about not overstating what a scan found: - **Port state is carried through verbatim** rather than flattened to open, so `filtered` stays distinguishable from `closed`. - **`banner` is only set when nmap actually fingerprinted the service.** A service name guessed from the port number isn't presented as an identified one, and a TLS-wrapped service reads as `ssl/http`, the way nmap's own output prints it. ## Security Parsing goes through `defusedxml`. An uploaded report is untrusted input, and its service banners are whatever the scanned hosts sent back. ## Known limitation — port identity is not host-scoped I want to flag this rather than have it surface in review. `repository.py` MERGEs on `(nodeLabel, sketch_id)`, and `Port.compute_label` builds the label from number + service + protocol with **no host in it**. So two hosts in one report that both have `80/tcp/http` collapse into a single shared `Port` node, and `SET n += $props` means the last host in the file wins its state and banner. I reproduced this end to end against the existing MERGE semantics: host A's port 80 `open`/`nginx` was overwritten to `filtered`/`null` by host B. **This is pre-existing** — the naabu enricher uses the identical MERGE. But this PR is what makes it reachable: naabu hardcodes `state="open"` and runs one IP at a time, so conflicting states weren't previously possible. A `/24` scan is the ordinary way people use nmap, and it's the first thing anyone will feed this. The parser can't fix it. The label is computed by `Port.compute_label` in `flowsint-types`, and changing it would change the naabu enricher's node identity too — that's a maintainer's call, not a first-PR call. Happy to follow up with it if you tell me which way you want it to go. ## Tests 637 lines covering the mapping, port-state handling, banner suppression, PTR vs command-line hostname edge direction, and malformed/hostile XML. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-08-30 17:56:12 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/flowsint#4134