Docs › Deployment & Data Sovereignty

Deployment & data sovereignty

For security, procurement, and data-protection reviewers — especially EU, government, defence, and regulated-financial buyers who must know exactly what leaves their environment. This page is a plain, verifiable account of where PullGuard runs and what data it handles.

Where your code goes: nowhere

PullGuard runs inside your CI and never receives your source code. The scanner analyses your code in place, on your runners, and emits results-only outputs — reports, SARIF, JSON — that you control. In its air-gapped configuration it makes zero external calls: no telemetry, no source upload, no phone-home. When run online, the only thing that leaves is license validation (an org name + token) — never your code.

One precise disclosure: free-tier scans running in GitHub Actions may attach an anonymous adoption signal (the public numeric GitHub repository/owner ids — no code, no findings, no PII) to the rules fetch, with opt-out via telemetry: false, PULLGUARD_TELEMETRY=off, or DO_NOT_TRACK=1. Paid, offline, and air-gapped scans never send any telemetry, ever.

Unlike SaaS scanners that upload your code to their cloud for analysis, PullGuard analyses in place and only ever produces a report that stays with you.

Data flow: what does (and doesn't) leave the runner

ModeSource codeFindings / resultsLicense checkRule updates
Air-gapped
self-hosted runner + mirrored image + offline key
never leaves stays in your CI validated locally (signed offline key, no call) uses the image's embedded rules
Online
GitHub-hosted runner + GHCR + online token
never leaves stays in your CI / your git host org + token → our licensing service (no source) signed rule bundle fetched (read-only)

Your code is never uploaded to PullGuard in either mode. The licensing service only validates subscriptions; it never receives scan data.

Fully air-gapped operation

For sovereign / classified / no-egress environments, PullGuard runs with no external dependency at scan time:

  1. Mirror the scanner image into your internal registry (from ghcr.io/pullguard-dev/pullguard:<tag>).
  2. Run on a self-hosted runner inside your network (not a GitHub-hosted runner).
  3. Use an offline license key — it validates locally against a signed key and never contacts our licensing service.

Result: the scan reads your code, writes a report, and makes zero network calls. CVE scanning stays available offline via the local vulnerability database — update it on a connected machine and copy the directory across your air-gap (the directory is the database; there is no archive step). Most SAST tools cannot operate this way.

Populating that database is a required step. It is the only CVE source on an air-gapped runner — without it there is nothing to check against, and PullGuard says so rather than implying your dependencies are clean: the scan is marked limitedCoverage with builtin/dependency-vulnerabilities in incompleteAnalyzers (visible in the JSON, the SARIF run properties, and the GitHub Security tab), plus a log warning. Zero CVE findings without a database means not checked, not clean. See Air-gapped mode for the setup and how to verify it.

Supply-chain verifiability

The published scanner image ships SLSA build provenance and an SBOM. Your security team can verify how the image was built (source commit, builder, materials) and what it contains — before it ever runs in your environment:

docker buildx imagetools inspect ghcr.io/pullguard-dev/pullguard:<tag>

The image is also cryptographically signed with keyless cosign (Sigstore) — verify it came authentically from PullGuard's build pipeline, independent of the registry and enforceable by signed-images-only admission controllers (Sigstore policy-controller, Kyverno, Connaisseur):

cosign verify ghcr.io/pullguard-dev/pullguard:<tag> \
  --certificate-identity-regexp '^https://github.com/pullguard-dev/PullGuard/\.github/workflows/publish-image\.yml@refs/tags/v' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Windows (Git Bash): the shell rewrites the backslashes in --certificate-identity-regexp, breaking the pattern. Prefix the command with MSYS2_ARG_CONV_EXCL='*', or use the exact --certificate-identity 'https://github.com/pullguard-dev/PullGuard/.github/workflows/publish-image.yml@refs/tags/<tag>' (no regex). Linux, macOS, and CI need no change.

Release tags (e.g. v1.4.1, the first cosign-signed scanner release) are immutable and preserved forever; pin an exact tag or a sha256: digest (the image-pin workflow input) for change-controlled environments.

Self-hosted server: results-only, on your infrastructure

Want a live, multi-repo dashboard without giving anyone your data? The self-hosted PullGuard server (Enterprise) runs inside your boundary, on your domain. Your CI POSTs scan results only — findings, scores, grades — never source code; the ingest endpoint actively rejects source-looking payloads. The server validates its licence offline and makes no call home, so it too runs fully air-gapped.

Regulatory posture

What to verify yourself

Don't take our word for it — every claim above is testable in your own environment:

  1. Run a scan on a self-hosted runner with an offline key and capture the network traffic — you should see zero outbound calls.
  2. docker buildx imagetools inspect ghcr.io/pullguard-dev/pullguard:<tag> — confirm the SLSA build provenance + SBOM.
  3. Inspect any emitted report — it contains findings and metadata about your code, produced locally; nothing is sent to us.

Questions from a procurement or data-protection review are welcome — hello@pullguard.dev.