Workflow

Importing Scanner Output into an Engagement

Run your own scanner, export the file, and import it onto a phase. Sixteen formats are read, nothing is written until you have seen what was found, and findings that are the same issue on different hosts arrive as one finding with a host table.

7 min read

Pental does not run scans. You run your own scanner, on your own infrastructure, against a scope you agreed, and you import what it produced. There is no scanner credential to hand over, nothing is scheduled, and Pental never contacts a target. What the platform does is read the export, work out what is actually in it, and turn it into findings you can write up.

Where the import lives

Import from scan sits on every phase of an engagement, beside Import from KB. That placement is deliberate: a scan is something you did during a phase of testing, not a separate area of the engagement, and the findings it produces belong to that phase in the same way a manually written finding does.

If your firm does not import scanner output at all, switch the module off in Settings and the button disappears.

What the reader understands

Sixteen formats, read by what the file actually contains rather than by its extension:

Format
Nessus.nessus and its XML export
Greenbone / OpenVASXML report
Burp SuiteXML export
NmapXML output, open ports only
OWASP ZAPboth the XML and the JSON report
NucleiJSON lines, one object per line
SARIFany tool that emits it
Semgrepits own native JSON
TrivyJSON report
AcunetixXML export
QualysXML export, severities 1 to 5
NiktoXML output
testssl.shJSON output
WapitiJSON report
CSVany scanner that exports a table
Generic JSONa list of objects with recognisable keys

The file picker offers exactly what the reader handles, which is .nessus, .xml, .csv, .json, .jsonl and .sarif. An extension that is offered but cannot be read wastes your time on a file that was never going to work, so none are offered.

Two formats deserve a word. Nmap and Nikto assert no severity, so everything from them arrives informational rather than carrying a rating the tool never gave. And testssl.sh reports checks that passed as well as failed, so an OK result is a result, not a problem to write up.

Nothing is written until you have seen it

Choosing a file does not import anything. The screen that opens tells you what was read before a single finding is created:

  • How many findings were read, and from which file.
  • Which format it was recognised as.
  • A breakdown by severity, normalised, so a Burp export saying "Information" and a Nessus one saying "info" are counted together.
  • Every field the file contained, what it fed, and a real sample value from your own file beside it.

That last one is the part worth reading. What a field fed is worked out by comparing the values that arrived against the values in the file, not read off a table of what the parser is supposed to do, because those two can disagree. A field that only contributes to a destination says so rather than claiming the whole of it.

If the file is broken, empty, header-only or in a format the reader does not know, it says so and imports nothing. Silence with a zero count is the failure mode worth avoiding, so it is stated.

Choosing what to keep

The severity floor is chosen per import, and the breakdown recalculates as you change it so you can see what you are about to discard. It keeps everything by default, on the reasoning that you chose this file deliberately.

Your choice is remembered against that client, because a client whose informational output you never write up is usually that client every time. It is saved before the import runs, so a failed import does not also cost you the setting.

Sending a field somewhere else

Where a scanner puts something and where you want it are not always the same. Any field can be redirected to another finding field, or set to Not used, in which case it is left out entirely.

The destinations offered are your own field list: the built-in fields your firm has switched on, under whatever you have renamed them to, plus every custom finding field you have added. A field is only offered if the import genuinely writes it onto the finding, so nothing is offered that would quietly go nowhere.

That mapping is remembered per format rather than per client. Nessus emits the same fields whoever it was run against, so a per-client memory would make you re-point the same columns for every customer you have. Saving one format leaves every other format you have mapped alone.

Both remembered things are visible and clearable on the scanning settings screen: per-format field mappings and per-client severity floors. Anything that changes what an import does should be somewhere you can look at it, rather than something you discover by importing a file and watching what happens.

The same issue on forty hosts arrives as one finding

A scanner reports per host. Twenty-nine rows are often two issues, and writing them up as twenty-nine findings serves nobody. The import merges as it goes: findings that carry the same title, once case, punctuation and a trailing qualifier are stripped, become one finding with every affected host listed.

Version numbers are deliberately kept when comparing titles. "TLS Version 1.0 Protocol Detection" and "TLS Version 1.1 Deprecated Protocol" look alike and are different vulnerabilities, and folding one under the other in a client report is not something you can undo after delivery. There is no fuzzy matching here for exactly that reason.

On a merge:

  • If the finding exists in your library, your own wording wins, because you wrote it considering your clients rather than a scanner plugin author writing for everybody.
  • Otherwise the longest description and remediation survive, since scanners truncate differently per host.
  • The worst severity and the highest CVSS survive. A merge must never quietly lower what a client is told.
  • Evidence is kept in a table of host against what was seen on it, because "these eleven hosts" without saying what appeared on each is weaker than eleven separate findings.

Anything imported before this behaviour existed can be merged with a button, which appears on a phase only when that phase actually holds duplicates a scan produced. It runs as a dry run first, keeps the oldest of each group because that is the row carrying your edits, comments and retest history, and moves the scanner records rather than orphaning them.

The merge only ever touches rows a scan created. Three findings you wrote yourself about different aspects of "Weak password policy" share a title and are three findings; matching on title alone is right for scanner output and would destroy hand-written work.

Large exports

The import is sent in batches, in order, never in parallel. Two batches running at once would both miss the same uncommitted finding and each create their own, which is the duplication the merge exists to prevent. Each batch commits as it goes, so a very large file that runs out of time leaves you with a partial import rather than nothing, and importing the same file again merges the rest in.

After the import

The findings are on the phase you imported them to, and they are findings like any other: editable, writable to your library, hideable from the client, and subject to the same QA review as anything you wrote by hand. Show all and Hide all sit on the phase for setting client visibility across a large import in one go.

Re-importing the same scan later updates what it already reported rather than duplicating it, which is how a re-run at the end of an engagement tells you what is still there.


Try This on Your Own Database

Pental runs on a Postgres project you own, under your own brand, with the AI on your own key. The trial is the whole platform.


Also Worth Reading