Vulnerability Management

Assessment Runs and Scan Campaigns Inside a Project

By PMAP Security Team 18 min read

A security engagement is rarely a single event. A client gets tested in March, fixes a batch of issues, then gets retested in June. A continuous program runs a fresh wave every quarter. A red team comes in for an ad hoc exercise between scheduled cycles. Each of these moments produces its own scans, its own findings, and its own story about whether the security posture improved or slipped. The problem is that most tooling flattens all of it into one undifferentiated pile of scan results, so the question that actually matters gets lost. That question is simple. Compared with last time, what is new, what is still open, what got fixed, and what came back?

PMAP answers that question with assessment runs. A run is a bounded, named, numbered wave of testing that lives inside a long-lived project. It groups the scans for that wave, aggregates their findings, and on completion computes a delta against the previous wave automatically. This article explains why wave boundaries matter, how PMAP numbers and classifies runs, how a multi-scanner campaign launches in a single call, and how the completion delta turns a pile of findings into a progress report. Everything described here reflects how the assessment run domain actually behaves in the product.

For the broader picture of how PMAP organizes engagements, scopes, and assessment programs, start with the pillar on security assessment management. This article zooms into one layer of that picture. It covers runs and campaigns. The project, scope, and multi-firm structure that surrounds a run is covered separately in planning a multi-firm pentest project.

Why a Project Needs Wave Boundaries

In PMAP the hierarchy is deliberate. A project is the long-lived container for a client engagement. Below it sits the assessment run, which is one bounded execution within that project. Below the run sit the individual scans, whether they come from Nessus, Qualys, Rapid7, a DAST tool, a SAST tool, or a manual pentest. Findings roll up to a run either through the scans attached to it or directly, in the case of manually recorded pentest findings.

The reason this middle layer exists is that enterprise engagements span multiple test waves, and those waves need to stay distinct. If you merge unrelated scanner data into a single pool, you can still count findings, but you lose the ability to compare. You cannot say that wave two closed forty issues from wave one if there is no wave one and wave two to compare. You cannot anchor a report to a defined scope if every report draws from the same flat dataset. Wave boundaries give you three things that a flat pile cannot. They let you compare coverage across waves, they surface regression when a closed finding reappears, and they anchor report generation to a specific, time-boxed scope.

Personas use runs accordingly. A security manager organizes an engagement into numbered waves and launches a multi-scanner campaign in one step. A pentest lead marks the wave boundaries, assigns an owner, and tracks which scans belong to which wave. An analyst attaches or detaches scans and reviews the delta when the wave closes. A client-side stakeholder follows the run status and reads the wave summary without needing to understand the underlying scan plumbing. The run is the shared object that all four roles point at when they talk about a wave.

Sequential, Gap-Free Wave Numbering

A wave needs a stable reference. If two people on a call say “the second round of testing,” they need to mean the same thing regardless of the calendar date, the order scans were uploaded, or whether anything was edited afterward. PMAP gives every run a run_number that is unique and sequential within its project.

The numbering is not a casual auto-increment. The number is allocated by a database function, next_assessment_run_number, which uses an advisory lock to guarantee monotonic, gap-free numbering per project. That advisory lock matters under concurrency. If two people create a run at nearly the same moment, the lock serializes the allocation so they cannot both receive the same number and cannot accidentally skip one. The number is set when the run is created and is never mutated afterward. Editing a run’s name, status, or dates does not touch its number.

This behavior has a quiet but important consequence for trust. Because numbers are never reused and gaps are not possible under the advisory-lock function, a reference like “run 3” in a report is permanent and unambiguous. If a run were soft-deleted, the surrounding sequence would still read correctly, because numbers are never recycled. That stability is what lets reports, conversations, and audit trails refer to a wave by number and have everyone land on the same wave months later. Practitioners who want the click-by-click version of creating a run and watching the number assign can follow the companion guide on launching a scan campaign as an assessment run.

Classifying the Run

