Apache 2.0 · ghcr.io/zagware/zagware-scanner

Zagware Scanner Documentation

Everything you need to run IaC, SCA, and secrets scanning in CI. Free and open source — no account required for any of it. The last section covers what changes if you connect the optional Git Tracking Platform.

← Back to overview

1. Overview

Zagware Scanner runs on every pull request and posts a single, focused comment showing only the security findings introduced by that PR — not the hundreds that may already exist in the codebase. Three scan engines ship in one container:

EngineWhat it scansDetects
KICS (Checkmarx)Infrastructure-as-code files (Terraform, Kubernetes, Dockerfile, CloudFormation…)Misconfigurations, insecure defaults, open ports, missing encryption
Grype (Anchore)Package manifests and lockfiles (npm, pip, Go, Maven, Gem…)CVEs, GHSA advisories — with CVSS, EPSS, and KEV catalog status
betterleaksFilesystem contents (working-tree state)Leaked credentials — API keys, tokens, private keys, other secret patterns

Clone base + PR branch → scan both in parallel → diff by content fingerprint (not line number) → post the delta as a single, in-place PR comment updated on every push.

2. Free scanner vs. the platform

✅ Scanner — free, forever, no account

Everything above runs standalone: PR comments on GitHub, GitLab, Bitbucket, and Azure DevOps; IaC + SCA + secrets scanning; interactive and file-based suppressions; self-hosting; severity filtering and merge-blocking. ZAGWARE_PLATFORM_URL / ZAGWARE_PLATFORM_TOKEN are never required. Apache 2.0 licensed — read, fork, and self-host the image.

🔒 Git Tracking Platform — optional, paid add-on

Set ZAGWARE_PLATFORM_URL and ZAGWARE_PLATFORM_TOKEN to also upload every scan to app.zagware.io. That unlocks scan history and trend charts per repo, a durable suppression audit trail (who suppressed what, when, and why), CIS GitHub Benchmark compliance scans, and change-management evidence for SOC-2 audits — reporting the scanner itself intentionally doesn't do. See the platform page for details.

3. Quick start

GitHub Actions

Create .github/workflows/zagware-scanner.yml:

name: Zagware Security Scanner on: pull_request: types: [opened, synchronize, reopened] issue_comment: types: [created] # enables /zagware suppress comments permissions: pull-requests: write contents: write # required for /zagware suppress to commit suppressions.yaml jobs: security-scan: runs-on: ubuntu-latest steps: - name: Zagware Security Scanner uses: docker://ghcr.io/zagware/zagware-scanner:latest env: GITHUB_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} ZAGWARE_PLATFORM_URL: ${{ secrets.ZAGWARE_PLATFORM_URL }} # optional ZAGWARE_PLATFORM_TOKEN: ${{ secrets.ZAGWARE_PLATFORM_TOKEN }} # optional

GITHUB_TOKEN is provided automatically — no secrets to configure for the scanner itself. Omit ZAGWARE_PLATFORM_URL / ZAGWARE_PLATFORM_TOKEN to run fully standalone (PR comment only, no upload).

GitLab CI

GitLab's built-in CI_JOB_TOKEN can't post merge request notes — create a project or group access token with api scope and store it as a masked GITLAB_TOKEN CI/CD variable, then add to .gitlab-ci.yml:

zagware-scanner: image: ghcr.io/zagware/zagware-scanner:latest rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" script: - zagware-scan allow_failure: true # remove to block merges on new findings

Bitbucket Pipelines

Required repository variables: BITBUCKET_API_TOKEN (Atlassian API token) and ATLASSIAN_EMAIL.

pipelines: pull-requests: '**': - step: name: Zagware Security Scanner image: ghcr.io/zagware/zagware-scanner:latest script: - zagware-scan

Azure DevOps

$(System.AccessToken) is available to any step that maps it via env:. Usually needs no manual permission grant beyond that.

trigger: none pr: branches: { include: ['*'] } pool: { vmImage: ubuntu-latest } steps: - script: | docker run --rm -e SYSTEM_ACCESSTOKEN ghcr.io/zagware/zagware-scanner:latest env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) continueOnError: true

4. Configuration

