Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,13 @@ loopback-only. For a remote host, tunnel from your workstation:
ssh -L 8080:127.0.0.1:8080 user@docker-host
```

Then add notification destinations and create your first monitoring job in the
console. Runtime state and generated encryption keys live in `./data`; back
them up together.
Then open **Jobs → New job** and follow the guided monitor setup to choose authorized targets, scan
coverage, a schedule, and alerts. You can also add and test a notification
destination during setup; operators can select existing destinations, and
choosing no alerts is supported. Review the scan estimate and budget before
creating the monitor. Runtime state and generated encryption keys live in
`./data`; back them up together. See the [first-scan guide](https://edgewatch.offsec.nl/getting-started/first-scan/)
for the initial scan and baseline workflow.

## Documentation

Expand Down
62 changes: 50 additions & 12 deletions docs/src/content/docs/getting-started/first-scan.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,80 @@ Open **Notifications** and choose Email (SMTP), Discord webhook, ntfy, or
**Advanced Shoutrrr URL**. Add a name, connection details, and confirm your
account password. Credentials are write-only and encrypted; the console does
not return them after you save them. Use **Test** and check that the message
arrives. When you create a job, select the destination in its notification
routing.
arrives. You can also create a destination while setting up a monitor. An
operator can select destinations but cannot create or test them. A monitor can
also be created with the explicit **Continue without alerts** choice.

The [notification guide](/user-guide/notifications/)
covers routing, delivery health, and destinations imported from older deployments.

## Choose a scanner profile

Open **Scanner profiles**. Keep the built-in profile to begin with, or create
an administrator-managed profile suited to your network.
Open **Scanner profiles** only if you need an administrator-managed profile
suited to your network. The guided setup uses the built-in profile by default;
profile tuning is available in the full editor.

New TCP jobs default to Naabu connect discovery followed by Nmap confirmation.
UDP always uses Nmap. Read [Scanning and profiles](/user-guide/scanning/)
before selecting SYN discovery or expanding the probe scope.

## Create a monitoring job

Create a job with:
Open **Jobs → New job** and follow **Targets → Coverage → Schedule and
alerts → Review**. Use only systems you are authorized to scan. The guided form
starts with full-range TCP discovery: Naabu checks ports 1–65535 and Nmap
confirms discoveries. Choosing specific TCP ports switches to Nmap-only
partial coverage. UDP is optional and always uses Nmap. The full editor remains
available for advanced scanner and baseline settings.

Review the five-field cron schedule, its timezone, selected alert destinations,
baseline sample count, confirmation threshold, and the server's probe estimate
and unit budget before creating the monitor. The preview does not resolve DNS
or send probes; actual work can differ after DNS resolution and exclusions are
applied.

Choose **Create without starting** to save the monitor without requesting an
immediate scan, or **Create and start first scan** to request one immediately.
An enabled schedule still requests scans at its scheduled times. The full
editor's separate **Run at daemon startup** option can also request a scan when
EdgeWatch starts. A destination created during setup is saved separately and
remains in **Notifications** if you cancel the monitor.

The full editor lets you review or change:

- **Targets:** authorized IP addresses, CIDRs, or DNS names.
- **Protocols and ports:** the TCP and UDP surface you want to monitor.
- **Schedule:** five-field cron syntax with the selected IANA timezone.
- **Protocols and ports:** full-range or selected TCP coverage, plus optional
UDP ports.
- **Schedule:** five-field cron syntax with the selected IANA timezone, or a
paused schedule.
- **Baseline samples:** how many successful samples establish the expected surface.
- **Change confirmation:** how many matching changes confirm an incident.
- **Run at daemon startup:** an independent option to request a scan when
EdgeWatch starts.

Start with a small, known scope. Deployment probe budgets and target exclusions
still apply to the job.
still apply to the monitor. If the estimate exceeds the budget, you can save
without starting; narrow the scope or ask an administrator to approve a
high-cost scan before requesting a fresh preview.

## Establish the baseline

Run the job and inspect its results. Approve a successful scan as the baseline
once you have verified that it represents the surface you expect. Until the
job has collected its baseline samples, the scan detail describes each scan as
a baseline sample instead of a comparison; see
The job page's **Next steps** card shows baseline learning progress and the
next available action. EdgeWatch establishes the baseline automatically after
the configured number of successful scans with complete, consistent coverage;
new jobs require two samples by default. The schedule supplies future samples
when it is enabled. Use **Run another sample** in Next steps when you want to
request one sooner. Until learning finishes, scans appear as baseline samples
rather than comparisons; see
[Scan comparison](/user-guide/jobs-baselines-incidents/#scan-comparison).

A complete baseline with zero positive ports in the configured TCP and UDP
coverage is valid. **Use as baseline** is an optional, explicit override after
you review the scan evidence; ordinary learning does not require approval.
If learning stalls or a run is rejected or incomplete, review the scan result,
correct the target or scanner configuration, and follow the
[baseline and scan lifecycle guide](/user-guide/jobs-baselines-incidents/#first-scan-and-baseline-learning).

:::note[Incomplete observations do not change expectations]
Failed, canceled, timed-out, or incomplete scans remain available for
troubleshooting. They cannot establish or advance the baseline or turn a
Expand Down
100 changes: 100 additions & 0 deletions docs/src/content/docs/reference/api-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,106 @@ defensible duration estimate is available. Clients should treat this field as
optional and must not infer that a scan will finish within any fixed time from
the probe count alone.

## Job creation preview

v0.32.0 adds `POST /api/v1/jobs/preview`. Send the same new-job JSON payload
accepted by `POST /api/v1/jobs`; the response returns the normalized public
job form, the existing `WorkEstimate`, the existing `ScanBudget` outcome, and
at most five stable warnings:

```json
{
"job": {
"name": "edge inventory",
"schedule": "0 * * * *",
"timezone": "UTC",
"run_on_start": false,
"assume_alive": true,
"targets": ["198.51.100.10"],
"dns_comparison_mode": "address_sensitive",
"max_expanded_hosts": 256,
"tcp": {
"ports": "22,443",
"mode": "connect",
"service_detection": false,
"engine": "nmap"
},
"udp": null,
"timing": "balanced",
"timeout": "1m0s",
"resume_window": "192h0m0s",
"baseline_samples": 2,
"change_confirmations": 2,
"enabled": true,
"allow_high_cost": false
},
"scan_estimate": {
"hosts": 1,
"tcp_ports": 2,
"udp_ports": 0,
"probes": 2,
"naabu_probes": 0,
"nmap_probes": 2,
"nmap_invocations": 1,
"naabu_invocations": 0,
"unknown_dns": 0
},
"scan_budget": {"exceeded": false},
"warnings": [
{
"code": "elapsed_time_unknown",
"message": "Probe and process counts are preflight estimates; elapsed scan time depends on DNS, scanner behavior, target responses, retries, and discovered ports."
},
{
"code": "tcp_partial_coverage",
"field": "tcp.ports",
"message": "This Nmap TCP selection covers only the configured ports, not the full TCP port range."
}
]
}
```

The preview applies the same new-job defaults, selected scanner-profile
resolution, destination routing, deployment target exclusions, and permission
rules as creation. It then compares the prepared estimate with the current
unit probe budget. When TCP engine/profile is omitted, the existing default
Naabu-to-Nmap profile and its full TCP discovery range are returned in `job`;
clients that deliberately select only some TCP ports must send
`engine: "nmap"`. Optional UDP work is included in the estimate.

`scan_estimate` is a bounded preflight, not a duration promise. Each DNS name
is counted as one logical address and increments `unknown_dns`; preview does
not resolve names. Naabu's known discovery pass covers ports 1–65535, while
the subsequent Nmap confirmation work depends on discovered ports and is not
included before discovery. Warnings have stable `code` values, an optional
field path, and user-facing text. Clients should handle unknown warning codes
as generic advisories.

A valid estimate above the unit budget still returns `200`. Its
`scan_budget.exceeded` is `true`, with `estimated_probes`, `limit`, and
`approval_would_fit`; the latter says whether the unit's high-cost ceiling
would admit the estimate if the caller is authorized to enable that approval.
A job over the absolute probe ceiling cannot fit even with that approval.
Creating a job remains compatible with existing behavior, but preview does
not reserve budget and a later run checks the current limits again. Do not
promise that an over-budget job will start.
If the unit budget cannot be read, preview returns `503 preview_unavailable`
with `details.reason: "scan_budget_unavailable"` and does not claim a fit.

Preview is advisory and read-only: it creates no job or revision, baseline,
scan, audit entry, outbox item, or schedule change; it does not resolve DNS,
start scanner or notification processes, reserve capacity, or publish an SSE
event. Creation and run remain authoritative and revalidate current policy and
resources. Invalid input and stale profile selections keep creation's
validation/conflict semantics. A foreign unit's profile or destination ID is
indistinguishable from an unknown ID.

The route requires `jobs.write`, so unit administrators and operators may
preview; viewers, platform administrators, and anonymous callers may not. It
uses the existing authenticated POST session and CSRF checks. It has no
additional route-specific Origin check; sending an Origin does not replace
CSRF validation. `allow_high_cost` remains administrator-only.

## Scan history

The authenticated scan-history endpoints provide metadata, full results, and
Expand Down
37 changes: 37 additions & 0 deletions docs/src/content/docs/user-guide/jobs-baselines-incidents.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,43 @@ editing the job, and **Resume schedule** starts them again; **Scan now** keeps
working while a job is paused. Pausing and resuming are unavailable while the
job's scan is running.

## First scan and baseline learning

On the final review, choose **Create and start first scan** to save and open the
job page, then request one scan, or choose **Create without starting** to save
the job without requesting an immediate scan. Creating the job and starting its
scan are separate actions.
If the scan request fails, the job remains saved on its page; retrying starts a
scan for that job and does not create another copy. Refreshing the page or
returning to it later does not replay the one-time create-and-start action.
Existing schedule settings and the configured `run_on_start` behavior remain
authoritative, including when EdgeWatch restarts.

The job page's **Next steps** card reads the saved job, scan, and baseline
state. It shows successful samples collected against the configured sample
count and, while the schedule is enabled, the next scheduled sample in the
job's timezone. A paused schedule has no automatic next sample. A job with no
schedule can still be sampled manually by a permitted operator or
administrator. If the unit's scan slots are full, an explicit manual request
waits in the queue and can be canceled before it starts.

Only successful scans with complete coverage count toward learning. Failed,
canceled, timed-out, and incomplete scans stay in history but do not count as
samples or remove expected results. If learning stalls after incomplete
coverage, open the scan evidence and correct target reachability or the saved
scanner profile, then retry. The scan uses the job's current saved settings;
the next scheduled run also uses the current revision. Once the configured
number of complete successful samples has been recorded, EdgeWatch establishes
the baseline automatically. It never lowers that sample count or performs the
explicit **Use as baseline** override on your behalf. Incident acceptance
remains an explicit decision.

An active baseline applies to the targets and TCP/UDP ports configured for that
job. It can be complete even when no positive ports were found; the job page
states this as a valid empty baseline. When the card says **Ready (updating
scope)**, the existing baseline remains active while its stored scope is
re-keyed by the next finalized scan or a saved job change.

Archiving stops a job while keeping its results and incidents available. An
administrator can permanently delete an archived job by typing its exact name;
this also removes that job's scan results, incidents, saved scan progress, and
Expand Down
8 changes: 8 additions & 0 deletions docs/src/content/docs/user-guide/notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ provider accepted the test send; check the recipient to confirm the message
arrived. Destinations added after existing job routing is frozen remain
opt-in. Select a destination in each job that should use it.

You can also add and test a destination while creating a monitor. This uses the
same provider fields and account-password confirmation as this page. The
destination is saved independently from the monitor: if you cancel the monitor
or its creation fails, the destination remains here. Credentials and the
account password stay in the temporary form and are cleared after saving.
Operators can select available destinations but cannot add or test them. They
can explicitly create a monitor without alerts.

## Routing and update alerts

Each job can select its own destinations. On the **Notifications** page,
Expand Down
8 changes: 8 additions & 0 deletions docs/src/content/docs/user-guide/scanning.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ EdgeWatch observes authorized targets using fixed scanner executables and
validated argument arrays. Choose the engine and profile that match your
network and runtime capabilities.

The guided monitor setup starts with full-range TCP discovery: Naabu checks
ports 1–65535 and Nmap confirms discoveries. Choosing specific TCP ports
switches that job to Nmap-only partial coverage. UDP is optional and uses Nmap
for the selected ports. Switching between full-range and selected TCP coverage
preserves the prior settings for each mode; the full editor also keeps a pinned
profile revision and its tuning when you return to that mode. Review the actual
engine, port scope, profile revision, and estimate before creating the job.

## Nmap or Naabu to Nmap

Each TCP job chooses a scanner engine:
Expand Down
2 changes: 1 addition & 1 deletion e2e/accessibility-control-regressions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test('the dark scheme and form controls remain legible under either system prefe
test('keyboard focus is visible on switches, host filters, unit rows and tabs (#985)', async ({ page, browser, baseURL }, testInfo) => {
test.skip(testInfo.project.name !== 'desktop', 'Keyboard focus states are verified once on desktop.')
await mockConsole(page)
await page.goto('/jobs/new')
await page.goto('/jobs/new/advanced')
const checkbox = page.locator('.switch-row input[type="checkbox"]').first()
await focusWithKeyboard(page, checkbox)
await expect.poll(() => checkbox.evaluate(element => getComputedStyle(element).outlineStyle)).toBe('solid')
Expand Down
1 change: 1 addition & 0 deletions e2e/admin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ test('setup, login, and build a TCP/UDP job in the console', async ({ page }) =>
await expect(page.getByRole('heading', { name: '192.0.2.1' })).toBeVisible()
await navigateFromShell(page, 'Jobs')
await page.getByRole('button', { name: 'New job' }).click()
await page.getByRole('link', { name: 'Open full editor' }).click()
await expect(page.getByRole('heading', { name: 'Create a monitoring job' })).toBeVisible()
await expect(page.getByLabel('TCP engine')).toHaveValue('naabu_nmap')
await expect(page.getByText('Stagger scheduled scans')).toBeVisible()
Expand Down
Loading
Loading