Not every wave is the same kind of work, and PMAP records that distinction with a run type. The run_type field is an enum with five values. A run can be classified as assessment, retest, ad_hoc, scheduled, or manual_pentest. The default on create is assessment, and an invalid value is rejected with a 400 error so the field cannot drift into freeform noise.

Each value carries operational meaning. An assessment is the baseline, the broad evaluation that establishes where things stand. A retest is a focused follow-up that checks whether previously reported issues were actually fixed, which is exactly the wave whose delta you care about most. An ad_hoc run captures unplanned work, the exercise that happens outside the regular rhythm. A scheduled run is part of a recurring cadence, the kind a continuous program produces on a fixed cycle. A manual_pentest run records hands-on testing where findings may be attached directly to the run rather than arriving through an automated scanner.

The classification is more than a label. It supports structured engagement workflows by letting teams filter, report, and reason about waves by their nature. A timeline that distinguishes baseline assessments from retests tells a clearer story than one that treats every wave identically. When a manager reviews a year of activity, the type field is what separates routine scheduled sweeps from the targeted retests that prove remediation worked.

Launching a Multi-Scanner Campaign in One Call

The most operationally useful capability in this feature is campaign launch. Setting up a wave by hand means creating each scan one at a time, pointing each at the right integration, and remembering to associate every one with the wave. That is tedious and easy to get wrong. PMAP collapses it into a single call.

When a run is created with Launch=true, PMAP atomically creates the run and spawns one scan per selected integration. The create request carries the integration IDs to fire against, and it accepts optional asset targeting so the campaign hits a defined scope, an optional scan name prefix so the spawned scans are easy to recognize, and an optional severity filter. In the interface this is the third step of the create-run wizard, where you pick the integrations and decide whether to launch immediately. One action, and a Nessus scan, a Qualys scan, and a Rapid7 scan all start against the wave’s scope, each already attached to the new run.

There is a hard guard on this. Launch mode requires at least one integration ID. A request with Launch=true and no integrations returns a 400 error rather than creating an empty, pointless run. That guard keeps the campaign meaningful. A launch always produces real scanning work or it produces an explicit error.

Why Per-Integration Failures Do Not Roll Back the Run

A campaign that fans out across several scanners has to decide what happens when one scanner fails to start. PMAP makes a deliberate choice here. Scan creation during a launch is fire-and-forget per integration. PMAP iterates over the integration IDs and creates each scan synchronously in a loop within the request, but a failure on any single integration is logged and discarded. The run row is always returned immediately.

The reasoning is that the run is the durable object and the campaign is a best-effort fan-out. If the Qualys integration is briefly unavailable while Nessus and Rapid7 are healthy, you still want the run created and you still want the two healthy scans running. Rolling the entire run back because one scanner hiccuped would be the wrong trade. Instead the run exists, the successful scans are attached, and a missing scan can be added later with an attach operation. One practical consequence is worth knowing. Because the loop runs inline within the HTTP request rather than as a queued background job, a very large list of integrations can add latency to the create response. For typical campaigns this is invisible. For unusually broad fan-outs it is something to expect rather than be surprised by.

Attaching and Detaching Scans Without Losing History

Campaigns are rarely planned perfectly up front. A scan gets uploaded before anyone created the wave it belongs to. A scan was attached to the wrong run. The scope of a campaign shifts after it starts. PMAP handles all of this with explicit attach and detach operations that operate on existing scans.

Attaching is straightforward. An analyst can attach an existing scan to a run after the fact, which reorganizes scans when the campaign scope changes without forcing anyone to re-run the work. A scan can be moved between runs the same way. The detach operation is where the design choice that protects your data lives. When a scan is detached from a run, PMAP does not delete it. The foreign key from scans to runs uses SET NULL, so detaching simply nulls the scan’s assessment_run_id while the scan and all of its historical findings are preserved.

