Vornin
Start free
Documentation

Vornin handbook.

Everything you need to know about using Vornin to secure your infrastructure — from onboarding to the REST API.

Vornin Documentation

Everything you need to know about using Vornin to secure your infrastructure.

Overview

Vornin is a vulnerability management platform that helps you find, track, and fix security issues across your servers, websites, and code. It combines 15 scanner engines into a single dashboard with compliance mapping (DORA, NIS2, and seven other frameworks), SLA enforcement, a tamper-evident evidence chain, and team collaboration.

First Steps

  1. Sign in — You'll receive a magic link email. Click it to sign in (no password needed).
  2. Complete onboarding — The wizard walks you through adding your first target, configuring branding, SLA policies, and notifications.
  3. Add targets — Add the domains, IPs, or internal hosts you want to scan.
  4. Run a scan — Select your targets and scan types, then launch.
  5. Review results — Vulnerabilities appear in your dashboard with severity ratings, remediation guidance, and compliance mappings.

Onboarding Wizard

When you first log in, the onboarding wizard walks you through the minimum setup needed to get a useful scan running:

  1. Add a Target — Enter a domain or IP address.
  2. Run Your First Scan — A quick scan with port scanning, SSL/TLS, and DNS security.
  3. Branding (optional) — Set your company name, website, report title, and brand color for white-label reports.
  4. SLA Policies (optional) — Define remediation deadlines per severity (e.g., Critical = 1 day, High = 7 days).
  5. Notifications (optional) — Enable email reminders and set escalation contacts.

All settings can be changed later under Configuration.

Managing Targets

Targets are the hosts you want to scan — domains, IP addresses, or internal servers.

Adding Targets

  • Go to Targets and click Add Target.
  • Enter a name, host (domain or IP), and optional description.
  • Assign to a group for organization (e.g., "Production", "Staging").
  • Assign a scan agent if the target is on an internal/private network.

To group many targets at once, tick their checkboxes in the targets table — a bulk bar appears letting you assign (or ungroup) all selected targets to a group in one action. The same groups are shared with your code repositories, so a group like "Production" can span both domains and repos.

Target Kinds

Every Target has a Kind that controls which scanners can run against it:

  • Hostname — vhost/SNI-aware scans (Web/API use the Host header; subdomain enumeration allowed).
  • IP Address — whole-IP scans (PortScan, SSL/TLS, Nuclei). Web scanners use the default vhost.
  • IP Range (CIDR) — expanded to per-IP PortScan tasks. CIDR capped at /16.
  • URL — preserves path and port for Web scanners targeting a specific app endpoint.

Vornin auto-detects the Kind from the Host string; you can override it before saving. Once a scan has run against a Target, the Kind is locked — create a new Target if you need different scan semantics.

If a Target's IP belongs to a CDN range or its TLS certificate covers many unrelated brands, Vornin shows a Shared infrastructure warning. Findings on shared infra may not be yours to remediate — confirm ownership before action. Dismiss the warning from the Target's edit dialog if it's a legitimate multi-brand cert under your control.

CSV Import

Bulk-import targets from a CSV file. The CSV should have a Domain column header, one domain per row.

Target Groups

Groups let you organize targets and scan them together. Create groups with custom colors for visual identification.

Targets list page with three example targets
The Targets page — the list of hosts Vornin will scan, with their groups and assigned scan agents.

Scan Types

Fifteen native engines are available. Most run fully automatically once you've added a target; only Web / API (authenticated mode), the code-repo scanners, and the cloud / Kubernetes scanners (which need credentials) require extra configuration, covered in their own sections below.

ScannerWhat It ChecksRequirements / Tool
Port Scan (TCP + UDP)Open ports, running services, service versions. TCP top-1000 ports by default (Quick / Standard / Thorough preset configurable per scan); UDP top-100 ports when Nmap is available. Scans on hosts without Nmap fall back to a TCP-connect scan and surface a clear "Limited port scan" notice.Nmap (recommended — required for UDP + service-version detection)
SSL/TLS SecurityCertificate validity, protocol versions, cipher suites, weak signature algorithms, weak key sizes, HSTS — plus protocol-level vulnerabilities via SSLyze: Heartbleed (CVE-2014-0160), ROBOT (CVE-2017-13099), OpenSSL CCS Injection (CVE-2014-0224), insecure renegotiation, CRIME / TLS compression, fallback-SCSV.None for the core checks; Docker on the scanner host enables the SSLyze augmentation
Web VulnerabilityBacked by OWASP ZAP baseline (passive scan + AJAX spider for SPA crawling) plus our curated path-probe library (~70 sensitive paths: .git, .env, backup files, admin panels, server-status, …), security header and cookie checks, CORS, HTTP methods, HTTPS-redirect behavior, and outdated JS library detection — every script the page references is fetched and version-fingerprinted, including copies served straight out of a bower_components or node_modules folder. Where a vendored library is found, Vornin also probes for the demo and sample pages that ship inside it: those files are meant for local evaluation, several carry their own CVEs, and because nothing links to them no crawler finds them. Opt into Active attack probes on the ScanNow page to run ZAP’s full active scan (SQLi, XSS, LFI, command injection payloads) against non-production targets. Supports authenticated scanning (cookie, bearer token, basic auth).Docker (bundled on the scanner host; agents fall back to curated checks only)
API SecurityCORS misconfiguration, authentication bypass, verbose error disclosure, endpoint discovery, rate limiting, API doc exposure. Optionally provide an OpenAPI / Swagger spec URL in Settings → Scanning to additionally run OWASP ZAP's API scan against the spec.None for the behavioural probes; Docker on the scanner host enables the optional ZAP API scan
Nuclei Templates (opt-in)Thousands of community-maintained templates for CVEs, misconfigurations, and technology signaturesNuclei binary on PATH (agent or server)
DNS SecuritySPF, DKIM, DMARC, DNSSEC, CAA (issuance pinning), MTA-STS (TXT signal + .well-known/mta-sts.txt policy file), and AXFR (zone-transfer attempt against your authoritative name servers).None
Subdomain DiscoveryFinds subdomains via DNS brute-force, certificate-transparency logs (crt.sh), and 45+ passive sources via Subfinder. Wildcard-DNS suppression and live-resolution filtering applied across all sources.None for DNS + crt.sh; Docker on the scanner host enables Subfinder's broader passive sources
SAST (Static Analysis)Code vulnerabilities via Opengrep, which traces untrusted input across functions and files rather than matching patterns line by line. Runs 829 security rules vendored into the product plus Vornin's own rule pack — nothing is fetched from a rule registry at scan time. Findings are normalised to canonical CWE categories.Code repository connected; opengrep on PATH (falls back to the Semgrep CLI, then to Docker)
Secret ScanningLeaked credentials, API keys, tokens via Gitleaks. Severity is calibrated per rule type — private keys + cloud root credentials are Critical, named provider tokens are High, generic high-entropy strings are Medium. Configure a per-tenant .gitleaksignore in Settings → Scanning to suppress known false positives (e.g. test fixtures with synthetic credentials). Optionally enable "Verify detected secrets with TruffleHog" in the same settings page: a second pass calls the issuing provider to check whether each detected credential is still live, marking confirmed ones as real and demoting the rest — High and Critical findings are never hidden by a negative result. Off by default, since verification means the scanner contacts the provider from its own IP.Code repository connected; Gitleaks CLI on PATH (TruffleHog additionally required for the optional verification pass)
Dependency ScanningKnown CVEs in third-party dependencies via three engines: Trivy, OSV-Scanner, and NuGetAudit for .NET, run over the same clone and merged on shared advisory aliases so one CVE never lands twice. Trivy and OSV read committed lockfiles; the .NET engine additionally resolves the full transitive NuGet graph straight from .csproj PackageReference, so a .NET project with no committed lockfile is still fully covered. For JavaScript repositories that have a package.json but no committed lockfile, Vornin generates one first (npm install --package-lock-only, locking the versions installed in node_modules where present) so Trivy/OSV can resolve them. Turn any engine off with "Disable the OSV-Scanner second engine", "Disable the .NET NuGet audit engine", or "Disable JavaScript lockfile synthesis" in Settings → Scanning. Findings carry a Reachability tag (Reached / Unreached / Unknown) so you can deprioritise vulns in packages that aren't actually called from your code. Optional "Hide CVEs without an available patch" toggle in Settings → Scanning filters out unfixable findings so the queue only contains actionable work.Code repository connected; Trivy CLI on PATH (.NET repos: dotnet SDK or Docker on the scanner host)
Container Image ScanningOS + language-package CVE scanning of container images via Trivy. Configure registry credentials in Settings → Scanning to scan private images; public Docker Hub / GHCR images need no credentials. Layered-image awareness so a CVE in a shared base layer isn't double-counted across the apps that inherit it.Docker on the scanner host; Trivy CLI on PATH
Kubernetes PostureCluster misconfiguration scanning against Pod Security Standards and CIS Kubernetes Benchmark. Detects privileged containers, hostPath mounts, missing resource limits, default-namespace workloads, weak network policies, and exposed kube-apiserver / etcd endpoints. Requires a read-only kubeconfig connected via Cloud Connections; the credential never leaves the encrypted database column.Connected Kubernetes cluster (kubeconfig with read-only RBAC)
Cloud Posture (AWS / Azure / GCP)Cloud-configuration scanning via Prowler, which runs 600+ checks per provider covering the CIS Benchmarks and each provider's own security baselines: public S3 buckets, overly permissive IAM, unencrypted EBS / managed disks, default-VPC exposure, missing CloudTrail / Activity Log, public Lambda / Function App URLs, RDS / Cloud SQL public-access flags, and more. Only failed checks at Medium and above are reported, so a pass isn't turned into noise. Connect an AWS IAM role / Azure service principal / GCP service account with read-only audit-tier permissions via Cloud Connections.Connected cloud account (read-only audit-tier credentials); Docker on the scan host
Subdomain TakeoverDetects dangling DNS records pointing at unclaimed cloud-provider hostnames (S3 buckets, Azure App Services, Heroku apps, GitHub Pages, Fastly, Netlify, etc.) where an attacker can claim the destination and serve content under your subdomain. Runs after Subdomain Discovery so every newly-discovered host is automatically evaluated, and re-checks hosts you already know about on a rolling weekly basis — a CNAME that is safe today can start dangling the moment a third-party service is de-provisioned.None
WordPress SecurityWordPress-specific vulnerability scanning: fingerprints the WordPress core, plugin and theme versions in use (asset inventory), then runs Nuclei's community WordPress template pack for known CVEs and common misconfigurations (exposed admin endpoints such as xmlrpc and user enumeration). No API token or per-site configuration required.Docker or the nuclei binary on the scanner host

