Home Vooki Pro Features Pricing About Contact
DocsAutomationCLI & CI/CD

CLI & CI/CD

Vooki Pro ships a command-line runner, vooki-pro-cli, so a scan can happen without a person driving the interface — in a nightly job, in a pipeline, or on a machine with no display. Its exit code tells the pipeline whether to pass or fail.

10 min readAutomation

How It Works

One idea, and it keeps the whole thing simple: the config file is the single source of truth. Everything — target, scope, discovery, authentication, policy, throttling, report formats, the security gate — is a key in a JSON file. There are no dozens of flags to remember, and the same config always produces the same scan.

A run does three things, in this order:

  • Checks the licence — this machine must be activated, and the target must sit inside a licensed domain.
  • Runs the scan — the same engine the desktop app uses, with the same checks.
  • Writes the reports and sets the exit code — 0 pass, 2 the security gate failed, 1 something went wrong.

Getting the CLI

Every desktop build ships the CLI beside the app, and there is a standalone package for build servers that should not have the full desktop install. The standalone package carries its own runtime — Python and Node.js are not required — and contains no interface, no browser engine and no AI model.

The installer puts vooki-pro-cli.exe next to the app, and offers an optional Add CLI to system PATH task — it is unchecked by default, so tick it if you want the short command:

powershell
& "C:\Program Files\Vooki Pro\vooki-pro-cli.exe" --version

For a build agent, unzip the standalone package instead and keep the whole folder together:

powershell
Expand-Archive vooki-pro-cli-6.0.0-windows-x64.zip -DestinationPath C:\tools
& C:\tools\vooki-pro-cli\vooki-pro-cli.exe --version

Licence & Activation

A CLI scan needs an active licence on the machine it runs on, exactly like the desktop app. Activate the machine once and every later run is silent about it. These commands use the short name — if the CLI is not on your PATH, use the full path shown above instead.

powershell
$env:VOOKI_LICENSE_KEY = "XXXX-XXXX-XXXX-XXXX"
vooki-pro-cli license activate
vooki-pro-cli license status

license activate reads the key from the VOOKI_LICENSE_KEY environment variable, or from standard input if you prefer to pipe it. The key is never printed. Activating in the desktop app on the same machine works just as well.

license status prints the plan, expiry, domain slots and whether CI/CD export is included — a quick way to check an agent before you wire up a pipeline.

Activate as the user that runs the scans

The activation is stored in that user's home folder. If your CI agent runs as a service account, activate while logged in as that account — activating as yourself will not license the agent.

Licensed Domains

A licence grants a number of domain slots. A slot covers a registrable domain and everything beneath it — example.com also covers staging.app.example.com — and all local or private targets share one slot. The scan target must fall under a bound slot.

Assigning a slot is permanent for the life of the subscription, so the CLI never does it on its own — the same reason the desktop app asks you to confirm first:

SituationWhat happens
Target is covered by a bound slotThe scan runs.
Target is not bound, no --bind-domainExit 1, naming the domain and how many slots are free.
Target is not bound, --bind-domain given, a slot is freeThe slot is assigned, then the scan runs.
No free slotExit 1. Your licensed limit is never exceeded.

So the first pipeline run for a new domain is either done once by hand, from Settings → Licensed Domains, or with the flag:

bash
vooki-pro-cli --config scan.json --bind-domain   # first run for a new domain
vooki-pro-cli --config scan.json                 # every run after that

Anything the scan discovers on a host your licence does not cover is skipped rather than attacked, and the run says so.

Getting a Config File

Export it from the interface. This is by far the easier route, because the correct method, URL, headers, cookies and body are filled in for you:

  • Open Scans → New Scan for a website, or the API Builder for a single API request.
  • Configure it exactly as you would for a normal scan — target, auth, discovery, policy.
  • Step through to the final Review step.
  • Click Export Config and choose where to save the .json file.

That file is ready to run. Add the CI-only keys — the security gate and any extra report formats — by hand, or pass them on the command line.

Config Template

Writing one by hand is also fine. Only target and requests are required; every other key is optional and shown here with its default:

