Pro · new: what changed since the last scan →

Did we get better?
Show what changed since the last scan.

One command puts what is new, what is resolved and what changed severity into the self-contained HTML report you send the client. When the report can see a finding was not measured the same way twice, it sets that finding aside as not comparable, reason on the row, instead of counting it as fixed — and its two measured limits are stated plainly, not buried (see how it works). Underneath: exploit-first triage that puts CISA KEV-listed vulnerabilities, and those with an EPSS score of 0.10 or more, at the top of the queue (from the KEV and EPSS data you load), plus CVE matching, MITRE ATT&CK technique annotations and composite risk scoring.

Annual Monthly Save 20%
$39/mo
Billed as $470/yr · saves $118 vs monthly
Subscribe Annual — $470/yr →
Cancel anytime; an annual plan stays active to the end of its paid year. · Compare all plans · Need Enterprise?

“Did we actually get better?” — answered in the report you send

Consultants hear it at the re-test. MSPs hear it at the quarterly review. A simple before-and-after diff answers fast and can cost you credibility. A finding vanishes because its host was left out of the re-test, a plugin timed out, the CVE lookup failed, or NVD stopped matching a CVE to software that never changed — and the diff shows it as fixed. That line can end up in front of a client’s board. Nobody fixed it: the scanner stopped looking, or the data moved under it.

With Pro, one command — nsauditor-ai report --from <dir> --format executive --since <runId|prior> — compares two scan runs and writes new, resolved and changed-severity findings into the print-ready executive HTML report you hand the client. No side-by-side spreadsheets, no rows copied from one report into the next.

New in 1.3.0: two kinds of narrower re-test no longer read as a fix

A re-test is rarely the same scan twice. A slow plugin gets dropped from --plugins, or the SSH probe is left out and port 22 still answers, but nothing identified what is on it. In the previous release, the rows that depended on what was left out read resolved. In the two cases below the report now sets them aside instead of crediting a fix (the two measured limits are stated further down):

  • A plugin left out of the scan. Each analysis agent reads what named plugins write. When one it reads was not requested, the scan records an input gap and the agent’s earlier rows are filed NOT-COMPARABLE. (For port discovery, the port scanner (003) or Nmap (024) is enough — either one.) Measured on the release candidate: a re-test that dropped the TLS scanner (011), one of the plugins the crypto agent reads, reported 0 resolved and 4 not comparable, among them two TLS-version findings and a self-signed certificate set aside on that input gap. On the same two plugin sets, the previous release read those three as resolved.
  • A port that answered unidentified. With the SSH probe left out and the port scanner (003) seeing port 22 open, an end-of-life OpenSSH row and a CVE row on port 22 were filed NOT-COMPARABLE (port-not-measured), reported neither as fixed nor as new, until a rescan can read the program and version.
  • Titles that no longer move with a probe. The exposure agent’s titles now name only the port, and the service agent’s end-of-life titles name the program without the version, so one unchanged port no longer reads as one resolved row and one new one.
  • A Basis cell that shows its work. Every new, resolved and changed row says what was checked for that row.

Still holding since EE 1.2.0: 22 of 22, and 5 of 5

22 UDP findings (since EE 1.2.0). We compared a real router scan with a twin of it that drops its 22 UDP rows. The release before the rule reported all 22 resolved; with the rule, the delta reported all 22 not comparable: the re-test never recorded those ports as closed or answering, so it does not get to call them fixed. That router pair was measured when the rule shipped; the 1.3.0 release test re-checked the rule on UDP rows of its own.

5 dnsmasq CVEs (since EE 1.2.0). A pre-publish test run caught this one. NVD stopped matching five CVEs to dnsmasq 2.78 on a router that was still answering with dnsmasq 2.78. The earlier delta called all five resolved, and the compliance pack counted them as closed findings with a mean time to remediate of about a day. On the same pair of scans, the delta with the rule read 0 resolved and 5 not comparable — and the 1.3.0 release test reads the same on a local dnsmasq 2.78 pair — reason vulnerability-data-changed — the vulnerability data changed, not your estate.