When a required CLI binary is missing (or a scanner times out or can't reach its target) at scan time, Vornin never silently produces a clean report. Instead of adding a vulnerability finding, the scan surfaces a scan-level "Partial coverage" notice on the Scan Detail page naming the affected scanner(s) and why. Install the binary (or route the scan to an agent that has it) and re-run — the notice clears on the next scan where that tool completes successfully.

Reachability Analysis

Every dependency finding is tagged with one of three reachability states. The goal is to answer a simple question: does the vulnerable package actually end up in your call tree, or is it sitting in a lockfile no one ever calls? Industry benchmarks suggest 30–50% of dependency findings are "unreached" — tagging them lets you focus remediation where real risk lives.

StateWhat it meansHow to treat it
Reached The vulnerable package is imported from at least one first-party source file that isn't a test. The vulnerability lives in code your app can actually hit. Treat as real risk. Prioritise by severity + exploit signal.
Unreached The package is either not imported anywhere in your source tree, or only imported from test/spec files. A transitive dependency that shipped but is never called. Safe to deprioritise in triage. Still worth an eventual upgrade for supply-chain hygiene.
Unknown Analysis hasn't been performed yet — the finding was created before the analyser ran, your tenant has the kill switch enabled, or the repo had no supported source files to analyse (JS/TS, Python, Go, Java, C#). Expected during rollout. Re-run the scan and most findings will resolve to Reached or Unreached.

How it works

Vornin uses an import-usage heuristic, not a full call-graph. For each dependency finding the two engines produce, the analyser walks your cloned repository, builds a per-language index of imported package names, and checks whether the vulnerable package appears in any non-test source file. Import parsing is language-aware:

  • JS/TSimport … from 'pkg' and require('pkg'). Scoped packages (@scope/pkg) and sub-paths are resolved to their root.
  • Pythonimport pkg and from pkg import …. Submodule imports match the root package.
  • Go — grouped and single-line import "github.com/…" statements. Matched as module-path prefix so sub-paths like gin/binding still resolve to github.com/gin-gonic/gin.
  • Javaimport org.apache.… matched against the Trivy groupId as a dotted-path prefix.
  • C#using Newtonsoft.Json; matched against the NuGet package id as a dotted-path prefix.

Test files are detected by path segment (test/, tests/, __tests__/, spec/) and filename conventions (*.test.*, *.spec.*, *_test.go, test_*.py, *Test.java, *Tests.cs). Build outputs and vendored code (node_modules, vendor, bin, obj, dist, build, target) are excluded so a package imported only inside node_modules doesn't falsely read as Reached.

Known limitations

This is a pragmatic heuristic, not exploit-path analysis. Situations where it's conservative (leans towards Reached):

  • A package you import but never actually call will still read as Reached — import-presence, not invocation.
  • Ambiguous Java groupId or C# namespace prefixes are treated as matches. False positives favour visibility.
  • Dynamic dispatch, reflection, and plugin loaders are invisible to static import parsing — a plugin-loaded vulnerable package will read as Unreached even if it runs at runtime.

The Reachability state refreshes on every scan. A finding never regresses from a known state (Reached or Unreached) back to Unknown — if a later scan can't compute the state (analyser skipped, repo deleted), the previous signal is preserved.

Filtering and API access

The Vulnerabilities page in the app has a Reachability filter alongside severity and status. The REST API returns reachability as a string on every vulnerability, and the vulnerability.found webhook includes the same field — downstream automation can route Unreached findings to a lower-priority channel.

Tenant kill switch

If you don't want the extra walk on every dependency scan, set DisableReachabilityAnalysis = true on the tenant's ScanSettings. All findings will stay Unknown until you re-enable it.

Running Scans

Go to Scan now to start a scan. The 3-step wizard lets you:

  1. Select Targets — Pick individual targets, groups, or code repositories.
  2. Choose Scanners — Select which scan types to run. Choose "Full Scan" for all applicable scanners.
  3. Review & Launch — Name your scan and launch it.

Tip: Private/internal IP targets (192.168.x.x, 10.x.x.x, etc.) require a scan agent installed on the internal network. Vornin will never scan private networks directly from the cloud for security reasons.

Scan now wizard with targets selected
The Scan now wizard — pick targets, choose scanners, name the run, and launch.

Re-running a Scan

To repeat a past scan, open the Scans list and click the Re-run (↻) button on any completed, failed, or cancelled row. It launches a fresh scan with the same targets, scan types, and settings — including any saved authentication — and leaves the original scan in place so you can compare results. Re-runs count against your plan's scan quota just like any other scan.

Authenticated Scanning

Scan behind login pages by providing credentials in Step 2 of the Scan now wizard. Expand the "Authenticated Scanning" panel and choose a method:

  • Session Cookie — paste the Cookie header from your browser dev tools
  • Bearer Token — provide a JWT or API token
  • Basic Auth — enter username and password

Authenticated scans find significantly more vulnerabilities than unauthenticated scans because they can access protected pages and API endpoints.

Use the Validate button in the authenticated-scanning panel to confirm your credentials actually log in before launching. Vornin fetches the target with and without your credentials and reports whether an authenticated session was established. Validation is available for public targets; for agent-routed (private) targets, credentials are applied at scan time.

Attack surface Discovery

When a Subdomain Enumeration scan runs, discovered subdomains are added to the Attack surface inbox (Scanning → Attack surface) instead of immediately triggering additional scans. Each found subdomain appears with its source, IP addresses, and first/last seen timestamps.

From the inbox you can Add as Target to promote a subdomain for a focused follow-up scan, or Ignore to suppress it from the active view. Status never regresses — re-discovering an Ignored subdomain keeps it ignored and only refreshes its metadata. Vornin also monitors your attack surface daily and sends an in-app notification when new assets appear for your registered domains. The same sweep re-tests known subdomains for takeover on a rolling weekly cycle, and notifies you when a host starts looking takeover-prone rather than repeating the same alert every cycle.

Attack surface inbox with discovered subdomains
The Attack surface inbox — subdomains discovered by enumeration, ready to promote to targets or ignore.

Scheduled Scans

Set up recurring scans under Schedules. Configure the targets, scan types, and schedule (daily, weekly, monthly). Scheduled scans run automatically and results appear in your dashboard.

Attack surface

Every scan you run automatically populates a tenant-scoped Attack surface inventory: hosts, IPs and open services, TLS certificates (deduplicated by SHA-256 fingerprint), DNS posture (SPF / DMARC / DKIM / CAA), and detected technology versions. Each entity type has its own sub-page under /attack-surface with KPIs, filters, and staleness chips.

Inventory is forward-only — rows hydrate from the next scan after the feature is enabled and persist through subsequent scans. A row stops appearing in the default "Active (30d)" filter once its LastSeenAt is older than 30 days; switch to "All" to see stale entries.

Code Scanning

Connect your GitHub, GitLab, or Azure DevOps repositories under Repositories:

  1. Add a Connection — Enter your GitHub, GitLab, or Azure DevOps PAT (Personal Access Token).
  2. Import Repositories — Select repositories and the branch to scan.
  3. Scan — Include repositories in your scans from the Scan now page. Code scanners (SAST, Secret, Dependency) will analyze the selected branch.

Private package feeds (Azure DevOps). Dependency scanning resolves your package graph, so a repository whose dependencies live on your organization's private Azure DevOps Artifacts feed needs that feed authenticated or resolution fails. Vornin reuses the same connection token to authenticate npm, Bun, and NuGet resolution against your own organization's feeds only. It never sends the token to any other registry a manifest names. For least privilege, issue a dedicated read-only Packaging-scoped Personal Access Token for the connection. Third-party private registries such as FontAwesome Pro are not yet supported, so a repository that also depends on one of those may report partial coverage until you add that credential.

Scan Agent Overview

The Vornin Scan Agent is a lightweight application you install on your internal network. It allows Vornin to scan internal/private targets that aren't accessible from the internet.

Scan Agents admin page
The Scan Agents admin page — register new agents and watch their connection status.

The agent:

  • Runs on Windows or Linux (x64 and ARM64)
  • Connects outbound to Vornin (no inbound ports needed)
  • Shows a local, loopback-only setup dashboard for first-run onboarding and live connection status
  • Receives scan tasks, executes them locally, and reports results back
  • Includes port scanning, SSL/TLS, DNS, web vulnerability, and subdomain scanners

Agent Installation

The agent ships as a self-contained binary — there is no .NET runtime to install. A one-line command downloads it, verifies its SHA256 checksum, and installs it. Installing from a shell (rather than double-clicking a downloaded file) means Windows doesn't tag the binary as web-downloaded, so it runs without a SmartScreen prompt — no code-signing certificate involved.

Step-by-step: register and install the agent

  1. Sign in to Vornin and go to Admin → Scan Agents.
  2. Click Register Agent, give it a name (e.g. branch-office-01), and click Create.
  3. Copy the generated agent key (starts with cva_). It is shown only once — if you lose it, you can regenerate from the same page.
  4. On the target machine, run the install command for your platform (below). It installs the agent and opens a local dashboard at http://127.0.0.1:5099 (your default browser launches automatically on a desktop; on a headless box, browse to the printed URL).
  5. In the dashboard, paste the agent key and click Connect agent. The agent connects outbound to https://app.vornin.com — no inbound ports need to be opened.
  6. Back in Admin → Scan Agents, confirm the agent shows as Online.

Windows (PowerShell)

irm https://get.vornin.com/install.ps1 | iex

Leave the agent window open — closing it stops the agent. To skip the dashboard and supply the key up front:

& ([scriptblock]::Create((irm https://get.vornin.com/install.ps1))) -Key cva_your_key_here

Linux

curl -fsSL https://get.vornin.com/install.sh | bash

To run it headless as a systemd service instead (no dashboard, so the key is supplied up front):

curl -fsSL https://get.vornin.com/install.sh | sudo bash -s -- --key=cva_your_key_here --service

Manual download and verify

Prefer not to pipe a script? Download the archive for your platform from https://get.vornin.com/latest/ (vornin-agent-win-x64.zip, vornin-agent-linux-x64.tar.gz, or vornin-agent-linux-arm64.tar.gz) and verify it against the published SHA256SUMS before running:

curl -fsSLO https://get.vornin.com/latest/vornin-agent-linux-x64.tar.gz
curl -fsSLO https://get.vornin.com/latest/SHA256SUMS
grep vornin-agent-linux-x64.tar.gz SHA256SUMS | sha256sum -c -
tar xzf vornin-agent-linux-x64.tar.gz && ./vornin-agent/vornin-agent

The checksums file is also minisign-signed (SHA256SUMS.minisig) for anyone who wants to verify authenticity offline.

Agent Configuration

The agent connects to https://app.vornin.com automatically. You can supply the agent key non-interactively instead of entering it in the dashboard on first run:

SettingEnvironment VariableCLI ArgumentDefault
Agent KeyVORNIN_AGENT_KEY--key=KEYEntered in the dashboard on first run (or console prompt if headless)
Local dashboardVORNIN_AGENT_UIauto — on when run interactively, off under a service/CI. Set on or off to force it.
Dashboard portVORNIN_AGENT_UI_PORT--ui-port=PORT5099 (loopback-only, never network-reachable)
Log levelVORNIN_AGENT_LOG_LEVELInformation. Set Debug for per-scanner tracing when diagnosing a stuck or failing scan.

The agent saves the key locally so you only need to provide it once. To change the key later, run vornin-agent --reset-key.

From version 1.2.19 the agent also writes a rolling local log (kept 14 days) — %LOCALAPPDATA%\VorninAgent\logs\ on Windows, ~/.local/share/VorninAgent/logs/ on Linux. If a scan misbehaves, that log is the first thing to check (and to attach when contacting support).

Nmap (optional, recommended): For UDP scans and richer port/service detection, install Nmap on the agent host:

  • Windows — download the installer from nmap.org/download and run it.
  • Debian / Ubuntu — sudo apt-get install nmap
  • RHEL / Fedora — sudo dnf install nmap
  • Alpine — apk add nmap

Without Nmap, the agent falls back to a basic TCP-connect scanner (TCP only, service versions limited).

Scanning with Agents

Targets are automatically routed to agents when:

  • The target has a scan agent assigned in its settings, OR
  • The target's host is a private/internal IP address (RFC 1918)

To assign an agent to a target: edit the target and select the agent from the dropdown. The agent's online/offline status is shown in the Targets list.

Vulnerability Dashboard

The Vulnerabilities page shows all discovered security issues across your targets. Each vulnerability includes:

  • Priority — a computed P0–P3 rank (0–100) that blends severity, exploitability, SLA urgency and age so the most urgent findings sort to the top. Shown as a single chip whose dot carries the raw severity.
  • Severity — Critical, High, Medium, Low, or Info
  • Status — Open, Acknowledged, Resolved, or Accepted Risk — filter by status using the summary tiles at the top of the page
  • Affected host and port/service
  • Description and Solution — plain-language write-ups from Vornin AI when available, with the raw scanner text preserved in the detail page's Table / JSON views
  • CVE ID and CVSS score (when available)
  • First/Last detected timestamps
  • SLA deadline and SLA state column (Breached / Due soon / OK) — sortable and filterable when SLA policies are configured; closed findings show

For code findings (SAST, secret scan, dependency scan), the detail view also shows the exact file path, line number, and the offending code snippet — or the package name, installed version, and available fix version for dependency findings. Use the Group by File option in the list to collapse all findings from the same rule in the same file into a single row, making it easy to work through a scanner rule that fires on many lines.

Vulnerabilities list with mixed severities
The Vulnerabilities page — every finding across your targets, filterable by severity, scanner, target and SLA state, with status filtered via the summary tiles.

Vulnerability Lifecycle

StatusMeaningTransitions
OpenDetected and needs attention→ Acknowledged, Dismissed, Resolved
AcknowledgedTeam is aware, working on it→ Resolved, Dismissed, Open
DismissedAccepted risk or false positive→ Open
ResolvedFixed — auto-detected when a scan that re-tested the finding no longer reports it→ Open (if redetected)

Auto-resolve: When a later scan re-tests a previously open vulnerability and no longer detects it, it's automatically marked as Resolved, and the closure is recorded as verified-by-rescan.

Silence only counts as a fix when the scanner actually looked. A finding is never auto-resolved off a scan that couldn't test it. If its scanner crashed, timed out, or wasn't available on the host — the same conditions that raise the "Partial coverage" notice described under Scan Types — the finding is left exactly as it was, and a scan that fails outright closes nothing at all. The rule also applies inside a single scanner: a passive web scan won't close a finding that only an active probe can test, because it never sent the request that would have found it. Absence of evidence isn't evidence of a fix, so Vornin leaves a finding open rather than telling you it's gone.

Verify a fix without waiting for the next scan. Open a finding and choose Verify fix (retest) from its action menu. Vornin queues a re-scan scoped to that finding alone: its one target, its one scanner, and the same authentication and active-probe setting as the scan that last reported it, so the check that found it is the check that re-runs. A retest never turns active probing on by itself: if the finding came from a passive scan, the retest is passive too. And if the scan that last reported the finding is no longer available, the action is unavailable rather than falling back to a weaker check — for the same reason as above, a scan that couldn't test the finding must never be allowed to close it. If the finding no longer fires, it closes through the same auto-resolve rules described above, recorded as verified-by-rescan and counted in the re-scan verification log of the audit evidence pack. If it still fires, nothing changes and the finding stays open, which is the answer you wanted. A retest counts as a scan against your hourly limit, and the action is unavailable while one is already queued or running for that finding. It appears on findings from the network scan types (web, API, Nuclei, WordPress, ports, TLS, DNS, subdomains and takeover), which are the ones a single target can be re-tested against. Code findings and cloud, Kubernetes or container-image findings are re-tested by running their own scan.

Auto-reopen: If a resolved vulnerability reappears in a later scan, it's automatically reopened.

Vulnerability detail page showing lifecycle and evidence
The vulnerability detail page — the Vornin AI write-up (description + remediation), CVE/CVSS, full event timeline, and the tamper-evident evidence chain.

False Positive Handling

Static and dynamic scanners are naturally noisy — the same rule that catches a genuinely leaked secret also fires on a code identifier named LoginModel, a test-fixture password, or a public key baked into a frontend build. Vornin reduces this noise in two layers before a finding reaches your list:

  • Deterministic rules — path and identifier heuristics run at scan time, with no AI call and no delay. They catch the unambiguous cases: a match that's actually a code symbol, import, or type name rather than a secret; a match inside a vendored or generated directory (node_modules, dist, database migrations, and similar); and a match inside a test/example fixture or a frontend build-time config file — these are downgraded to Low severity rather than dropped outright, so you can still glance at them if you want to.
  • Vornin AI triage — for the ambiguous middle the path and identifier rules can't resolve on their own, a background pass judges each finding using its code context (file, snippet, surrounding rule) and assigns a confidence score.

A finding either layer flags as a likely false positive shows an amber FP-likely badge with its confidence percentage and a short reason — visible in the vulnerability list and on the detail page — and sorts below its peers. The Hide likely false positives toggle above the vulnerability list (on by default) keeps these out of your everyday view; turn it off any time to review everything Vornin has flagged.

Only low-risk, high-confidence findings are ever hidden automatically. A finding is auto-dismissed only when it's Low or Info severity, the confidence score is very high, and it matches a rule with a demonstrated history of false positives — everything else stays visible, just flagged. Every auto-dismissal is reversible: open the finding and click Reopen — not a false positive to restore it and override the assessment. Critical and High severity findings are never auto-dismissed regardless of confidence — they're flagged for your review, never hidden.

Suppression rules

The false-positive handling above works automatically; suppression rules let you make the same call yourself and have it stick — they automatically dismiss future detections matching criteria you choose. Create rules from:

  • The vulnerability row — click the suppress icon to create a rule pre-filled with that vulnerability's details.
  • The Accept Risk dialog — check "Create suppression rule for future scans".
  • Admin → Suppression rules — manage all rules centrally.

Rules can match by fingerprint (exact), title pattern (contains), host pattern (exact or wildcard), or scanner type.

SLA Policies

SLA policies define how quickly vulnerabilities must be remediated. Configure per severity level under Configuration → SLA Policies:

SeverityDefault RemediationEscalation
Critical1 dayImmediate
High7 days5 days
Medium30 days25 days
Low90 days80 days

When a vulnerability exceeds its SLA deadline, escalation emails are sent to the configured contacts. If no escalation contacts are configured on a policy, notifications automatically fall back to the tenant's Owner(s).

Policy changes backfill immediately. When you change a severity's Remediation days, every open or acknowledged vulnerability at that severity has its SLA due date recomputed on save — you do not need to wait for the next scan.

Severity corrections re-derive the deadline too. A re-scan can also reassess a finding's severity when the scanner refines its own rating. The deadline is then recomputed from the date the finding was first detected plus the new severity's remediation window — not from the date of the re-scan. A finding demoted from High to Medium therefore can't hand back time it has already consumed, and one promoted to Critical is measured against the tighter window from day one. If the recomputed deadline is still in the future, an existing breach clears and escalation can fire again should the new deadline later be missed. As with policy changes, only Open and Acknowledged findings are recomputed: Resolved and Dismissed ones keep their historical dates so past compliance reporting stays reproducible. Every reassessment is recorded on the finding's event timeline.

SLA state in the Vulnerabilities list. The Vulnerabilities page shows an SLA column with one of four states:

  • Breached — past the SLA deadline
  • Due soon — within 3 days of the deadline
  • OK — within SLA
  • N/A — no SLA policy applies for that severity
  • — the finding is closed (Resolved or Accepted Risk); the SLA clock no longer applies

The column is sortable, and the list has a dedicated filter for Breached only, Due soon, and Without SLA. The vulnerability detail page shows a live countdown chip ("Due in 2d 4h", "Breached 3d ago") in the same color coding.

MTTR & SLA Metrics

The Executive Dashboard surfaces two related metrics:

  • MTTR (30-day rolling) — median time to remediate, in days, across all vulnerabilities resolved in the last 30 days. Computed from the ResolvedAt timestamp set when a vulnerability transitions to Resolved. The dashboard shows an overall value and a collapsible per-severity breakdown (Critical / High / Medium / Low). Empty cohorts render as "–" rather than "0".
  • SLA Compliance — percentage of vulnerabilities inside SLA at snapshot time. Shown both as an overall figure and as a per-severity breakdown with progress bars — so you can see at a glance whether Critical compliance is slipping even if the overall number looks healthy.

Both metrics are computed nightly by the daily snapshot job and stored per tenant, so trends over time are preserved even after vulnerabilities are resolved or deleted. Vulnerabilities that were already resolved before the ResolvedAt timestamp was introduced are excluded from MTTR (they have no recorded resolution time).

Reports

Vornin generates audit-ready PDF vulnerability reports containing:

  • Executive summary with risk score, grade, and a Priority actions list — the top remediation items deduplicated across hosts
  • Vulnerability breakdown by severity and by target
  • Detailed findings with remediation guidance, plus real CVSS scores and CVE IDs where the scanner supplied them (findings without a published score are marked Not scored — Vornin never invents one)
  • A Methodology & definitions appendix — severity definitions, recommended response times, how the score is computed, and the assessment disclaimer auditors expect
  • Your company branding (logo, colors, footer text) on Business and Scale plans — see Report Branding
  • An optional Prepared for field on the cover — useful when preparing a report for a client or business unit
Reports page
The Reports page — one-click PDF exports plus the schedule list for recurring auditor-ready deliveries.

Step-by-step: generate a report

  1. Go to Reports.
  2. Pick the scope — a single scan, all targets, a group, or specific targets (multi-select the exact domains you want in the report) — and the severity filter (e.g. High + Critical only).
  3. Choose the report template (Executive, Technical, or Compliance).
  4. Optionally set Prepared for to put a client or business-unit name on the cover.
  5. Click Generate. The PDF renders in-browser for review and downloads when ready.

Step-by-step: schedule recurring reports

  1. Go to Report Schedules → New Schedule.
  2. Configure the target filter, template, cadence (daily / weekly / monthly), and recipient email list.
  3. Save. Each scheduled run emails the PDF to the configured recipients automatically.

SARIF, SBOM & CSV Exports

Every completed scan exposes three machine-readable exports alongside the PDF report, available on the Team plan and above:

FormatEndpointUse case
SARIF v2.1.0GET /api/reports/{scanId}/sarifImport findings into GitHub code scanning, Azure DevOps, or any SARIF-aware tool. Code findings (SAST, secrets, dependencies) carry file paths, line/column regions, and snippets, so alerts annotate the exact source line. Stable fingerprints mean re-uploads update existing alerts instead of duplicating them.
CycloneDX SBOMGET /api/reports/{scanId}/sbomSoftware bill of materials for supply-chain and compliance requests. Generated by dependency scans (per repository) and container image scans (per image). A scan covering multiple repositories returns an array keyed by target so you can split them downstream.
CSVGET /api/reports/{scanId}/csvThe scan's findings with full detail — code location (file, line, rule, snippet), package coordinates, CVE/CVSS/CWE/EPSS/KEV, SLA, ownership and closure trail. Same columns as the dashboard export.

All three accept your browser session or an API key as a bearer token, so CI pipelines can pull them directly:

curl -H "Authorization: Bearer cvk_your_api_key" \
  https://app.vornin.com/api/reports/1234/sarif -o vornin.sarif

To surface findings as pull-request annotations in GitHub, upload the SARIF file with the github/codeql-action/upload-sarif action after your Vornin scan completes. The SBOM download also appears as a Download SBOM button on the scan detail page once a dependency or container scan finishes.

Compliance

The Compliance dashboard maps your vulnerabilities to industry compliance frameworks:

  • CIS Controls v8 — Center for Internet Security
  • NIST 800-53 Rev 5 — National Institute of Standards and Technology
  • ISO 27001:2022 — Information Security Management
  • SOC 2 Type II — Trust Services Criteria
  • PCI DSS 4.0 — Payment Card Industry Data Security Standard
  • HIPAA — Health Insurance Portability and Accountability Act
  • GDPR — General Data Protection Regulation
  • DORA — Digital Operational Resilience Act (EU 2022/2554)
  • NIS2 — Network and Information Security 2 (EU 2022/2555)

Vulnerabilities are automatically mapped to specific controls in each framework based on the scanner type and severity. Compliance scores update in real-time as vulnerabilities are found and resolved. Download audit-ready PDF compliance reports.

Each framework has its own page at /admin/compliance/<slug> with three things the index page doesn't show:

  • Per-control drill-down. Click any failing control to see exactly which open findings caused it to fail — severity, asset, scanner, first-seen date, SLA status — each row links straight to the underlying vulnerability.
  • 90-day score trend. A line chart of the framework score over the last 90 days. Daily snapshots are written by the background snapshot service, so the trend grows from your first run forward (no retroactive backfill).
  • Mapping transparency. A panel that lists exactly which control IDs every Vornin scanner contributes to that framework — so auditors and prospects can see the algorithm rather than trust a black box.

What Vornin's compliance layer is (and isn't)

Vornin is a continuous technical-evidence layer for the controls your scans can prove — it is not a full GRC platform. Each framework catalog holds the obligations your vulnerability data can evidence, split into three honest states you'll see on every control:

  • Auto-tested — a scanner maps to the control; the control is clear or failing based on open findings, with the last-run date shown.
  • Needs your sign-off — governance, training, business-continuity, third-party and breach-notification controls that no scanner can test. These are tracked by manual attestation and are excluded from the automated score (never silently passed).
  • Not assessed — an in-scope control with no scanner mapping yet; shown honestly rather than counted as a pass.

Control-health scores reflect open technical findings against mapped controls — they are evidence of your vulnerability-management posture, not a legal determination of regulatory compliance.

Compliance workflow

The end-to-end path from a fresh workspace to an auditor-ready evidence bundle:

  1. Choose your frameworks. Under Configuration → Compliance, select the standards you answer to. A live preview shows how many controls are auto-testable versus needing sign-off. (All nine frameworks with the attestation workflow are on the Team plan and above.)
  2. Run the scans that evidence the most controls. SSL/TLS, web-application, dependency and SAST, port and DNS scans each map to multiple controls. The framework page shows, per control, exactly which scanners test it and how fresh the evidence is.
  3. Triage the findings. Resolving a finding automatically clears the controls it mapped to — the finding detail shows how many controls across how many frameworks each fix clears. Dependency findings the reachability analyzer marks Unreached are informational and do not fail controls.
  4. Review coverage and close the gaps. Each framework's Path to audit-ready plan orders the work: resolve failing controls, record or refresh manual sign-offs, then refresh stale or missing scan evidence.
  5. Attest the controls scanners can't test. Record a sign-off with a supporting evidence file and a re-attestation date. History is append-only, and expiry reminders fire before a sign-off lapses. (Team plan and above.)
  6. Export the evidence. Per-framework PDF and control-mapping CSV/JSON on every paid tier; a per-control evidence pack (findings + remediation stats + attestation + chain verification) and the tenant-wide auditor pack — with offline-verifiable tamper-evident chain — on the Business plan and above. The SHA-256 evidence chain runs for every tenant from day one; the exportable pack is what the paid tiers add.
  7. Check audit readiness and download the evidence pack. Each framework page opens with an Audit readiness panel tracking the eight artifacts auditors actually request from a vulnerability-management program — remediation SLAs, asset coverage, scan cadence history, remediation-vs-SLA performance, re-scan fix verification, the risk-acceptance register, management review, and threat-advisory monitoring — each marked Ready, Partial, or Missing, and every one reachable through your own configuration. The one-click audit evidence pack PDF (Business and above) bundles all of it over the last 12 months: month-by-month scan cadence with gap months flagged, a per-target coverage matrix, per-severity SLA adherence plus a per-finding SLA log (date found vs deadline vs date closed, breaches first), a log of fixes verified by re-scan, every risk acceptance with its type, approver and review date, framework attestations, and the chain-verification stamp. A companion data export ships the same tables as a ZIP of CSVs for your own analysis.
  8. Generate the program documents. Two auditors always ask for, both produced from your live configuration: a Vulnerability Management Policy (your real SLA targets, scan cadence, asset scope and roles written into an editable policy document) and a quarterly Management Review report (posture trend, SLA performance, cadence adherence, top risks and acceptance decisions for leadership sign-off). Both on the Business plan and above.

Targets can carry a scan-cadence policy (explicit, or inherited from a scan schedule); a target that falls behind its cadence is flagged in the compliance attention panel and — for the frameworks that mandate scanning frequency (PCI DSS, HIPAA, DORA, NIS2) — counts as a failing control until it's re-scanned, with an optional webhook digest when a target slips.

Compliance dashboard
The Compliance dashboard — coverage scores across nine frameworks (CIS, NIST, ISO, SOC 2, PCI, HIPAA, GDPR, DORA, NIS2).

Executive View

The Executive View provides a high-level risk overview designed for management and stakeholders: risk scores, severity distribution, top-5 risky targets, month-over-month comparisons, 90-day trends, and per-severity SLA compliance and MTTR (30-day rolling) — everything leadership needs without technical detail. See MTTR & SLA Metrics for how these values are computed.

Report Branding

Customize your reports under Configuration → Branding. Custom branding is applied to every PDF export — interactive, compliance, auditor pack, and scheduled — on the Business and Scale plans:

  • Company name and website
  • Report title, footer/tagline, and contact email
  • Company logo (PNG/JPG, max 2MB)
  • Primary color (cover and page-edge ink) and accent color (highlights, section markers, links)
  • Confidentiality label (e.g. TLP:AMBER) shown on the cover and every page footer

On Business, branded reports carry a discreet “Powered by Vornin” byline. On Scale, full white-label removes Vornin from the PDF entirely: page headers carry your company name, report IDs use your company initials instead of VRN-, download filenames and scheduled-report email subjects use your brand. One deliberate exception: the auditor pack's trust-anchor statements still name Vornin, because they describe who recorded the tamper-evidence hash chain — that is an integrity fact your auditor needs, not a logo.

Values saved below your plan tier are kept and apply automatically when you upgrade. Fields on lower tiers render with Vornin's default branding; your company name still appears on the cover as the report subject (“Prepared for”). Restore Defaults, beside Save, puts every field on this tab back to Vornin's defaults and removes your uploaded logo. It saves immediately, so the next report you generate already uses them.

Notifications

Enable email reminders for open vulnerabilities under Configuration → Notifications. Set the reminder interval (e.g., every 7 days). Team members with assigned vulnerabilities will receive periodic email reminders.

Per-user notification preferences

Each person chooses how they want to be notified, under Account → Notifications. The preference matrix has a row per event (scan complete, new vulnerabilities, SLA breach, posture drift, compliance changes, cadence breach, emerging threats, newly discovered assets, and vulnerabilities assigned to you) with an In-app and an Email toggle. In-app alerts appear in the bell menu; email goes to your account address. Every event supports both channels. Preferences are per person, so muting an event affects only your own inbox and bell, not your teammates'. Assignment notifications are opt-out: you get them by default when a finding is assigned to you, and can turn either channel off.

Per-scan completion emails

Both the Scan now wizard and the scheduled-scan dialog expose an "Email when this scan completes" toggle. When enabled, a Send to field appears — it is prefilled with your account's email address and accepts any number of additional recipients separated by ; or , (up to 10 per scan). The same options are accepted by the API: include notifyOnCompletion and notifyEmails in the body of POST /v1/scans. Each recipient receives a one-page HTML summary with severity counts, scan duration, and a direct link to the scan. Failed scans are emailed too, with a red Failed status pill and the error message inline; cancelled scans are silent because they are user-initiated.

User Management

Manage your team under Admin → Users. Roles:

RolePermissions
OwnerFull access including billing, user management, and tenant settings
AdminManage scans, targets, users, suppression rules, and configuration
MemberView dashboards, run scans, manage assigned vulnerabilities

Invite users via email from the Users page. They'll receive an invitation link to join your workspace.

Single Sign-On (SSO)

Vornin supports Microsoft Entra ID (formerly Azure AD) for SSO via a one-click admin-consent flow — you do not need to create or paste any client secrets. Once connected, users from your Azure tenant can sign in with their existing corporate account.

Step-by-step: connect Entra ID

  1. Sign in to Vornin as an Owner or Admin of your tenant.
  2. Go to Settings → Single Sign-On (SSO).
  3. Click Connect with Entra ID. You'll be redirected to Microsoft for admin consent.
  4. Sign in to Microsoft with an account that is a Global Administrator (or has the Privileged Role Administrator / Cloud Application Administrator role) of your Azure tenant. See Microsoft Entra built-in roles.
  5. Review the requested permissions and click Accept on the Microsoft consent screen. Microsoft will redirect you back to Vornin.
  6. Confirm the green "Connected to Microsoft Entra ID (Tenant: your-tenant-id)" banner on the SSO page.
  7. Share the tenant login URL with your users: https://app.vornin.com/auth/entra/login?tenantSlug=<your-slug>.

Background reading: Register an application with Microsoft Entra ID, Admin consent on the Microsoft identity platform, and OpenID Connect on the Microsoft identity platform. Vornin's Entra integration is OIDC-based — the SAML protocol is not required.

Disconnecting Entra ID

From Settings → Single Sign-On (SSO), click Disconnect Entra ID. Users lose the ability to sign in via Microsoft but their Vornin accounts, magic-link access, and TOTP enrolment remain unchanged. You can reconnect at any time.

Step-by-step: SAML 2.0

Vornin is a fully featured SAML 2.0 service provider. Each tenant gets its own per-tenant SP entity ID, ACS URL, and metadata URL. Bring any IdP that speaks SAML 2.0 — Okta, Microsoft Entra ID (SAML app), Google Workspace, PingFederate, JumpCloud, OneLogin.

The flow is the same on every IdP: copy Vornin's three SP URLs from Settings → SAML 2.0 SSO into the IdP's SP form, paste the IdP's entity ID + SSO URL + signing certificate back into Vornin, then enable. Detailed walkthroughs:

Okta

  1. In Okta admin: Applications → Create App Integration → SAML 2.0 → Next.
  2. App name Vornin. On the SAML Settings page paste the SP ACS URL from Vornin Settings into "Single sign-on URL" and the SP Entity ID into "Audience URI (SP Entity ID)".
  3. Set Name ID format to EmailAddress and Application username to Email.
  4. Save. Open the new app's Sign On tab → View Setup Instructions. Copy the Identity Provider Issuer, Identity Provider Single Sign-On URL, and the X.509 Certificate.
  5. In Vornin Settings → SAML 2.0 SSO, paste those three values into IdP Entity ID, IdP SSO URL, and IdP X.509 Signing Certificate. Toggle Enable SAML SSO and click Save.
  6. Click Test SSO. The IdP login page opens; sign in with an Okta user that already has a Vornin invitation accepted.

Microsoft Entra ID (SAML application)

  1. In Entra: Enterprise applications → New application → Create your own application. Name it Vornin, choose Integrate any other application you don't find in the gallery (Non-gallery).
  2. Open the new app → Single sign-on → SAML.
  3. In step 1 (Basic SAML Configuration) paste the Vornin SP Entity ID as Identifier, SP ACS URL as Reply URL, leave Sign-on URL blank.
  4. In step 3 (SAML Certificates) download the Certificate (Base64).
  5. From step 4 (Set up Vornin) copy Login URL and Microsoft Entra Identifier.
  6. In Vornin Settings → SAML 2.0 SSO: paste Microsoft Entra Identifier into IdP Entity ID, Login URL into IdP SSO URL, the certificate file contents into the cert box. Enable and Save.

Google Workspace

  1. In Google Admin: Apps → Web and mobile apps → Add app → Add custom SAML app.
  2. Copy the SSO URL and Entity ID, download the Certificate.
  3. Paste the Vornin SP ACS URL and SP Entity ID into Google's "Service provider details" form. Set Name ID format to EMAIL.
  4. Add an attribute mapping: Primary email → Email.
  5. Back in Vornin Settings → SAML 2.0 SSO, paste Google's three values, enable, save. In Google Admin grant the app to the right organisational units.

PingFederate / JumpCloud / OneLogin

Same pattern: create a SAML SP integration in the IdP, paste Vornin's three SP URLs into the IdP's SP form, paste the IdP's entity ID + SSO URL + signing certificate back into Vornin Settings → SAML 2.0 SSO, enable.

Notes. SAML 2.0 SSO is on the Business plan and above. The asserted email must already match an existing Vornin user with a tenant membership — JIT user creation is a Phase 2 feature. Single Logout (SLO) and IdP-initiated flows are also Phase 2.

Sign in with your email (SSO auto-discovery)

Once your domain is verified, users don't need to remember a special SSO link: entering a work email on the sign-in page that matches a verified domain routes them straight to your configured Entra ID or SAML SSO automatically, no tenant slug required.

  1. In Settings → Single Sign-On (SSO), add and verify your domain by publishing the shown TXT record at _vornin-verify.<your-domain>. Verification is DNS-based and re-checked periodically; two consecutive failed checks unverify it.
  2. Once verified, optionally turn on Require SSO to refuse magic-link sign-in for that domain (platform admins are exempt). Leave it off to let users choose either SSO or magic-link.

Social login

Sign in to Vornin with Google or GitHub. The first time you use a social provider with an email Vornin has never seen, you'll be sent through the normal sign-up wizard with your email pre-filled. If your email already has a Vornin account, your social identity is automatically linked the first time you use it — no extra confirmation step required, because Google and GitHub already verified the email.

Two-factor (TOTP) requirements still apply to social sign-in: if your workspace requires TOTP, you'll be prompted after the social round trip.

Two-factor authentication (TOTP)

TOTP (Time-based One-Time Password) adds a second factor on top of the magic link. It is compatible with any RFC 6238 authenticator — Microsoft Authenticator, Google Authenticator, 1Password, Authy, etc.

Step-by-step: enrol your authenticator

  1. Install an authenticator app on your phone (see the list above).
  2. Sign in to Vornin, then visit /auth/totp-setup (or follow the "Enable TOTP" prompt if it's shown to you).
  3. Scan the displayed QR code with your authenticator app. If you can't scan, use the manual key below the QR code and add it as type "Time-based" in the app.
  4. Enter the 6-digit code your authenticator shows and click Verify.
  5. Save the recovery codes Vornin shows you. Store them in a password manager — they are the only way to regain access if you lose your phone.

See RFC 6238 for the underlying algorithm.

Tenant-wide TOTP enforcement

Owners and Admins can require every member of the tenant to enrol a TOTP authenticator before they can sign in. When enabled, members without TOTP are redirected to /auth/totp-setup at their next login. Existing sessions continue until expiry.

API Keys

API keys grant programmatic access to the same endpoints the UI uses. All keys are tied to a tenant and respect the same role-based authorisation as the user who created them.

Step-by-step: create a key

  1. Go to Admin → API Keys.
  2. Click Generate API Key.
  3. Give the key a descriptive name (e.g. ci-pipeline, grafana-integration) so it's easy to revoke later.
  4. Select the minimum role scope required — prefer Member (read-only-ish) over Admin unless the caller must create scans.
  5. Optionally set an expiry date.
  6. Click Create. Copy the key immediately — it starts with cvk_ and is shown only once.
  7. Use the key with Bearer-token authentication:
Authorization: Bearer cvk_YOUR_API_KEY

Rotating & revoking

  1. To rotate: create a new key, deploy it to the caller, then delete the old one.
  2. To revoke immediately: go to Admin → API Keys and click Revoke on the affected row. All in-flight requests using that key start receiving 401 Unauthorized on the next request.

SCIM 2.0 User Provisioning

Vornin exposes a SCIM 2.0 provisioning endpoint so identity providers (Azure AD / Entra ID, Okta, JumpCloud, OneLogin) can automate user lifecycle — new hires get access the moment they're added to the right IdP group, and leavers are deactivated automatically when their IdP account is removed.

SCIM is provisioning only; sign-in uses the Entra ID SSO or magic-link flow covered above. The two systems are complementary: SCIM creates the user, SSO authenticates them.

Endpoint

https://app.vornin.com/scim/v2

Supported resources:

  • GET /Users — list users (supports startIndex, count, and filters userName eq "...", emails[value eq "..."], active eq true|false)
  • POST /Users — provision a new user; creates the AppUser record and the tenant membership in one shot
  • GET /Users/{id} — fetch a single user
  • PUT /Users/{id} — replace (idempotent upsert)
  • PATCH /Users/{id} — partial update (supports active, displayName; userName changes are rejected — the email is the identity anchor)
  • DELETE /Users/{id} — deprovisioning; deactivates the user (soft delete; preserved for audit)
  • GET /Groups, GET /Groups/all-users — read-only; returns a single synthetic all users group per tenant
  • GET /ServiceProviderConfig, GET /ResourceTypes, GET /Schemas — discovery metadata (per RFC 7644 §4)

Step-by-step: enable SCIM

  1. Go to Admin → Integrations → SCIM.
  2. Click Generate SCIM token. Copy the token immediately — it starts with scim_ and is shown only once.
  3. In your IdP's provisioning UI, paste:
    • Tenant URL: https://app.vornin.com/scim/v2
    • Secret token: the scim_... value
  4. Run the IdP's Test connection action. It should return Success.
  5. Map attributes (IdP-side): at minimum userName, emails[primary].value, displayName, active. Vornin tolerates missing givenName/familyName.
  6. Save the provisioning configuration in the IdP. New users assigned to the Vornin app will appear in the customer portal within the IdP's sync interval (typically 20–40 minutes for Azure AD).

Authentication

Authorization: Bearer scim_YOUR_TOKEN

SCIM tokens are separate from REST-API keys (cvk_). They are tenant-scoped, stored as SHA-256 hashes, and rotatable; generating a new token revokes the previous one immediately.

Behavior notes

  • New users are added with Member role. Promote to Admin / Owner manually in the app if required.
  • Deprovisioning via DELETE /Users/{id} deactivates the account (soft delete). Historical audit / vulnerability-assignment references are preserved.
  • Group management is read-only in this release. Vornin's role model is per-tenant (Member / Admin / Owner) and not currently synced from IdP groups — promote admins in-app.
  • SCIM endpoints do not count against the /api/v1 rate limit. They have their own higher ceiling intended for bulk sync.

Webhooks

Webhooks deliver real-time scan and vulnerability events to any HTTPS endpoint you control — use them to wire Vornin into Slack / Teams bridges, incident-management platforms, or internal notification services. Each delivery is a signed HTTP POST with a JSON payload.

Webhooks admin page
The Webhooks admin page — configured integrations, event filters, and recent delivery status.

Step-by-step: add a webhook

  1. Go to Admin → Integrations → Webhooks → Add Webhook.
  2. Enter a name and the endpoint URL (must be HTTPS).
  3. Set the minimum severity filter (e.g. High to only ship Critical and High findings).
  4. Pick the events you want to subscribe to (see the table below).
  5. Click Save. Vornin generates a signing secret — copy it and store it on your endpoint server; it is shown only once.
  6. Click Test to send a signed sample payload to your endpoint. The UI shows the response code and latency.
  7. On your endpoint, verify every incoming request by recomputing HMAC-SHA256 over the raw request body using your signing secret and comparing it to the X-Vornin-Signature header. Reject anything that doesn't match.

Events

EventFires whenPayload highlights
scan.completeA scan finishes (success or failure)Scan ID, status, start/end, target count, finding counts
vulnerability.foundA new vulnerability is first detectedVulnerability ID, title, severity, affected host, scanner
sla.breachAn open vulnerability crosses its SLA deadlineVulnerability ID, title, severity, affected host, SlaDueAt
posture.driftNew High/Critical findings appear versus the target's previous scan (Business and above)Scan ID, count of new High/Critical, count resolved, finding preview
asset.discoveredA scan discovers new hosts on your attack surfaceCount, discovered host list, discovery source
threat.emergingAn actively-exploited CVE (CISA KEV) matches an open finding of yours, or a newly-catalogued one matches your detected stackCVE ID, product, matched hosts, KEV date, match_source
compliance.control_changeDaily digest: controls that flipped passing/failingCounts and lists of newly-failing and newly-cleared controls
compliance.cadence_breachDaily digest: targets that went past their scan-cadence policyNewly-breached and total-breached counts, per-target detail

Each event above is an opt-in checkbox when you add or edit a webhook. In-app and email notifications are configured separately, per person, under Account → Notifications.

A webhook can also post straight into Jira for automatic ticket creation on vulnerability.found. That is fire-and-forget: it creates a ticket and forgets it. If you want tickets Vornin tracks — deduplicated per finding, so a re-scan never files the same ticket twice — use the issue-tracking integration below instead.

Emerging threats

Every day, Vornin reads the CISA Known Exploited Vulnerabilities catalog — the authoritative list of CVEs confirmed to be under active attack — and checks it against your estate two ways. When one matches, it appears under Emerging threats, subscribed team members get a notification (in-app, and email if they've enabled it), and an opt-in webhook fires. The intent is that you hear "this exploited CVE affects your stack" from Vornin before you read about it elsewhere.

Each row is labelled with which of the two checks found it, because they say different things:

  • Confirmed — you already have an open finding for that exact CVE. A scanner reported it on a specific host, the finding is still open, and CISA confirms the CVE is being exploited. Nothing is inferred, so treat these as jump-the-queue work. These are checked against the entire KEV catalog, not just recent additions: how long ago CISA listed a CVE says nothing about whether you still have it open, and an older entry is usually past its remediation deadline. If a batch of older matches surfaces at once — typically after your first few scans — the notifications are spread over several days so the backlog arrives at a readable pace. Every match appears on the page immediately regardless.
  • Detected tech — we fingerprinted a product the CVE affects. It says the product is present, not that your version is vulnerable, and the fingerprint may predate a decommission, so check the evidence date before acting. Because this one is an inference, it is limited to CVEs catalogued in the last 30 days — an old guess is not worth an alert.

An empty list is the expected state, not a broken feature. Technology matching is deliberately conservative: Vornin alerts only when the affected product is one it recognises AND it has actually detected that product on one of your hosts. It will miss things — the recognised-product list grows over time — and that is the trade we chose, because an alert wrongly telling you a live exploit affects you is worse than no alert at all. Emerging threats supplement your scans; they do not replace them.

Technology matches are driven by the inventory under Attack surface → Technologies, so the more of your estate is scanned, the more of it this covers; confirmed matches come from your open findings and are bounded by neither how recently a host was fingerprinted nor how recently the CVE was catalogued. Each row links the CVE to its NVD entry and shows the hosts we matched, what matched, how old that evidence was when we raised the alert, and CISA's remediation due date. To receive these over a webhook, enable Emerging threats on the webhook under Admin → Integrations; it is off by default, so existing webhooks are unaffected. The payload carries a match_source field so an automation can tell the two apart.

A match says we detected the product, not that it is definitely still installed. Vornin only alerts on technology confirmed within the last 90 days, so a host you decommissioned last year cannot keep generating alerts — and it shows you when that technology was last confirmed, in the alert as well as on the page, so you can judge for yourself rather than take our word for it. That date is a snapshot of what we knew when the alert was raised; the current state of your stack lives under Attack surface → Technologies. If a technology has drifted out of the 90-day window, re-scan the target to bring it back into scope — and if your whole inventory has, the page says so rather than telling you an empty list is normal.

Issue tracking

Connect an issue tracker and Vornin files a ticket for each new finding at or above a severity you choose, so remediation lands in the queue your team already works from. Four providers are supported: GitHub, GitLab, Azure DevOps and Jira Cloud.

Each filed ticket is recorded against the finding's fingerprint, which is what makes it safe to leave running: the same finding re-detected by the next scan does not open a second ticket, and neither does a re-scan of the same target. Tickets carry the severity, scanner, affected host and URL, any CVE, and the remediation guidance from the finding itself.

Step-by-step: Jira Cloud

  1. Create the credential. Best practice is a service account: in Atlassian Admin go to Directory → Service accounts (five are free), create one for Vornin, give it access to the target project, and generate its API token. The integration then keeps working when a person leaves. A personal token from id.atlassian.com → Security → API tokens works too. Either way the account needs permission to create issues in the project. The organization API keys under Atlassian Admin → Settings are a different credential and will not work here — those authenticate the admin APIs (org settings, user management), not Jira itself.
  2. In Vornin, go to Admin → API & Integrations → Issue tracking → Add integration and choose Jira Cloud.
  3. Enter your site URL (e.g. https://acme.atlassian.net), the account email the token belongs to (the service account's address, if you made one), and the token itself. Jira authenticates on the email and token together, which is why both are needed.
  4. Set Default issue repo to the Jira project key — the prefix on issue ids, e.g. SEC in SEC-123. Not the project name. Optionally set an issue type; it defaults to Bug.
  5. Click Test connection. A working setup reports the account it authenticated as.
  6. Turn on File issues for new vulnerabilities and pick a minimum severity. High or above is the default and is usually the right starting point.

GitHub, GitLab and Azure DevOps are configured the same way, with a personal access token instead of an API token, and the repo identifier in that provider's format (owner/repo, group/repo, and the project name respectively). Tokens are encrypted at rest and never shown again after saving — leave the field blank when editing to keep the existing one.

CSV Export

Export your vulnerability data as CSV from the Vulnerabilities page using the "Export CSV" button. The export carries every field we hold on a finding, so it can be triaged in a spreadsheet without opening the app:

  • Identity — id, title, severity, status, scanner, fingerprint
  • Where — target, host, port, service, URL; and for code findings the file path, start/end line and column, rule id, engines, and the (secret-masked) code snippet
  • Dependencies — package, installed version, fixed version, manifest file, reachability
  • Risk — CVE, CVSS score and vector, CWE, EPSS score and percentile, CISA KEV listing with due date and ransomware use, known-exploit flag
  • Lifecycle — first/last detected, last scan, SLA due date and breach flag, escalation, assignee, acknowledgement, resolution method, dismissal with risk-acceptance type, approver and review date, suppression rule, event count
  • Assessment — false-positive verdict, confidence, reason and source, plus the full description and remediation text

The same column set is returned by GET /api/reports/{scanId}/csv. An "Export event ledger" option in the same menu emits one row per lifecycle event, including the hash chain, for offline audit verification.

Import Scan Results

Import vulnerability findings from third-party tools under Import Results. Supported formats:

  • CSV — columns: Title, Severity, Host, Port, Description, Solution, CVE
  • Nessus — .nessus XML export files
  • OpenVAS — XML report export files
  • JSON — array of finding objects

Preview findings before importing. Imported results are created as a completed scan and run through the standard vulnerability tracking pipeline (dedup, compliance mapping, SLA assignment).

Target Health Scores

Each target on the Targets page displays a health score (A–F) calculated from its open vulnerabilities:

  • Base score: 100
  • Critical vulnerability: -20 points
  • High vulnerability: -10 points
  • Medium vulnerability: -3 points
  • Low vulnerability: -1 point

Scores update automatically as vulnerabilities are found and resolved. Use health scores to prioritize which targets need attention first.

Cloud Security Scanning

Vornin scans your AWS, Azure, and GCP environments read-only — nothing is installed in your account, and no write permission is ever requested. The engine is Prowler, which runs 600+ checks per provider covering the CIS Benchmarks for that cloud plus the provider's own security baselines. Every finding is mapped to your active compliance frameworks (NIS2, DORA, PCI DSS, ISO 27001, HIPAA, and more).

Coverage spans the areas an auditor asks about:

  • Identity and access — root / privileged accounts without MFA, stale or unused access keys, overly permissive role bindings, service accounts with primitive roles.
  • Storage — publicly readable buckets and blobs, missing encryption at rest, absent versioning or backup, weak bucket-level access controls.
  • Network exposure — security groups, NSGs, and VPC firewall rules open to the internet on administrative ports, public database endpoints, internet-facing functions and workloads.
  • Logging and monitoring — missing CloudTrail / Activity Log / audit-log configuration, no flow logs, no long-term log retention, threat-detection tiers left off.
  • Key management and workload configuration — key rotation lapses, weak vault policies, insecure instance metadata settings, legacy cluster authentication.

Only failed checks at Medium severity and above become findings, so a passing control never turns into queue noise. Cloud checks are configuration findings rather than CVEs, so they carry no CVSS score — Vornin does not invent one. A full-account scan takes minutes rather than seconds, and runs under a 15-minute budget; if a provider exceeds it, the scan reports partial coverage instead of a falsely clean result.

Step-by-step: AWS

  1. In the AWS console, create an IAM user (or role) and attach the managed policy SecurityAudit. This grants read-only visibility into security-relevant services without any write capability.
  2. Generate an access key ID and secret access key for that IAM user. See Managing access keys for IAM users.
  3. In Vornin, go to Cloud Connections → Add Connection, choose AWS, paste the access key ID + secret, and optionally restrict to specific regions.
  4. Click Test Connection to confirm the credentials resolve.

Step-by-step: Azure

  1. In Microsoft Entra ID, register an application and create a service principal.
  2. On the subscriptions you want to scan, assign the Reader built-in role to the new service principal.
  3. Note the Tenant ID, Client ID, and create a Client Secret for the app.
  4. In Vornin, go to Cloud Connections → Add Connection, choose Azure, and paste the three values plus the subscription ID.

Step-by-step: Google Cloud

  1. In GCP IAM, create a service account and grant it the Security Reviewer role on the projects or the organisation you want to scan.
  2. Create a JSON key for the service account and download it.
  3. In Vornin, go to Cloud Connections → Add Connection, choose GCP, and upload (or paste) the JSON key.

Cloud findings surface in the Vulnerabilities list with Scanner = Cloud Security Posture, identified by account and resource, and are mapped to the same compliance frameworks as your other scanner results.

Code Repositories

Connecting a code repository unlocks the SAST, Secret, and Dependency scanners. Vornin clones the selected branch into a temporary directory for each scan, analyses it, and deletes the clone when done — no code is stored after the scan completes.

Step-by-step: GitHub

  1. In GitHub, create a Personal Access Token. The classic PAT only needs the repo scope (read-only is sufficient for public repos). See Managing personal access tokens, or for a fine-grained token, Creating a fine-grained PAT (grant Contents: read + Metadata: read on the repos you want to scan).
  2. Copy the token. It starts with ghp_ (classic) or github_pat_ (fine-grained).
  3. In Vornin, go to Repositories → Add Connection, choose GitHub, paste the token, and click Connect.
  4. Select the repositories you want to import and choose the default branch to scan (usually main or master). You can change the branch later.
  5. Trigger a scan from Scan now and include the imported repos in the targets list.

Step-by-step: Azure DevOps

  1. In Azure DevOps, create a Personal Access Token scoped to Code (Read). See Use PATs to authenticate to Azure DevOps.
  2. Copy the token.
  3. In Vornin, go to Repositories → Add Connection, choose Azure DevOps, and paste the token plus your organization URL (e.g. https://dev.azure.com/contoso).
  4. Pick the project and repositories you want to import, and set the branch to scan.
  5. Trigger a scan from Scan now and include the imported repos in the targets list.

The branch can be changed at any time from the Repositories page without reconnecting.

Repositories can be organised into the same groups as your scan targets. Tick repository checkboxes on the Repositories page to bulk-assign the selected repos to a group, so a group like "Production" covers both your domains and the code behind them.

REST API

The Vornin API allows programmatic access to scans, vulnerabilities, and targets. Full interactive documentation is available at:

https://app.vornin.com/api-docs

Authenticate with an API key using the Authorization: Bearer cvk_YOUR_KEY header. The API supports creating scans, listing vulnerabilities, and downloading reports.

Generate an API key from Settings → API Keys in the app. Keys are tenant-scoped; choose Admin or Owner role for write endpoints (scan create, target create).

CI/CD build gate (Scale)

After a scan completes, call GET /api/v1/scans/{id}/gate?failOn=high to get a pass/fail verdict for your pipeline. It returns HTTP 200 when no unresolved finding at or above the threshold exists, and HTTP 422 when blocking findings are present — so a curl --fail step fails the build directly, no JSON parsing required. failOn accepts critical, high (default), medium, or low.

Open and Acknowledged findings both block. Acknowledging a finding records that someone looked at it, not that the risk was accepted — use Dismissed for that, which the gate ignores.

409 means “no verdict yet, ask again”: the scan is still queued or running, or its results are still being ingested. Poll GET /api/v1/scans/{id} and retry. 422 is a failing verdict, and it also covers a scan that cannot be gated — one that Failed or was Cancelled produced no coverage, so it is never reported as a pass. A 422 of that kind carries an error and scanStatus instead of the usual blocking/counts keys. The verdict body for a real gate result includes passed, blocking, and per-severity counts.

Python recipes

Copy-pasteable snippets using only the requests library. No SDK install required.

1. Auth + session setup

Reuse a requests.Session so TCP connections keep alive across calls — cuts wall-clock time roughly in half when you chain requests.

import os
import requests

BASE = "https://app.vornin.com/api/v1"
API_KEY = os.environ["VORNIN_API_KEY"]  # cvk_...

session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {API_KEY}",
    "Accept": "application/json",
})

2. Create a target

Adds a host/domain to the tenant's target list. Requires Admin or Owner API key.

r = session.post(f"{BASE}/targets", json={
    "name": "Acme Production",
    "host": "acme.example.com",
    "environment": "Production",
})
r.raise_for_status()
target = r.json()
print("created target", target["id"])

3. Trigger a scan

Kicks off a scan and returns the scan ID. scanTypes can be any subset of the engines (PortScan, SslTls, WebVulnerability, ApiSecurity, DnsSecurity, SubdomainEnum, SubdomainTakeover, Sast, SecretScan, DependencyScan, NucleiTemplate, Wordpress, ContainerImage, Kubernetes). Omit it and each scanner appropriate to what you supplied is selected for you.

r = session.post(f"{BASE}/scans", json={
    "targetIds": [target["id"]],
    "scanTypes": ["SslTls", "WebVulnerability", "DnsSecurity"],
})
r.raise_for_status()
scan = r.json()
scan_id = scan["id"]
print("scan queued", scan_id)

Supply codeRepositoryIds to scan source instead of (or as well as) hosts. A repo-only scan needs no targetIds:

r = session.post(f"{BASE}/scans", json={
    "codeRepositoryIds": [repo_id],
    "scanTypes": ["Sast", "SecretScan", "DependencyScan"],
})

Each scanner needs a subject it can run against: code scanners need codeRepositoryIds, host scanners need targetIds, ContainerImage needs a target whose host starts with docker://, and Kubernetes one starting with k8s://. Request a scanner with nothing for it to run against and the scan says so — either a 400 at creation, or a coverage notice on the finished scan that the build gate treats as a failure rather than a pass.

4. Poll until the scan completes

The status field progresses Pending → Running → Completed (or Failed). A typical mixed scan takes 2–10 minutes; poll every 15 seconds to stay within rate limits on the Team tier.

import time

while True:
    r = session.get(f"{BASE}/scans/{scan_id}")
    r.raise_for_status()
    status = r.json()["status"]
    print("status:", status)
    if status in ("Completed", "Failed"):
        break
    time.sleep(15)

5. List vulnerabilities for a scan

Returns a paginated list. severity accepts Critical, High, Medium, Low, Info. status accepts Open, Acknowledged, Resolved, Dismissed.

vulns = []
page = 1
while True:
    r = session.get(f"{BASE}/vulnerabilities", params={
        "scanId": scan_id,
        "severity": "Critical",
        "status": "Open",
        "page": page,
        "pageSize": 100,
    })
    r.raise_for_status()
    batch = r.json()
    vulns.extend(batch["items"])
    if page * batch["pageSize"] >= batch["total"]:
        break
    page += 1

print(f"found {len(vulns)} critical open vulnerabilities")
for v in vulns[:5]:
    print(f"  - {v['title']} @ {v['affectedHost']}")

6. Mark a vulnerability resolved

Use status: "Resolved" when you've fixed it, or status: "Dismissed" with a reason if it's a false positive.

r = session.put(f"{BASE}/vulnerabilities/{vulns[0]['id']}", json={
    "status": "Resolved",
    "note": "Upgraded to nginx 1.27.1",
})
r.raise_for_status()

7. Download a PDF report

Executive, technical, or compliance reports. Severity filter optional — defaults to all.

r = session.get(
    f"https://app.vornin.com/api/reports/{scan_id}/pdf",
    params={"minSeverity": 3},  # Medium and above
)
r.raise_for_status()
with open(f"report-{scan_id}.pdf", "wb") as f:
    f.write(r.content)
print("saved report")

8. CI/CD: fail a PR on new Critical findings

This is the headline recipe. Drop the script into your repo and point a GitHub Action at it — the build fails if the latest scan has any new Critical vulnerabilities.

GitHub Actions workflow (.github/workflows/vornin.yml):

name: Vornin security scan

on:
  pull_request:
  schedule:
    - cron: "0 6 * * *"   # daily at 06:00 UTC

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install requests
      - run: python .github/scripts/vornin_gate.py
        env:
          VORNIN_API_KEY: ${{ secrets.VORNIN_API_KEY }}
          VORNIN_TARGET_ID: ${{ vars.VORNIN_TARGET_ID }}

Script (.github/scripts/vornin_gate.py):

"""Fail the build if the latest Vornin scan has new Critical findings."""
import os
import sys
import time
import requests

BASE = "https://app.vornin.com/api/v1"
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {os.environ['VORNIN_API_KEY']}"})

target_id = int(os.environ["VORNIN_TARGET_ID"])

# trigger a scan. Every scanner named here needs something to run against: the code scanners
# need codeRepositoryIds, so attach them or leave those types out.
scan_id = session.post(f"{BASE}/scans", json={
    "targetIds": [target_id],
    "codeRepositoryIds": [int(os.environ["VORNIN_REPO_ID"])],
    "scanTypes": ["SslTls", "WebVulnerability", "DnsSecurity", "Sast", "SecretScan", "DependencyScan"],
}).json()["id"]

# wait for completion (max 20 min)
deadline = time.time() + 20 * 60
while time.time() < deadline:
    status = session.get(f"{BASE}/scans/{scan_id}").json()["status"]
    if status in ("Completed", "Failed"):
        break
    time.sleep(20)
else:
    sys.exit("Scan timed out after 20 minutes")

# gate: fail if there are any Critical findings
r = session.get(f"{BASE}/vulnerabilities", params={
    "scanId": scan_id,
    "severity": "Critical",
    "status": "Open",
    "pageSize": 1,
})
critical_count = r.json()["total"]

if critical_count > 0:
    print(f"::error::Vornin found {critical_count} critical vulnerabilities in this PR")
    sys.exit(1)
print(f"PASS: no new critical findings (scan {scan_id})")

Store VORNIN_API_KEY as a repository secret and VORNIN_TARGET_ID as a repository variable. The script exits 0 on pass, 1 on any Critical finding — so GitHub will red-cross the PR automatically.

Need a language other than Python, or a full-service SDK? Talk to us — we'll prioritise it if there's demand.