← Getting Started

Configuration Reference

Every knob PullGuard exposes. All fields are optional — the scanner works out of the box with sensible defaults. Override only what you need.

On this page

Workflow inputs

Pass values via the with: block on pullguard-dev/pullguard-action@v1. Every input is optional.

Input Default What it does
license-key none Your pg_live_* token from Stripe checkout. Unlocks Pro (42 of 43 analyzers, 1 repo), Team (all 43, 10 repos), or Enterprise (all 43, unlimited repos + SSO + SLA). Leave empty for the Free tier (14 analyzers).
fail-on-severity empty Fail the workflow if any finding at this severity or above exists. Values: info, minor, moderate, major, critical. Empty (default) means PullGuard only reports — never blocks merge.
hourly-rate 150 Developer hourly rate in USD. Used to convert finding-effort estimates into dollar figures in the cost-of-change report. Set to your fully-loaded engineer rate for an accurate ROI signal.
config auto-detect Path to a .driftrc.yml file relative to the repo root. If not set, PullGuard looks for .driftrc.yml at the repo root.
path . Subdirectory to scan, relative to the repo root. Default scans the whole repo. Useful for monorepos — run one PullGuard step per package, each with its own path + config.
report-to-app false Enterprise When true, post the report to the PullGuard GitHub App so findings render as native Check Run annotations on the PR Files-changed view. Requires the Marketplace App to be installed.

Outputs (for downstream steps)

Output Type What it contains
steps.<id>.outputs.score integer 0–100 Drift score (lower is better). Drives Grade.
steps.<id>.outputs.grade A–F Letter grade derived from score and severity mix.
steps.<id>.outputs.findings integer Total finding count (all severities).

Example: fail builds on Major+, custom rate

      - id: pullguard
        uses: pullguard-dev/pullguard-action@v1
        with:
          license-key: ${{ secrets.PULLGUARD_LICENSE_KEY }}
          fail-on-severity: major
          hourly-rate: 220
          path: services/payments

      - name: Post score to internal dashboard
        if: always()
        run: |
          curl -X POST https://dashboard.example.com/scores \
            -d "score=${{ steps.pullguard.outputs.score }}" \
            -d "grade=${{ steps.pullguard.outputs.grade }}"

.driftrc.yml

Per-repository configuration file at the repo root. All fields are optional. An empty file is valid — the schema fills in defaults so you only specify what you want to override.

Schema validation runs on load. Invalid types or out-of-range values produce a clear error before the scan starts (no silent ignores).

Top-level fields

Field Type Default Purpose
exclude string[] standard ignores Glob patterns appended to the built-in exclude list (node_modules, dist, build, .git, etc.). User patterns add to defaults — you cannot accidentally start scanning node_modules.
maxDepth number 8 Maximum directory depth to traverse. Guards against symlink loops.
maxFiles number auto Hard cap on files analyzed. Hit this in a monorepo → scan per package instead.
analyzers map {} Per-analyzer enable / severity / options overrides. See Analyzers block.
thresholds map see below Numeric thresholds for complexity / duplication / monolithic-file / nesting / type-coverage.
output map see below Format, minimum severity, grouping, remediation toggle.
plugins string[] [] Explicit plugin allowlist. Plugins are sandboxed; auto-discovery is disabled by design.
baseline string none Path to a baseline report — the scanner reports only NEW findings vs the baseline.
cost { hourlyRate } 150 Centrally-managed hourly rate (alternative to the workflow input).
compliance map SOC 2 only Enable HIPAA / PCI DSS / NIST / ISO 27001 evidence sections.
repo { type } auto-detect Override the repo-type heuristic that gates OSS-hygiene checks.
taint map {} Custom taint sources, sinks, and sanitizers for proprietary frameworks.
db map OSV remote Local vulnerability database location and freshness policy. See Air-gapped mode.

analyzers: — per-analyzer overrides

Keyed by analyzer ID (e.g. builtin/complexity, builtin/security). Each entry can disable the analyzer, override its default severity, or pass analyzer-specific options.

