User documentation
Guides to connecting assets, running scans, resolving findings and preparing evidence. Start with the basics, or use the contents to find setup instructions, integrations and troubleshooting for your next task.
Get started#
Connect an asset, run a scan, then review what needs attention. Your findings, remediation work and supporting evidence stay together in your workspace.
- Sign in to your workspace. Follow your invitation or sign in with your work email. If your organisation uses single sign-on, use its configured provider.
- Add something to scan. Open Assets in the sidebar and choose Hosts, Repositories, Cloud accounts, Containers or Clusters. Start with an asset you own or have permission to test.
- Start a scan. Select the + next to Scans in the sidebar to open New scan. Pick the asset, choose scanners, review the settings and select Launch Scan.
- Check the scan, then its findings. Confirm the scan finished and review any coverage notices before opening Vulnerabilities.
An Owner or Admin can finish workspace setup at any time. Select Finish workspace setup on the dashboard, or open Settings → General settings and select Open setup wizard. The wizard covers the workspace name, timezone, remediation deadlines (days per severity) and compliance frameworks. Branding and notification preferences have their own tabs under Settings.
Find your way around#
- Dashboard, Vulnerabilities and Emerging threats
- See your risk at a glance, then review findings, assign work, record decisions and verify fixes.
- Assets
- Manage what you scan: Hosts, Repositories, Cloud accounts, Containers and Clusters. Groups are shared across these categories.
- Attack surface
- Review discovered hosts and the IPs, services, certificates, DNS records and technologies observed by your scans.
- Scans
- Inspect earlier runs on All scans, set up recurring checks on Schedules and compare runs on Compare. The + next to Scans opens New scan.
- Compliance, Reports and Executive brief
- Review control evidence and export findings for colleagues or auditors.
- Settings and Administration
- Visible to Owners and Admins only. Settings holds General settings and Billing. Administration holds Users & roles, API & integrations, Suppression rules, Audit log and Scan agents.
- Help & updates
- Wish box, What's new, System status and the API docs.
Use the table of contents to jump to a chapter. Each heading has a permanent link you can bookmark or share. On a phone, open Contents to move between chapters.
Hosts and asset groups#
- Open Assets → Hosts and select Add target.
- Enter a Name, then the domain, IP address, IP range or URL in Host. Check the Detected kind shown under the field, and change Kind if it is wrong.
- Choose a Group if you want to organise related assets together.
- For an internal host, choose a scan agent that can reach it in Scan agent. Leave it empty for internet-facing hosts.
- Tick the authorisation confirmation and select Save.
Choose a target kind
| Kind | Use it for | Example |
|---|---|---|
| Hostname | A named website or service, including checks that need its hostname and subdomain discovery. | app.example.com |
| IP address | A specific host's ports and services. Web checks reach the default website on that address. | 192.0.2.10 |
| IP range (CIDR) | Port scans across an authorised address range. A port scan covers at most 256 addresses (IPv4 /24, IPv6 /120), so add a wider range as several targets. The form accepts IPv4 ranges up to /16 and IPv6 ranges up to /112, but scanning one wider than 256 addresses fails with Network targets are limited to 256 addresses per task. | 192.0.2.0/24 |
| URL | A web application at a specific path or port. | https://app.example.com:8443/login |
Review the detected kind before saving. A bare website address such as https://example.com is detected as a hostname; a path, non-default port or query selects URL. Once a target has scan history its kind is locked (Locked: scans already exist for this target.). To scan the address as a different kind, delete the target and add it again.
To add many hosts at once, select Import CSV on the Hosts page and paste the list into Paste CSV content. Put one host per line. The Domain header is optional, duplicates are skipped and lines that are not a valid host are ignored. Lines can be domains, IP addresses, ranges or URLs, and each one gets its detected kind:
Domain
app.example.com
api.example.com
If you reach your plan's target limit, the import stops there and reports how many rows were skipped.
To group several assets, tick them in the list, choose a group in Assign to group and select Apply. Create a group with New group in the groups rail. Groups are shared across hosts, containers, clusters, repositories and cloud accounts. Deleting a group leaves its assets in place, ungrouped.
Code repositories#
You need the Team plan or higher to run code scans, and the Owner or Admin role to add, edit or delete a connection. Create the provider token first (see Repository token permissions).
- Open Assets → Repositories and select Add connection.
- Choose the Connection type and fill in the fields in the table below. Use a dedicated token limited to the repositories you need.
- Select Test Connection and wait for the green result, then select Save.
- Select Import repositories and pick the Connection. For Azure DevOps, also pick the Project. Tick the repositories, choose a Branch for each, then select Import selected. The branch you pick is the one Vornin scans by default, and it is the only branch the pull request gate accepts pull requests into. Each imported repository shows its ID next to its organisation and project.
- In New scan, open the Repositories tab, select those repositories and the code checks you need.
| Field | What to enter |
|---|---|
| Display name | Any label, for example Acme GitHub. |
| Organization URL (Azure DevOps) | Your organisation's URL, for example https://dev.azure.com/acme. The older https://acme.visualstudio.com form also works. |
| GitHub Owner / Organization | The organisation or user name as it appears in github.com/<owner>, for example acme. A pasted URL is reduced to the owner. For a personal account, the token must belong to that user or be a collaborator on the repositories. |
| GitLab Group Path (optional) | The group's path, for example acme or acme/platform, never its display name. Subgroups are included and archived projects are skipped. Leave it blank to cover every project the token can see. |
| Self-hosted Base URL (GitLab, optional) | Your instance root, for example https://gitlab.example.com. Leave blank for gitlab.com. |
| Personal access token (PAT) | The token you created. It is stored encrypted. When editing, leave it blank to keep the current token, or enter a replacement to rotate it. |
| Scan only through our own ScanAgent | Optional. See below. |
Vornin lists repositories and branches from its own servers, so the Git host must be reachable from the internet. A self-hosted GitLab or Azure DevOps Server on a private address cannot be connected.
To keep a connection's repositories off Vornin's hosted scanners, turn on Scan only through our own ScanAgent when you add or edit the connection. Its repositories are then scanned only by one of your agents. If your agent is busy or offline the scan waits for it; if none of your agents can run code scans, those repositories are skipped with a warning.
Repository checks cover source code (SAST), exposed secrets, vulnerable dependencies and infrastructure-as-code configuration. Vornin clones only the latest commit of the selected branch, so secret scanning examines that working tree, not the Git history.
Repository token permissions
Vornin uses the token to list repositories and branches through the provider's API and to clone over HTTPS. It never writes to the repository. Pull request comments and issue filing use the separate token in your issue tracking integration. Set an expiry and rotate the token from Edit connection before it runs out.
| Provider | Where to create it | Permissions to grant |
|---|---|---|
| GitHub (fine-grained token) | Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token. Set Resource owner to the owner you enter in Vornin, and Repository access to Only select repositories. | Repository permissions: Contents: Read-only. GitHub adds Metadata: Read-only automatically. If the organisation requires approval, the token stays pending until an organisation owner approves it. |
| GitHub (classic token) | Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token (classic). | repo scope, or public_repo for public repositories only. If the organisation uses SAML single sign-on, authorise the token for that organisation after creating it. |
| GitLab | Your avatar → Edit profile → Access → Personal access tokens. On current versions choose Legacy token from Generate token. | read_api and read_repository. |
| Azure DevOps | User settings → Personal access tokens → New Token. Choose the organisation in Organization URL, not all accessible organisations. Under Scopes, choose Custom defined. | Code: Read. Add Packaging: Read if your repositories restore packages from Azure Artifacts feeds in the same organisation. If Test Connection reports that no projects are visible, also add Project and Team: Read. |
Test Connection signs in with the token and lists what it can see. Success starts Connection successful and gives the number of repositories found for GitHub and GitLab, or reads Connected. N projects found. for Azure DevOps. Fix these results before saving:
- Token works, but no repositories are visible for '…': the owner or group path is wrong, or the token cannot see that owner's repositories. Check Resource owner and repository access on a fine-grained token.
- Token works, but '…' is not a GitHub owner name, or That is not a GitLab group path: enter the bare owner name or group path, not a URL or display name.
- Token works, but no projects are visible for this organization: check the Organization URL and the token's organisation and scopes.
- Connection failed: followed by
401or403: the token is wrong, expired, not yet approved or lacks a scope.
Testing an edited connection needs the token typed in again; the stored token cannot be re-tested from the dialog. Changing the organisation, owner, group or base URL of a saved connection requires a replacement token.
Private dependencies
Vornin reads committed lockfiles directly, with no feed access. It restores packages itself for .NET projects and for JavaScript projects without a committed lockfile, and private packages then need a feed it can authenticate. Only Azure Artifacts feeds are supported: NuGet feeds listed in nuget.config and npm registries listed in .npmrc or bunfig.toml, hosted at pkgs.dev.azure.com/<organisation> in the same organisation as an Azure DevOps connection, using the connection's token with Packaging: Read. GitHub and GitLab tokens are never sent to any package registry, so GitHub Packages, GitLab registries and other private registries are not authenticated. When a private package cannot be restored, the scan shows a coverage notice saying some dependencies were not analysed. On your own agent, private JavaScript feeds also need Docker.
Containers and Kubernetes#
Use Assets → Containers → Add container image for an image, or Assets → Clusters → Add Kubernetes cluster for a cluster. These forms pre-fill the Host field with the right prefix: docker:// for images and k8s:// for clusters. Both scan types run only on one of your own scan agents, never on Vornin's hosted scanners, so set Scan agent on the form to an agent that can reach the registry or cluster.
Container images
Enter the image reference after the prefix, for example docker://ghcr.io/acme/api:1.4. Repository names must be lowercase; tags may be mixed case. Registry ports and @sha256: digests are accepted. The form has no registry credential fields, so plan on images the agent can pull without them.
Kubernetes prerequisites
A scan that cannot inspect any Kubernetes resources reports incomplete coverage. This includes an empty namespace and credentials that cannot list workloads. It does not mark previous findings as fixed.
Use agent 1.2.70 or later. It includes the native Trivy that Kubernetes scans need, so you do not install Trivy yourself. On Windows, 1.2.54 is the oldest release whose workload misconfiguration checks run at all.
- Prepare a dedicated scan agent that can reach the cluster API.
- Create a read-only identity for the cluster (see below) and save a kubeconfig file for it on the agent machine.
- Set
KUBECONFIGin the agent's environment to the absolute path of that file. The account running the agent must be able to read it, and it must not be empty. If you supply multiple paths, every file must meet both rules. - Set
VORNIN_KUBERNETES_CONTEXTto the context name in that kubeconfig. - Restart the agent so it reads the new environment. Its log shows
Agent scan types:at startup, and the list includesKubernetesonly when Trivy,KUBECONFIGandVORNIN_KUBERNETES_CONTEXTare all usable. - Open the cluster target, set Scan agent to that agent and save.
Where to set the variables depends on how you installed the agent. For a Linux service, add the lines KUBECONFIG=/etc/vornin/vornin.kubeconfig and VORNIN_KUBERNETES_CONTEXT=vornin-prod to /etc/vornin-agent.env (with your own path and context) and run sudo systemctl restart vornin-agent. Re-running the installer keeps them. The service runs as root, so root must be able to read the kubeconfig. See Run the agent as a Linux service. On Windows the agent runs from the Vornin Agent scheduled task. Set them as system environment variables, then end and run the task again in Task Scheduler, or restart the machine if the log still does not list Kubernetes.
The agent's configured context selects the cluster. Use k8s://cluster for all namespaces or k8s://<namespace> for one namespace. Any value other than cluster is treated as a namespace name. Kubeconfigs cannot be stored under Cloud accounts; Kubernetes is configured only here.
Read-only access for the cluster
The agent runs Trivy, which lists workloads (pods, deployments, replica sets, replication controllers, stateful sets, daemon sets, jobs, cron jobs), services, service accounts, config maps, resource quotas, limit ranges, network policies, ingresses, and roles and bindings at both namespace and cluster level. This ClusterRole grants exactly that, read-only. It leaves out Secrets, which Vornin does not read, and nodes: with node access, Trivy tries to start a node-collector job, which a read-only identity cannot create.
apiVersion: v1
kind: Namespace
metadata:
name: vornin
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vornin-reader
namespace: vornin
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: vornin-reader
rules:
- apiGroups: [""]
resources: ["pods", "replicationcontrollers", "services", "serviceaccounts", "configmaps", "resourcequotas", "limitranges"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
verbs: ["get", "list"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list"]
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies", "ingresses"]
verbs: ["get", "list"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vornin-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: vornin-reader
subjects:
- kind: ServiceAccount
name: vornin-reader
namespace: vornin
Apply it, request a token for the service account, and write a kubeconfig that uses it. Replace the server address and CA file with your cluster's. The API server may issue a shorter token than you ask for; when it expires, scans report incomplete coverage until you replace it.
kubectl apply -f vornin-reader.yaml
TOKEN=$(kubectl -n vornin create token vornin-reader --duration=8760h)
kubectl config --kubeconfig=vornin.kubeconfig set-cluster prod --server=https://YOUR-API-SERVER --certificate-authority=ca.crt --embed-certs=true
kubectl config --kubeconfig=vornin.kubeconfig set-credentials vornin-reader --token="$TOKEN"
kubectl config --kubeconfig=vornin.kubeconfig set-context vornin-prod --cluster=prod --user=vornin-reader
With this file, set VORNIN_KUBERNETES_CONTEXT=vornin-prod. Check the identity with kubectl --kubeconfig=vornin.kubeconfig --context=vornin-prod get pods -A before you scan.
Select the asset and its matching scanner in New scan. Review the completed scan for coverage notices before treating an empty findings list as a clean result. Kubernetes scans cannot be added to a schedule; retest container and cluster assets by running another scan.
If a cluster is skipped, the scan names the reason: assign a customer agent with an explicit Kubernetes context means the target has no scan agent, and assigned agent unavailable or missing native Trivy and explicit Kubernetes context means the agent did not report a usable Trivy, KUBECONFIG and context. If the scan ran but shows Trivy reported no Kubernetes resources, so coverage could not be verified, check the namespace name and the identity's permissions.
Cloud accounts#
Cloud posture scans run Prowler against one AWS account, Azure subscription or Google Cloud project per connection. Only failed checks of Medium severity and above become findings. You need the Team plan or higher, and the Owner or Admin role to add, edit, test or delete a connection. Every member can view the list.
- Create a read-only identity in the cloud provider, using the steps for AWS, Azure or Google Cloud below.
- Open Assets → Cloud accounts and select Add connection.
- Choose the Provider, enter a Display name and the account identifier (the field is labelled AWS Account ID, Azure Subscription ID or GCP Project ID). Optionally pick a Group.
- Select Fill in a JSON template and complete it in Credentials (raw JSON), or paste the Google Cloud key file.
- Select Test connection, then Save.
- In New scan, open the Cloud accounts tab, select the account and the Cloud Security Posture scanner, and launch. Review any permission or coverage notices in the result.
| Provider | Account field | Credentials (raw JSON) |
|---|---|---|
| AWS | 12-digit account ID | accessKeyId and secretAccessKey. Optional region (default us-east-1) sets the region for the first API calls; Prowler still checks every enabled region. Temporary keys with sessionToken are accepted, but scans fail once they expire. |
| Azure | Subscription ID (GUID) | tenantId, clientId, clientSecret and subscriptionId. subscriptionId must equal the Azure Subscription ID field. |
| Google Cloud | Project ID, not the project name or number | The service-account key file exactly as downloaded. Its project_id must equal the GCP Project ID field. |
To scan another account, subscription or project, add another connection. When editing, leave Credentials (raw JSON) blank to keep the stored credentials. The provider and account identifier cannot be changed after saving.
Create a read-only AWS identity
- In the IAM console, choose Users → Create user. Name it, for example
vornin-audit, and do not give it console access. - On Set permissions, choose Attach policies directly and attach the AWS managed policies SecurityAudit and ViewOnlyAccess. Finish creating the user.
- Recommended: choose Policies → Create policy, open the JSON editor, paste the policy below, save it as
vornin-prowler-additionsand attach it to the user. Without it, some Prowler checks cannot collect data and the scan shows a coverage notice. - Open the user, go to Security credentials → Access keys and choose Create access key. Choose Other, then Next and Create access key. Copy the Access key and Secret access key into
accessKeyIdandsecretAccessKey; the secret is shown only once.
Additional read-only permissions policy
This is Prowler's published additions policy for the version Vornin runs, without the one Security Hub write permission Vornin does not use.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowMoreReadOnly",
"Effect": "Allow",
"Action": [
"account:Get*",
"amplify:ListApps",
"amplify:ListBranches",
"appstream:Describe*",
"appstream:List*",
"backup:List*",
"backup:Get*",
"bedrock:List*",
"bedrock:Get*",
"cloudtrail:GetInsightSelectors",
"codeartifact:List*",
"codebuild:BatchGet*",
"codebuild:ListReportGroups",
"cognito-idp:GetUserPoolMfaConfig",
"datapipeline:DescribePipelines",
"datapipeline:GetPipelineDefinition",
"datapipeline:ListPipelines",
"dlm:Get*",
"drs:Describe*",
"ds:Get*",
"ds:Describe*",
"ds:List*",
"dynamodb:GetResourcePolicy",
"ec2:GetEbsEncryptionByDefault",
"ec2:GetSnapshotBlockPublicAccessState",
"ec2:GetInstanceMetadataDefaults",
"ecr:Describe*",
"ecr:GetRegistryScanningConfiguration",
"elasticfilesystem:DescribeBackupPolicy",
"glue:GetConnections",
"glue:GetSecurityConfiguration*",
"glue:SearchTables",
"glue:GetMLTransforms",
"lambda:GetFunction*",
"logs:FilterLogEvents",
"lightsail:GetRelationalDatabases",
"macie2:GetMacieSession",
"macie2:GetAutomatedDiscoveryConfiguration",
"rolesanywhere:ListTagsForResource",
"rolesanywhere:ListTrustAnchors",
"s3:GetAccountPublicAccessBlock",
"s3:GetObjectAcl",
"s3:ListBucket",
"shield:DescribeProtection",
"shield:GetSubscriptionState",
"securityhub:GetFindings",
"servicecatalog:Describe*",
"servicecatalog:List*",
"ssm:GetDocument",
"ssm-incidents:List*",
"states:ListTagsForResource",
"support:Describe*",
"tag:GetTagKeys",
"wellarchitected:List*"
],
"Resource": "*"
},
{
"Sid": "AllowAPIGatewayReadOnly",
"Effect": "Allow",
"Action": ["apigateway:GET"],
"Resource": [
"arn:*:apigateway:*::/restapis/*",
"arn:*:apigateway:*::/apis/*",
"arn:*:apigateway:*::/domainnames",
"arn:*:apigateway:*::/domainnames/*"
]
}
]
}Some of these actions read more than configuration: lambda:GetFunction* can download function code and logs:FilterLogEvents reads log lines. Remove them if that is not acceptable; the checks that need them then report a coverage notice instead.
Test connection calls IAM's account summary. Success reads Connected to AWS. Credentials authenticated, followed by the root MFA state.
Create a read-only Azure identity
- In the Microsoft Entra admin center, go to Entra ID → App registrations → New registration. Name it, for example
vornin-audit, keep Accounts in this organizational directory only and select Register. - On the app's Overview, copy Application (client) ID into
clientIdand Directory (tenant) ID intotenantId. - Open Certificates & secrets → Client secrets → New client secret, choose an expiry and select Add. Copy the secret's Value (not the Secret ID) into
clientSecret. It is shown only once, and scans fail after it expires. - In the Azure portal, open Subscriptions, pick the subscription and copy its Subscription ID into both
subscriptionIdand the Azure Subscription ID field. - In that subscription, open Access control (IAM) → Add → Add role assignment. Choose Reader, select Next, then Select members, search for the app by name, select it and choose Review + assign.
- Recommended, for the Entra ID checks: in the app registration open API permissions → Add a permission → Microsoft Graph → Application permissions, add
AuditLog.Read.All,Directory.Read.AllandPolicy.Read.All, then select Grant admin consent. Granting consent needs an Entra administrator role such as Privileged Role Administrator. Without these permissions the Entra ID checks cannot run and the scan shows a coverage notice.
Prowler also documents an optional custom role with Microsoft.Web/sites/host/listkeys/action and Microsoft.Web/sites/config/list/Action for four Azure Functions checks. Those actions return function keys and app settings, which can include secrets, so leave the role out unless you want those checks.
Test connection reads the subscription's network security groups with the Reader role. Success reads Connected to Azure subscription … Credentials authenticated. Azure scans skip Prowler's Shodan public-IP check (network_public_ip_shodan) and say so in an informational notice. Key Vault checks that cannot collect key data produce an informational notice per vault; the vault's access policy, data-plane role or network rules may block access. Successful findings remain available, but unassessed resources are not treated as fixed.
Create a read-only Google Cloud identity
- In the Google Cloud console, select the project and open APIs & Services. Make sure the Identity and Access Management (IAM) API and the Compute Engine API are enabled. Test connection reads the project's default network through the Compute Engine API, so the test fails while it is disabled.
- Open IAM & Admin → Service Accounts → Create service account, name it, for example
vornin-audit, and select Create and continue. - Grant the roles Viewer (
roles/viewer), Service Usage Consumer (roles/serviceusage.serviceUsageConsumer) and Security Reviewer (roles/iam.securityReviewer), then select Done. Security Reviewer lets Prowler read bucket IAM policies; a custom role with onlystorage.buckets.getIamPolicyis the narrower alternative. - Open the service account, go to Keys → Add key → Create new key, choose JSON and select Create. Paste the whole downloaded file into Credentials (raw JSON) and keep no other copy. If key creation is blocked, your organisation enforces the Disable service account key creation policy (
iam.disableServiceAccountKeyCreation, oriam.managed.disableServiceAccountKeyCreationin newer organisations). An organisation policy administrator must allow keys for this project.
Success reads Connected to GCP project … Credentials authenticated.
Cloud connection and scan errors
- Could not authenticate to AWS, Could not authenticate to Azure or Could not authenticate to GCP: the credentials were rejected or the identity lacks the read permission the test uses. Check the values and role assignments above.
- The key authenticated, but the Compute Engine API is disabled … (Google Cloud): the key works; enable the Compute Engine API and the IAM API for the project, then test again. The key authenticated, but the service account lacks read permission …: grant the three roles above.
- … credentials are missing … or Invalid JSON: a required key is absent or the JSON is malformed. Use Fill in a JSON template.
- The connection test timed out: retry, and check that the provider's APIs are reachable.
- The credential subscription or project does not match this connection's account identity. No cloud checks were launched.: the account field and the credentials point at different subscriptions or projects.
- Prowler exceeded its 15-minute budget and was terminated: findings it collected are kept, but the rest of the account is unassessed. Split very large estates into separate accounts or projects.
- Prowler reported check or provider collection diagnostics: some checks could not collect data, usually because a permission above is missing. Earlier findings for the account stay open.
Cloud findings identify affected accounts and resources. They assess configuration such as identity permissions, storage exposure and logging. A successful connection test does not establish that every resource can be assessed; check the scan's coverage too.
Choose scan coverage#
Choose checks that match the asset and the question you need answered. A scanner needs a compatible subject and any required credentials.
| What you are checking | Coverage to select | Prepare first |
|---|---|---|
| Hosts and domains | Ports and services, TLS, DNS, subdomains and takeover checks. Azure Blob Storage CNAMEs are reported only when the target name no longer resolves; a Blob Storage "not found" page alone is not treated as takeover evidence. | Authorised target; an agent for internal hosts. |
| Websites and APIs | Web vulnerabilities, API security, WordPress, exposed Supabase backends (tables readable without login) and template-based checks where applicable. | Correct URL and authentication for protected pages. |
| Repositories | SAST, secrets, dependencies, IaC configuration, package licence review and runtime end-of-life checks. Licence results require human review. Runtime checks use declared Node.js, Python and .NET versions; ambiguous or missing metadata stays unknown. | Team plan or higher; repository, branch and package-feed access. |
| Containers and clusters | Image vulnerabilities and Kubernetes posture. | Image reference or cluster target with an assigned agent; for Kubernetes, agent 1.2.70 or later with readable KUBECONFIG and explicit VORNIN_KUBERNETES_CONTEXT. |
| Cloud accounts | Cloud security posture. | Team plan or higher; provider connection and assessment permissions. |
Recommended selects a default set for your selection, and Full selects every scanner your selection can run. Neither makes an unsupported asset compatible or supplies missing credentials; a scanner that cannot run says why next to its name. Check the selected coverage before launch.
Run and repeat a scan#
- Select the + next to Scans to open New scan. In Select targets, use the Hosts & infrastructure, Repositories and Cloud accounts tabs to pick assets; the group filter and Select all … matching pick a whole group.
- In Choose scanners, pick scanners or a preset. Add authentication if needed. Leave Active attack probes off unless you have permission to run intrusive tests against the target.
- In Review & launch, optionally enter a Scan name (blank gives an automatic name), check the summary and select Launch Scan. Tick Email me when this scan completes to get a summary.
- Open the scan from Scans. Check its status, result processing and coverage notices before reviewing findings.
Each launch counts against your plan's hourly scan allowance: 5 scans an hour on Free, 30 on Team, 300 on Business and no limit on Scale.
Active attack probes
Scans run in baseline mode unless you turn on Active attack probes (off by default) under Network options & authentication in Choose scanners. The option appears when you select Web Vulnerability, Nuclei, WordPress or API Security. Baseline mode uses passive checks and controlled path probes and is designed not to modify target data. Turning the option on adds intrusive test payloads: ZAP's active attack rules (such as SQL injection and cross-site scripting), Nuclei and WordPress templates tagged intrusive or default-login (which try known default credentials), and the API scanner's error-provoking requests. It also lets ZAP's crawler submit forms. These can create records, lock accounts or trigger a WAF, so use the option only for non-production targets you are authorised to test, and confirm the authorisation prompt. The setting applies to that scan only. Scheduled scans always run in baseline mode. A re-run reuses the setting of the scan it repeats, and Verify fix (retest) reuses the setting of the scan that last saw the finding. Through the API, send "enableActiveProbes": true when creating the scan; it defaults to false.
A baseline scan shows Active attack probes were not enabled for this scan on its results page, and reports name the affected areas. Findings that only the intrusive checks can detect are kept open from earlier scans rather than marked fixed, until a scan with the option turned on checks them again.
Repeat a scan
To repeat a run, select the re-run button on a completed, failed or cancelled scan in Scans, or Scan again on the scan's own page. It creates a new run with the previous targets, scan types and saved settings, including authentication. If a target's host has changed since, the re-run is refused rather than sending the stored credentials to the new host. Review those settings if access or scope has changed. Re-runs count against your scan allowance.
Scan authenticated pages#
In Choose scanners, select Web Vulnerability, Nuclei, WordPress or API Security, then open Network options & authentication and expand Authenticated Scanning (Optional). Under Authentication Method, choose Session Cookie, Bearer Token or Basic Auth for a dedicated test account.
- Confirm the account can access the pages or API you intend to test.
- Enter the credentials. For a session cookie, paste the whole
Cookieheader value from your browser's developer tools into Cookie Header. - For internet-facing targets, select Validate. To make the check more precise, open Refine validation (optional) and enter a Logged-in indicator (text that appears only when signed in, such as
Sign out) or a Probe URL for a page that needs sign-in. - Resolve an unsuccessful validation before launching. Server rejected the credentials., or a result starting Looks unauthenticated, means the scan would only see the sign-in page; an expired session does this too. A result starting Inconclusive means Vornin could not prove the credentials work, so add a logged-in indicator or probe URL and validate again.
- For private targets routed through an agent, validation is not available. The credentials are applied during the scan; review the result for authentication or access limitations.
The credentials are applied by Vornin's own web checks, the API security scanner and ZAP. Nuclei and WordPress checks run without them. Per-engine receipts identify where credentials were applied; they do not independently prove that the target accepted the session. They are sent only to the target's own HTTPS origin; a plain-HTTP target or a request to another host gets none, and the scan reports that authenticated coverage is incomplete. Credentials are encrypted at rest and reused by re-runs and retests. A re-run or retest refuses the saved credentials when the destination has changed or cannot be confirmed; start a new scan and enter credentials for the current destination. An Admin or Owner can create encrypted profiles under Settings → Scan credentials, bound to one asset and its HTTPS origin. Select a profile explicitly for a scan or a one-asset Specific assets… schedule. Rotation, expiry, revocation or a changed destination requires selecting a current profile again.
Use only the permissions needed for the assessment. Do not paste credentials into scan names, notes, screenshots or support messages.
Schedule recurring scans#
Scheduled scans need a paid plan: Team allows 5 schedules, Business 15 and Scale no limit. Over the limit you see Scheduled-scan limit reached; delete a schedule or upgrade. If a move to a smaller plan leaves more enabled schedules than it allows, nothing is deleted: the oldest ones within the limit keep running, and the rest show Over plan limit and skip their runs until you upgrade or pause an older schedule.
- Open Scans, select the Schedules tab and select New schedule.
- Enter a Schedule name. Under Scope, choose All assets, a Group, or Specific assets… to pick hosts, repositories and cloud accounts. A group is resolved again at each run, so assets you add to it later are included.
- Choose Scan Types. Only types that can run on the chosen scope are offered. Kubernetes is not offered, and Full Scan here means the internet-facing set: subdomains, ports, TLS, web, DNS, API, takeover and template checks.
- Choose the Frequency: Daily, Weekly (pick the Day of Week) or Monthly (pick the Day of Month, from the 1st to the 28th, or Last day of the month). Set the Time, which is in your workspace timezone.
- Optionally tick Email when each scheduled run completes and review the Send to list (up to 10 addresses, separated by
;or,). - Select Create Schedule.
Scheduled results appear alongside your other scans. Scheduled runs are baseline only and can use an explicitly selected credential profile for its one bound asset, and they count against the hourly scan allowance; a run that would exceed it is retried 15 minutes later. Targets assigned to an agent still need it: a task for a busy or offline agent waits in its queue, and a target whose agent has not connected for 7 days is skipped with a notice. Repository and cloud credentials must remain valid.
After the first scheduled run, check that it tested the intended assets. A recurring schedule does not repair expired credentials or incomplete coverage automatically.
Scan internal networks#
A scan agent runs inside your network and connects outbound to Vornin over HTTPS. Vornin never connects in to it. Install it on a machine that can reach the internal targets you intend to scan. Registering an agent needs the Admin or Owner role. Any member can then assign a target to it on the target's form.
Before you install
| Requirement | Details |
|---|---|
| Operating system | 64-bit Windows (x64), or Linux on x86_64 or aarch64. On Windows, run the installer from 64-bit PowerShell. |
| Docker | Windows: Docker Desktop running Linux containers, under the account that will run the agent. Linux: Docker Engine with a Linux daemon the installing account can use. The installer stops if docker info does not report a Linux daemon. |
| minisign | The installers verify the release signature with minisign and refuse to install without it. Windows: winget install jedisct1.minisign or scoop install minisign. Debian and Ubuntu: apt install minisign. Fedora: dnf install minisign (RHEL-family systems need EPEL first). |
| Linux tools | curl and sha256sum. A service install also needs root and systemctl. |
| Outbound HTTPS (TCP 443) | app.vornin.com at all times; get.vornin.com to install and upgrade. Scanners that run as Docker images are pulled when first needed from Docker Hub, ghcr.io and mcr.microsoft.com, and some download their vulnerability data at scan time. For code scans the agent also needs your git host. |
| Network reach | The agent machine, and Docker containers on it, must reach every host it will scan. |
The agent's own connection to Vornin follows the standard HTTPS_PROXY and NO_PROXY environment variables, and on Windows also the account's proxy setting. Docker image pulls and git use their own proxy configuration. On Windows the startup task has no network credentials, so a proxy that requires Windows authentication will not work.
Register, install and assign an agent
- Open Administration → Scan agents and choose Register agent. Enter an Agent name (for example
Office network scanner) and choose Register. - Copy the key from the New agent key field straight away. It starts with
cva_and is shown only once. Keep it in your approved secret store. - Install the agent on the scanning machine using the steps for Windows or Linux below. On a Linux server, use the service install.
- Give the agent its key: the Windows installer asks for it (paste with a right-click; Ctrl+V does not paste into that prompt). A Linux service takes it as
VORNIN_AGENT_KEY. An interactive Linux agent asks for it in the local dashboard athttp://127.0.0.1:5099: paste it into Agent key, leave Server URL ashttps://app.vornin.comand choose Connect agent. - Back in Scan agents, confirm the agent's Status reads Online and its Last heartbeat is recent. The agent reports every 30 seconds.
- Assign targets to it: open Assets → Hosts, open the target's actions menu, choose Edit, pick the agent under Scan agent and choose Save. Then run a scan of that target.
Status values: Online is connected and idle; Busy is running a task; Offline means no heartbeat for 5 minutes; Disabled means an administrator switched it off with the toggle in Actions, and the agent's calls are refused. An Outdated chip in the Version column means a newer release exists: run the installer again on that machine to upgrade.
For repositories, open Assets → Repositories, edit the connection and turn on Scan only through our own ScanAgent to keep its clones on your agents. A git host that is not reachable from the internet can only be cloned by one of your own agents that is online.
Windows installation
Sign in as the user who runs Docker Desktop and install minisign first. In PowerShell opened as Administrator, download and inspect the installer:
Invoke-WebRequest https://get.vornin.com/install.ps1 -OutFile install-vornin.ps1
Get-Content .\install-vornin.ps1
After review and your organisation's software approval, run the saved script. Windows blocks a downloaded script under the default execution policy, so this runs it with a one-off bypass:
powershell -NoProfile -ExecutionPolicy Bypass -File .\install-vornin.ps1
The installer verifies the signature and checksum, checks Docker and the bundled network tools, asks for the agent key and starts the agent. It is installed under C:\Program Files\Vornin\Agent and runs under your account from a startup task named Vornin Agent, without storing your password, so it keeps running after a reboot. The task runs headless: there is no local dashboard. Scans need Docker Desktop running; it starts when a user signs in, so after a reboot scans wait for that unless Docker is set to start without a sign-in. In Task Scheduler the task's last result often reads 0x800710E0: that is the five-minute watchdog trigger being refused because the agent is already running, not an error.
| Option | What it does |
|---|---|
| (none) | Installs, or upgrades in place. A re-run keeps the saved key, settings and undelivered results. |
-Key cva_... | Supplies the key without the prompt. $env:VORNIN_AGENT_KEY works the same way and keeps the key out of your command history. |
-Version 1.2.90 | Installs a specific release, for example to roll back. |
-Uninstall | Removes the startup task and the program files; run it elevated. The key, logs and undelivered results stay in %LOCALAPPDATA%\VorninAgent; delete that folder when you no longer need them. |
-NoStartupTask | Installs for the current user only, with the local dashboard, and needs no elevation. It does not start again after a reboot. |
-RunAsSystem | Runs the task as SYSTEM. Use it only with a Docker engine that serves the default endpoint and can read SYSTEM's files, and run a repository scan with a known finding afterwards. |
To pause the agent, run Disable-ScheduledTask 'Vornin Agent' and then Stop-ScheduledTask 'Vornin Agent'. This stops the running agent; wait for active scans to finish first if you want them to complete. An upgrade keeps a disabled task disabled. To resume, run Enable-ScheduledTask 'Vornin Agent' and then Start-ScheduledTask 'Vornin Agent'.
Linux installation
Download and inspect the script before running it:
curl -fsSL https://get.vornin.com/install.sh -o install-vornin.sh
less install-vornin.sh
After review, install the agent:
bash install-vornin.sh
Without options this installs for your user under ~/.local/share/vornin-agent and does not start anything. Start it with ~/.local/share/vornin-agent/vornin-agent, open http://127.0.0.1:5099 on that machine and connect it with your key. It runs only while that process runs and does not start after a reboot. For a server, install it as a service instead.
If your device blocks the installer or its verification fails, stop and consult your administrator or Vornin support. Do not disable verification to continue.
Run the agent as a Linux service
The dashboard listens only on 127.0.0.1 of the agent machine. On a headless machine, run the installer as root with --service and pass the key in VORNIN_AGENT_KEY. Reading it into the environment keeps it out of your shell history:
read -rs VORNIN_AGENT_KEY && export VORNIN_AGENT_KEY
sudo --preserve-env=VORNIN_AGENT_KEY bash install-vornin.sh --service
This installs the agent to /opt/vornin-agent, writes the key to /etc/vornin-agent.env (readable by root only) and creates the systemd unit vornin-agent. The service runs as root, starts at boot, restarts 10 seconds after it stops, runs without a dashboard and uses the system Docker socket /var/run/docker.sock. The installer refuses a DOCKER_HOST or DOCKER_CONTEXT override.
systemctl status vornin-agent
journalctl -u vornin-agent -f
sudo systemctl restart vornin-agent
To upgrade, download the installer again and re-run it with --service. It reuses the key saved in /etc/vornin-agent.env:
sudo bash install-vornin.sh --service
A re-run replaces only the VORNIN_AGENT_KEY line of /etc/vornin-agent.env and keeps every other line, so put other settings there, one NAME=value per line (for example VORNIN_AGENT_LOG_LEVEL=Debug), and restart the service. To change the key, pass the new one in VORNIN_AGENT_KEY as for a first install. To install a specific release, add --version=1.2.90.
To remove the service, run the installer with --uninstall. It stops and disables the service and deletes the unit, /opt/vornin-agent and any per-user install. It keeps /etc/vornin-agent.env, any drop-in you created with systemctl edit, and the logs and undelivered results in /root/.local/share/VorninAgent, so a later install reconnects with the same key and settings. Add --purge to delete those as well:
sudo bash install-vornin.sh --uninstall
sudo bash install-vornin.sh --uninstall --purge
For a per-user install, stop the agent process first, then run bash install-vornin.sh --uninstall without sudo; --purge also deletes ~/.local/share/VorninAgent. The bundled network tools Docker image stays loaded; remove it with docker image prune when nothing uses it.
Verify a release yourself
The installer scripts are not signed, which is why you read them first. Each release's SHA256SUMS file is signed with minisign, and the installers check that signature before they trust a checksum. To check an archive by hand, download it with the checksum file and signature, then verify both:
curl -fsSLO https://get.vornin.com/latest/SHA256SUMS
curl -fsSLO https://get.vornin.com/latest/SHA256SUMS.minisig
curl -fsSLO https://get.vornin.com/latest/vornin-agent-linux-x64.tar.gz
minisign -Vm SHA256SUMS -P RWQrwgpnb6Pd2sXudGk5f53+AbVpLbcLahecg6nZGDlyVDplWU7cmjQc
sha256sum -c --ignore-missing SHA256SUMS
The archives are vornin-agent-win-x64.zip, vornin-agent-linux-x64.tar.gz and vornin-agent-linux-arm64.tar.gz. Replace latest with a version number to check a specific release. The public key above is the one built into both installers.
Agent configuration
Agent configuration and logs
| Variable | Effect |
|---|---|
VORNIN_AGENT_KEY | The agent key. It takes precedence over a key saved by the dashboard. |
VORNIN_AGENT_UI | on, off or auto (default). auto shows the local dashboard only when the agent runs in an interactive console. The --no-ui argument also turns it off. |
VORNIN_AGENT_UI_PORT | Dashboard port on 127.0.0.1. Default 5099. |
VORNIN_AGENT_LOG_LEVEL | Debug or Verbose when diagnosing a problem. Default Information. |
VORNIN_AGENT_NETWORK_PARALLELISM | Network scanners run at once within a task, 1 to 8. Default 2; use 1 on a small machine. The agent always runs one task at a time. |
KUBECONFIG, VORNIN_KUBERNETES_CONTEXT | Required on an agent assigned to a Kubernetes cluster target: the agent selects the cluster through them. |
Restart the agent after changing a variable. For the Windows startup task, set it as a system environment variable and restart the task. For the Linux service, add it to /etc/vornin-agent.env as shown above.
To replace a saved key on an interactive install, clear it and start the agent again; it then asks for a new one. Run the command from the install directory: ~/.local/share/vornin-agent on Linux, or %LOCALAPPDATA%\Vornin\Agent for a Windows -NoStartupTask install, where the file is vornin-agent.exe:
./vornin-agent --reset-keyFor the Windows startup task, re-run the installer with the new key in $env:VORNIN_AGENT_KEY. For the Linux service, re-run the service install with the new key. To revoke a key, register a new agent, assign the old agent's targets to it, then delete the old agent in Scan agents.
Logs roll daily as agent-YYYYMMDD.log and are kept for 14 days, beside the saved key and the outbox of undelivered results:
- Windows:
%LOCALAPPDATA%\VorninAgent\logs\of the account the agent runs as (with-RunAsSystem:C:\Windows\System32\config\systemprofile\AppData\Local\VorninAgent\logs\). - Linux, per user:
~/.local/share/VorninAgent/logs/. - Linux service:
/root/.local/share/VorninAgent/logs/, plusjournalctl -u vornin-agent.
Do not delete files in the outbox folder: they are completed results the agent has not yet delivered. If you run the agent interactively, keep its process running. Closing it stops the agent. For unattended operation, use the Windows startup task or the Linux service.
The agent sends its status throughout scans and uploads, and keeps completed results on disk until Vornin accepts them, retrying after a restart or an outage. It checks for cancellation every 10 seconds while a task runs, so cancelling a scan stops local work shortly after; an unavailable server delays that signal.
Hosted scans and the agent use the same packaged port and service scanner (Naabu and Nerva, bundled with the agent since release 1.2.59), plus targeted UDP probes that report reduced coverage. The installer loads and checks the included network tools. Split ranges into targets of at most 256 addresses (IPv4 /24 or smaller): a larger range fails with Network targets are limited to 256 addresses per task; split the range into smaller targets.
Other scanner types can require additional tools or Docker images. Windows repository scans support deeply nested file paths. Review coverage notices for missing prerequisites and checks that could not finish.
A target assigned to an agent is scanned only by that agent. Vornin never moves it to its hosted scanners, and its own background monitoring does not probe it either. While the agent is busy or offline the scan waits for it, and work it does not pick up within the scan's time limit ends with a notice that starts Your ScanAgent "<name>" did not pick up. An agent that has not connected for 7 days, or never has, is not waited for: its targets are skipped with a notice that it has not connected since a date, or has never connected. A disabled agent's targets are skipped too. An agent that still has targets assigned cannot be deleted, so its targets never move to Vornin's hosted scanners by accident: assign them to another agent first, or disable the agent to pause them.
Agent problems and fixes
| What you see | Cause and fix |
|---|---|
minisign is required to verify this release, and is not installed. | Install minisign (see Before you install) and run the installer again. |
Signature verification FAILED or Checksum mismatch | The download is not the one Vornin signed, or it was corrupted. Do not override it. Download again; if it repeats, contact support. |
Docker is required, Docker Desktop is required or A reachable Linux Docker daemon is required for this agent account. | Start Docker (Linux containers on Windows) under the account that runs the installer, confirm docker info works, then re-run. |
Installing the agent needs an elevated PowerShell (Run as Administrator). | Open PowerShell with Run as administrator, or use -NoStartupTask for a per-user install. |
That is not an agent key or No valid agent key entered. | The key must start with cva_. Paste it with a right-click, or set $env:VORNIN_AGENT_KEY and re-run. |
--service needs --key=cva_... | A service has no dashboard, and /etc/vornin-agent.env holds no key to reuse. Pass the key in VORNIN_AGENT_KEY with sudo --preserve-env=VORNIN_AGENT_KEY. |
--service requires the local system Docker socket /var/run/docker.sock. | The service uses root's Docker. Switch the Docker context back to the default local daemon and re-run. |
The agent did not stay running | The Windows agent exited at start and the installer rolled back. Read the newest log under %LOCALAPPDATA%\VorninAgent\logs\. |
Dashboard: That key wasn't accepted. | The key is wrong, or the agent was deleted. Register a new agent and use its key. |
Dashboard: Couldn't reach https://app.vornin.com. | The machine cannot open HTTPS to app.vornin.com. Check the firewall and proxy settings. |
| The agent shows Offline | No heartbeat for 5 minutes. Check the process or service is running, then read its log for Agent heartbeat failed or Server connection failed. |
Scan notice: not publicly routable and needs a ScanAgent | The target has a private address and no agent. Assign one under Scan agent on the target. |
Scan notice: its assigned ScanAgent "<name>" is disabled | Enable the agent in Scan agents, or assign another. |
Scan notice: upgrade the assigned agent to support network scan controls or update the assigned agent | The agent release is too old for the options this scan used. Run the installer again on that machine. |
If an agent is online but cannot scan a target, check connectivity from the agent machine to that target. Docker-based tools must also be able to reach it from their container network.
Review and assign findings#
Open Vulnerabilities. The list opens on open findings, sorted by priority (P0 to P3). The toolbar has a search box, a severity filter and an Evidence filter (for example Fix available, KEV listed or Code reached). More filters holds the Scanner, Target, Repository, Cloud account and SLA filters, presets such as Assigned to me and SLA breached, and Save view. The counters across the top (Open findings, Exploitable, Acknowledged, Resolved, Dismissed) switch the list to that set. An acknowledged finding leaves the default list, so use the Acknowledged counter to find it again. Priority helps order the queue; it does not replace checking the finding's evidence.
- Select a finding to open its side panel. Read its affected location, description and recommended fix on Overview, and the scanner output on Evidence.
- For code findings, check the file and line; for dependencies, check the installed and fixed versions. References and the detection history are on the full page under Technical details.
- Choose Assign owner, and choose Acknowledge from the panel's ⋯ menu when work begins.
- When the fix is in, choose Mark resolved and describe the change in Resolution note (optional), then verify the fix.
Every workspace role, Member included, can assign, acknowledge, resolve and dismiss findings. The side panel keeps the finding in context. Choose Open full page for the Technical details, Activity and Compliance tabs. When an AI assessment is available, compare it with the original scanner evidence before acting. AI features run on OpenAI in the United States. AI assessments and triage send the finding's title, description, affected host, CVE id and known solution; the AI false-positive review of code findings also sends the rule id, file path, the matched code and up to eight lines on either side of it (common credential patterns, such as passwords, tokens, keys and connection-string secrets, are masked first); AI report summaries send up to six affected hosts and the top finding titles. OpenAI is listed on our sub-processors page.
Grouped / Single, under More filters, changes how dependency upgrades appear. A grouped row stands for every finding fixed by the same package upgrade in one manifest: select it and open the panel's Related CVEs tab to see them. Assigning, acknowledging, resolving, dismissing or reopening the grouped row affects every finding it represents; grouping does not combine their individual histories or deadlines. Suppression rules cannot be created from a grouped row: switch to Single first.
Resolve and verify fixes#
| Status | What it means |
|---|---|
| Open | Detected and awaiting attention. |
| Acknowledged | The team has recognised the finding; it still needs resolution. |
| Resolved | Marked fixed. On the Activity tab, Resolved is a manual change and Auto-resolved means a scan no longer detected it. |
| Dismissed | A recorded decision that the finding is a false positive, an accepted risk or covered by a compensating control, with its reason. This is not evidence of a fix. A finding dismissed by a suppression rule shows Auto-suppressed in its activity. |
After remediation, open the finding's full page and choose Verify fix (retest) from its menu. It is offered on open and acknowledged findings from the network and web scanners (subdomain discovery, port scan, SSL/TLS, DNS, web application, API, Nuclei, WordPress, Supabase and subdomain takeover) while the most recent scan that detected the finding still exists. It queues the relevant check with that scan's authentication and active-probing setting, and the finding resolves if the fix holds. Retesting counts against your concurrent-scan and hourly scan limits, and the item reads Retest running… while a retest is pending or running.
For repository, cloud, container and Kubernetes findings, run the appropriate scan again. When the relevant check no longer detects an open or acknowledged finding, it resolves with scan verification recorded. If a resolved issue returns in a later scan, it reopens as Open, with an SLA deadline counted from that scan. A dismissed finding stays dismissed when a scan detects it again.
If Supabase is still configured, a table that disappears from the application's references has not been verified fixed. Its held finding stays open until it is checked again successfully, or until you record a manual decision after verifying the remediation. A manual resolution does not count as rescan verification.
False positives and suppression#
Review the evidence before dismissing a finding. An FP-likely assessment is a review aid, not confirmation that a reported issue is harmless. These controls are under More filters:
- Hide likely false positives is on by default and hides Low and Info findings flagged as likely false positives. Medium and higher findings stay visible however they are flagged. Turn it off to include them in your review.
- Show N deprioritised reveals the findings collapsed from the queue. It appears only when something is collapsed.
- Reopen, in the panel's ⋯ menu, returns a dismissed or acknowledged finding to Open when your review shows that it requires attention. A resolved finding, or one a person dismissed, has a Reopen button on its full page.
To dismiss a finding, choose Accept risk / false positive in the panel's ⋯ menu, pick a Category, write a Reason, optionally set Approved by and Review by, then choose Accept risk or Mark false positive. Filtering changes the view. Dismissal changes the finding's status. When filters hide rows, a banner offers Show hidden findings. Check both filters and the status counters when looking for a missing finding. An accepted risk with a review date reopens automatically when that date passes. A dismissed code finding stays dismissed when its code only moves to another line in the same file: the decision, with its review date, follows the finding to the new line.
Make a suppression decision persistent
A suppression rule dismisses the open findings that match it when you create it, and every matching detection from later scans, whether the finding is new or detected again. The suppression rule dialog shows how many open findings it will dismiss before you confirm. The box in the dismiss dialog makes a rule for that exact finding, and the confirmation says when it also dismissed other open copies of it. It never touches a finding someone acknowledged, or one a person reopened: reopening a finding is a decision that it needs attention, and the rule leaves it open. Create a rule from a single finding with Suppression rule in the panel's ⋯ menu, Create Suppression Rule on the full page, or the Create suppression rule for future scans box when you dismiss it. Choose This finding only to match that exact finding (same URL, or same file and line), or This title from this scanner anywhere on this host to also cover the same issue at other URLs or after code moves to another line. That option matches the finding's exact title, so a rule made from SQL Injection does not cover Blind SQL Injection. Rules you type on the Suppression rules page match any title that contains the text.
To cover a class of findings, an Admin or Owner opens Administration → Suppression rules and fills in some of Title pattern (contains match), Scanner (optional), Host pattern (exact or *wildcard) and Fingerprint (exact match), plus a Classification and Reason. A finding must match every field you fill; empty fields match anything. Saving, or switching a rule back on, shows how many open findings match and dismisses them when you confirm. A rule saved before October 2026 keeps matching only newly detected findings until an Admin saves it again or switches it off and on. Keep the rule narrow enough to cover only the decision you intended.
Understand dependency reachability#
Reachability adds context to dependency findings from repository scans: whether your own source code imports the affected package. It reads JavaScript, Python, Go, Java and C# imports. It is an import check, not a call graph. Use it alongside severity, exploit information and your knowledge of the application.
- Reached
- Your code imports the affected package. That does not prove the vulnerable function is called. Review the affected package and available fix.
- Unreached
- Analysis did not find a reference. This does not prove the dependency cannot run.
- Unknown
- There is not enough supported analysis to make that distinction.
Filter on it with the Evidence options Code reached, Not reached and Reachability unknown. Runtime loading, reflection and unsupported code patterns can limit analysis. Do not accept risk solely because a finding is marked Unreached. Review dependency use and the captured evidence before making that decision.
Remediation deadlines#
Admins and Owners set deadlines under Settings → General settings → Compliance, in the SLA policies panel. For each severity, set Remediation days (1 to 365), Escalation days (how many days after the deadline to escalate; 0 escalates at the deadline) and the Escalation emails to notify, separated by commas, tick Enabled, then choose Save SLA Policies. The starting values are Critical 1 day, High 7, Medium 30 and Low 90. These are your team's deadlines; review them against the way your team handles remediation.
Vornin checks for breaches every hour, on every plan, and marks breached findings. On Business and Scale it also sends the sla.breach webhook and the in-app SLA alert at the breach, and escalates once the severity's escalation days have passed: each escalation contact gets one digest email listing the breaches for the severities that name them, not one email per finding, and a severity with no escalation emails notifies the workspace Owners. The in-app SLA alert is sent once per check, for every breach it found. Webhook deliveries and the tamper-evident audit trail stay per finding. On Free and Team, breaches are recorded and shown but not escalated.
A breached finding shows SLA breached in the list; its full page counts down to the deadline (for example Due in 3 days or Breached 2 days ago) or shows No SLA policy. The SLA filter under More filters offers Breached only, Due soon (due within 3 days) and Without SLA, to find overdue work and gaps in policy coverage.
Changing remediation days recalculates deadlines for open and acknowledged findings from their first-detected date. A severity reassessment also uses the finding's first-detected date, not the date of the new scan. Check existing work after tightening a policy.
Discovered assets and emerging threats#
Open Attack surface → Discovered hosts to review subdomains found by enumeration. The list opens on New; the chips switch between All, New, Tracked and Ignored. Confirm ownership and scope before choosing Add as target, on a row or for several selected rows. The dialog lets you set the Kind and tick Run scan now. Adding fails when you have reached your plan's target limit or the host is already a target. Choose Ignore for discoveries you do not want in the active list, and Restore to New to bring one back. The list shows hosts seen in the last 30 days; switch on Include hosts not seen in 30 days to review older ones, such as a name that no longer resolves.
The other Attack surface pages show observed services (IPs & services), Certificates, DNS posture and Technologies, each with a Last seen column. They open on the Active (30d) filter, which omits observations older than 30 days; choose All to review them. The other filter chips, such as Expiring ≤30d or Outdated only, replace the 30-day filter rather than adding to it. An old observation is not proof that the service disappeared. The attack-surface pages of a report follow the same rule and include only observations from the last 30 days. When you delete a target, or change its host, its observations are removed within a day unless another target or discovered host still covers them.
Emerging threats lists newly exploited CVEs from the CISA known-exploited catalog that match your assets. A confirmed match is an open finding for that CVE: choose View findings. A detected tech match means a technology seen on your assets in the last 90 days matches the advisory and needs investigation: choose Check technology, open the linked assets and CVE evidence, check relevance, and scan affected systems as needed. Narrow the list with Confidence or Ransomware only. The page filters and pages through the complete matching set. Export CSV downloads every matching row across all pages. An inventory match alone is not a confirmed vulnerability.
Generate and schedule reports#
- Open Reports. On the Create report tab, choose a Report type: Executive summary, Technical detail, Auditor pack (lifecycle + chain) or Comparison report.
- For a summary or detail report, set Scope to From scan (then Select scan), All assets, a Group, or Specific assets… to pick hosts, container images, Kubernetes clusters, repositories or cloud accounts. A comparison report takes Period A and Period B dates instead.
- Set Minimum severity. The default is Low and above.
- Optionally enter Prepared for (optional), the client or business unit printed on the cover.
- Choose Generate report, then Download PDF. Review the scope, dates and coverage limits in the PDF before sharing.
The auditor pack always covers the whole workspace, every target and severity, and needs Business or Scale. Per-framework compliance PDFs are on the Compliance pages. After generating, Other Formats offers the same findings as CSV, JSON or XML on Team and above; that CSV has six columns (severity, title, host, category, description, solution).
For recurring delivery, open the Scheduled reports tab and choose New schedule. Enter a Name, choose Report type (Executive summary or Technical detail), Scope (All assets, Specific assets… or a Group), Frequency, the time, Minimum severity and Recipients (comma-separated emails), then choose Save. Frequency is Every N days (set Interval (days), 1 to 365), 1st of every month or Last day of every month. Each run emails the PDF to the recipients. Use Run now on a schedule to test delivery, and review the recipient list whenever the report's scope changes.
Branding
Admins and Owners open Settings → General settings → Branding. Under Report branding, set the company name, report title, primary and accent colours, website, contact email, footer, confidentiality label and logo (Upload company logo: PNG or JPEG, up to 2 MB), then choose Save branding. Custom branding needs Business or Scale; Business reports carry a small "Powered by Vornin" byline, and Scale removes Vornin branding entirely. Restore defaults asks for confirmation, then saves immediately and removes the uploaded logo.
Export findings and software inventories#
A completed scan's page offers Export CSV, Export SARIF and, when the scan produced an inventory, Download SBOM. The Vulnerabilities list offers Export findings for everything matching your current filters, with more formats in its menu. Check the selected scope before sharing: exports can contain code snippets, asset names and ownership details.
| Format | Where | Use it for |
|---|---|---|
| CSV | Export CSV on a scan; Export findings on Vulnerabilities | Finding analysis, including available severity, location, ownership, SLA and closure details. |
| CSV + metadata (.zip) | Vulnerabilities menu | The loaded findings with a description of the export. |
| Package rollup (CSV) | Vulnerabilities menu | Dependency findings rolled up by package. |
| SARIF | Export SARIF on a scan | Importing the scan's findings into compatible developer tools. Refused while results are still processing, or when a check did not complete (SARIF export requires complete coverage), because a SARIF consumer closes alerts missing from an upload. |
| CycloneDX SBOM | Download SBOM on a scan | Software inventory from dependency or container image scans. A scan with several inventories downloads a JSON file holding each one. |
| Event ledger | Vulnerabilities menu, Export event ledger | One row per lifecycle event, with the hash chain for audit review and offline evidence verification. Covers up to 2,000 loaded findings. |
Every export in this table, and the Emerging threats CSV, needs Team or above; on Free they are refused with Data export (CSV / SARIF / SBOM) requires the Team plan or higher. The scan CSV, SARIF and SBOM downloads also accept an API key with reports:export, for CI pipelines. An SBOM requires a scan that produced the relevant inventory; scanning a website alone does not create a repository SBOM.
Review compliance evidence#
- An Admin or Owner chooses the frameworks under Settings → General settings → Compliance: pick them in Tracked frameworks and choose Save Framework Selection. Free tracks one framework; Team and above track all nine (CIS Controls, NIST 800-53, ISO 27001, SOC 2, PCI DSS, HIPAA Security Rule, GDPR, DORA and NIS2).
- Open Compliance and choose Review controls → on a framework. The Controls tab lists each control; Evidence coverage shows supporting scans and whether evidence is Fresh or Stale (>30d).
- On a failing control, choose the red N open button to see the linked findings. Assign and remediate them from the finding pages.
- For controls that need human sign-off, open Manual evidence and choose Record. Set Status, write a Note, optionally set Re-attestation due (optional) and attach a file (PDF, PNG, JPEG, ZIP or text, up to 5 MB), then choose Save attestation.
- For DORA, mark each asset that supports a critical or important function. Turn on Supports a critical or important function (DORA) in the asset's edit dialog (Hosts, Containers, Clusters, Cloud accounts) or on its row under Repositories. A marked asset is held to a weekly scan cadence unless you set its own cadence, as RTS 2024/1774 Art. 10(2) requires, and the cadence control's findings view and the auditor pack show how many marked assets were scanned in the last 7 days.
- Review missing or stale evidence, then use the framework's Export menu for the PDF report, mappings, or an evidence pack.
Clear means measured without a current failing result. A failing control shows a red N open button and the caption Failing with its open-finding count. Not measured identifies a gap. Unmeasured controls are excluded from the score and must not be read as passing.
On the Attention tab, Path to audit-ready lists the outstanding work in three steps: resolve findings on failing controls, record and refresh manual sign-offs, and refresh scan evidence. Attestation needs Team or above. The evidence packs (Audit evidence pack (12 months) and Evidence pack data (ZIP, full CSVs)), the management review and the VM policy PDF need Business or Scale. Exports and attestations are for Admins and Owners; Members see the pages read-only. Evidence packs always cover the whole workspace, even when a scope filter is applied.
Read dashboards and metrics#
The Dashboard has two tabs: Needs attention and Trends & performance. Its Remediation overdue card counts active findings past their SLA. On Team and above, Executive brief gives leadership a one-page view: Security Posture Score, SLA Compliance, MTTR (30-day rolling), Critical / High Active and Risk accepted, with trends, severity distribution and the five riskiest targets. Open the underlying findings to understand what changed; a headline score alone does not describe scan coverage.
- MTTR
- The median time from first detection to resolution for findings resolved in the last 30 days. An empty cohort shows a dash or N/A: it has no measured value, which is not zero-day remediation.
- SLA compliance
- The share of open and acknowledged findings with an SLA deadline that have not breached it. Review the headline alongside SLA compliance by severity and the Dashboard's overdue findings. A healthy overall value can hide overdue critical work.
- Current values and history
- SLA compliance, MTTR and severity counts are calculated from current findings. The posture score and the trend charts come from daily snapshots taken at midnight UTC, so their update cadence differs.
Invite and manage your team#
Owners and Admins manage invitations under Administration → Users & roles; Members do not see the Administration menu. Click Invite user, enter the person's address in Email Address, choose Member or Admin under Role, then click Send Invitation.
Vornin emails them "You've been invited to Vornin" with a Sign in to Vornin button. The email carries no personal link: the invitee joins by signing in to Vornin with the invited address within 7 days, by email sign-in link, or by Google or GitHub with that address. Signing in through your SSO accepts the invitation only when the address is in a domain your workspace has verified; see Configure single sign-on. Pending invitations are listed on the same page with Resend, which sends the email again and restarts the 7 days, and Revoke. Inviting an address again replaces its earlier pending invitation. Invitations your identity provider created through SCIM show Managed by your identity provider and cannot be resent or revoked here.
The Free plan allows 2 seats, counting members and pending invitations; Team and above have no seat limit. When the Free limit is reached, inviting shows Team-member quota reached with a link to upgrade.
What each role can do
| Role | Can do |
|---|---|
| Member | Everyday work: add and edit hosts, container images and clusters (including their scan agent), start scans and schedules, view repositories, cloud accounts, scans and reports, triage, assign and change the status of findings, import scan results, and manage their own account and notifications. |
| Admin | Everything a Member can, plus Settings (every tab, including IP allowlist, SCIM and TOTP enforcement; single sign-on is read-only), Billing, Users & roles, API & integrations, Suppression rules (including creating one from a finding), Audit log, Scan agents, cloud connections, repository connections and compliance attestations. |
| Owner | Everything an Admin can, plus Transfer Ownership and single sign-on: SAML, Entra ID, SSO domains and Require SSO. A workspace has one Owner, who cannot be demoted or removed until ownership has been transferred. An Admin cannot deactivate the Owner or reset the Owner's MFA. |
Through the API, managing webhooks needs Admin or above. Starting and cancelling scans, updating a finding's status and requesting AI triage need Member or above, as in the app. Both are checked against the role of the person who created the API key. If a control is unavailable, check your role with a workspace administrator.
Change roles and remove people
Open the menu on a person's row under Users & roles:
- Make Admin or Make Member changes their role.
- Transfer Ownership (Owner only) makes them the Owner and demotes you to Admin. You cannot undo it yourself.
- Reset MFA removes their authenticator and recovery codes. Only the Owner can reset the Owner's MFA. See Lost authenticator.
- Remove from workspace takes away their access to this workspace only. Their Vornin account and other workspaces are unaffected, and you can invite them again later.
- Deactivate signs them out and blocks their Vornin account from signing in until an Admin clicks Activate. It is refused for someone who also belongs to another workspace; use Remove from workspace instead. Only the Owner can deactivate the Owner.
A role change or removal takes effect on the person's next click. An open page then shows "Your access changed. Reload and sign in again."
Review access when people change roles or leave. If your organisation uses SCIM provisioning, make access changes in your identity provider so the next sync does not undo them. SCIM adds people as Members by default. Opt-in role reconciliation requires both the workspace SCIM policy and a SCIM-managed role on the membership. The vornin-admins group then grants Admin while included and Member after removal. Owners, platform administrators and marked break-glass members are protected. A human role edit clears the managed flag; other roles remain under Users & roles.
Sign-in and two-factor authentication#
Use the sign-in method configured for your workspace. Work-email sign-in can redirect you to your organisation's verified SSO provider. If SSO is required for your domain, a magic link will not bypass it. An emailed sign-in link expires after 15 minutes.
A sign-in link or email-change link opens a page with one button. Click it to finish. The extra step stops email security scanners from using up the link before you open it.
When first linking Google or GitHub to an existing Vornin account, complete the confirmation sent to the account's email address. Signing up with GitHub also sends a confirmation email before the account is created. Open these confirmation links in the same browser where you started signing in; a link opened anywhere else is refused.
Changing your account email disconnects Google and GitHub sign-in. To use one again, that provider account must use the new address.
Set up an authenticator
- Open the account menu (your name, top right), choose Account, select the Security tab and click Set up authenticator. If your workspace requires TOTP, Vornin takes you to the setup page at sign-in instead.
- Scan the QR code with your authenticator app, or type in the key shown under it. Enter the app's current 6-digit code and click Verify and enable.
- Vornin shows 8 recovery codes once. Copy or download them (Download .txt) and keep them somewhere secure and separate from that device. Each code works once.
Always scan the latest QR code Vornin shows. If setup expires, sign in again and start over.
If you signed in through SSO and are setting up an authenticator for the first time, Vornin first asks you to confirm your email: click Send confirmation email and open the link in the same browser.
At each sign-in, enter the code in 6-digit code or recovery code. If a code fails, check the device's time and try a fresh code. After too many wrong codes, sign-in is blocked for 15 minutes; a recovery code still works. Under Account → Security you can see how many recovery codes remain, click Regenerate recovery codes for a fresh set (your old codes stop working), or click Reset MFA to remove the authenticator. Both ask for a current code or a recovery code first.
Lost authenticator and recovery codes
Use a saved recovery code if you lose access to the authenticator, then set up the new device under Account → Security. If you have lost the recovery codes too, ask an Owner or Admin to open Administration → Users & roles, open the menu on your row and click Reset MFA. You then sign in without a code and, if the workspace requires TOTP, set up a new authenticator. A workspace admin cannot reset someone who also belongs to another workspace; Vornin support handles that.
Require TOTP for everyone
On Business or above, an Owner or Admin can open Settings → General settings, select the Security tab and tick Require TOTP for all users in this tenant in the Multi-factor authentication panel, then confirm with Enforce. Everyone without an authenticator is sent to set one up at their next sign-in, including people who sign in through SSO. Untick the box and click Stop enforcing to turn it off.
Restrict access by IP address
On Business or above, an Owner or Admin can open Settings → General settings, select the Security tab and enter one IPv4 address, IPv6 address or CIDR range per line in the IP allowlist panel, then click Save IP allowlist. The page shows your current IP. Once a list is saved, requests from any other address are refused, including API keys, SCIM provisioning and support access, so add your identity provider's and CI's egress addresses first. Vornin asks you to confirm before saving a list that excludes your own address. You can click Clear IP allowlist at any time, including after a downgrade; if you lock yourself out, contact support.
Configure single sign-on#
Single sign-on lets your team sign in to Vornin through your organisation's identity provider. It needs the Business plan or above, and only the workspace Owner can set it up or change it. Admins can view the settings. Whoever controls the identity provider can sign in as anyone at your verified domains, so this stays with the Owner. Open Settings → General settings and select the Security tab on the left, or go straight to app.vornin.com/settings/security. The tab shows these panels from top to bottom: Multi-factor authentication, IP allowlist, Single sign-on (SSO), SAML 2.0 SSO, SSO domains and SCIM user provisioning. The SAML panel starts collapsed: click the line SAML 2.0 SSO · Not enabled to open it. On a lower plan these panels say SSO is available on the Business plan, and Connect with Entra ID is greyed out.
Work in this order:
- Connect one identity provider. Use Connect with Entra ID if your directory is Microsoft Entra ID and you want the shortest setup. Use SAML 2.0 SSO for Okta, Google Workspace, any other SAML 2.0 provider, or an Entra enterprise application you manage yourself.
- Verify your email domain under SSO domains, so the sign-in page sends people at that domain to your provider.
- Invite your team under Administration → Users & roles, or set up SCIM provisioning. SSO never lets in someone who was not invited.
- Test sign-in yourself and with a member account.
- Decide on Require SSO. Turn it on only after you have signed in through SSO yourself. See Require SSO.
Option 1: Connect with Entra ID
Vornin signs people in through its own Microsoft Entra application. You do not create an app registration, a client secret or a redirect URI: an administrator in your directory grants consent once, and Vornin records your directory's tenant ID. When someone signs in, Vornin requests only the openid, email and profile scopes.
- Check who can consent. The person who clicks the button must be the Vornin workspace Owner and, in Microsoft Entra, hold a role that can grant tenant-wide admin consent: Privileged Role Administrator, Cloud Application Administrator or Application Administrator. If those are separate people, transfer ownership to the Entra administrator for the few minutes this takes, or have them grant consent in Entra first.
- Start the connection. In Vornin, open Settings → General settings, select the Security tab and click Connect with Entra ID in the Single sign-on (SSO) panel.
- Grant consent. Microsoft asks you to sign in and shows the permissions Vornin's application requests. Review them and click Accept. Finish within ten minutes of clicking the button, or start again.
- Check the result. Microsoft sends you back to the Security tab, where a message at the top says Microsoft Entra ID is connected. The panel now reads Connected to Microsoft Entra ID (Tenant: …) with your directory ID, and shows the direct sign-in address
/auth/entra/login?tenantSlug=…for your workspace. If consent did not complete, the message at the top says why; see the troubleshooting table below. - Optionally restrict who can try. Consent lets anyone in your directory reach Vornin's sign-in, although only invited members get in. To limit it further, open Entra ID → Enterprise apps → All applications in the Microsoft Entra admin center, select the Vornin application, set Assignment required? to Yes under Properties, and assign people under Users and groups.
- Verify your email domain and test, as described under Verify your email domain. Entra ID has no Test SSO button in Vornin: sign out, enter your work email on the sign-in page and click Continue with single sign-on, or open the direct sign-in address in a private window.
Vornin matches the person by the email claim in the Entra token, then preferred_username, then upn. The address must be the one they were invited with. Successful sign-ins appear in the audit log as Login.Entra. To disconnect, click Disconnect Entra ID in the same panel and confirm; people at your domain then sign in with email links, or with SAML if you have it. If you connect Entra ID and also configure SAML, the sign-in page uses Entra ID.
Option 2: SAML 2.0
On the Security tab of Settings → General settings, expand SAML 2.0 SSO. The panel has four numbered steps. Step 2 lists the values your provider needs from Vornin, each with a copy button. You enter those in the provider, then copy three values from the provider back into step 3 in Vornin.
| Vornin shows | Microsoft Entra ID | Okta | Google Workspace |
|---|---|---|---|
SP Entity IDhttps://app.vornin.com/saml/<id>/sp | Identifier (Entity ID) | Audience URI (SP Entity ID) | Entity ID |
SP ACS URL (HTTP-POST)https://app.vornin.com/saml/<id>/acs | Reply URL (Assertion Consumer Service URL) | Single sign-on URL | ACS URL |
SP Sign-on URLhttps://app.vornin.com/saml/<id>/login | Sign on URL | Not used | Not used |
SP Metadata URLhttps://app.vornin.com/saml/<id>/metadata | Open it, save it as an .xml file and use Upload metadata file. Entra does not accept a metadata URL. | Not needed | Not needed |
| Vornin field | Microsoft Entra ID | Okta | Google Workspace |
|---|---|---|---|
| IdP Entity ID | Microsoft Entra Identifier | Identity Provider Issuer | Entity ID |
| IdP SSO URL | Login URL | Identity Provider Single Sign-On URL | SSO URL |
| IdP X.509 Signing Certificate (PEM) | Certificate (Base64) | X.509 Certificate | Certificate |
Copy the values from your own Settings page: <id> is your workspace number. Vornin reads the person's email from the first of these it finds: the http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress claim, an attribute named emailaddress or email, urn:oid:0.9.2342.19200300.100.1.3, then the NameID. It must match the address they were invited with; letter case does not matter. Vornin signs its requests, checks the response signature against the certificate you paste, and does not support encrypted assertions or single logout, so leave token encryption and the logout URL off.
When you click out of the certificate box, Vornin checks it. A good certificate shows Valid certificate, expires <date>. Note that date: it is when sign-in will stop working unless you paste a new one. The SSO URL must start with https://. Saving with the switch on and any of the three fields empty is refused.
Step by step: Microsoft Entra ID enterprise application
- Create the application. In the Microsoft Entra admin center, open Entra ID → Enterprise apps → All applications, then New application → Create your own application. Name it Vornin, choose Integrate any other application you don't find in the gallery (Non-gallery) and click Create. You need at least the Cloud Application Administrator or Application Administrator role.
- Choose SAML. In the new application, open Single sign-on and select SAML.
- Enter Vornin's values in section 1, Basic SAML Configuration. Click Edit and set Identifier (Entity ID) to the SP Entity ID, Reply URL to the SP ACS URL and Sign on URL to the SP Sign-on URL. Leave Relay State and Logout URL empty, then save. Instead of typing the first two, you can use Upload metadata file with the metadata saved from Vornin; add the Sign on URL afterwards either way.
- Leave section 2, Attributes & Claims, as it is unless some users have no mailbox. The default
emailaddressclaim carriesuser.mail; when that is empty, Vornin uses the NameID, which defaults to the user principal name. - Copy the certificate from section 3. Download Certificate (Base64), open the .cer file in a text editor and paste everything, including the
BEGIN CERTIFICATEandEND CERTIFICATElines, into IdP X.509 Signing Certificate (PEM) in Vornin. - Copy the provider values from section 4. Paste Microsoft Entra Identifier into IdP Entity ID and Login URL into IdP SSO URL. Vornin does not use the Logout URL.
- Assign people. Open Users and groups in the application, click Add user/group and add the users or groups who should sign in. Assigning a group needs Entra ID P1 or P2; on the free tier, assign users one by one.
- Enable and test. In Vornin, switch on Enable SAML SSO for this tenant, click Save SAML Config, then Test SSO. The test opens a new tab, signs you in through Entra and returns you to the Security tab. Your own account must be assigned in Entra for the test to pass. Entra's own Test button and the My Apps tile work too, because the Sign on URL makes them start at Vornin.
Okta and Google Workspace
Okta: in the Admin Console, open Applications → Applications, click Create App Integration, select Classic experience if Okta asks, and choose SAML 2.0. On the SAML settings step, enter the SP ACS URL as Single sign-on URL and the SP Entity ID as Audience URI (SP Entity ID). Set Name ID format to EmailAddress and Application username format to Email. Leave Assertion Encryption unencrypted and Enable Single Logout off. After saving, open the application's Sign On tab and click View SAML setup instructions: it lists the Identity Provider Single Sign-On URL, Identity Provider Issuer and X.509 Certificate to paste into Vornin. Assign people under Assignments.
Google Workspace: in the Admin console, open Apps → Web and mobile apps → Add app → Add custom SAML app, name it and click Continue. The next page shows the SSO URL, Entity ID and Certificate to paste into Vornin. On the service-provider page, enter the SP ACS URL as ACS URL and the SP Entity ID as Entity ID, leave Start URL empty, set Name ID format to EMAIL and Name ID to Primary email. Then open the application, click User access and turn it on for everyone or for the right organisational units. Google says changes can take up to 24 hours, though they usually apply sooner.
Vornin accepts only sign-ins that start at Vornin. Opening the application from the Okta dashboard or the Google app launcher, or clicking Google's Test SAML login, sends a response Vornin did not ask for, and it is refused. Test with Test SSO in Vornin, and have people sign in at app.vornin.com. In Okta you can hide the tile with Do not display application icon to users. Entra's My Apps tile works because of the Sign on URL.
Verify your email domain
A verified domain tells the sign-in page which provider to use: anyone who types an address at that domain is offered your workspace's SSO.
- Add the domain. In the SSO domains panel on the Security tab of Settings → General settings, enter the part after the @ in your team's addresses in Domain (e.g. acme.com), for example
acme.com, and click Add domain. Public mail domains such as gmail.com cannot be added. The domain appears in a list with a Pending label and the DNS record to create. - Create the TXT record at your DNS host. Type TXT, name
_vornin-verify(some DNS hosts want the full name,_vornin-verify.acme.com), and the value Vornin shows, which starts withvornin-verify=. Use the copy buttons next to the name and value. - Click Verify now. The label changes to Verified and the record details disappear. If Vornin says TXT record not found yet. DNS can take a few minutes., wait and try again. To check the record yourself, run
nslookup -type=TXT _vornin-verify.acme.com. - Keep the record. Vornin rechecks every verified domain weekly and unverifies it after two failed checks in a row. A check that cannot reach DNS does not count as a failure. An unverified domain shows Pending again, with a Verify now button.
Matching is per exact domain: acme.com does not cover eng.acme.com, so verify each one your team uses. A domain can be verified by one workspace only; if another workspace already holds it, Vornin says This domain is already verified by another workspace. Contact support. A verified domain does nothing until a provider is connected and enabled.
To stop using a domain, click Remove on its row and confirm. People at a removed domain are no longer sent to your provider from the sign-in page, and Require SSO no longer applies to them. You can add and verify it again later.
Require SSO
The switch is Require SSO (users at a verified domain must sign in with SSO), at the bottom of the SSO domains panel. Change it, then click Save SSO policy.
With Require SSO off, people at a verified domain see Continue with single sign-on on the sign-in page and can still choose an email sign-in link. With it on, someone at a verified domain whose only Vornin workspace is yours, or who has no account yet, is sent straight to your provider. Email sign-in links, Google and GitHub sign-in are refused for them, including through registration, for as long as your workspace has a usable SSO provider (see the note below). Addresses outside your verified domains, such as outside contractors, are not affected.
Require SSO protects your workspace, not the person's whole account. Someone who also belongs to another Vornin workspace that does not require SSO can still sign in with an email link, but it opens only that other workspace. To open yours, they must sign in through your provider. A session that did not come from your provider stops working in your workspace on the next page load, and shows Sign in with your company SSO with a Continue with SSO button. That includes your own session if you signed in with an email link, so sign in through SSO before you turn this on. Then test with a member account. New people at a verified domain join by invitation and SSO as usual.
Troubleshooting SSO
When a SAML sign-in fails after the provider has authenticated someone, the browser shows only {"error":"SAML sign-in failed."}. The specific reason is recorded under Administration → Audit log: set Action to saml.signin.fail and read the Name column. Successful SAML sign-ins are logged as saml.signin.ok. Entra ID sign-in failures send the person back to the Vornin sign-in page with a message, and are not written to the audit log.
| What you see | Cause and fix |
|---|---|
| AADSTS50105 | The person is not assigned to the application. Add them, or a group they are in, under Users and groups. |
| AADSTS700016 | Entra has no application with Vornin's identifier. The Identifier (Entity ID) must equal the SP Entity ID exactly. |
| AADSTS50011 | The reply URL does not match. The Reply URL must equal the SP ACS URL exactly. |
| Audit log: Signature validation failed. | The certificate in Vornin is not the one your provider signs with, often after a certificate rollover. Paste the current certificate. |
| Audit log: Audience does not match this tenant. | The provider's Identifier or Audience URI differs from the SP Entity ID. Copy it again from Vornin. |
| Audit log: Assertion has expired or is not yet valid. | The response arrived outside its validity window. Check that the provider's clock is correct and start again from Vornin. |
| Audit log: IdP returned status … | Your provider refused the sign-in, for example because the person is not assigned to the application. Check the provider's own sign-in log. |
| Audit log: SAML response failed validation. | Another check on the response failed. Compare IdP Entity ID in Vornin with the issuer your provider shows, character for character. |
| Audit log: Your IdP returned an email that's not a member of this Vornin workspace, not a member, or user not found | The address your provider sent does not belong to a member of this workspace. Invite that address, or change the email claim so it sends the address the person was invited with. A first SSO sign-in creates the account only for a pending invitation at a verified domain. |
| Audit log: SAML assertion is missing an email attribute. | The response carried no email claim and no NameID. Add an email claim, or set the NameID to the user's email. |
| Audit log: AuthnResponse missing InResponseTo (IdP-initiated flow not supported in MVP). | Sign-in was started from the provider's dashboard or test button. Set the Sign on URL in Entra, or sign in from Vornin. |
| Audit log: InResponseTo unknown or expired (possible replay), or Browser correlation failed. | More than ten minutes passed, the page was reloaded, or sign-in finished in a different browser. Start again from Vornin in one browser. |
| Audit log: SSO not configured for this tenant. | SAML was switched off or its settings were incomplete when the response arrived. Check the SAML panel and save it again. |
{"error":"SAML SSO is not configured for this tenant."} | Sign-in started while SAML is off or incomplete. Switch on Enable SAML SSO for this tenant, fill all three provider fields and click Save SAML Config. |
{"error":"SAML SSO requires the Business plan."} | The workspace's plan does not include SSO. Upgrade under Settings → Billing. |
| IdP entity ID, SSO URL, and signing certificate are all required. | Shown when you save with SAML switched on. Fill all three fields in step 3 of the panel. |
| IdP SSO URL must be an absolute HTTPS URL without embedded credentials. | Paste the full https:// sign-in URL from your provider. |
| That doesn't look like a valid PEM certificate, Signing certificate failed to parse, or This certificate expired on … | Paste the Base64 (PEM) certificate including the BEGIN and END lines, not the binary .cer or a fingerprint. An expired certificate must be replaced in your provider first. |
| The sign-in page does not offer SSO | The domain is not verified, it is a subdomain you have not verified, or SAML is switched off or was never saved. |
| Test SSO is greyed out | Switch on Enable SAML SSO for this tenant and click Save SAML Config first. |
| After Connect with Entra ID, the panel still shows the button | Consent did not complete. The message at the top of the Security tab gives the reason, and the address bar shows its code after ?error=: entra-consent-denied (Microsoft returned an error: consent was declined, or the account cannot grant it, so use an account with one of the roles above), entra-consent-failed (Microsoft did not confirm admin consent: start again), invalid-state (more than ten minutes passed, or Vornin restarted in between: start again), sso-busy (too many connections are in progress: wait ten minutes and start again), sso-required (Require SSO is on and you signed in without SSO: sign in through your current provider first), sso-plan-required (the plan does not include SSO), not-authorized (you are not the workspace Owner, or stopped being the Owner before consent finished), entra-not-configured (contact Vornin support). |
| Your email is not provisioned for this organization | Entra ID authenticated the person, but the address in the token is not a member of the workspace. Invite that address, or make sure the user's Email in Entra matches the invited address. |
| Could not retrieve your email from SSO | The Entra token carried no email, preferred username or UPN. Give the user an email address in Entra. |
| SSO sign-in failed. Please try again or use email. | Entra ID returned an error or a token Vornin could not validate, or the sign-in took longer than ten minutes. Start again from the Vornin sign-in page in one browser. |
| SSO is not configured for this organization. | Entra ID is not connected for the workspace in the sign-in address, or was disconnected. Check the address, or connect Entra ID again. |
| Your account is not authorized. Please contact an administrator. | The person's Vornin account is deactivated, or the workspace is not active. An Owner or Admin can reactivate the account under Administration → Users & roles. |
| Your organization requires single sign-on, or This organization requires single sign-on. Use your SSO to sign in. | Require SSO is on for the person's domain, so email links, Google and GitHub sign-in are refused. Use SSO, or see the note under Require SSO. |
Choose notifications#
Open the account menu (your name, top right), choose Account and select the Notifications tab. For each event, tick In-app, Email or both, then click Save preferences. Changes affect your own notifications, not your teammates'. In-app alerts appear in the bell menu. Tick Send emails as a daily digest instead of immediately to get one email a day.
The events are Scan complete, New vulnerabilities, SLA breach, Posture drift, Compliance changes, Cadence breach, Emerging threats, New assets discovered and Assigned to me. Owners and Admins also see Pull request not checked, Ticket sync drift and Getting-started tips (at most two tips in a new workspace's first days). Email starts switched on only for Assigned to me, Pull request not checked, Ticket sync drift and Getting-started tips.
Workspace reminders live under Settings → General settings, on the Notifications tab, for Owners and Admins: tick Enable email reminders for assigned vulnerabilities, set Reminder interval (days) between 1 and 90 and click Save Notification Settings.
Per-scan completion emails are set on the scan itself. On the New scan page, tick Email me when this scan completes and fill in Send to. For a schedule, open Scans → Schedules and tick Email when each scheduled run completes. Separate recipients with commas or semicolons, up to ten per scan.
Failed scans can send completion emails too. Read the status and any error instead of relying on the finding count. Cancelled scans do not send those completion emails.
To post to Slack, Microsoft Teams or Discord, add a webhook of that type. Webhooks need the Team plan or above.
Connect issue tracking#
An Owner or Admin opens Administration → API & integrations, selects the Issue tracking tab and clicks Add integration. Issue tracking is available on every plan. Choose the Provider: Jira Cloud, GitHub, GitLab or Azure DevOps. GitHub Enterprise Server and self-managed GitLab work through Base URL (optional); Jira Server and Data Center are not supported.
Fields and tokens for each provider
Every integration needs a Display name. Save stays greyed out until the required fields for the provider are filled. Vornin stores the token encrypted and never shows it again; when you edit an integration, leave the token empty to keep it. Changing the provider, site URL or Jira email needs a new token.
| Provider | Fields | Token and permissions |
|---|---|---|
| Jira Cloud | Jira site URL, such as https://your-org.atlassian.net. Jira account email: the account that owns the token. API token. Default issue repo: the project key, such as SEC, rather than its display name. Issue type: defaults to Bug. | Create a token at id.atlassian.com → Security → API tokens, or use a service account (Atlassian Administration → Directory → Service accounts). In the project, the account needs Browse Projects and Create Issues, plus Schedule Issues for due dates, and Transition Issues and Add Comments for status sync. Atlassian tokens expire after at most 365 days; paste a new one before that. |
| GitHub | Base URL (optional): leave blank for github.com. Personal Access Token. Default issue repo: owner/repo. | A classic token with repo for a private repository, or public_repo for a public one. A fine-grained token needs access to that repository with Issues: Read and write. |
| GitLab | Base URL (optional): leave blank for gitlab.com. Personal Access Token. Default issue repo: the project path, such as acme/widgets or acme/sub/widgets. | A token with the api scope, from a user who is a member of the project. |
| Azure DevOps | Organization URL, such as https://dev.azure.com/your-org. Personal Access Token. Default issue repo: the project name only, not project/repo. | A PAT with Work Items: Read & write. Vornin creates work items of type Bug. |
With a Jira service account, or any API token created with scopes, Atlassian only accepts the token through its API gateway. Enter https://api.atlassian.com/ex/jira/<cloud id> as the site URL instead; your cloud id is shown at https://your-site.atlassian.net/_edge/tenant_info. Grant the scopes read:jira-user, read:jira-work and write:jira-work. Tickets still link to your own Jira site, and Test connection shows which address they will use.
Click Test connection before saving. Success reads Authenticated as '…'., followed by any warnings, for example that priority or due date is not on the Jira create screen. A failure always reads Connection test failed. Check the URL and token, then try again.: check the URL format, the token and its expiry.
Choose what gets filed
Switch on File issues for new vulnerabilities, then choose a Minimum severity: Critical only, High or above (the default), Medium or above or Low or above. Info findings are never filed. Max new issues per scan caps how many tickets one scan opens, most severe first; leave it empty for no limit. Vornin files tickets when it processes a scan's results, not when you save, and there is no button to file a single finding by hand. Each ticket is labelled vornin, severity:<level> and scanner:<type>, plus the CVE id when there is one, and kev, exploit-known or reachable when they apply. New tickets also carry an Exploitability section (CISA KEV listing, known exploit, EPSS, CVSS, reachability and the fixed version, where known). A very long scanner description is shortened in the ticket, with a link to the full finding. To confirm it works, look in your tracker after the next scan.
Vornin files each finding once per integration. Dependency findings are the exception: every vulnerability of one package version in one manifest shares one ticket, for example axios 1.12.2 in package-lock.json. A new vulnerability in that package is added to its open ticket with a comment, and the ticket closes when all of them are resolved. The finding's full page lists every ticket filed for it, with a link to each one in your tracker. With Close tickets when fixed, reopen if the finding returns switched on, Vornin notices when a ticket was deleted in your tracker and shows it on the finding as deleted in the tracker; an Owner or Admin can click Re-file, and the next scan that detects the finding files a new ticket. Moving an asset to another group does not move its existing tickets: the integration for the new group files new ones, and the old tickets stay open until you close them. Deleting an integration stops filing, pull request comments and ticket sync through it and removes its token; the tickets it already filed stay linked on their findings.
To send each team its own findings, add one integration per team and pick the team's asset groups under Only findings from these groups. For example, put team A's repositories in one group and point it at Jira project TEAMA, and team B's in another pointed at TEAMB. Leave the field empty to file issues for every finding. An integration whose groups are later emptied or deleted files nothing until you update it. When you move an asset out of an integration's groups, that integration stops syncing the tickets it filed for the asset's findings: it no longer closes, reopens or edits them, and they leave its sync health page. Vornin leaves those tickets as they are, so close or move them in the tracker yourself; the integration that now covers the asset files its own. The finding page marks them as no longer synced. Move the asset back and sync resumes on the same tickets. Once every integration is scoped to groups, findings on ungrouped assets are not filed anywhere, so keep one unscoped integration or put every asset in a group.
New integrations file only findings first detected after you open the Add integration dialog, so connecting a tracker does not open a ticket for every existing finding. Turn off Only file findings first detected from now on to file the open backlog: each open finding is filed the next time a scan detects it.
For Jira and Azure DevOps, Set priority from severity maps each severity to a priority. Jira uses your site's priority names (Highest, High, Medium and Low by default, for Critical, High, Medium and Low); Azure DevOps uses 1 to 4. Set the due date from the remediation deadline (SLA) copies the finding's SLA date to the ticket in Jira, GitLab and Azure DevOps. Every ticket links back to the finding, and states the remediation deadline when the finding has one. Use Test connection to check that the Jira project accepts both fields. If the tracker rejects them, the ticket is filed again without them, and Vornin leaves both fields off for the next 6 hours. With ticket sync switched on (below), Vornin keeps these fields current: a changed SLA policy moves the due date, a severity change moves the priority and the severity: label once it has held for a day, and a finding added to the CISA KEV list takes the Critical priority. Vornin only changes a field when its own value changes, so a priority or due date someone set by hand stays until then. In Jira these edits are silent when the integration's account can administer the project; otherwise they follow the project's notification scheme.
Turn on Close tickets when fixed, reopen if the finding returns to keep tickets in step with findings. Vornin closes the ticket when the finding is resolved or dismissed, reopens it when the finding is open again, and adds a comment when it changes the ticket. When a scan resolved the finding, the ticket closes after the resolution has held for a day, or three days if one scan resolved more than 50 of that integration's tickets, so one scan with a coverage gap does not close tickets. A code finding that only moved, because lines were added or removed above it, keeps its ticket when Vornin can tell which line moved where: the ticket follows the finding to its new line, with a comment saying where it went, and the finding keeps its first-detected date and due date. When the move is not clear, the old ticket closes on the next sync, without waiting, and a ticket is filed for the new line. Sync runs at least every ten minutes, so a finding you resolve or dismiss by hand closes its ticket within about ten minutes. It uses the workflow's own done and to-do statuses; if the workflow has no matching transition, Vornin asks for the change in a comment instead. Vornin only changes what it changed before, so a ticket someone closed by hand while the finding is still open stays closed. The token needs permission to edit and transition issues. If the tracker refuses the token or rate-limits Vornin, sync for that integration pauses for 30 minutes.
When a finding is dismissed, its ticket closes with a comment naming the decision (accepted risk, false positive or compensating control), who approved it and the review date, and gets the label vornin-risk-accepted, vornin-false-positive or vornin-compensating-control. The label comes off when the acceptance lapses and the finding reopens. In Jira, Vornin closes it through a Won't Do transition or resolution when the workflow has one.
In Jira, Vornin also reads the project once an hour. The finding shows who its ticket is assigned to in Jira (display only: Vornin's own assignee is separate and is never sent to Jira). To ask for a risk acceptance from Jira, add the label risk-accept-request to the ticket. Within the hour the finding shows the request, and an Owner or Admin either accepts it, giving the type, approver and review date, or declines it, which removes the label and keeps the finding open. The Sync health page (the heart icon next to the integration) lists findings in scope without a ticket, tickets someone closed by hand while the finding is open, tickets deleted in the tracker, closes waiting to settle and pending requests. Owners and Admins get one email a day when any of these change; each of them can switch it off with Ticket sync drift under notifications. Comments Vornin writes in your tracker on behalf of a person name that person, except a Vornin support engineer working in your workspace, who appears as a Vornin support engineer; your audit log records who it was.
Import existing scan results#
Open Scans and click Import results, or choose Import scan results… from the Export findings menu on Vulnerabilities. Any workspace member can import, on every plan.
- Choose the Format: CSV, Nessus (.nessus XML), OpenVAS (XML report) or JSON (array of findings).
- Optionally change the Scan name.
- Click Upload file to load a
.csv,.nessus,.xml,.jsonor.txtfile of up to 10 MB, or paste the content into Paste file content. The file must be plain text. - Click Preview. Vornin shows how many findings it parsed, by severity.
- Click Import n finding(s). Vornin opens the new scan.
For CSV, the documented columns are below. Click Download CSV template for a file with these headers:
Title,Severity,Host,Port,Description,Solution,CVE
What each format reads
| Format | How Vornin reads it |
|---|---|
| CSV | The first row must be the header. Header names ignore case, and IP or Target also work for Host, Remediation for Solution and CVE_ID for CVE. Rows with an empty title are skipped. Severity accepts Critical, High, Medium, Low and Info, or 4 to 0; a blank or unrecognised value becomes Medium. |
| Nessus | One finding per report item, with host, port, plugin name, description, solution, the first CVE and the CVSS vector. Nessus severities 4 to 1 become Critical to Low; informational items are skipped. |
| OpenVAS | One finding per result in a GVM or OpenVAS XML report. Log, Debug and False Positive results are skipped. |
| JSON | An array of objects with the fields title, severity ("High", or 0 to 4), affectedHost, affectedUrl, port, description, solution, cveId and cvssVector. host, ip or target also work for affectedHost. Every finding needs a host; a file with a finding that has none is refused, and the message names it. A finding without a severity is imported as Info. |
The import appears as a completed scan with Partial result processing and feeds the normal finding workflow. Imported findings are not linked to a target, and no completion email or webhook is sent. Importing the same finding again (same host, title and URL) updates the existing finding instead of adding a duplicate. A later import that reports a finding you resolved can reopen it. A finding missing from a later import is not resolved automatically; resolve it yourself. The import records supplied results; it does not mean Vornin independently scanned every imported asset. Keep the original tool's scope and coverage with your evidence.
API keys and automation#
Every REST API key needs at least one explicit permission. Select only the scopes your automation uses: scan:create, scan:read, scan:cancel, vuln:read, vuln:write, vuln:triage, target:read, webhook:read, webhook:write or reports:export. A valid key receives HTTP 403 for an operation outside its scopes. Keys with missing or unrecognised scopes are refused. API access is available on Team, Business and Scale, subject to the operation's own entitlement and the creator's current workspace role.
The REST API is included in Team, Business and Scale. Only an Owner or Admin can create or revoke keys; a Member who opens the page sees Admin access required. On Free, Generate API key answers API access is not available on your current plan. Build gates and pull request scanning need Scale.
- Open Administration → API & integrations, stay on the API keys tab and click Generate API key.
- Enter a Key Name that says where the key will live, such as
GitHub Actions. Choose when it Expires: Never, 30 days, 90 days (the default), 180 days, 1 year or Custom date…, which asks for an Expiration date; the key stops at 00:00 UTC on that date. Select the permissions under Permissions, then click Generate. To use the target-list example below, includetarget:read. - Copy the key from the Copy your API key now banner. It starts with
cvk_, is 36 characters long and is shown only once. The list keeps only its first 12 characters, under Key prefix, so you can tell keys apart. - Store it in your automation's secret store and send it in the
Authorizationheader on every request. Vornin reads keys from that header only, never from a query string. - Test it. A JSON list of your targets means the key works, and its Last used date updates.
Authorization: Bearer <your-api-key>
curl -sS -H "Authorization: Bearer $VORNIN_API_KEY" https://app.vornin.com/api/v1/targets
The key list shows each key's Created, Expires and Last used dates and a Status of Active, Expired or Revoked. To revoke a key, click Revoke and confirm; clients using it lose access on their next request, and a revoked key cannot be restored. Any Owner or Admin can revoke any key in the workspace, and each generate and revoke is recorded in Administration → Audit log. There is no rotate button: to rotate, generate a replacement, update and test your integration, then revoke the old key.
What a key can do
Scopes limit a key to the operations selected at creation. A key with only read permissions cannot change data. For calls that change something, Vornin also checks the creator's current workspace role: starting and cancelling scans and changing or triaging findings need Member or above, the same as in the app, and managing webhooks needs Owner or Admin. Demotion to Member blocks webhook management even when the scope is present. Departure, workspace-membership suspension or global account deactivation revokes the creator's keys in the affected workspace. Workspace suspension blocks access; downgrading to Free removes API access.
If your workspace has an IP allowlist, it applies to API keys too. A call from any other address gets 403 with the text Access denied: IP not in allowlist. Hosted CI runners (GitHub-hosted runners, Microsoft-hosted Azure Pipelines agents) have no fixed address, so run API jobs on a self-hosted runner or agent with a fixed outbound address and put that address on the list.
Rate limits
Each workspace has an hourly request budget, counted over the last 60 minutes across every /api/v1 call from every key: Team 1,000, Business 5,000 and Scale 50,000. Each response carries X-RateLimit-Limit and X-RateLimit-Remaining. Over the limit, the answer is 429 with Retry-After: 60 and a body such as {"error": "API rate limit exceeded (1000/hour)"}. Creating scans has two more limits, both answered with 429: standard scans started per hour (Team 30, Business 300, Scale unlimited) and scans queued or running at once (Team 3, Business 10, Scale 25).
Endpoints
All paths are under https://app.vornin.com/api/v1. Responses are JSON with camelCase field names.
| Call | What it does |
|---|---|
GET /targets | Every target in the workspace with its id, name and host. Use it to find a target ID. Not paged. |
POST /scans | Creates a scan (creator must be a Member or above). Body fields are below. Returns 200 with id, name, status (Queued) and purpose. |
GET /scans | Scans, newest first: id, name, status, createdAt, completedAt, purpose. Paged with take (default 50, at most 200) and skip. purpose is described under pull request scanning. |
GET /scans/{id} | One scan: status (Queued, Running, Completed, Failed or Cancelled), progressPercent, progressMessage, createdAt, startedAt, completedAt, purpose, pullRequestNumber, scannedCommitSha. |
DELETE /scans/{id} | Cancels a queued or running scan (creator must be a Member or above). Any other status answers 409. |
GET /scans/{id}/gate | Build gate verdict (Scale). See CI/CD build gate. |
GET /vulnerabilities | Findings, highest severity first, with id, title, description, severity, status, sourceScanner, affectedHost, affectedUrl, cveId, cvssScore, firstDetectedAt, lastDetectedAt, lastSeenInScanId, slaDueAt, slaBreached and reachability. Filter with severity (Info, Low, Medium, High, Critical), status (Open, Acknowledged, Dismissed, Resolved) and scanId. An unrecognised filter value answers 400. Merged duplicates are not listed: each finding appears once. Paged with take (default 100, at most 500) and skip. |
PUT /vulnerabilities/{id} | Changes a finding's status (creator must be a Member or above). Body: status (required, one of the four above), optional note, and with Dismissed an optional dismissType of FalsePositive, RiskAccepted or CompensatingControl. |
POST /vulnerabilities/{id}/triage | Generates an AI triage suggestion (creator must be a Member or above). Answers 503 when AI enrichment is not switched on for the platform. |
GET, POST /webhooks; PUT, DELETE /webhooks/{id}; POST /webhooks/{id}/test | Manage outbound webhooks (Team and above; changes need the creator to be Owner or Admin). A key whose creator is now a Member sees each webhook URL reduced to its scheme and host. |
Paging uses take and skip only. page and pageSize are ignored, so a loop that sends them reads the first page forever. Increase skip by take until a call returns fewer rows than take, and deduplicate by id, because the order can shift while a scan is being processed.
The POST /scans body accepts these fields. Send at least one of targetIds and codeRepositoryIds. The body is capped at 1 MB (413 above that).
| Field | Meaning |
|---|---|
targetIds | Target IDs from GET /targets. |
codeRepositoryIds | Repository IDs, shown after ID next to each repository in Assets → Repositories. |
scanTypes | Scan type names, for example ["Sast", "SecretScan", "DependencyScan"]. Target types: PortScan, SslTls, WebVulnerability, DnsSecurity, SubdomainEnum, SubdomainTakeover, ApiSecurity, NucleiTemplate, Wordpress, SupabaseExposure, ContainerImage (a docker:// target), Kubernetes (a k8s:// target), and Full, which stands for SubdomainEnum, PortScan, SslTls, WebVulnerability, DnsSecurity, ApiSecurity, SubdomainTakeover and NucleiTemplate. Repository types: Sast, SecretScan, DependencyScan, IacConfig, LicenseScan, EndOfLife. Omitted, Vornin runs Full on the targets and every repository type on the repositories. Cloud posture scans cannot be started through the API. |
name | Optional. Defaults to API Scan plus the date and time. |
notifyOnCompletion, notifyEmails | Optional. notifyEmails is a comma-separated list of up to 10 addresses that get the completion email. |
enableActiveProbes | Optional, default false. Turns on active attack probes. |
pullRequest | Makes this a pull request scan. See Pull request scanning. |
Errors
| Response | Cause and fix |
|---|---|
| 401 Unauthorized | The key is missing, mistyped, expired or revoked, or its creator has left the workspace. Vornin returns HTTP 401. Keep redirect following disabled in your client and treat an unexpected redirect as a failure. Check the header reads Bearer cvk_…, and generate a new key if needed. |
| 400 | A field is missing or invalid. The body says which, for example One or more targetIds do not exist or do not belong to this tenant. |
403 with upgrade_url | Your plan does not include the feature, for example REST API access is not included in your current plan. Upgrade under Settings → Billing. |
403 This action requires tenant role Admin or higher. | The key's creator is now a Member. Have an Owner or Admin generate a new key. |
403 Access denied: IP not in allowlist | The caller's address is not on the workspace's IP allowlist. |
| 404 | The ID does not exist in this workspace. |
| 429 | A rate limit or scan limit was reached. Wait Retry-After seconds, or for running scans to finish. |
The interactive API reference lists every route and query parameter; the OpenAPI document is at https://app.vornin.com/api-docs/v1/swagger.json. Sign in to the app first, or send your key. It describes the request bodies of POST /scans, PUT /vulnerabilities/{id} and the webhook routes; the tables above say which fields are required.
CI/CD build gate
On Scale, request a verdict for the scan you ran. Do not infer a pass from an empty findings list.
GET /api/v1/scans/{id}/gate?failOn=high
The threshold accepts critical, high, medium or low, and defaults to high. Findings this scan detected that are Open or Acknowledged and at or above the threshold block the gate; Info never blocks. A 200 or 422 verdict carries passed, blocking (the number of blocking findings), counts by severity, failOn, scanStatus and degradedCoverage, a list of coverage notices that did not stop the verdict. mode applies only to pull request scans and answers 400 on a standard scan.
| Response | Pipeline action |
|---|---|
| 200 | Read the verdict and coverage notices. A pass applies to the assessed scope. |
| 400 | Invalid severity threshold. Correct failOn and do not pass the build. |
| 401 | Authentication failed: the key is missing, wrong, expired or revoked. API requests return 401. Do not pass the build. |
| 403 | Access denied: the plan is not Scale (CI/CD build gates are not included in your current plan.) or the IP allowlist excludes the runner. Check access and plan, and do not pass the build. |
| 404 | Scan not found in this workspace. Check the scan ID and workspace, and do not pass the build. |
| 409 | No verdict yet: the scan is queued or running, or its results are still being processed. Poll with a bounded timeout and retry. |
| 422 | Fail the gate. Either the verdict has blocking findings (passed: false with counts), or Vornin refused a verdict and error says why: the scan failed or was cancelled, a scanner did not run (degradedScanTypes names it), or its results could not be processed. Re-run the scan after fixing the cause. |
| Other error or timeout | Do not pass. Handle throttling or connection failure. |
This executable Python example requires requests (python -m pip install requests). Store VORNIN_API_KEY as a CI secret, set VORNIN_TARGET_ID to a target's id from GET /api/v1/targets and VORNIN_REPO_ID to the repository ID shown in Assets → Repositories. The key needs scan:create and scan:read; include target:read if you use the target-list call to find IDs. The key's creator must be a Member or above; build gates require Scale. Set the CI job timeout above 180 minutes.
This example adopts a strict coverage policy: it rejects even standing coverage notices that the API reports with a passing verdict. Missing fields, unknown verdicts, HTTP failures, redirects, invalid JSON and connection errors cannot produce a successful exit.
"""Fail closed unless the Vornin gate confirms this scan is clean."""
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']}"})
# Both subjects must exist in the workspace and support the selected scan types.
r = session.post(f"{BASE}/scans", json={
"targetIds": [int(os.environ["VORNIN_TARGET_ID"])],
"codeRepositoryIds": [int(os.environ["VORNIN_REPO_ID"])],
"scanTypes": ["SslTls", "WebVulnerability", "DnsSecurity", "Sast", "SecretScan", "DependencyScan"],
}, timeout=30, allow_redirects=False)
if r.status_code not in (200, 201):
sys.exit(f"Scan creation refused: HTTP {r.status_code}")
scan_id = r.json()["id"]
if type(scan_id) is not int or scan_id <= 0:
sys.exit("Scan creation returned an invalid scan id")
# Bound polling to 180 minutes; HTTP calls also have a 30-second timeout.
deadline = time.monotonic() + 180 * 60
while time.monotonic() < deadline:
r = session.get(f"{BASE}/scans/{scan_id}/gate", params={"failOn": "high"},
timeout=30, allow_redirects=False)
if r.status_code == 409:
time.sleep(20) # Scan or finding ingestion is pending.
continue
if r.status_code != 200:
sys.exit(f"Vornin gate refused scan {scan_id}: HTTP {r.status_code}")
verdict = r.json()
if not isinstance(verdict, dict) or verdict.get("passed") is not True or verdict.get("degradedCoverage") != []:
sys.exit(f"Vornin scan {scan_id} has blocking findings or incomplete coverage")
print(f"PASS: no unresolved High or Critical findings (scan {scan_id})")
break
else:
sys.exit("Scan or ingestion timed out after 180 minutes")
A failed or cancelled standard scan cannot produce a passing verdict. (A pull request scan that fails is reported as not checked under onError: warn, below.) A standard gate request does not identify a pull-request revision or compare findings against a baseline. Pull request scans do, as described next.
Pull request scanning
This section is the API reference. For a setup walkthrough, see Set up the pull request gate.
On Scale, scan the code a pull request would merge and block only on findings it introduces. Send a pullRequest object with exactly one entry in codeRepositoryIds, no targetIds, and repository scan types only (Sast, SecretScan, DependencyScan, IacConfig, LicenseScan, EndOfLife; omitted means all six). Vornin clones the named commit, or fails the scan. It never scans a different commit.
"pullRequest": {
"number": 17,
"ref": "refs/pull/17/merge",
"commitSha": "<40 or 64 lowercase hex characters>",
"targetBranch": "refs/heads/main",
"gate": { "failOn": "high", "mode": "new" }
}
gate is optional and fixed when the scan is created. It takes failOn (default high), mode (new or all, default new) and onError (warn or block, default warn). onError decides what happens when Vornin cannot check the pull request: warn lets it through with a warning, block refuses it. Mode new blocks on new findings only. Mode all also blocks on existing findings that are still open on the default branch. Findings dismissed on the default branch never block. The accepted ref is refs/pull/{n}/merge for Azure DevOps, refs/pull/{n}/merge or refs/pull/{n}/head for GitHub, and refs/merge-requests/{n}/head or refs/merge-requests/{n}/merge for GitLab. Prefer merge: a head ref is compared against a base that may be stale. The number inside ref must equal number. targetBranch accepts main or refs/heads/main.
Poll GET /api/v1/scans/{id}/gate as above. 200 means passed. 200 with evaluated: false means Vornin could not check the pull request and onError is warn: warning carries the code from the table below and message the reason. For every one of these codes, the pull request comment says the same, and the workspace's Owners and Admins get a Pull request not checked notification in the app and by email, under onError: block too (at most one per repository and reason per day). For a scan that failed or was cancelled (scan_not_gateable) or was never evaluated (not_evaluated), the comment and the notification follow within about ten minutes of the scan ending. A scan cancelled because a newer scan of the same pull request replaced it gets neither: the newer scan decides. 422 means blocked, or refused under onError: block: when passed is false with a verdict body, read blocking and newFindings; when the body carries an error code, see the table below. Send no failOn or mode, or send the stored values, otherwise the answer is 400. A verdict lists passed, blocking, scannedCommitSha, the baseline scans used, counts (the blocking findings by severity), a breakdown of new, existing, existing still open, fixed and suppressed findings, and up to 50 newFindings. Check that scannedCommitSha is the commit your pipeline built.
| Response | Meaning and remedy |
|---|---|
422 passed: false | The pull request adds blocking findings. Read blocking and newFindings and fix them. This is a verdict, not a refusal, and onError does not change it. |
200 evaluated: false | Vornin could not check the pull request, so it is not blocked (onError: warn). warning is one of the codes below, scan_not_gateable when the scan failed or was cancelled, or not_evaluated when no evaluation was recorded. Fix the cause in message; until then, review such pull requests yourself. |
422 no_baseline at creation | The pull request targets a branch other than the repository's branch in Vornin (the one chosen at import). Vornin refuses the scan before it starts, whatever onError says, and the script stops with Pull request scan refused: HTTP 422. Pull requests into other branches are not supported yet. |
422 no_baseline (onError: block) | The default branch has no usable scan for a requested type. A baseline must be under 30 days old and not degraded. When one is missing, Vornin starts a default-branch scan with the missing types and evaluates the pull request when it finishes (the creation response carries baselineScanId), so you see this only if that scan did not finish within 120 minutes, could not start (scan quota), or a default-branch scan already ran in the last six hours without producing a usable baseline. Fix the reason in the message, scan the default branch, and re-run the pipeline. |
422 baseline_busy (onError: block) | A default-branch scan is still being processed. Re-run the pipeline once it finishes. |
422 degraded_coverage (onError: block) | A scanner did not run or returned partial results. Fix the named scanner and re-run the pipeline. |
422 commit_mismatch (onError: block) | The scanner host could not check out the named commit. Check that the commit is pushed and ref points at it, then re-run. |
422 with a sentence in error (onError: block) | The scan failed, was cancelled or was never evaluated, so there is nothing to read. Fix the cause in error and re-run the pipeline. |
| 422 for an older run | A newer scan of the same pull request replaced this one, whatever onError says. The newer pipeline run decides; ignore the older one. |
| 400 at creation | Bad ref (the message lists the accepted forms), bad commitSha (it must be the full 40 or 64 lowercase hex characters), missing targetBranch, more than one repository or any target, a non-code scan type, an invalid gate.failOn, gate.mode or gate.onError, a repository that does not exist in this workspace or is paused, a repository on a host other than Azure DevOps, GitHub or GitLab, notifyOnCompletion or notifyEmails, or pullRequest combined with the top-level pullRequestNumber or commitSha fields. |
| 403 at creation | The plan is not Scale, the pull request quota is 0, the API key's creator is now a Member, or the IP allowlist excludes the runner. If the creator has left the workspace or their account was deactivated, the key is revoked and the answer is HTTP 401 instead and the script stops with Pull request scan refused: HTTP 401. Generate a key as a current Owner or Admin. |
| 429 at creation | The hourly pull request scan quota (Scale: 120) is used up, the workspace already has 25 scans queued or running, or the hourly API budget is spent. The script retries 429 until its deadline. |
GET /api/v1/scans lists standard scans by default. Add ?purpose=pull_request for pull request scans or ?purpose=all for both; standard is the default, and any other value is a 400. Each item carries purpose, and GET /api/v1/scans/{id} adds pullRequestNumber and scannedCommitSha. Page with take and skip, as above.
A newer scan for the same repository and pull request cancels older queued or running ones. Pull request scans do not appear in the default scan list, dashboards, compliance evidence or the audit pack, and do not change the last-scanned date. In the app, filter the scan list by Pull requests to see them. To run one again, re-run the pipeline. If Comment on pull requests is on for the repository's integration (Administration → API & integrations, Issue tracking tab, edit the integration; see step 8 of the setup for the matching rules), Vornin keeps one comment on the pull request and edits it in place. Its first line names the commit it describes and says it may not reflect newer commits.
Save this script as vornin_pr_gate.py in the repository. Store VORNIN_API_KEY as a pipeline secret and set VORNIN_REPO_ID to the repository's ID in Vornin, shown under the repository name in Assets → Repositories. Set the job timeout above 120 minutes. It fails the pipeline on blocking findings, a setup error (any other 4xx, a redirect, an invalid scan id), an unknown verdict, or a passing verdict for a different commit than the one the pipeline built. It retries network errors, 429 and 5xx until its 120-minute deadline. When Vornin cannot check the pull request (still unreachable or failing at the deadline, not finished within 120 minutes, or the gate answers evaluated: false), it prints a warning and lets the pull request through. Set VORNIN_ON_ERROR=block to fail on those too.
"""Vornin pull request gate: block on new blocking findings, warn when Vornin cannot check the PR."""
import os
import sys
import time
import requests
BASE = "https://app.vornin.com/api/v1"
SEVERITY = {0: "Info", 1: "Low", 2: "Medium", 3: "High", 4: "Critical"} # the API sends severity as a number
# "warn" (default): Vornin being unreachable or unable to check the PR never blocks a merge.
# "block": every problem fails the pipeline.
ON_ERROR = os.environ.get("VORNIN_ON_ERROR", "warn").strip().lower()
commit = os.environ["COMMIT_SHA"]
# Bound the whole run to 120 minutes; HTTP calls also have a 30-second timeout.
deadline = time.monotonic() + 120 * 60
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {os.environ['VORNIN_API_KEY']}"})
def not_checked(message):
if ON_ERROR == "block":
sys.exit(message)
# Shown as a warning annotation on Azure DevOps and GitHub Actions.
print(f"##vso[task.logissue type=warning]{message}" if os.environ.get("TF_BUILD") else f"::warning::{message}")
print("NOT CHECKED: the pull request was not checked by Vornin and is not blocked. Review it yourself.")
sys.exit(0)
def call(method, path, **kwargs):
# Network errors, 429 and 5xx are retried until the deadline, so a deploy or one slow answer never lets a
# pull request through. Only the deadline turns them into "not checked".
while True:
try:
r = session.request(method, f"{BASE}{path}", timeout=30, allow_redirects=False, **kwargs)
if r.status_code != 429 and r.status_code < 500:
return r
problem = f"HTTP {r.status_code}"
except requests.RequestException as e:
problem = type(e).__name__
if time.monotonic() >= deadline:
not_checked(f"Vornin was unreachable or kept failing for 120 minutes ({problem})")
time.sleep(20)
r = call("POST", "/scans", json={
"codeRepositoryIds": [int(os.environ["VORNIN_REPO_ID"])],
"scanTypes": ["Sast", "SecretScan", "DependencyScan"],
"pullRequest": {
"number": int(os.environ["PR_NUMBER"]),
"ref": os.environ["PR_REF"],
"commitSha": commit,
"targetBranch": os.environ["TARGET_BRANCH"],
"gate": {"failOn": "high", "mode": "new", "onError": ON_ERROR},
},
})
if r.status_code not in (200, 201):
sys.exit(f"Pull request scan refused: HTTP {r.status_code}") # A setup problem: fix the key, repo id or plan.
scan_id = r.json()["id"]
if type(scan_id) is not int or scan_id <= 0:
sys.exit("Scan creation returned an invalid scan id")
while time.monotonic() < deadline:
r = call("GET", f"/scans/{scan_id}/gate")
if r.status_code == 409:
time.sleep(20) # Scan or evaluation is pending.
continue
try:
body = r.json()
except ValueError:
body = {}
if r.status_code == 422 and isinstance(body, dict) and body.get("passed") is False and "blocking" in body:
for f in body.get("newFindings", [])[:20]:
print(f" [{SEVERITY.get(f.get('severity'), f.get('severity'))}] {f.get('title')} {f.get('file') or ''}:{f.get('line') or ''}")
sys.exit(f"BLOCKED: {body['blocking']} blocking finding(s) in this pull request (scan {scan_id})")
if r.status_code != 200:
reason = body.get("error", "") if isinstance(body, dict) else ""
sys.exit(f"Vornin gate refused scan {scan_id}: HTTP {r.status_code} {reason}")
if isinstance(body, dict) and body.get("evaluated") is False and body.get("passed") is True:
not_checked(f"Vornin could not check this pull request (scan {scan_id}, {body.get('warning')}): {body.get('message')}")
if not isinstance(body, dict) or body.get("passed") is not True or body.get("scannedCommitSha") != commit:
sys.exit(f"Vornin scan {scan_id} has blocking findings or describes another commit")
print(f"PASS: no new blocking findings in this pull request (scan {scan_id})")
break
else:
not_checked(f"Vornin did not finish scan {scan_id} within 120 minutes")
Azure DevOps:
# azure-pipelines.yml. Attach it as a Build validation branch policy on the target branch.
# trigger: none stops every push to the target branch from running it as a CI build with no PR variables.
trigger: none
pr: none
jobs:
- job: vornin_pr_gate
pool: {vmImage: ubuntu-latest}
timeoutInMinutes: 150 # Job-level: Azure Pipelines rejects it at the pipeline root.
steps:
- script: |
python -m pip install requests
python vornin_pr_gate.py
displayName: Vornin pull request gate
env:
VORNIN_API_KEY: $(VORNIN_API_KEY)
VORNIN_REPO_ID: $(VORNIN_REPO_ID)
PR_NUMBER: $(System.PullRequest.PullRequestId)
PR_REF: refs/pull/$(System.PullRequest.PullRequestId)/merge
TARGET_BRANCH: $(System.PullRequest.TargetBranch)
COMMIT_SHA: $(Build.SourceVersion)
GitHub Actions:
# .github/workflows/vornin-pr.yml
on: pull_request
jobs:
vornin-gate:
runs-on: ubuntu-latest
timeout-minutes: 150
steps:
- uses: actions/checkout@v4
- run: |
python -m pip install requests
python vornin_pr_gate.py
env:
VORNIN_API_KEY: ${{ secrets.VORNIN_API_KEY }}
VORNIN_REPO_ID: ${{ vars.VORNIN_REPO_ID }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_REF: refs/pull/${{ github.event.pull_request.number }}/merge
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
COMMIT_SHA: ${{ github.sha }}
Limits. The first pull request on a repository waits for its default-branch scan, so it can take as long as a full scan. The baseline is the default branch as it is when the pull request is evaluated, not when the branch was cut. A renamed or moved file shows its findings as new. A second instance of the same rule in the same file with the same code reads as existing. A finding the default branch's automatic false-positive filter would dismiss can count as new in a pull request. Pull requests into a branch other than the default are not supported yet. On GitHub, a workflow run for a pull request from a fork does not receive repository secrets, so the gate fails on fork pull requests. Vornin does not check the provider's current revision, so the comment describes the latest commit submitted.
Set up the pull request gate#
The pull request gate scans the code a pull request would merge and fails the build when the pull request adds findings at or above a severity you choose. It needs the Scale plan. You need the Owner or Admin role in Vornin, and on the repository host the rights to add a pipeline or workflow and to change branch protection.
Supported: Azure DevOps Repos and GitHub, with the pipelines in Pull request scanning. GitLab merge requests are accepted by the API (refs/merge-requests/{n}/head or /merge), and Vornin can comment on them (step 8), but there is no published GitLab CI recipe yet. Bitbucket is not supported. Only pull requests into the repository's branch in Vornin can be checked.
Steps
- Connect the repository host. Open Assets → Repositories and click Add connection. Choose the Connection type (Azure DevOps, GitHub or GitLab) and enter a Display name. Then fill in the fields for that type and a Personal access token (PAT):
- Azure DevOps: Organization URL, for example
https://dev.azure.com/acme. Token scope Code: Read; add Packaging: Read if dependency scanning must read your private Azure Artifacts feeds, and Project and Team: Read if Test Connection finds no projects. All providers' scopes are in Repository token permissions. - GitHub: GitHub Owner / Organization, for example
acme. A classic token needs thereposcope (public_repofor public repositories only). A fine-grained token needs Contents: Read-only on the repositories to scan (GitHub adds Metadata: Read-only itself); set its Resource owner to the organisation, which may have to approve the token before it works. - GitLab: GitLab Group Path (optional), and Self-hosted Base URL (optional) for your own GitLab instance (leave it blank for gitlab.com). The token needs the
read_apiandread_repositoryscopes.
- Azure DevOps: Organization URL, for example
- Import the repository on the branch pull requests merge into. Click Import repositories, choose the Connection (and Project for Azure DevOps), tick the repository, set its Branch to the target branch of your pull requests, usually
mainormaster, and click Import selected. The gate refuses pull requests into any other branch. You can change the branch later in the repository list. - Note the repository ID. In Assets → Repositories, each repository shows ID and a number after its organisation and project. That number is
VORNIN_REPO_ID. - Create an API key. As an Owner or Admin, open Administration → API & integrations, stay on the API keys tab and click Generate API key. Enter a Key Name such as
PR gate, choose when it Expires, selectscan:createandscan:readunder Permissions, then click Generate. Copy the key now; it is shown once. The key acts with its creator's role, so it stops working for pull request scans if that person stops being an Owner or Admin, and is revoked if they leave the workspace. When it expires, the pipeline stops withPull request scan refused. - Add the script to the repository. Commit the Python script from Pull request scanning as
vornin_pr_gate.pyat the repository root. It scans with SAST, secret scanning and dependency scanning, blocks on new findings of High or Critical severity, and lets the pull request through with a warning when Vornin cannot check it. To fail in that case too, setVORNIN_ON_ERRORtoblockin the pipeline. - Azure DevOps: add the pipeline and make it required.
- Commit the Azure DevOps YAML from Pull request scanning as
azure-pipelines.yml, or under another name. - In Azure DevOps, open Pipelines → New pipeline, choose Azure Repos Git, the repository and Existing Azure Pipelines YAML file, select the file and choose Save from the Run menu.
- Click Edit, then Variables → New variable. Add
VORNIN_API_KEYwith the key and tick Keep this value secret. AddVORNIN_REPO_IDwith the repository ID. Save. - Open Repos → Branches, open the menu next to the target branch and select Branch policies. Click + next to Build validation, choose the pipeline as Build pipeline, set Trigger to Automatic and Policy requirement to Required, give it a Display name such as
Vorninand click Save. You need Project Administrator rights or Edit policies on the repository.
- Commit the Azure DevOps YAML from Pull request scanning as
- GitHub: add the workflow and make it required.
- Commit the GitHub Actions workflow from Pull request scanning as
.github/workflows/vornin-pr.yml. - In the repository, open Settings → Secrets and variables → Actions. On the Secrets tab click New repository secret and add
VORNIN_API_KEY. On the Variables tab click New repository variable and addVORNIN_REPO_ID. - Open a test pull request so the workflow runs once. GitHub only offers a check for required status after it has run.
- Open Settings → Branches and add or edit the branch protection rule for the target branch (or use Settings → Rules → Rulesets). Tick Require status checks to pass before merging, search for
vornin-gate(the job name in the workflow) and add it. Save.
- Commit the GitHub Actions workflow from Pull request scanning as
- Optional: comment on pull requests. The comment uses its own integration and token, not the repository connection's. Open Administration → API & integrations, select the Issue tracking tab and click Add integration. Choose the Provider, enter a Display name, and enter:
- Azure DevOps: Organization URL exactly as in the repository connection, letter case included (a trailing slash does not matter). A Personal Access Token with Code (Read & write); add Work Items (Read & write) if you leave issue filing on. In Default issue repo, the project name exactly as Vornin shows it. One integration covers every repository in that project.
- GitHub: leave Base URL (optional) blank. A Personal Access Token: classic with the
reposcope, or fine-grained with Pull requests: Read and write on the repository (add Issues: Read and write if you leave issue filing on). In Default issue repo, the repository asowner/name, exactly as Vornin shows it, letter case included. GitHub needs one integration per repository. GitHub Enterprise Server is not supported for comments. - GitLab: leave Base URL (optional) blank for gitlab.com, or enter exactly the connection's Self-hosted Base URL. A Personal Access Token with the
apiscope. In Default issue repo, the project path such asacme/widgets, exactly as Vornin shows the repository name. GitLab needs one integration per repository.
Authenticated as '…', orConnection test failed. Check the URL and token, then try again.); it does not prove the token can comment. If the values do not match the repository exactly, Vornin skips the comment without an error. - Optional, Azure DevOps: hold the pull request on the comment too. Vornin opens its comment thread as Active when a pull request is blocked or was not checked, and closes it when the pull request passes. Setting Check for comment resolution to On and Required in the same branch policies makes an open Vornin thread hold the pull request as well. Note that this also holds pull requests the gate let through because Vornin could not check them.
Verify it works
- Open a test pull request into the target branch. The pipeline starts. The first pull request on a repository waits for a default-branch scan, which Vornin starts automatically, so it can take as long as a full scan, up to 120 minutes.
- Read the job log. It ends with
PASS: no new blocking findings, with the blocking findings followed byBLOCKED: n blocking finding(s), or withNOT CHECKEDand the reason. WithVORNIN_ON_ERRORset toblock, an unchecked pull request ends withVornin gate refused scan n: HTTP 422and the reason code instead. - Check Vornin. Open Scans and select the Pull requests filter. Open the scan: the Pull request evaluation panel shows Passed or Blocked with the counts of new, existing and fixed findings and the baseline scans used, or Not evaluated with the reason.
- Check the comment, if you set it up. It starts with the commit it describes, then Vornin PR check, the verdict, up to ten new findings and a link to the scan. Vornin edits the same comment on every run.
- Check the merge is blocked. On a blocked run, the Azure DevOps policy or the GitHub required check prevents completing the pull request.
Troubleshooting the pull request gate
| What you see | Cause and fix |
|---|---|
Pull request scan refused: HTTP 401 | The API key is wrong, revoked or expired, or the job received no key. Vornin answers API calls with 401, never a redirect to the sign-in page. Generate a new key and update VORNIN_API_KEY. On GitHub, a pull request from a fork gets no secrets, so the key is empty. |
Pull request scan refused: HTTP 403 | The plan is not Scale, the key lacks scan:create or scan:read, the creator is no longer an Owner or Admin, or your IP allowlist excludes the build agent. Check the plan under Settings → Billing, generate the key as an Owner or Admin, or add your build agents' addresses to the allowlist. |
Pull request scan refused: HTTP 400 | VORNIN_REPO_ID is not a repository in this workspace or the repository is paused, or the ref or commit is malformed. Copy the ID again from Assets → Repositories and check its Active switch. Azure DevOps accepts only refs/pull/{n}/merge. |
Pull request scan refused: HTTP 422 | The pull request targets a branch other than the repository's branch in Vornin. Change the repository's branch in Assets → Repositories, or only require the check on that branch. |
Vornin was unreachable or kept failing for 120 minutes (HTTP 429) | The script retries a 429 answer until its 120-minute deadline. Either the hourly pull request scan quota (Scale: 120 per hour) or the concurrent scan limit (Scale: 25 queued or running scans, pull request scans included) stayed full. With the default VORNIN_ON_ERROR the pull request is let through as not checked. Re-run the pipeline later. |
NOT CHECKED with no_baseline | The default branch has no usable scan under 30 days old and the automatic one did not finish in 120 minutes, could not start, or ran in the last six hours without a usable result. Scan the repository's branch from New scan, wait for it to finish and re-run the pipeline. Under block, the same cause reads Vornin gate refused scan n: HTTP 422 no_baseline. |
NOT CHECKED with baseline_busy, degraded_coverage or commit_mismatch | A default-branch scan was still processing, a scanner did not run, or the scanner could not check out the commit. Fix the reason in the message and re-run the pipeline. Under block, these read Vornin gate refused scan n: HTTP 422 followed by the code. |
| The pipeline times out | The job timeout is shorter than the script's 120-minute wait. Set it to 150 minutes, as in the recipes. |
GitHub does not list vornin-gate as a status check | The workflow has not run yet, or the job has a different name. Run it once on a pull request, then search again. |
| No comment on the pull request | The integration's provider, Organization URL, Base URL or Default issue repo does not match the repository exactly, Enabled or Comment on pull requests is off, or the token cannot write comments (Azure DevOps: Code (Read & write); GitHub fine-grained: Pull requests: Read and write; GitLab: api). Fix the integration; the next run posts the comment. Vornin retries a comment the provider refused for up to 24 hours. |
| Owners and Admins got a notification Pull request #n in … was not checked | Vornin could not check a pull request. With onError: warn the gate let it through; with block it failed. The notification links to the scan, which names the reason. It is sent in the app and by email, at most once per repository and reason per day. |
Webhooks and user provisioning#
Webhooks
Webhooks send Vornin events to your own endpoint, or to Slack, Microsoft Teams, Discord or Jira. They need the Team plan or above, and an Owner or Admin sets them up.
- Open the Webhooks tab. Open Administration → API & integrations, select the Webhooks tab and click Add webhook.
- Describe the destination. Enter a Name and choose the Type: Generic Webhook, Slack, Microsoft Teams, Discord or Jira. Enter the URL. The field is labelled Webhook URL, or Slack Webhook URL, Teams Webhook URL, Discord Webhook URL or Jira Base URL for those types. It must use
https://and resolve to a public address. Save refuses any other URL and says why, for exampleThe webhook URL must start with https://.For Jira, prefer issue tracking, which adds duplicate protection and status sync; the Jira webhook type also needs an Authorization Header (Basicfollowed by the base64 ofemail:api-token), a Project Key and an Issue Type. - For a Generic Webhook, set a signing secret. Make up a long random value, enter it in Signing Secret (optional) and store the same value in your receiver. Vornin signs only Generic deliveries, and only when a secret is set. Authorization Header (optional) is sent as the
Authorizationheader on Generic deliveries. - Choose the events. Tick the Event Filters you want (see the table below). Minimum Severity (optional) applies to Vulnerability Found only. Click Save.
- Test it. Click Test in the webhook's row. Vornin sends a
test.pingevent and shows Test Passed, or Test Failed with the error and HTTP status.
| Event filter | event value | Sent when |
|---|---|---|
| Scan Complete | scan.complete | A scan completes or fails. Cancelled scans and pull request scans send nothing. |
| Vulnerability Found | vulnerability.found | A new finding is recorded, at or above Minimum Severity when set. |
| SLA Breach | sla.breach | A finding passes its remediation deadline. |
| Posture Drift (Business+) | posture.drift | A scan finds new High or Critical findings compared with the previous scan of the same targets. |
| Compliance control changes (daily digest) | compliance.control_change | Once a day, when controls changed between passing and failing. |
| Scan-cadence breaches (daily digest) | compliance.cadence_breach | Once a day, when assets went past their scan cadence: targets, plus repositories and cloud accounts marked as supporting a critical or important function. |
| Emerging threats (a newly-exploited CVE matches your stack) | threat.emerging | A newly exploited CVE matches technology on your assets. |
| New assets discovered (new hosts on your attack surface) | asset.discovered | New hosts are found on your attack surface. |
| Test button | test.ping | You click Test. |
A Generic delivery is a JSON POST. Every payload has event and timestamp (UTC). Nested objects use the field names shown below, and scan.Status is a number: 2 completed, 3 failed. A scan.complete payload looks like this (values are examples):
{
"event": "scan.complete",
"scan": { "Id": 6448, "Name": "Weekly external scan", "Status": 2, "CompletedAt": "2026-10-05T09:12:44Z" },
"vulnerabilities_found": 12,
"results_processing_state": "Succeeded",
"vulnerabilities_found_reliable": true,
"error_message": null,
"timestamp": "2026-10-05T09:12:45Z"
}
vulnerability.found carries a vulnerability object with Id, Title, Severity (text, such as High), AffectedHost, AffectedUrl, CveId, CvssScore, Fingerprint and Reachability. sla.breach carries Id, Title, Severity, AffectedHost and SlaDueAt. Slack, Teams and Discord receive a formatted message that contains the same JSON, and Jira receives a new issue.
For scan.complete, trust the finding count only when vulnerabilities_found_reliable is true. Check results_processing_state and any error_message. Use the build gate for pipeline verdicts.
Verify the webhook signature
With a signing secret, every Generic delivery carries X-Webhook-Timestamp (Unix seconds) and X-Webhook-Signature-V2, which is sha256= followed by the lowercase hex HMAC-SHA256 of {timestamp}.{raw body}, keyed with the UTF-8 bytes of your signing secret. Recompute it over the raw request body, before any JSON parsing, compare it in constant time, and reject a delivery whose timestamp is more than five minutes old, so a captured request cannot be replayed. Each retry is signed again with a new timestamp. The older X-Webhook-Signature header (HMAC-SHA256 of the body alone) is still sent for existing receivers, but it does not protect against replay.
import hashlib
import hmac
import time
def is_valid_vornin_delivery(secret: str, raw_body: bytes, timestamp: str, signature: str) -> bool:
"""secret: your signing secret. raw_body: the request body bytes as received.
timestamp, signature: the X-Webhook-Timestamp and X-Webhook-Signature-V2 headers."""
try:
if abs(time.time() - int(timestamp)) > 300:
return False # older than five minutes: possibly a replay
except ValueError:
return False
signed = timestamp.encode() + b"." + raw_body
expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Delivery, retries and limits
Vornin treats any 2xx answer within 10 seconds as delivered. Redirects are not followed, so a 3xx counts as a failure. A failed delivery is retried four times, about 30 seconds, 2 minutes, 8 minutes and 32 minutes apart, and then becomes a dead letter. A workspace sends at most 60 deliveries per minute, retries included; an event over that limit becomes a dead letter at once, and so does a payload larger than 64 KB. After ten failed deliveries in a row Vornin disables the webhook; switch it back on from the row's menu with Enable. The Recent deliveries tab lists every delivery with its status (Sent, Pending, Retry #n or Dead-letter), the last HTTP status and the last error. Fix the receiver, then click Replay on a dead letter to send it again.
Vornin refuses destinations on private and internal addresses: loopback, RFC 1918, link-local (including cloud metadata), carrier-grade NAT, multicast, IPv6 unique-local and site-local, localhost, and names ending in .local or .internal. It checks the name again when it connects, so a DNS change cannot point a saved webhook at an internal address. Public IPv6 addresses are allowed. To reach a receiver inside your network, expose it through a public HTTPS endpoint.
| What you see | Cause and fix |
|---|---|
Test Failed: URL failed SSRF validation (must be HTTPS, not localhost or a private address). | The URL is not https://, or it names a private or internal address. Use a public HTTPS URL. |
Test Failed: Request timed out (10 s). | The receiver did not answer within 10 seconds. Answer first, and do slow work after responding. |
| Test Failed with an HTTP status | The receiver answered with that status. A 3xx means the URL redirects: use the final URL. A 401 or 403 usually means the Authorization Header is missing or wrong. |
Dead-letter with tenant_rate_limit | The workspace sent more than 60 deliveries in a minute. Narrow the Event Filters or set Minimum Severity, then Replay what you need. |
Dead-letter with payload_too_large | The event was larger than 64 KB and was not sent. |
| Your receiver rejects the signature | It hashed the parsed and re-serialised JSON instead of the raw body, used the wrong secret, or checked X-Webhook-Signature with the V2 formula. Use the raw bytes and {timestamp}.{raw body}. |
SCIM provisioning
SCIM lets your identity provider create, update and deactivate workspace members. It needs the Business plan or above. SCIM uses its own token, not a REST API key. An Owner or Admin generates it in Vornin:
- Open the SCIM panel. Open Settings → General settings, select the Security tab and scroll to SCIM user provisioning.
- Copy the base URL. Copy SCIM base URL. It is the same for every workspace:
https://app.vornin.com/scim/v2
- Generate the token. Click Generate token. A dialog shows a token starting with
scim_. Copy it with the copy button and keep the dialog open until you have pasted it into your provider. Vornin stores only a hash, so the token cannot be shown again. The panel then shows Token active with the token's first characters and when it was issued.
Rotate token issues a new token and stops the old one at once, so paste the new one into your provider straight away. Revoke token turns SCIM off: your provider can no longer change members, and existing members stay as they are. Every generate, rotate and revoke is recorded under Administration → Audit log. If you use an IP allowlist, it applies to SCIM requests too.
Microsoft Entra ID
You need at least the Cloud Application Administrator or Application Administrator role in Entra.
- Open the application. In the Microsoft Entra admin center, open Enterprise applications and the non-gallery Vornin application you created for SAML. If you connected with Connect with Entra ID instead, create a non-gallery application as in step 1 of the SAML steps and use it for provisioning only; it needs no SAML settings.
- Enter the connection. Select Provisioning, then + New configuration (on older screens, Get started and Provisioning Mode: Automatic). Under Admin Credentials, paste the SCIM base URL into Tenant URL and the token into Secret Token. Click Test Connection, then Create or Save.
- Decide on groups. Under Mappings (Attribute mapping on newer screens), Provision Microsoft Entra ID Groups is on by default. Keep it on if you want the
vornin-adminsgroup to set roles (see below), and assign that group under Users and groups. Vornin stores other groups but they grant nothing, so you can also set Enabled to No and provision users only. - Check the email mapping. Vornin identifies a person by email. It reads the primary email (Entra maps
mailto it by default), then the first email, thenuserName. If some users' user principal name differs from their email, edit theuserNamemapping in Provision Microsoft Entra ID Users to usemail. - Assign people under Users and groups, and keep the provisioning scope on assigned users and groups.
- Start provisioning. Use Provision on demand for one pilot user, then set Provisioning Status to On. Entra then syncs about every 40 minutes.
Okta
- Enable SCIM on the application. Open the Vornin SAML application, select the General tab, click Edit under App Settings, choose SCIM for Provisioning and save.
- Enter the connection. On the Provisioning tab, under Integration, click Edit. Set SCIM connector base URL to the SCIM base URL and Unique identifier field for users to
userName. Under Supported provisioning actions tick Push New Users and Push Profile Updates, and Push Groups if you want thevornin-adminsgroup to set roles. Set Authentication Mode to HTTP Header and paste the token into Authorization. Vornin expects the headerAuthorization: Bearer scim_…: if Okta shows no Bearer label in front of the field, typeBearer, a space and the token. Click Test Connector Configuration, then Save. - Choose what Okta changes. Under To App, click Edit and enable Create Users, Update User Attributes and Deactivate Users. Save.
- Assign people on the Assignments tab. To push the admins group, add
vornin-adminson the Push Groups tab.
What SCIM does in Vornin, and how to check it
A provisioned person whose email is in a domain you have verified joins the workspace as a Member at once. Anyone else becomes a pending SCIM invitation under Administration → Users & roles. They join after signing in with an email link to that address and accepting the workspace invitation. Vornin does not email them, so tell them to sign in. A pending invitation stays open for as long as your provider keeps the person assigned and SCIM is connected. If you revoke the SCIM token, pending invitations expire 30 days after your provider last updated them.
SCIM keeps the workspace membership's active state in sync. Deactivation suspends access to this workspace and revokes its API keys. It does not deactivate the person's global account or their access to other workspaces. After reactivation they must sign in again before returning to this workspace; revoked API keys remain revoked. SCIM deletion removes this workspace membership. Owners, platform administrators and members with explicit break-glass protection cannot be suspended or removed through SCIM. SCIM cannot reactivate an account that an administrator disabled globally.
Roles stay under human control by default. An Owner can enable Reconcile SCIM-managed roles in the SCIM panel and explicitly mark members as SCIM-managed under Users & roles. For those members only, membership of the vornin-admins SCIM group assigns Admin; removal assigns Member. Human role edits stop SCIM role management for that member. Owners, platform administrators and break-glass members remain protected. Vornin finds groups by displayName, id or externalId, which is what Entra ID and Okta group push send. Providers that cannot push groups should keep role reconciliation disabled.
Vornin takes the email when it creates a person; SCIM cannot change it. Display-name updates apply only to an account that belongs exclusively to this workspace and is not a platform administrator.
To verify, provision one pilot user and confirm they appear under Administration → Users & roles with the right email, then deactivate them in your provider and confirm this workspace refuses access. If they also belong to another workspace, verify that access there still works.
| What you see | Cause and fix |
|---|---|
| Test connection fails with 401 | The token is wrong, was rotated or was revoked, or the header is not Bearer scim_…. Generate a new one in Vornin and paste it again. |
| Test connection fails with 402 | The plan does not include SCIM. It needs Business or Scale. |
Test connection fails with 403 Access denied: IP not in allowlist | Your IP allowlist excludes the provider. Add its egress addresses or clear the allowlist. |
| The panel says the plan no longer includes SCIM | The token still exists but Vornin refuses every request. Upgrade, or revoke the token. |
Cannot mutate 'displayName' … | The person also belongs to another Vornin workspace, so the provider cannot change their global display name. SCIM active suspends or reactivates this workspace membership only; access to other workspaces remains available. |
| A user is created with the wrong email | The provider sent a primary email or userName that is not the person's real address. Fix the mapping, delete the wrong member and provision again. |
Troubleshooting#
Most problems show up as a message on the scan's page or as a notification when you act. Find the text you see in the first column. Setup problems have their own tables in each chapter: agents, repository connections, cloud accounts, single sign-on, API errors, the pull request gate, webhooks and SCIM.
| What you see | Cause and fix |
|---|---|
No checks ran. None of the requested scan types could run against what this scan supplied | The selected scanners do not apply to the chosen assets, for example a code scanner against a host. The message names each scan type and why. Choose scanners that fit the asset, or add the missing repository, cloud account or credentials. |
No checks ran. Every target was skipped or Some targets were skipped | Each skipped host is listed with its reason. A private address needs an online scan agent assigned to the target; an assigned agent that is disabled, deleted or silent for 7 days is named in the reason. |
| A completed scan has no findings | Read the notices at the top of the scan page. Some targets didn't respond during this scan means checks were skipped for unreachable hosts. Active attack probes were not enabled for this scan means checks that send attack payloads did not run. Some dependencies could not be resolved lists manifests that were not analysed. Confirm the intended asset and scanners were selected, and that credentials were valid. |
Scan rate limit reached | Your plan's hourly scan limit is used: Free 5, Team 30, Business 300 scans per hour; Scale has no hourly limit. Wait, or upgrade. Retests count too. |
Concurrent scan limit reached, or a scan stays queued | Your plan runs a set number of scans at once: Free 1, Team 3, Business 10, Scale 25. Let current scans finish. A target assigned to a busy or offline agent also waits for that agent. |
Target limit reached | You have as many targets as your plan allows, which also blocks Add as target on discovered hosts. Delete unused targets or upgrade. |
Your plan includes 3 targets and this workspace has 10 | The workspace has more targets than its plan allows, usually after a trial ended or the plan changed. Nothing was deleted: the oldest targets within the limit can still be scanned, and scheduled runs skip the rest. Upgrade, or delete targets you no longer need, to scan the paused ones again. |
| Repository or cloud access fails | Check token expiry, selected scope and permissions, then use the connection test again and rescan. For repository dependencies, check private-feed access separately. A git host that is not reachable from the internet needs one of your own agents online. |
| A finding disappeared from the list | The list shows Open findings by default. Use the Acknowledged, Resolved and Dismissed counters, clear the severity and Evidence filters, and under More filters turn off Hide likely false positives and choose Show N deprioritised. A banner offers Show hidden findings when filters hide rows. On the finding, the Activity tab shows Auto-resolved, Auto-suppressed or a manual change. |
| Verify fix (retest) is missing | It appears only on the full page of an open or acknowledged network or web finding whose most recent detecting scan still exists; otherwise it answers The scan that last reported this finding is no longer available. For code, dependency, cloud and container findings, run that scan again. A retest is already in progress for this finding. means one is pending: wait for it. |
Data export (CSV / SARIF / SBOM) requires the Team plan or higher. or Exporting findings (CSV/JSON/XML) requires the Team plan or higher. | Exports are part of Team and above. Upgrade under Settings → Billing. |
SARIF export requires complete coverage | A check in that scan did not complete, so a SARIF upload would close real alerts in your developer tools. Fix the coverage problem the message names and run the scan again. While results are still processing, wait and retry. |
| The Settings or Administration menu is missing | Those menus appear only for Admins and Owners. Ask one to change your role under Administration → Users & roles. |
| A control or page says it needs another plan | Plan-gated features show the plan they need, for example The executive brief is a Team feature or Auditor pack · Business plan. An Owner or Admin can upgrade under Settings → Billing. |
| A report looks incomplete | Review its scope, minimum severity and the PDF's coverage limits. The scan page shows up to 5,000 findings, the event ledger up to 2,000, and evidence-pack PDF tables 80 rows. Emerging threats searches and pages the complete workspace set; its CSV exports every matching row. Review the stated scope of other CSV or ZIP exports. |
Contact support
Include the scan ID, affected asset, time of the problem, visible error and steps you already tried. For an internal scan, include relevant redacted agent logs. Remove tokens, cookies and sensitive code before sharing screenshots or logs.
Third-party notices#
The scan agent you download includes open-source software from third parties. Each licence text ships inside the agent archive next to the component it covers, and the full attribution notice is at THIRD-PARTY-NOTICES.md in the extracted folder.
- Bundled port and service scanners
- naabu 2.6.1 (MIT, ProjectDiscovery) and nerva 1.69.6 (Apache-2.0, Praetorian), with their licence texts under
tools/network-tools/licenses/. - Bundled vulnerability scanner
- Trivy 0.74.0 (Apache-2.0, Aqua Security) at
tools/, with its licence text attools/LICENSE-trivy: the native binary the agent runs for its Trivy-based checks, including Kubernetes scans. Its vulnerability database is not bundled; Trivy downloads it at scan time. - Bundled container image
- Debian bookworm-slim, with each installed package's copyright notice retained under
/usr/share/doc/and the exact package list recorded inos-packages.tsv. - Static-analysis rule corpus
- 1,091 rules under
Configuration/sast-rules-vendor/: 651 written by Vornin, plus GitLab's SAST rules (MIT and LGPL-3.0) and elttam's (MIT). Each source ships its own licence text beside its rules. The previously bundled Commons-Clause corpus was removed in September 2026. - Browser automation and fonts
- Playwright (Apache-2.0) with its bundled Node.js (MIT), and the Lato typeface (SIL Open Font License 1.1).
- Reference data
- A runtime end-of-life snapshot from endoflife.date (MIT) and subdomain-takeover fingerprints from can-i-take-over-xyz (CC BY 4.0).
- .NET dependency set
- 114 packages, almost entirely MIT, Apache-2.0 and BSD. The full list with versions and licences is in the notices file.
Scanners that run as container images are pulled from their public registry at scan time rather than distributed by Vornin, so they are listed in the notices file for reference and no copy of them is included in the download.