CI/CD Pipelines
Add a single Vygl step to your pipeline to scan every push and pull request. The CLI auto-detects branch, commit, and repository URL from CI environment variables — the only secret you need to configure is VYGL_API_KEY.
Pipeline configs
Section titled “Pipeline configs”name: Vygl Security Scanon: push: branches: [main] pull_request:
jobs: security-scan: runs-on: ubuntu-latest container: image: vygl/vygl-cli:latest options: --entrypoint "" env: VYGL_API_KEY: ${{ secrets.VYGL_API_KEY }} steps: - uses: actions/checkout@v4 - run: vygl scan --fail-on high . # Optional: post scan summary as a PR comment. # Add a Git Token in project settings, then uncomment: # - run: vygl scan --fail-on high --pr ${{ github.event.pull_request.number }} .Add VYGL_API_KEY under Settings → Secrets and variables → Actions. Branch, commit, and repo URL are auto-detected from the GitHub Actions environment.
vygl-scan: stage: test image: name: vygl/vygl-cli:latest entrypoint: [""] variables: VYGL_API_KEY: $VYGL_API_KEY script: - vygl scan --fail-on high . # Optional: post scan summary as an MR comment. # Add a Git Token in project settings, then use: # - vygl scan --fail-on high --pr $CI_MERGE_REQUEST_IID . # Optional: restrict to MRs and the default branch only. # rules: # - if: $CI_PIPELINE_SOURCE == "merge_request_event" # - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHAdd VYGL_API_KEY under Settings → CI/CD → Variables (mark as masked). Works with GitLab.com and self-hosted instances.
pipelines: default: - step: name: Vygl Security Scan image: name: vygl/vygl-cli:latest entrypoint: [""] script: - vygl scan --fail-on high . # Optional: post scan summary as a PR comment. # Add a Git Token in project settings, then use: # - vygl scan --fail-on high --pr $BITBUCKET_PR_ID . pull-requests: '**': - step: name: Vygl Security Scan image: name: vygl/vygl-cli:latest entrypoint: [""] script: - vygl scan --fail-on high . # Uncomment to post scan summary as a PR comment: # - vygl scan --fail-on high --pr $BITBUCKET_PR_ID .Add VYGL_API_KEY as a repository variable in Repository settings → Pipelines → Variables.
docker run --rm \ -v "$(pwd)":/scan \ -e VYGL_API_KEY=vgl_your_key \ vygl/vygl-cli scan --fail-on high /scan# Optional: add --pr <number> to post a scan summary as a PR/MR commentWorks with any CI system that supports Docker containers (Jenkins, CircleCI, Drone, Buildkite, Gitea Actions, …).
Large repositories
Section titled “Large repositories”On a large codebase most of the scan is SAST. Three things keep it inside a CI job.
Cache SAST results between runs. SAST keeps each file’s results and, next time, analyses only the files that changed. It is still a full scan, so findings that disappear are still closed as fixed. A file is reused only if its contents and path, the exact rules and the engine version all match, so a ruleset update or engine upgrade starts over, and a cache file that can’t be read is ignored. CI runners start empty, so keep the cache directory between pipeline runs:
env: VYGL_API_KEY: ${{ secrets.VYGL_API_KEY }} # Relative to the workspace, which the job can always write (in a container # job ${{ github.workspace }} is the host's path). The CLI never scans its own cache. VYGL_CACHE_DIR: .vygl-cache steps: - uses: actions/checkout@v4 # After checkout: checkout cleans the workspace. - uses: actions/cache@v4 with: path: .vygl-cache key: vygl-sast-${{ github.ref_name }}-${{ github.run_id }} restore-keys: | vygl-sast-${{ github.ref_name }}- vygl-sast-${{ github.event.repository.default_branch }}- - run: vygl scan --fail-on high .vygl-scan: # ...image as above variables: VYGL_API_KEY: $VYGL_API_KEY # GitLab only caches paths inside the project; the CLI never scans its own cache. VYGL_CACHE_DIR: $CI_PROJECT_DIR/.vygl-cache cache: key: vygl-sast-$CI_COMMIT_REF_SLUG fallback_keys: [vygl-sast-$CI_DEFAULT_BRANCH] paths: [.vygl-cache/] script: - vygl scan --fail-on high .The cache holds excerpts of your code, the same as the checkout it came from. Restore a protected branch’s cache only from runs of that branch. On GitHub a pull request cannot write its base branch’s cache; on GitLab, keep Use separate caches for protected branches turned on.
Budget. SAST gets 45 minutes and every other engine 15. The engines run side by side, so the scan takes about as long as the slowest one. An engine that runs out of time is stopped, and the scan ends INCOMPLETE with exit code 2. Its results are missing everything that engine would have found, so it isn’t a pass. Raise the budget with scan.engine_timeout_seconds (keep it under your CI’s own job timeout), or accept incomplete scans with --allow-incomplete.
Files cut short. SAST also limits how long each rule may spend on one file, and gives up on a file after three rules run out of time — more likely on a busy runner. The scan output lists such files, and Vygl leaves their existing findings as they were rather than marking them fixed.
Skip code that isn’t yours. Generated and vendored code — bundled libraries, minified files, generated data — costs scan time and produces findings you won’t fix. List it under scan.sast.exclude_paths. Findings already open in those files are retired as Out of policy, not counted as fixed.
What gets auto-detected
Section titled “What gets auto-detected”When run inside CI, the CLI reads environment variables to fill in scan metadata:
- Repository URL, branch, commit SHA — from CI-native variables (
GITHUB_REPOSITORY,CI_COMMIT_REF_NAME,BITBUCKET_BRANCH, etc.). - PR number — auto-detected on GitHub Actions, GitLab CI, and Bitbucket Pipelines (from
GITHUB_REF=refs/pull/N/merge,CI_MERGE_REQUEST_IID, andBITBUCKET_PR_IDrespectively). Use--pronly to override or in unsupported CI systems. - Trigger type (push / pull_request / scheduled) — derived from CI variables.
- Commit author (email, name, external ID) — from CI env or
git log. Disable withscan.send_committer: falsein.vygl.ymlif you don’t want author metadata in the dashboard.
If detection fails, override with --repo-url, --branch, --commit, --pr, or --git-provider flags (VYGL_GIT_PROVIDER env var also works).