analyzers:
  builtin/complexity:
    enabled: true
    severity: minor          # downgrade complexity findings
  builtin/duplication:
    enabled: false           # turn this analyzer off entirely
  builtin/security:
    enabled: true            # security analyzers cannot be disabled
                             # (validation rejects this with a clear error)
  builtin/naming:
    severity: info
    options:
      ignorePatterns:
        - "_test\\.ts$"

Note: security-category analyzers cannot be disabled or downgraded below their built-in floor — the schema rejects attempts to do so. This is the integrity guarantee enterprise buyers (CISO / SOC auditor) rely on.

thresholds: — numeric tuning

thresholds:
  complexity:
    maxCyclomatic: 15        # default 10
    maxCognitive: 20         # default 15
    maxParameters: 6         # default 5
    maxFunctionLength: 80    # default 50 (honest line count, not raw)
  duplication:
    minBlockSize: 6          # default 5 (lines)
    maxDuplicationPercent: 5 # default 3 (% of total LoC)
  monolithicFile:
    maxFileLines: 600        # default per-language; this overrides for all
    maxExports: 30           # default 20
  nesting:
    maxDepth: 5              # default 4
  typeCoverage:
    minCoveragePercent: 90   # default 85
    maxAnyPerFile: 3         # default 5

Per-language defaults (Java 1000 / Go 400 / TS 600 etc.) apply automatically. Override with a single thresholds.monolithicFile.maxFileLines value to apply globally; per-language overrides are roadmap.

output: — format and noise control

output:
  format: markdown           # text | json | sarif | markdown
  minSeverity: moderate      # info | minor | moderate | major | critical
  groupBy: severity          # category | severity | file
  showRemediation: true
  sarif:
    toolName: "PullGuard"
    toolVersion: "1.0.0"

minSeverity filters the rendered output but does NOT change what is scanned — the JSON artifact always contains every finding for archive / audit purposes.

compliance: — framework opt-ins

compliance:
  hipaa: true
  pci-dss: true
  nist: false
  iso-27001: false

SOC 2 evidence (8 controls) renders by default. Enable the others when your regulatory scope demands them. Each section emits a "provides evidence for" disclaimer — PullGuard helps auditors; it does not grant compliance.

repo.type: — hygiene-check gating

Some checks are appropriate for public OSS but irrelevant for private customer-delivery repos (e.g. missing LICENSE or SECURITY.md). PullGuard auto-detects via GitHub Actions env + local signals; override here when the heuristic is wrong.

repo:
  type: customer-delivery    # public-oss | private-enterprise |
                             # customer-delivery | internal-service |
                             # fork-research

taint: — custom sources, sinks, sanitizers

Enterprise CMS platforms, internal RPC layers, and proprietary frameworks have their own taint surfaces that the built-in patterns do not know about. Add them here. Patterns are language-keyed regular expressions.

taint:
  sources:
    java:
      - "\\bContentManagementData\\.get\\s*\\("
    python:
      - "\\binternal_rpc_call\\s*\\("
  sinks:
    java:
      - "\\bTemplateEngine\\.render\\s*\\("
  sanitizers:
    - "\\bSecurityUtils\\.escape\\s*\\("

cost: — centrally-managed hourly rate

cost:
  hourlyRate: 220

Same effect as the hourly-rate workflow input but lives in the repo so platform teams can manage rate centrally without editing every workflow file.

plugins: — explicit allowlist

plugins:
  - "@acme/pullguard-plugin-payments-rules"
  - "@acme/pullguard-plugin-internal-rpc"

Plugin auto-discovery is intentionally disabled. Each plugin must be explicitly named here AND installed in the runner. Plugins execute in a sandboxed context with a limited API surface — supply-chain defence by design.

.pullguardignore

Suppression file at the repo root for known-acceptable findings. File-glob patterns suppress entire paths; rule: entries suppress a specific rule globally or in a path.

Syntax

# Comments start with #

# Suppress all findings in vendored / generated paths
vendor/**
src/generated/**
**/*.gen.ts