VariableDefaultDescription
ZAGWARE_PLATFORM_URLBase URL of the Zagware platform, e.g. https://app.zagware.io. Required for dashboard upload only.
ZAGWARE_PLATFORM_TOKENAPI token (gtp_…) from Settings → API Tokens. Required for dashboard upload only.
ZAGWARE_MIN_SEVERITYallMinimum severity to report: CRITICAL HIGH MEDIUM LOW INFO.
ZAGWARE_FAIL_ON_NEWfalseExit 1 when new findings are found at or above the severity threshold — blocks the merge.
ZAGWARE_EXCLUDE_PATHS.gitComma-separated paths or globs to exclude from IaC scanning.
ZAGWARE_SCA_ENABLEDtrueSet false to skip Grype dependency scanning entirely.
ZAGWARE_SECRETS_ENABLEDtrueSet false to skip betterleaks secrets scanning entirely.
ZAGWARE_SECRETS_FAIL_ON_PUBLICtrueExit 1 on a new secret in a public repo, regardless of ZAGWARE_FAIL_ON_NEW.
ZAGWARE_TELEMETRYonSet off to disable anonymous usage telemetry.
ZAGWARE_TELEMETRY_INCLUDE_REPO_NAMEfalseSet true to send your org/repo name in clear instead of a one-way hash.

5. Supported IaC formats (KICS)

PlatformFile types
Terraform.tf, .tfvars
KubernetesYAML manifests
HelmChart templates
DockerfileDockerfile*
AWS CloudFormationJSON, YAML templates
Azure Resource ManagerARM JSON templates
AnsiblePlaybooks and task files
OpenAPI / SwaggerAPI specification files
Docker Composedocker-compose.yml
GitHub ActionsWorkflow files
Serverless Frameworkserverless.yml
PulumiInfrastructure programs

KICS auto-detects file types — no configuration needed.

6. Supported dependency ecosystems (Grype)

EcosystemDetected via
Node.js / npmpackage-lock.json, yarn.lock, pnpm-lock.yaml
Pythonrequirements.txt, Pipfile.lock, poetry.lock
Gogo.sum
RubyGemfile.lock
Java / Mavenpom.xml, build.gradle
RustCargo.lock
PHPcomposer.lock
.NETpackages.lock.json
OS packagesAlpine (apk), Debian/Ubuntu (dpkg), RHEL/CentOS (rpm) in Dockerfiles

Enabled by default when manifest files are detected. Set ZAGWARE_SCA_ENABLED=false to disable.

7. Secrets detection

betterleaks scans working-tree contents of both branches for leaked API keys, tokens, private keys, and other secret patterns. Unlike IaC/SCA, it has no severity taxonomy — findings are prioritized by repository visibility: a secret leaked in a public repo is immediately exposed, so public repos are always treated as urgent regardless of ZAGWARE_FAIL_ON_NEW.

Scanning runs against current filesystem state only (working-tree diff, matching the IaC/SCA architecture) — not full git history. If a secret was introduced and later removed within the PR's own history, it won't be re-flagged once it's gone from the working tree.

Findings never include the raw secret value anywhere — PR comment, platform upload, or scan artifacts — only rule id, file path, line number, tags, and whether betterleaks confirmed the credential is live.

8. Suppressions

The easy way — comment on the PR

Every scan comment lists each new finding with a short id. Post the suggested command as a PR comment:

/zagware suppress 96e6e0d1 false positive — test resource, not production

The scanner resolves the id, writes it to .zagware/suppressions.yaml on the PR branch, commits, pushes, and re-runs in the same comment — no waiting for a second run. Currently GitHub-only; GitLab, Bitbucket, and Azure DevOps use the manual file method below.

The manual way — edit the file directly

# .zagware/suppressions.yaml - id: abc123def456... # similarity_id, full or 6+ char prefix reason: "False positive — test resource, not production" - id: def789abc012... reason: "Accepted risk — mitigated by network policy"

Suppressed findings are excluded from the PR comment but still logged in scan output. With the platform connected, every scan uploads the full current set of active suppressions — a durable, queryable audit record of who suppressed what and why, attributed precisely for PR-comment suppressions and via best-effort git blame for hand-edited ones.

9. Severity filtering