json
{
  "target": "https://example.com",
  "project_name": "My Project",

  "report_format": "html",
  "report_type": "standard",
  "compliance_framework": "owasp",
  "fail_on_severity": "none",

  "export_scan_data": false,
  "export_scan_data_path": "",
  "export_scan_data_redact_credentials": false,

  "speed": "normal",
  "scan_types": ["all"],
  "testing_mode": "full",
  "concurrency": 5,
  "delay_ms": 0,
  "requests_per_second": 7,
  "max_payloads_per_type": 0,
  "skip_timing": false,
  "ai_detect": false,
  "attack_include": [],
  "attack_exclude": [],

  "scope": "same_host",
  "scope_hosts": [],

  "discovery": "quick",
  "crawl_depth": 2,
  "max_urls": 0,
  "crawl_max_duration_sec": 0,
  "discover_background_urls": false,
  "discovery_include": [],
  "discovery_exclude": [],
  "test_state_changing": false,

  "auth": {
    "headers": { "Authorization": "Bearer ${DAST_TOKEN}" },
    "cookie": "session=${SESSION_COOKIE}"
  },

  "requests": [
    {
      "url": "https://example.com/login",
      "method": "POST",
      "headers": { "Content-Type": "application/json" },
      "body": { "username": "admin", "password": "${APP_PASSWORD}" }
    },
    {
      "url": "https://example.com/search?q=test",
      "method": "GET"
    }
  ]
}

The keys that matter most for a pipeline are fail_on_severity (the security gate), report_format as a list, and the ${...} placeholders in auth — all three are covered below.

Key Settings

Report output — report_format takes html (default), json, sarif, docx or pdf, and accepts a list so one run can produce several: "report_format": ["json", "sarif", "html"]. Set report_type to compliance and add compliance_framework — one of owasp, pci_dss, nist, iso27001, hipaa, gdpr or sox — for a framework-mapped report. JSON and SARIF are machine-readable and always come out as themselves. Compliance reports are HTML or PDF only: ask for docx with a compliance report and the CLI writes HTML instead, with a warning.

Production Safe — "testing_mode": "passive" runs the non-intrusive checks only: responses the scan already had to fetch, and requests a normal browser would make anyway. No payloads, no guessed paths, no state changes — the mode for production targets (it still sends traffic to the target). full (the default) is the complete active scan. An unrecognised value stops the run rather than quietly scanning wider than you asked.

Speed — speed is a shortcut for three presets:

  • stealth — 2 parallel requests, 2 second delay. Avoids WAF alerts.
  • normal — 5 parallel requests, 500 ms delay. A good default.
  • aggressive — 10 parallel requests, no delay. May trip WAF blocks.

Setting concurrency, delay_ms or requests_per_second directly overrides the preset; the larger effective delay wins.

Which checks run — "scan_types": ["all"] runs every class. To narrow it, list groups by name:

json
"scan_types": ["sqli", "xss", "lfi", "ssrf"]

Scope and discovery — scope takes all, same_host, same_domain or custom (with scope_hosts). discovery takes quick (only the listed requests), crawl, or hybrid, tuned with crawl_depth, max_urls, crawl_max_duration_sec, discover_background_urls and the discovery_include / discovery_exclude wildcards. requests must list at least one seed URL even when crawling.

Saving the scan data — a report is a document; the scan data is the scan itself, every finding plus the HTTP request and response behind it. Set:

json
"export_scan_data": true,
"export_scan_data_path": "",
"export_scan_data_redact_credentials": false

An empty path saves to your Downloads folder. Give a folder and the file is named after your config; give an exact .vpscan path and it is used verbatim. There is no command-line flag for this — the config decides, so the same config always produces the same files. The scan data is written even when a scan finds nothing, and the full path is printed when the scan ends.

The archive keeps the credentials the scan sent, because a retest has to replay them. If you publish it as a build artefact, set export_scan_data_redact_credentials to true — the header names stay, the values are replaced. Authenticated retests of that imported scan then need fresh credentials.

Secrets & Environment Variables

