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
with: block on pullguard-action@v1
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.
|
| 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). |
- 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.ymlPer-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).
| 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 tuningthresholds:
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 controloutput:
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-inscompliance:
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, sanitizersEnterprise 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 ratecost:
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 allowlistplugins:
- "@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.
# 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.
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.
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.
docker run --rm -v "$PWD/db:/db" \
ghcr.io/pullguard-dev/pullguard:latest \
db update --path /db
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
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
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.
| 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. |
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 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.