A resolved column that shows its work

Before a missing finding is called resolved, the comparison looks at how each run measured it. When the report can see the two runs did not measure it the same way, it files the finding under NOT-COMPARABLE, with the reason on the row. Among the reasons:

  • its host was not scanned in the other run;
  • it carries no producer identity to check (producer-unknown);
  • the plugin that reports it did not run, errored or timed out;
  • the analysis agent that reports it did not run, or a plugin it reads was left out of the scan or did not complete (evidence-gap);
  • the licensed package that runs the analysis agents and CVE matching failed to load on that host — now named on stderr instead of passing in silence (evidence-gap);
  • the scanner recorded an evidence gap there — an AccessDenied, or a CVE lookup that failed, for example (evidence-gap);
  • the other run had a narrower scope — on Enterprise cloud scans, fewer AWS regions or a different Azure subscription or GCP project;
  • the check on its port could not complete (probe-not-measured);
  • its TCP port, seen open by the port scanner, stopped answering between the two scans, or answered without its service being identified (for a CVE or end-of-life row) — or its UDP port was not recorded as closed or answering by the other scan (port-not-measured);
  • whatever produced it changed what identifies a finding across the upgrade — across the 1.2.0 → 1.3.0 upgrade, the exposure agent, the service agent and plugin 1160, so rescan after upgrading before you compare (identity-basis-changed);
  • the vulnerability data stopped attributing its CVE while the same program and version still answer (vulnerability-data-changed).

And the report tells your client in plain words: a finding that could not be compared is not a finding that was fixed. When someone asks why a finding was set aside, the answer is on its row — along with which way it went: absent from this run, or appeared in it.

Pro tip: rescan after upgrading, include the port scanner (003), and keep --plugins identical between the scans you compare. This release states its two measured limits up front, so you meet them here and not in front of a client. One: a scan made before EE 1.3.0 could not record a plugin left out of it, so in a comparison with one, an agent’s row that scan lacks is not refused — the report’s Basis cell says so on the row. Two: a scan that discovered ports with the Nmap plugin (024) alone records no port oracle, so an analysis agent’s or the CVE mapper’s row on a port it did not measure can read RESOLVED — and on Enterprise, with SLA tracking on, it can count as closed in MTTR and the control it failed can read PASS. Include the port scanner (003) in scans you compare.

On Enterprise, the same rule reaches the compliance report. With SLA tracking on (--compliance-history <dir> or --sla-policy <file>) and a prior --compliance scan of the same host in the history, a control that failed on a finding whose port, probe, region or producer the new scan did not measure is held FAILED, with the gap leading its explanation and counted among the evidence gaps — never shown as a current finding. New in 1.3.0, that now includes the controls behind a failed CVE lookup (a LOOKUP NOT MEASURED record) and the controls behind an analysis agent’s rows when a plugin it reads was left out; and SLA age and time to remediate in the HIPAA, NIST CSF, PCI DSS, ISO 27001, CIS, GDPR and NIST SP 800-171 packs no longer reset on every scan, with a history written by any earlier release read correctly and no pack regenerated (stated limit: two findings whose prose differs only by a control id would share one identity; none does among the emitted texts, and a test holds that at zero). The second limit above reaches it too: on a scan that discovered ports with Nmap alone, a row on a port it did not measure can count as closed in MTTR and its control can read PASS.

Some pairs are refused outright, with no rows guessed: two runs at different licence tiers, or two runs on either side of a release that changed what the reported findings count means. A refused comparison exits 2, so a script cannot mistake it for a clean delta; when the executive report is still written, its body says no comparison was made.

A tamper-evident baseline, checked before anything is compared

A quarter-over-quarter story needs a baseline that is still there and still intact. Pro keeps your scan history past the Community 7-day window, and for every scan run on 1.1.0 or later, the run record and each host’s findings files are sealed with SHA-256 digests and chained to the previous run. Both runs are checked first. If a sealed findings file was corrupted, partly restored or changed by an ordinary edit, the report names it and refuses the comparison — so you find out at report time, not in front of the client.

