Running Scans (CLI)
The Vygl CLI runs every scan engine locally and uploads only findings metadata. It works on a developer laptop, a CI runner, or any environment with Docker.
The scan command
Section titled “The scan command”vygl scan [OPTIONS] [PATH]The most common flags:
| Flag (long / short) | Purpose |
|---|---|
--scan-types / -t |
Comma-separated list of engines (default: sast,sca,secrets,iac,malware). Valid values: sast, sca, secrets, iac, malware, container. malware and container are gated by org/project settings — when the cloud policy says they’re off, the CLI skips them automatically and prints a short notice. |
--image |
Scan a container image instead of a directory. Forces --scan-types container. |
--fail-on |
Exit non-zero when findings reach this severity. One of critical, high, medium, low. |
--no-upload |
Run engines locally without pushing results to the cloud. |
--name / -n |
Project name (use when there’s no git remote). |
--project-id / -p |
Bind to an existing project explicitly (also VYGL_PROJECT_ID). |
--branch / -b |
Override the auto-detected branch (useful in detached-HEAD CI). |
--commit |
Override the auto-detected commit SHA. |
--repo-url |
Override the auto-detected repository URL. |
--git-provider |
Force the SCM type (github, gitlab, bitbucket, gitea); also VYGL_GIT_PROVIDER. |
--pr |
Pull/merge request number — enables PR comment posting. Auto-detected on GitHub Actions, GitLab CI, and Bitbucket Pipelines. |
--config / -c |
Path to a .vygl.yml (overrides auto-discovery). |
--show-all |
Print every finding to stdout, not just the gate-triggering ones. |
--trigger |
Mark the trigger source on the dashboard (default ci; e.g. manual, scheduled). |
--api-url |
Override the API base URL. Defaults to VYGL_API_URL then https://api.vygl.io. |
Common patterns
Section titled “Common patterns”Scan only secrets and dependencies, fail on high or above:
vygl scan --scan-types secrets,sca --fail-on high .Local-only run (no upload):
vygl scan --no-upload .Scan a container image:
vygl scan --image myorg/myapp:1.2.3CI run with explicit branch (detached HEAD):
vygl scan --branch main .Scan a PR and post a comment:
vygl scan --fail-on high --pr 1234 .Write machine-readable output:
vygl scan --no-upload --json evidence/vygl.json .SAST rules
Section titled “SAST rules”The CLI image ships no SAST ruleset. Rules come from your organisation: when you are signed in, the CLI fetches the org policy and scans with the rules seeded for your account. Nothing to configure.
For self-hosted or air-gapped installs, point the CLI at a ruleset you hold a licence for:
vygl scan --rules /opt/vygl-rules --no-upload .or set it once in .vygl.yml:
scan: sast: rules_path: /opt/vygl-rulesThe org policy still wins when you are signed in, so a local ruleset is a fallback for disconnected use rather than an override.
Vygl never uses opengrep’s built-in auto ruleset. That config is fetched from Semgrep’s registry on every scan — it cannot work without egress, and it would mean contacting a third party on each run, which is not how a local-first scanner should behave.
JSON output
Section titled “JSON output”The console render is a summary — it stops at 15 findings unless you pass --show-all, and shows only severity, rule and file:line. --json PATH writes the whole result instead: every finding with its message, CWE, snippet and fingerprint, the per-engine outcome, and the dependency inventory.
It needs no account. On the --no-upload path — or with no API key at all — the document is fully populated, so you can run Vygl as a scanner on code that never leaves your machine.
vygl scan --no-upload --json out.json .
# one row per findingjq -r '.findings[] | [.severity, .rule_id, .file_path, (.start_line|tostring)] | @tsv' out.json
# did every engine actually run?jq '.engines' out.json{ "schema_version": 1, "tool": { "name": "vygl", "version": "0.1.0" }, "scan": { "root": "/scan", "local_only": true, "verified": false, "scan_id": null, "scan_types_requested": ["sast","sca","secrets","iac"], "started_at": "…", "completed_at": "…", "git": { "branch": "main", "commit_sha": "…", "repo_url": "…" } }, "engines": { "sast": { "status": "ok", "duration_s": 41.2, "findings": 12, "error": "" }, "secrets": { "status": "error", "duration_s": 0.3, "findings": 0, "error": "engine did not complete: binary_not_found" } }, "summary": { "total": 15, "critical": 1, "high": 4, "medium": 6, "low": 3, "info": 1 }, "type_counts": { "sast": 12, "sca": 3 }, "findings": [ /* … */ ], "dependencies": [ /* … */ ]}Things worth knowing:
- The file is written for every outcome — a clean pass, a
--fail-onbreach, a Block-mode trip, or a failed upload. It lands before the gate runs, so--fail-on high --json out.jsongives you exit code 1 and a complete document. - A write failure never fails your build. An unwritable path prints a warning to stderr; the exit code is whatever the scan itself decided.
- Paths are repo-relative (
server/deploy.js, not/scan/server/deploy.js), so a scan inside a container resolves against your checkout on the host. - Findings are ordered deterministically — severity, then path, line, rule — so two runs over an unchanged tree diff cleanly.
verifiedis alwaysfalseandscan_idalwaysnull. The document is written before results are pushed, so AI verdicts aren’t in it. They live in the dashboard.
Auth commands
Section titled “Auth commands”| Command | What it does |
|---|---|
vygl auth set-key <KEY> |
Store an API key locally in ~/.vygl/credentials (chmod 600). |
vygl auth test |
Verify your key works and show the org it belongs to. |
Project commands
Section titled “Project commands”| Command | What it does |
|---|---|
vygl project list |
List all projects in your organization. |
Results
Section titled “Results”| Command | What it does |
|---|---|
vygl results <SCAN_ID> |
Fetch and print results from a previous scan. |

