# Business rules — vulnerability_scan

Evidence date: 2026-09-29. Revision: `main` `de29c65`.

No business-rules document exists in the source repository. Every rule below is **Inferred** from implementation unless the line says **Documented**. Rules are what the code does, not a confirmed product policy.

Secrets are not copied here. `.env` key names are listed because the README and the script comments publish them.

## Who may run a scan

**BR-001 — One GVM user is the only credential the CLI checks.**  
The script authenticates with `USERN` and `PASS` or with a prompted username and password. It does not read a role, a group, or an allow-list of targets. **Inferred.**

**BR-002 — Authentication fails only when GMP returns status `400`.**  
Any other status attribute ends the retry loop and the script continues. Up to three attempts are made. **Inferred** from the `authenticated.attrib["status"] != '400'` test.

**BR-003 — Environment credentials suppress the prompt.**  
If `USERN` is set, the script does not ask for a username. If `PASS` is set, it does not ask for a password. A wrong value in `.env` is reused for every attempt. **Inferred.**

**BR-004 — The Greenbone UI is a loopback service in the compose file.**  
`gsa` publishes `127.0.0.1:9392:80`. The README tells the operator to read port lists, configs, and scanners from `http://127.0.0.1:9392/login`. **Documented** URL. **Implemented** bind address.

## Targets and tasks

**BR-005 — The target name is the task name, and both equal the CLI string.**  
Matching uses equality on `name`. A different Greenbone name for the same host is not recognised. **Inferred.**

**BR-006 — Several names in one run are separated by a colon.**  
**Documented** in the README. **Implemented** by `split(":")`.

**BR-007 — The first time a name is seen, the operator must choose a port list, a scan config, and a scanner, unless flags supply them.**  
**Documented** for the menu and for `--defaults`. **Implemented.**

**BR-008 — Defaults are three fixed Greenbone names.**  
`--defaults` means port list `All IANA assigned TCP`, configuration `Full and fast`, scanner `OpenVAS Default`. **Documented** and **Implemented.** Those objects must already exist. The CLI does not create them.

**BR-009 — Spaces in option values are written as underscores on the command line.**  
The script replaces `_` with a space and strips quotes before matching Greenbone names. **Documented** and **Implemented.**

**BR-010 — A target or task that already exists is not reconfigured.**  
Port list, config, and scanner flags do nothing for a name that already matches. **Inferred.** The README describes those flags as what to use for scanning. It does not say they are create-only.

**BR-011 — Every new target is created with the same alive test.**  
The value is `ICMP, TCP-ACK Service & ARP Ping`. **Inferred.** Not stated in the README.

**BR-012 — Tasks are searched across the whole task list.**  
`get_tasks` is called without a filter. The first row whose name equals the target string wins. If two tasks shared that name, the last one in the API list would win because the loop overwrites `task_id`. **Inferred.** Whether Greenbone allows duplicate names is **Unknown / Requires Confirmation**.

## Running a scan

**BR-013 — A task that is already `Running` is not started a second time.**  
The CLI attaches to `current_report` and waits. **Inferred.**

**BR-014 — `--skipscan` means "use the last finished report", except when a scan is running.**  
If status is `Running`, the attach path is taken even when the flag is set. **Inferred.**

**BR-015 — The CLI considers the scan finished when `in_use` is not `1`.**  
It does not also require a status of `Done`. **Inferred.**

**BR-016 — Status polling tolerates ten errors and then gives up.**  
The operator-facing text says the Greenbone scan keeps going, and that running the command again can pick it up. The wait between polls is 20 seconds. **Inferred** from the loop. The "latch" wording is in the printed error.

**BR-017 — A report with no discovered hosts is not mailed.**  
The script tells the operator the host was not reachable and suggests changing the alive test in Greenbone. **Inferred.** Commit `06d81ec` introduced this handling.

**BR-018 — There is no severity gate on sending.**  
If at least one host was counted and `--mail` was provided, the HTML is sent with every grouped finding the XML contained, including Log entries when those threat values are present. **Inferred.** No minimum CVSS or QoD filter is set in `get_report`.

## Report contents

**BR-019 — The report format requested from Greenbone is the one named `XML`.**  
**Inferred.**

**BR-020 — Findings are grouped by the threat string.**  
Buckets are `High`, `Medium`, `Low`, `Log`, and the literal `False`. Anything else is omitted from the sections. **Inferred.**

**BR-021 — The HTML states that dates are UTC.**  
That sentence is hardcoded. Scan start and scan end are the report timestamps with `T` turned into a space and the last character removed. **Inferred.** The sample file `test.json` does record timezone `Coordinated Universal Time`, but the CLI does not read that field.

**BR-022 — A URL reference is linked. Other reference types are plain text.**  
**Inferred** from the ref loop (`url` versus any other type key).

**BR-023 — The HTML file is not kept by the active path.**  
The write to `scan_results.html` is commented out. The report leaves the process by email or by the Supabase insert. **Inferred.**

## Mail and storage

**BR-024 — Recipients are whatever addresses were passed to `--mail`.**  
They are split on `:`. The script does not check that they are Overdrive addresses or that they match a directory. **Inferred.** README examples use `joshua@overdrive.co.za` and `jaco@overdrive.co.za`.

**BR-025 — Sending is Microsoft 365 OAuth application mail, not the SMTP settings in the README.**  
Active inputs are `ms_app_id` and `ms_app_secret_val`, plus `o365_token.txt`. `ms_tenant_id` is read and not passed to `Account`. **Inferred.** **Discrepancy** with the README SMTP block and with the screenshot's tenant-id example.

**BR-026 — A missing token file triggers an interactive `authenticate` call.**  
Scopes are `IMAP.AccessAsUser.All`, `POP.AccessAsUser.All`, `SMTP.Send`, `Mail.Send`, and `offline_access`. **Inferred.**

**BR-027 — Supabase storage is opt-in and stores the HTML, the command line, and the scan times.**  
`type` is the literal `greenbone`. `to_start`, `intervals`, and `result_summary` are stored as false. **Inferred.** No rule in this repository decides when a row should be scanned again.

**BR-028 — The process arguments, including the target names, are stored in `command` when Supabase insert runs.**  
**Inferred.** Operators should treat that column as sensitive if targets are internal names. The mail addresses are also on `sys.argv` when `--mail` was used.

## Access to the socket

**BR-029 — The install and start scripts make `/tmp/gvm` mode `777`.**  
The Dockerfile also runs `chmod -R 777 /tmp/gvm`. **Inferred** as an access rule of the packaging, not as a written security policy. Commit `d32a02c` is the permissions fix.

**BR-030 — The CLI container runs as uid/gid 1001, user `gvm`.**  
`entrypoint.sh` activates the virtualenv and `exec gosu gvm ./cli.py`. **Inferred.**

## What is not a rule here

- No data-retention period for reports, tokens, or Greenbone tasks.
- No rule that stops a scan of a production host versus a non-production host.
- No confirmation prompt before `start_task`.
- No rate limit on how often a target may be scanned.
- No test that the mail send succeeded. The script prints `Report sent!` after `m.send()` without checking a return value.