Already scanning? Baselines written since EE 1.0.0 are compared across the upgrade; one scanned before 1.1.0 carries no integrity digest, and the report says so. From your first two scans on 1.1.0 or later, both sides of the comparison are sealed.

The --since comparison is a Pro capability, included in Enterprise; its code ships in the free Community package and your licence unlocks it. The watch-mode last-vs-current webhook alert delta stays free.

report --from <dir> --format executive --since prior
→ new · resolved · changed severity
→ set-aside rows carry their reason
→ IN THE REPORT YOU SEND ✓
Host left out of the re-test’s target list
→ its findings are absent from the new run
→ host-not-scanned, reason on the row
→ NOT COMPARABLE — NOT “FIXED”
SNMP on 161/udp, gone from the re-test
→ the re-test never recorded the port closed or answering
→ port-not-measured, reason on the row
→ NOT COMPARABLE — NOT “FIXED”
Five CVEs gone; dnsmasq 2.78 still answering
→ NVD stopped matching them to that version
→ vulnerability-data-changed, reason on the row
→ THE DATA CHANGED, NOT YOUR ESTATE
Findings file edited after the scan (runs made on 1.1.0 or later)
→ its SHA-256 digest no longer matches
→ the file is named
→ COMPARISON REFUSED
Baseline on another licence tier
→ no rows are guessed
→ exits 2, so a script cannot read it as a clean delta
→ REFUSED OUTRIGHT
Baseline, re-test — then the answer, in the file you send
# a client LAN is a private range: set NSA_ALLOW_ALL_HOSTS=1 for these scans —
# the SSRF guard refuses RFC 1918 and loopback addresses by default

# Q3 · the baseline scan, into the client’s engagement directory
nsauditor-ai scan --host 192.168.1.0/24 --plugins all --out ~/engagements/acme/

# Q4 · the re-test, into the SAME directory with the SAME --plugins, so the report
# finds both runs and compares like with like
nsauditor-ai scan --host 192.168.1.0/24 --plugins all --out ~/engagements/acme/

# the newest run against the one before it, rendered into the executive report
nsauditor-ai report --from ~/engagements/acme/ --format executive --since prior \
    --out ~/engagements/acme/Acme_Q4_since_Q3.html

# or name the baseline yourself: --since <runId> — the <id> in the baseline’s
# scan_run_<id>.json at the --out root. report prints it as [report] baseline: runId …
Upgrading? Both packages move together
npm i -g nsauditor-ai@0.2.57 @nsasoft/nsauditor-ai-ee@1.3.0

# Move both in one command. With Community below the floor shown under this box,
# the scan skips Pro's CVE matching and analysis agents: since Community 0.2.56
# that is named on stderr; an older Community says nothing.

# Rescan after upgrading before you compare: across the 1.2.0 -> 1.3.0 upgrade the
# exposure agent's, the service agent's and plugin 1160's rows changed what identifies
# them, so the report sets them aside (identity-basis-changed) rather than counting
# them as fixed or new. Coming from before 1.1.0? The same reason covers the findings
# whose identity 1.1.0 sharpened.