The same protection applies when a whole run is deleted. Deleting a run does not cascade-delete its scans. Every previously attached scan has its assessment_run_id set to null, and no scan rows and no finding rows are removed. This is a guardrail against an entire class of accidents. An analyst who attaches a scan to the wrong wave, or who deletes a run during cleanup, cannot destroy historical findings by doing so. The worst case is a scan that needs to be re-attached, not data loss. That guarantee is what makes it safe to reorganize waves freely as an engagement evolves.

The Completion Delta: New, Persisting, Resolved, Reopened

The payoff of structuring work into numbered waves arrives when you complete a run. A run’s status moves through a four-state lifecycle. It starts at planned, advances to in_progress, and ends at either completed or cancelled. The transitions are user-controlled. The API validates that a status is one of the four valid values but does not impose a strict transition guard beyond that enum check, so advancement is in the team’s hands.

The moment that matters is the transition to completed. When a run is marked complete, PMAP runs computeRunDelta. That function identifies the immediately preceding run on the same project by run number and compares the two waves’ findings. It produces four numbers that together describe wave-over-wave progress:

  • New findings are present in the current run but were not seen in the previous run. This is fresh exposure that emerged since the last wave.
  • Persisting findings are present in both runs. These are issues that survived from the last wave into this one, the backlog that has not moved.
  • Resolved findings were in the previous run and are absent from the current one. This is the work that got done, the evidence that remediation landed.
  • Reopened findings are the regressions. PMAP identifies them by a reopened status-history row dated within the current run’s time window, which is how it catches an issue that was closed and has since come back.

The finding sets behind these numbers are derived from finding_scan_occurrences joined to the scans attached to each run, so the comparison reflects what the scans for each wave actually saw. On completion PMAP also emits an assessment_run_completed event carrying the delta, which notification subscribers and live SSE clients consume. The delta computation and event emission are best-effort and run alongside the status change rather than gating it. If the event emission goroutine fails, the status change is already committed, so a delivery hiccup never blocks a manager from closing a wave.

Two edge behaviors are worth stating plainly. If a run is the first run on a project, there is no prior wave to compare against, so every finding is classified as new. And because the delta is computed against the immediately preceding run by number, the comparison always lines up two real, adjacent waves rather than guessing at an arbitrary baseline.

Comparing Waves on the Timeline

A single delta tells you how one wave compared with the one before it. Across a long engagement you want the trend, the shape of the whole campaign. PMAP surfaces this on the project Timeline tab. The timeline is a chronological wave view that shows each run with its delta badges and severity dots, so a quarter-by-quarter or round-by-round progression is visible at a glance. A manager can scan the timeline and see whether resolved counts are climbing and reopened counts are staying near zero, which is the signal that a remediation program is working.

For a direct head-to-head, the timeline lets you select up to two runs and open a side-by-side comparison. The comparison view itself reaches the runs through a deep link from the timeline. It is worth noting that this comparison page is present in the product but is reached only through that timeline deep link rather than appearing as a standalone navigation item, so the timeline is the natural entry point.

The run detail page complements the timeline for the single-wave view. It carries a header with the run-number badge, name, status, type, owner, and timestamps, a KPI row showing scan count, findings count, run number, and type, a scans section listing the attached scans with attach and detach controls, and a findings section. The findings section previews the first twenty findings of the wave for a quick read, with a link out to the full filtered findings list when an analyst needs the complete set rather than the preview. Between the timeline for the trend and the run detail for the wave, a manager has both the longitudinal and the point-in-time view of the engagement.

How PMAP Structures Test Waves

Pulling the threads together, PMAP treats a test wave as a first-class object rather than an afterthought. A run is a named, numbered, classified, time-boxed campaign inside a project. Its number is allocated under an advisory lock so it is stable and gap-free for the life of the engagement. Its type records what kind of wave it is, from baseline assessment to retest to manual pentest. A campaign launch spins up a scan per integration in one call, with severity filtering and asset targeting, and tolerates a single scanner failing without losing the run. Scans attach and detach freely because the foreign key nulls rather than deletes, so history is never at risk. And on completion the run computes a new, persisting, resolved, and reopened delta against the prior wave automatically, then publishes it as an event for downstream notification and reporting.