ZAGWARE_MIN_SEVERITYIaC findings shownSCA findings shown
(unset)All (HIGH, MEDIUM, LOW, INFO)All (CRITICAL … NEGLIGIBLE)
HIGHHIGH onlyCRITICAL, HIGH
MEDIUMHIGH, MEDIUMCRITICAL, HIGH, MEDIUM
LOWHIGH, MEDIUM, LOWCRITICAL, HIGH, MEDIUM, LOW

Secrets findings are unaffected by ZAGWARE_MIN_SEVERITY — betterleaks has no severity taxonomy. Use ZAGWARE_SECRETS_FAIL_ON_PUBLIC as the equivalent gate.

10. How findings are fingerprinted

Reformatting or line shifts never create spurious new findings; fixing a finding removes its fingerprint even if similar issues remain elsewhere; moving a vulnerable package to a new manifest file is treated as new.

11. Image tags & release channels

TagDescription
:<version>Immutable per release, e.g. :2.2.0. Pin by digest for the strongest guarantee.
:latestNewest release. Moves on every tag push. Not security-vetted.
:stablePromoted from :latest after a 14-day cooling period + clean CVE scan.
:secureIdentical digest to :stable; explicitly marks the security-audited image.

Recommendation: pin :stable (or :secure) by digest for production CI; use :latest for experimentation.

12. Supply chain security

Every release is cosign-signed (keyless, GitHub OIDC → sigstore Rekor), carries an SPDX SBOM as an OCI attestation, and a SLSA Build Level 3 provenance attestation linking the image digest to its exact source commit and workflow run. Every bundled binary (KICS, Syft, Grype, betterleaks) is SHA256-checksum-verified at build time.

# Verify the image signature cosign verify ghcr.io/zagware/zagware-scanner:latest \ --certificate-identity-regexp "^https://github.com/zagware/zagware-scanner/.+$" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com # Verify SLSA provenance gh attestation verify oci://ghcr.io/zagware/zagware-scanner:latest \ --repo zagware/zagware-scanner

13. Self-hosting

git clone https://github.com/zagware/zagware-scanner.git cd zagware-scanner docker build -t your-registry/zagware-scanner:latest . docker push your-registry/zagware-scanner:latest

Build-time internet access is needed to pull KICS, Syft, Grype, and betterleaks from their public GitHub releases. At scan time, the image only needs access to clone your repository and post the PR comment. Self-hosted builds verify binary SHA256 checksums but not the upstream cosign signatures on Syft/Grype checksum files — that step runs only in the official publish CI.

14. Telemetry

Anonymous usage telemetry (via PostHog) reports which CI platforms are used and rough usage volume. No code, file paths, finding descriptions, CVE/package details, branch names, commit SHAs, or credentials are ever sent.

SentNever sent
CI platform, hashed repo/org id, scan durations, which scan types ran, bucketed finding counts (0/1-5/6-20/21+), exit code, scanner versionFile contents, file paths, finding descriptions, CVE IDs, package names/versions, branch names, commit SHAs, CI tokens/secrets, IP geolocation

Disable entirely with ZAGWARE_TELEMETRY: "off". Best-effort and fail-silent — a 3-second timeout, never blocks or slows the pipeline even if unreachable.

15. FAQ

Does the scanner send my code anywhere?

No. It runs entirely within your CI environment, clones with your CI token, scans locally, and posts results via your CI platform's API. No code, file contents, or file paths ever leave your infrastructure.

What if I already have thousands of existing findings?

That's exactly the scenario this tool is built for — existing findings are ignored. You only see what the PR under review introduces.

Can I use it without the platform?

Yes. The scanner posts PR comments standalone. ZAGWARE_PLATFORM_URL and ZAGWARE_PLATFORM_TOKEN are entirely optional.

What permissions does it need?

PlatformWhat's needed
GitHub Actionspermissions: pull-requests: write (GITHUB_TOKEN is automatic)
GitLab CIGITLAB_TOKEN with api scope
BitbucketBITBUCKET_API_TOKEN (Atlassian API token) + ATLASSIAN_EMAIL
Azure DevOpsBuild Service: Contribute to pull requests + OAuth token access

Looking for the full README?

Organization-wide enforcement (GitHub rulesets / GitLab Pipeline Execution Policy), the complete OpenAPI-style upload payloads, and contribution guidelines live in the repo.

View zagware/zagware-scanner on GitHub