# Suppress a specific rule everywhere
rule:builtin/complexity:cyclomatic-too-high

# Suppress a specific rule in a specific path
rule:builtin/duplication:type-1-clone src/legacy/**

# Suppress an analyzer's findings on one file
rule:builtin/dead-code:* src/api/public-surface.ts

Hard floor: security-category analyzers cannot be suppressed via rule: entries. The loader rejects any suppression targeting a security rule with a clear error. File-glob suppressions still apply (a path you do not scan is a path you do not scan), but a security rule cannot be silently ignored on a file that IS scanned.

Adding suppressions from a PR comment

Team Enterprise Comment /pullguard ignore <rule-id> on a PR. The PullGuard App opens a follow-up PR adding the entry to .pullguardignore with the original finding linked in the body for audit purposes. Reviewer of that follow-up PR is the auditor of record.

Tier limits

Each plan has a different scope. Tier limits are enforced at scan time via online validation; the scanner falls back to Free tier with a clear banner when a cap is hit, so a scan never silently breaks.

Plan Analyzers Repositories Contributors Enforcement
Free 14 Unlimited public; 1 private Unlimited Hard — analyzers gated client + server side
Pro 42 of 43 1 private (bound at first scan) Unlimited Hard — three-layer repo binding (Worker + scanner + Worker server-side)
Team All 43 Up to 10 private Unlimited Hard — repos accumulate on first scan; 11th repo runs Free until a slot is freed or the customer upgrades
Enterprise All 43 Unlimited Unlimited Contractual (no code-level cap)

Team-tier 10-repo cap behaviour

When a Team-tier license scans a repository, that repository is recorded against the license. The customer can scan up to 10 distinct repositories. Behaviour at the boundary:

No contributor cap

PullGuard does not cap contributors on any tier. A 5-developer team and a 50-developer team on the same set of repositories pay the same price. Repository count is the single tier dimension.

Air-gapped mode

For runners behind a firewall or with no outbound internet, PullGuard ships a local vulnerability database. Update it from a connected machine, transfer the archive across your air-gap, and run scans with no network calls.

1. Download the OSV mirror (connected machine)

docker run --rm -v "$PWD/db:/db" \
  ghcr.io/pullguard-dev/pullguard:latest \
  db update --path /db

2. Export to a portable archive

docker run --rm -v "$PWD/db:/db" -v "$PWD:/out" \
  ghcr.io/pullguard-dev/pullguard:latest \
  db export --path /db --to /out/pullguard-db.zip

3. Transfer + import on the air-gapped runner

docker run --rm -v "$PWD:/in" -v "$PWD/db:/db" \
  ghcr.io/pullguard-dev/pullguard:latest \
  db import --from /in/pullguard-db.zip --path /db

4. Point .driftrc.yml at the local DB

db:
  path: /opt/pullguard/db   # absolute path on the runner
  maxAgeDays: 30            # warn (do not fail) when DB exceeds this

With db.path set, no outbound calls to OSV are made. The archive is integrity-checked on import (SHA-256). On-disk files are created with restrictive permissions.

Secrets & permissions

Repository secrets

Secret name Required for Value
PULLGUARD_LICENSE_KEY Pro / Team / Enterprise Your pg_live_* token from Stripe checkout email.
GITHUB_TOKEN All tiers Auto-provided by GitHub Actions — you do not create this.

Workflow permissions

PullGuard needs the following permissions: block at the workflow or job level:

permissions:
  contents: read           # to read your code
  pull-requests: write     # to post the PR comment
  checks: write            # to write Check Runs (Enterprise: report-to-app)

If your organisation default permissions are restrictive (recommended), the block above is required. If your defaults are permissive (read-write), the workflow runs without an explicit block.

Repository-level vs organisation-level secrets

Repository-level is the CISO-friendly default — each repo gets its own secret, with explicit per-repo audit trail and no cross-repo blast radius. Organisation-level with a repo allowlist works for platform teams managing many repos. Both models are supported; the workflow file is identical.


← Getting Started PullGuard home →