The throughline is that structure produces comparison. By insisting that work belongs to a numbered wave, PMAP can always answer the question that flat scan piles cannot. Did this wave move the needle against the last one? That answer, computed automatically and anchored to stable numbering, is what turns a stack of scan results into a defensible account of whether security is improving.

This run and campaign layer sits inside the wider engagement model. The project, its scope, and the firms working it are covered in planning a multi-firm pentest project. The way coverage and the wave matrix expose what each scanner actually saw across an asset is covered in scan coverage and the wave matrix, which is a different lens on waves than the run delta described here. And the broader program view lives in the security assessment management pillar. For an external standard on how to structure and scope security testing waves, the NIST SP 800-115 technical guide to information security testing and assessment and the Penetration Testing Execution Standard are useful reference points alongside the OWASP Web Security Testing Guide.

Download the assessment datasheet and launch your first multi-scanner campaign as a run in PMAP.

Frequently Asked Questions

What is an assessment run versus a project?

A project is the long-lived container for a client engagement. An assessment run is one bounded execution inside that project, a named and numbered wave of testing that aggregates the scans and findings for that wave. The project holds the overall scope and structure across time. The run holds a single time-boxed campaign within it. Findings belong to a run through the scans attached to it or, in the case of manual pentest findings, directly. This separation is what lets PMAP compare one wave against the next inside a single ongoing engagement.

How do I launch scans against multiple scanners at once?

Create a run with launch mode enabled and select the integrations you want to fire. PMAP atomically creates the run and spawns one scan per selected integration, so a single call can start a Nessus scan, a Qualys scan, and a Rapid7 scan against the same wave scope. The campaign accepts optional asset targeting, a scan name prefix, and a severity filter. Launch mode requires at least one integration. A launch request with no integrations is rejected with a 400 error rather than creating an empty run.

What does the run completion delta measure?

When you mark a run completed, PMAP compares its findings against the immediately preceding run on the same project and reports four numbers. New findings appeared in this wave but not the last. Persisting findings are present in both. Resolved findings were in the previous wave and are gone from this one. Reopened findings are regressions, identified by a reopened status-history row dated within the current run’s time window. Together these describe wave-over-wave progress without manual counting. If the run is the first on a project, there is no baseline and every finding is classified as new.

Does detaching a scan from a run delete its findings?

No. Detaching a scan does not delete anything. The foreign key from scans to runs uses SET NULL, so detaching simply nulls the scan’s run association while the scan and all of its historical findings are preserved. The same protection applies when an entire run is deleted. Its scans are detached by nulling the run reference, and no scan rows or finding rows are removed. The worst case is a scan that needs to be re-attached, never lost data.

How is run numbering kept stable and unique?

Each run receives a run_number allocated by a database function that uses an advisory lock to guarantee monotonic, gap-free numbering per project. The number is set on create and never mutated afterward, and numbers are never reused. Because the allocation is serialized under the lock, two runs created at nearly the same time cannot collide on a number. The result is a stable reference that reports, conversations, and audit trails can use to point at the same wave months later.

What run types can I assign, and why do they matter?

A run is classified with one of five types. They are assessment, retest, ad_hoc, scheduled, and manual_pentest, with assessment as the default and invalid values rejected. The type records what kind of wave the run represents, from a baseline evaluation to a focused remediation retest to hands-on manual testing. It supports structured engagement workflows by letting teams filter and report on waves by their nature, which makes a timeline far more readable than one that treats every wave identically.

Can I add a scan to a run after the campaign already started?

Yes. Attaching is an explicit operation on an existing scan, so a scan uploaded before its wave existed, or one that started outside the campaign, can be attached to the run afterward. A scan can also be moved between runs the same way. This is how teams reorganize a campaign when its scope shifts mid-wave without re-running any scanning work, and because detaching preserves history, moving scans around carries no risk to existing findings.

author avatar
PMAP Security Team

Newsletter

Get the next writeup in your inbox

One short email when a new case writeup or detection deep dive ships. No marketing drip, no third-party tracking.