Never commit a token. Write a placeholder in the config and let the CLI resolve it from the environment when the scan runs:

SyntaxMeaning
${NAME}The value of NAME. Unset or empty is a configuration error, so a missing secret stops the run instead of silently scanning unauthenticated.
${NAME:-default}NAME, or default when it is unset or empty.
$${A literal ${.

Placeholders work in any string value, anywhere in the config. Names must look like [A-Za-z_][A-Za-z0-9_]*, so template payloads such as ${7*7} in a request body are left alone, and a bare $NAME is never expanded — passwords containing $ keep working. Add "env_substitution": false at the top level if a config genuinely needs literal ${NAME} text.

powershell
$env:DAST_TOKEN = "eyJhbGciOi..."
vooki-pro-cli --config scan.json

Credentials are kept out of everything the run produces. Values in the auth block, credential headers such as Authorization and Cookie, and tokens minted by API auto-login are replaced with [REDACTED] in the console output and in every report.

Running a Scan

--config is the only required parameter:

powershell
& "C:\Program Files\Vooki Pro\vooki-pro-cli.exe" --config scan.json --output reports\ --fail-on high

Every parameter:

ParameterWhat it does
--config FILEThe scan config JSON. Required.
--output PATHA file, a base name, or a directory (existing, or ending in a separator) for the reports. Missing folders are created. Defaults to next to the config.
--fail-on LEVELThe security gate, overriding the config: none, info, low, medium, high or critical.
--bind-domainAssign a free licensed-domain slot to the target if it is not licensed yet.
--verbosePrint all scan progress messages, not just findings.
--versionPrint the CLI version.

There are also two sub-commands: license status / license activate (above), and retest, which re-checks findings already stored on that machine.

Findings print to the terminal as they are found, colour-coded by severity, and the reports are saved when the scan finishes.

The Security Gate & Exit Codes

This is what makes a pipeline fail on its own. Set a threshold and the run exits 2 when anything at that severity or above is found:

json
"fail_on_severity": "high"

Or on the command line, which wins over the config — handy when one config serves several stages:

bash
vooki-pro-cli --config scan.json --fail-on critical
ValueThe run fails when it finds
none (default)Never — the gate is off, and a completed scan always exits 0.
infoAnything at all.
lowLow, Medium, High or Critical.
mediumMedium, High or Critical.
highHigh or Critical.
criticalCritical only.

The exit code is the contract your pipeline reads:

CodeMeaningPipeline
0The scan completed and the gate passed, or no gate was configured.Pass
1Something went wrong, so there is no trustworthy result: no licence or an unlicensed target, a bad config or an undefined ${VARIABLE}, an unreachable target, nothing left to scan, or a report that could not be written.Fail — tool error
2The scan completed and the gate failed.Fail — vulnerabilities
An unreachable target is never a pass

If every request fails, or the filters leave nothing to scan, the run exits 1 rather than reporting a clean bill of health. "No findings" always means the scan actually ran.

Reports & Artefacts

A report is written for every completed scan, including a clean one — so a missing report can only mean the run failed.

FormatFileUse it for
json.jsonThe machine-readable result: severity counts, the gate verdict and every finding.
sarif.sarifSARIF 2.1.0, for security tooling that ingests it. Needs CI/CD Export on your licence.
html.htmlThe readable report to publish or send on.
docx.docxA Word document for report packs.
pdf.pdfNeeds the bundled browser; the standalone CLI package writes HTML instead, with a warning. Don't make PDF a pipeline requirement.

With several formats, --output is a base name and each report adds its own extension; point it at a directory and the reports are named after the config file. The JSON report carries everything a pipeline needs to make its own decision:

json
"metadata": {
  "total_findings": 7,
  "severity_counts": { "Critical": 0, "High": 2, "Medium": 3, "Low": 1, "Info": 1 },
  "scan_status": "completed",
  "requests_total": 42,
  "requests_failed": 0,
  "security_gate": {
    "threshold": "high",
    "failed": true,
    "blocking_findings": 2
  }
}

All five severities are always present, so a consumer never has to treat a missing key as zero.

In a Pipeline

The runner is a plain executable that reads a file and writes files, so it drops into Jenkins, GitHub Actions, GitLab CI or Azure Pipelines without special support. There is no plugin to install. A typical arrangement:

  • Run it on a persistent self-hosted agent, activated once. Hosted, throwaway agents get a fresh machine every run and have no activation.
  • Keep the exported config in the repository, next to the code it tests, with ${...} placeholders instead of credentials.
  • Map your CI secret into the environment for the scan step only.
  • Set a gate that matches the stage — critical on a pull request, high on nightly.
  • Ask for ["json", "html"] (plus sarif if your licence includes it): JSON for the machine, HTML for the human who has to read it.
  • Use a tight policy so the scan finishes inside your build timeout — a Deep policy does not belong in a pull-request check. Run the heavier policies nightly against staging.
  • Publish the reports whether the gate passed or failed.

Azure Pipelines

A complete nightly pipeline. The step's exit code fails the run on its own — no parsing required:

yaml
trigger: none

schedules:
  - cron: "0 2 * * *"
    displayName: Nightly DAST
    branches:
      include: [main]
    always: true

pool:
  name: Self-Hosted-Vooki          # an agent activated with `license activate`

variables:
  - group: vooki-dast              # holds DAST_TOKEN as a secret variable
  - name: VOOKI_OUT
    value: $(Build.ArtifactStagingDirectory)/vooki

steps:
  - checkout: self

  # Fails early with a clear message if the agent lost its activation.
  - bash: |
      set -euo pipefail
      mkdir -p "$(VOOKI_OUT)"
      vooki-pro-cli --version
      vooki-pro-cli license status
    displayName: Check Vooki licence

  # Secret variables reach a script only when mapped into env:.
  # The CLI resolves ${DAST_TOKEN} itself and never prints it.
  - bash: |
      vooki-pro-cli --config security/scan.json --output "$(VOOKI_OUT)/" --fail-on high
    displayName: Vooki DAST scan
    env:
      DAST_TOKEN: $(DAST_TOKEN)

  # Publish the reports whether the gate passed or failed.
  - task: PublishPipelineArtifact@1
    condition: succeededOrFailed()
    inputs:
      targetPath: $(VOOKI_OUT)
      artifact: vooki-dast

On a Windows agent, swap the scan step for a PowerShell one:

yaml
  - powershell: |
      & "C:\Program Files\Vooki Pro\vooki-pro-cli.exe" `
          --config security\scan.json `
          --output "$(Build.ArtifactStagingDirectory)\vooki\" --fail-on high
      exit $LASTEXITCODE
    displayName: Vooki DAST scan
    env:
      DAST_TOKEN: $(DAST_TOKEN)

When it detects Azure Pipelines, the CLI also writes gate failures and fatal errors as ##vso[task.logissue] lines, so they appear on the run summary. If you would rather mark the run "partially succeeded" on a gate failure than fail it outright, read the exit code yourself: 2 is the gate, 1 is a real error.

Bringing Results Back

Set export_scan_data to true and the pipeline produces a .vpscan archive alongside the reports. Pull that file onto a workstation and open it with Scans → Import: it appears like any scan you ran locally, with all findings and evidence intact, ready for triage and retest.

Good to Know

  • Persistent agents only. The CLI uses the machine's own activation, so hosted, throwaway build agents are not supported yet.
  • SARIF needs CI/CD Export on the licence. Without it the run stops with a clear message instead of quietly skipping the file.
  • The gate counts every finding in the run, not just new ones. There is no baseline comparison yet.
  • retest needs the local scan database, so it belongs on a workstation or a persistent agent that keeps its Vooki data — not in a throwaway pipeline job.
  • The standalone package has no browser engine, so PDF output and background XHR discovery (discover_background_urls) are unavailable there; the desktop build has both.
  • A WAF can quietly limit coverage. If it blocks the scanner, the run can finish with few findings — check the scan's coverage, not just the exit code.

This site uses cookies to enhance user experience. See Privacy Policy