# Also since 1.1.0: the offline NVD store default is ~/.nsauditor/nvd_cache
# (NVD_CACHE_DIR or NSAUDITOR_NVD_CACHE_DIR override it). An old .nvd-cache
# folder is no longer read, and a scan run from its directory says so.
# Move it once, from the directory that holds it:
mkdir -p ~/.nsauditor/nvd_cache && mv .nvd-cache/* ~/.nsauditor/nvd_cache/

Needs nsauditor-ai v0.2.57+ alongside your Pro or Enterprise licence — upgrade both packages.

One file. No network. Hand it to the client.

A scan ends with a directory of artifacts. An engagement ends with something a client reads. nsauditor-ai report closes that gap: point it at a finished run and it renders an executive HTML report — one self-contained, print-ready file that opens with no external network reference of any kind. It survives an email attachment, a laptop with no connectivity, and a reviewer’s locked-down browser.

Add --brand when the report goes out under your own name and the cover page carries it. --format jira writes a Jira-importer CSV instead, so a findings list becomes a backlog. ⚠️ That CSV is written for Jira’s own CSV importer — the field mapping is done inside Jira, and this is not a live-instance integration we have verified.

What goes inside brand.json

Every field is optional. Omit the file and the report renders unbranded under its default title.

// brand.json — put it next to your logo
{
  "title":        "Q3 External Perimeter Review",
  "companyName":  "Acme Manufacturing GmbH",
  "preparedBy":   "J. Rivera, Security Consulting LLC",
  "contact":      "security@example.com",
  "logoPath":     "acme-logo.png"
}

title replaces the headline; companyName is who the report is for; preparedBy is who prepared it. logoPath is a local PNG or JPEG, resolved next to the brand file and embedded into the HTML as a data URI.

That logo rule is strict on purpose, and every part of it protects the same promise — that the file opens with no external network reference. A URL is refused and never fetched: a remote logo is egress at render time and a tracking pixel every time your client opens the report. SVG is refused, because SVG is markup that can carry a script and break the no-script guarantee from inside the image. The path may not escape the brand file’s own directory, checked as typed and again after symlinks resolve. The cap is 2 MB, and the format is read from the file’s magic bytes rather than its extension — renaming an SVG to .png does not get it past the check. A brand file that cannot be read, is not valid JSON, or whose logo is refused is a fatal error and no report is written: a flag you passed is never silently dropped, because that is how a report goes out unbranded without anyone noticing.

It states what it could not read

A report that quietly skips a container reads exactly like a report that had nothing to skip. So every report carries a container census: where finding-like records sit in a place the report does not read, that place is named and counted on the page. Silence is disclosed — never rendered as absence.

report is a Pro capability and is included in Enterprise. It produces no PDF: the HTML is print-ready, and your browser prints it when a PDF is what the client asked for.

report --from <dir> --format executive
→ one self-contained .html
→ zero external references
→ SENDABLE ✓
report --from <dir> --format jira
→ Jira-importer CSV
→ you map the fields in Jira
→ BACKLOG-READY
Records the report does not read
→ container named and counted
→ printed on the page itself
→ DISCLOSED, NOT DROPPED
Scan once — then render the thing you actually send
# 1 · a normal scan, as always
nsauditor-ai scan --host 192.168.1.0/24 --plugins all

# 2 · turn that finished run into the document you send — --from is the scan’s
#     output root (out/ by default); report picks its newest run unless you pass --run <id>
nsauditor-ai report --from out/ --format executive

# or the same run as a Jira-importer CSV — you map the fields in Jira
nsauditor-ai report --from out/ --format jira

Requires nsauditor-ai v0.2.57+ alongside your Pro or Enterprise licence. The report reads a run that already finished — it never re-scans, and it never reaches the network to render.

A worked engagement, licence to sent file

A consulting firm audits a client’s gateway at 192.168.1.1 and has to hand back something the client can actually read. Six steps — the same six on a /24, on a cloud account, or on a hundred hosts.

1 · Confirm the tier — and get authorization in writing
nsauditor-ai license --status

# On Community, `report` refuses at exit 2 and writes no file:
#   "`report` is a Pro capability. Your licence resolves to tier "community".
#    No file has been written."

# The other precondition is not a command: signed authorization for the
# target range. The scope on that letter is what the report cover should match.
2 · Build the brand kit once — the logo lives next to the JSON
~/consulting/brand/
├── brand.json
└── acme-logo.png

# The logo must sit inside this directory and be named by a plain relative
# path. Checked as typed, and again after symlinks resolve.
3 · Scan into a directory that belongs to this engagement
# 192.168.1.1 is a private address: set NSA_ALLOW_ALL_HOSTS=1 for this scan —
# the SSRF guard refuses RFC 1918 and loopback addresses by default
nsauditor-ai scan --host 192.168.1.1 --plugins all \
    --compliance all --out ~/engagements/acme-2026-09/

# Writes a run record `scan_run_<id>.json` at the root and a timestamped
# evidence folder beside it, with a .sha256 sidecar per artifact.

# One --out per engagement. `report` picks the NEWEST run in a directory and
# only refuses on an exact start-time tie — so a directory holding two
# engagements can render the wrong one silently. Name the run when in doubt.
4 · Render the deliverable, under your own name
# --run names YOUR OWN run: the <id> in the scan_run_<id>.json at the --out root
# (report prints it back as [report] runId …). The one below is an example.
nsauditor-ai report --from ~/engagements/acme-2026-09/ \
    --run 20260904T184352Z-d9d796 \
    --format executive \
    --brand ~/consulting/brand/brand.json \
    --out ~/engagements/acme-2026-09/Acme_192.168.1.1_executive.html

[report] runId 20260904T184352Z-d9d796 · started 2026-09-04T18:43:52.970Z
[report] wrote ~/engagements/acme-2026-09/Acme_192.168.1.1_executive.html
✓ exit 0

# Without --out it writes report_<runId>.html beside the run record — inside
# your evidence tree, named after an id no client recognises. Name it for them.
5 · Render the remediation list for the client’s own tracker
nsauditor-ai report --from ~/engagements/acme-2026-09/ \
    --run 20260904T184352Z-d9d796 \
    --format jira \
    --out ~/engagements/acme-2026-09/Acme_192.168.1.1_findings.csv

# Summary,Description,Priority,Labels,External ID — one row per finding.
# Passing controls are filtered out; nobody wants a ticket for a PASS.
# External ID is stable, so a re-test maps back to the same rows.
# ⚠ The column-to-field mapping is done inside Jira's own CSV importer.
#   That is not a live-instance integration we have verified.
6 · Read the two channels before the file leaves your machine
# (a) A container census runs on load. If finding-like records sit somewhere
#     the report does not read, you are told — by container, with a count:
[report] WARNING: N finding-like object(s) on host <h> live in `<container>`,
         which this report does not read. They are NOT in the output.

# (b) Coverage caveats print too, and each is independent of the other:
[report] coverage: partial — N of M not scanned
[report] coverage: incomplete — the run did not record completion

# (c) Then open it. A self-contained file with no external reference looks
#     the same offline as online — which is the first thing the client's own
#     security team will check.

Send the HTML on its own. It is one file — no asset folder, no stylesheet to lose, and it prints from the browser when the client asked for a PDF. What you keep is the evidence directory and its .sha256 sidecars: that is what lets you show, months later, that the report you sent came from that scan. Re-rendering the same --run overwrites the same filename rather than accumulating copies, so there is never a question of which one is current.

Prioritize by exploitation first, not by CVSS alone

Version-based CVE matching is just the start. Every finding is scored — CVSS weighted by verification status, with an uplift for findings carrying an initial-access technique — and the queue is then ordered exploit-first. Findings are emitted unverified — you adjudicate what stays, not the scanner.

OPEN
In the queue
Finding: TLS 1.0 on port 443
→ CVSS 7.4 → 0.74
→ UNVERIFIED ×0.6 → 0.44
→ RANKED — scored, then ordered
Finding: Default SNMP community 'public'
→ CVSS 9.8 → 0.98, UNVERIFIED ×0.6
→ initial-access technique +15% → 0.68
→ RANKED — uplift applied
Finding: Anonymous FTP (sanctioned host)
→ CVSS 7.5 → 0.75, UNVERIFIED ×0.6
→ no initial-access technique → no uplift
→ RANKED — below the two above it
Finding: CVE-2024-XXXX on legacy appliance
→ CVSS 6.1 → 0.61, UNVERIFIED ×0.6
→ not in CISA KEV → no exploit uplift
→ RANKED — below the exploited ones

A KEV-listed MEDIUM outranks an unexploited CRITICAL

Severity ranks how bad a vulnerability would be if it were exploited. It cannot tell you whether anyone is exploiting it. Pro closes that gap at scan time: every finding carrying a CVE is joined by CVE ID against a local CISA KEV catalog and a local FIRST EPSS scores file, then banded and reordered exploit-first.

The justification travels with the finding — the KEV flag, the EPSS score with its percentile, the CVE ids that matched, and the store’s own as-of date. Your ranking is always something you can date and defend. Both catalogs are free, public and operator-supplied: point Pro at files you control and the join runs entirely on your machine.

Your existing riskScore does not move — exploitPriority is a new axis beside it. And this is enrichment, not detection: it reorders what the scanner already found.

KNOWN_EXPLOITED
Listed by CISA as exploited in the wild
ELEVATED
EPSS ≥ 0.10 · roughly the top decile
BASELINE
Not elevated — asserts nothing more
Finding: MEDIUM, CVE on the KEV list
→ KEV flag + as-of date attached
→ band KNOWN_EXPLOITED
→ TOP OF QUEUE
Finding: CRITICAL, not on KEV, low EPSS
→ EPSS score + percentile attached
→ band BASELINE
→ RANKED BELOW IT
Store older than its window
→ KEV 14 days · EPSS 10 days
→ fails closed, withdraws the negative
→ NEVER SAYS “NOT EXPLOITED”

See Pro end to end — recorded on the 0.42 line:

The Pro overview covers exploit-first triage with CISA KEV and FIRST EPSS — free public feeds you download and point Pro at, failing closed when stale — offline-first CVE matching from detected service versions, findings emitted unverified for you to adjudicate, AI analysis on your own API keys, and risk-ranked reports built for one-click print to PDF.

Recorded on the NSAuditor AI 0.42 line · August 2026 · recorded before the report subcommand · exploit-first triage: CISA KEV + FIRST EPSS joined at scan time, feeds you download, stale feeds fail closed · findings emitted unverified — you adjudicate · AI on your own API keys · 6 min 13 s
Switch it on — two public feeds, two environment variables
# 1 · Download the feeds (public, no account needed for either)
mkdir -p ~/.nsauditor/feeds
curl -fsSL https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json -o ~/.nsauditor/feeds/kev.json
curl -fsSL https://epss.empiricalsecurity.com/epss_scores-current.csv.gz | gunzip > ~/.nsauditor/feeds/epss.csv

# 2 · Point the scanner at them (add both lines to your shell profile to persist)
export NSAUDITOR_EXPLOIT_KEV_STORE=~/.nsauditor/feeds/kev.json
export NSAUDITOR_EXPLOIT_EPSS_STORE=~/.nsauditor/feeds/epss.csv

# 3 · Scan as usual — findings that carry CVEs arrive banded and ordered exploit-first
nsauditor-ai scan --host 192.168.1.0/24 --plugins all

The stores are the feeds' own formats — no conversion step. Refresh KEV within 14 days and EPSS within 10 (a cron works); a stale or unparseable store fails closed and the scan says so — it never silently reports “not exploited”. Setting these outside a shell? ~ expands only in a shell — in a --env file or Claude Desktop’s MCP env config, write the absolute path (e.g. /Users/you/.nsauditor/feeds/kev.json); a path the scanner cannot read is reported as no-store, never as “not exploited”. Full walk-through: getting-started → exploit intelligence.

Exploit intelligence is a Pro-tier capability and is off until you configure it. Enterprise includes Pro.

Where Pro ends and the evidence pack begins

Pro sharpens the queue. Enterprise turns the result into something an assessor reads — 29 Enterprise plugins, 28 of them cloud auditors across AWS, Azure and GCP, and one read-only scan mapped to eight frameworks with an explicit out-of-scope column.

It is also where suppression lives — and suppression is an Enterprise capability, not a Pro one. Enterprise adds a suppression workflow for the compliance report: accepted-risk and false-positive records keyed on source and title pattern, approved with an approver’s signature, renewable on a cadence, and read by the compliance engine — which Pro does not run, so a suppression has no effect on a Pro report. The Enterprise compliance engine gives each decision an owner, a rationale, a date and an expiry inside the evidence pack — and as of EE 0.36.0 the report checks that approval’s signature against the key material the approver’s registry entry declares, rather than repeating what the record says about itself. A missing verdict reads not checked and never failed.

Isolated networks are Enterprise too: the offline install walkthrough — carry the package and the CVE data in, verify checksums, then import the feed — now lives on the Enterprise page.

None of that is part of a Pro licence. If your buyer is an auditor rather than a remediation queue, that is the tier to look at.

See NSAuditor AI Enterprise →

🎯

Exploit Intelligence

CISA KEV + FIRST EPSS joined by CVE ID at scan time, banded KNOWN_EXPLOITED / ELEVATED / BASELINE, queue ordered exploit-first. Operator-supplied catalogs; the join runs locally. See how →

🔎

Offline-First CVE Matching

Auto-generated CPEs from detected services matched against the NVD feed — offline-first: your local NVD store answers first, and on a miss the public NVD API is queried unless NSAUDITOR_OFFLINE_ONLY=1, which turns the miss into a coverage-gap finding instead of a request. Know which CVEs affect each host before writing a single line of your report.

🗺️

MITRE ATT&CK Annotations

Every finding is annotated with the ATT&CK techniques it relates to, with kill-chain context. Compliance teams love it. Clients understand it. Takes zero extra effort on your part.

🤖

Parallel Analysis Agents

Four analysis agents run concurrently on Pro — Auth, Crypto, Config and Service. The Exposure agent is Enterprise. Each produces structured findings for its category, then feeds the risk engine.

📊

Risk Scoring

CVSS weighted down while a finding is unverified — with a 15% uplift for findings carrying an initial-access ATT&CK technique. Reports render severity-grouped, highest-impact first.

📄

The Report You Send

report --from <dir> --format executive renders a finished scan into one self-contained, print-ready HTML file that opens with no external network reference — optional cover-page branding via --brand. --format jira writes a Jira-importer CSV. Every scan still writes HTML, JSON and Markdown as before. See how →

🧠

Intelligence-Enriched AI

Same OpenAI / Claude / Ollama providers you use in CE — but Pro injects CVE matches, MITRE techniques, and risk scores into every prompt. Vastly better output.

🔁

What Changed Since the Last Scan

Add --since to the executive report and it compares two runs: new, resolved and changed-severity findings, plus a not-comparable list with the reason on every row, in the HTML report you send. Scan history is kept past the Community 7-day window. See how →

🔏

Enhanced Redaction

Configurable profiles (strict / moderate / minimal), additional patterns for AWS keys, DB connection strings, and JWTs. Full redaction audit log for compliance.

🔌

What Pro unlocks in the MCP tools

The MCP server registers seven tools and lists all seven to every client; the licence gate is applied when a tool is called. Pro unlocks probe_service and get_vulnerabilities on top of the three every tier can run.

🔑
Auth
Weak auth, default credentials, anonymous access, missing auth on admin panels
🔐
Crypto
TLS < 1.2, weak ciphers, expired or self-signed certs, missing HSTS
⚙️
Config
Default SNMP communities, debug modes, directory listing, exposed .env files
🛠️
Service
CVE-specific vulnerabilities, end-of-life software, backport detection
🌐
Exposure Enterprise
Internet-facing services, lateral movement paths, unnecessary open ports
Feature
Pro
Community
AI analysis (OpenAI, Claude, Ollama)
✓
✓
Prompt content
CVEs + MITRE + risk scores
basic summary
CVE matching (offline-first NVD)
✓
—
MITRE ATT&CK annotations
✓ Kill-chain context
basic tagging
Finding suppression (accepted-risk / false-positive)
— Enterprise
—
Risk scoring
✓
—
Exploit intelligence — CISA KEV + FIRST EPSS, exploit-first ordering (stores you populate; no feed data ships)
✓ KEV-listed MEDIUM outranks unexploited CRITICAL
—
Parallel analysis agents
✓ 4 agents
—
Print-ready HTML reports (browser Print-to-PDF)
✓ Risk-ranked, CVE-annotated
✓ Ungated — same Print-to-PDF
Send-ready report subcommand — executive HTML or Jira-importer CSV
✓ Self-contained, no external references; container census on the page
—
What changed since the last scan (--since)
✓ New / resolved / changed severity in the client report; reason on every not-comparable row
watch-mode alert delta only
CTEM scan history
✓ Unlimited (JSONL)
7 days
MCP tools
✓ 5 of 7 usable
3 of 7
Email support
✓ 48hr SLA
—
probe_service Pro
Run one specific plugin against a single host:port. The narrow, surgical counterpart to a full scan — ask your assistant to check one service without re-scanning the host.
get_vulnerabilities Pro
Look up known CVEs for a CPE (Common Platform Enumeration) string via the NVD API — so your assistant can go from a detected version string to its published vulnerabilities.
scan_host
A full plugin scan of a target host: service detection, OS fingerprint and structured findings. Available on every tier.
compliance_matrix
The shipped coverage matrix for a framework — how many controls are Covered, Partial and Out of scope, with the per-group out-of-scope reasons. Available on every tier.

list_plugins completes the every-tier set; scan_cloud and get_findings are Enterprise. Seven tools in total — all seven are listed to every client, and the licence is checked when one is called.

Start the MCP server with nsauditor-ai-mcp — works with Claude Desktop, Cursor, and any MCP-compatible AI assistant.

1 · Authenticate with npm (token included in your purchase email)
# @nsasoft/nsauditor-ai-ee is a private (restricted) package.
# Use the npm read-token delivered with your license email.
npm config set //registry.npmjs.org/:_authToken npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Or, project-scoped, in an .npmrc file
echo "//registry.npmjs.org/:_authToken=npm_xxxx..." >> ~/.npmrc
2 · Install (requires nsauditor-ai v0.2.57+ and Node 20+)
# One line — installs both the CE platform and the Pro package
npm install -g nsauditor-ai @nsasoft/nsauditor-ai-ee
3 · Activate your license — one command, no shell-rc edits
# The CE license installer verifies the JWT signature before persisting
# and stores the key in macOS Keychain (or ~/.nsauditor/.env mode 0600 elsewhere).
nsauditor-ai license install pro_eyJhbGciOiJFUzI1NiIs...

✓ Pro license installed
  Stored at: macOS Keychain (service=nsauditor-ai)
  Org: you@example.com   Expires: 2027-04-29

# Verify
nsauditor-ai license --status
✓ Pro license active
4 · Scan with intelligence
# private ranges (RFC 1918, loopback) need NSA_ALLOW_ALL_HOSTS=1 —
# the SSRF guard refuses them by default
nsauditor-ai scan --host 192.168.1.0/24 --plugins all

# View your AI report in browser — one evidence folder per host
open out/192.168.1.1_*/scan_response_ai.html     # macOS
xdg-open out/192.168.1.1_*/scan_response_ai.html  # Linux

# Each scan writes HTML, JSON, and Markdown reports — print the HTML to PDF for clients
nsauditor-ai scan --host 10.0.0.0/24 --plugins all
open out/10.0.0.1_*/scan_response_ai.html      # print → PDF

# And turn that finished run into the one file you send the client —
# --from is the output root; report picks the newest run in it
nsauditor-ai report --from out/ --format executive

Ready to show your clients what changed?

Pro turns a finished scan into one self-contained HTML file you can send, or a Jira-importer CSV. Add --since and that file shows what is new, resolved and changed in severity since the last scan; a finding the report can see was not measured the same way twice is set aside with its reason on the row, not counted as fixed, and the comparison’s two measured limits are stated (see how it works). You also unlock CVE matching, MITRE ATT&CK annotations, risk scoring and parallel analysis agents. Cancel anytime; an annual plan stays active to the end of its paid year, then reverts to Community Edition.

Monthly option: $49/mo · Compare all plans