Skip to content

thinkgrid-labs/greengate

Repository files navigation

greengate

Stop the supply-chain attacks your CVE scanner can't see — one static binary, no Docker.

License: MIT Build GitHub release Crates.io Downloads MSRV

Docs · Config Reference · CI Integration · SAST Rules


CVE scanners and lock-file diffing share a blind spot: they only catch known vulnerabilities in packages that are already installed. They cannot see a malicious postinstall or build.rs script that drops a binary, exfiltrates your secrets, and deletes itself before the install finishes — the "phantom dropper" pattern. greengate watches the install as it happens and catches exactly that class of attack, for npm, yarn, pnpm, bun, pip, and cargo, in one static binary. No Docker, no daemon, no known-CVE database required.

Built for the AI-coding era. When an agent writes your code, it also picks your dependencies — and LLMs hallucinate package names that don't exist. Attackers register those names ("slopsquatting") and wait for the next agent to install them. Edit-distance typosquat checks miss it, because the invented name isn't close to anything real. greengate's slopsquat guard scores registry metadata (does it exist? how new? how adopted?) on every package an install introduces, and halts before a hallucinated dependency lands.

Once it's in your pipeline, the same binary also runs the rest of your security and quality gates — secret scanning, AST-based SAST, dependency CVE audit, SBOM attestation, coverage, and CI-config hygiene — so you don't string together six separate tools. But the reason to reach for greengate first is the supply-chain gate the others don't have.

No Node. No Python. No JVM. The core gates need no Docker either — just copy the binary and run. (A few opt-in features add a dependency: image scanning needs Docker, SBOM signing needs cosign, and the registry/CVE checks need network access.)


Contents


Why greengate?

The gap incumbents leave open. npm audit, Dependabot, and Trivy answer one question: "do any installed packages have a known CVE?" They are blind to a package that has no CVE but runs a malicious install script — the attack pattern behind most recent npm/PyPI compromises. That script writes and executes a payload, then deletes it, so a lock-file diff sees nothing and the CVE database has nothing to match. This is the gate greengate adds.

How the supply-chain gate works: wrap your install (greengate watch-install npm ci, greengate pip-install ..., greengate cargo-add ...) and greengate runs three layers — a pre-flight static scan of lifecycle/build.rs scripts for network + eval + exfiltration patterns, runtime detection of files created-then-deleted during the install window (the phantom dropper), and post-install executable-drop detection — halting before the compromised code can reach the rest of your pipeline. No known-CVE database, no Docker required.

Then it replaces the rest of your stack too. A typical pipeline also strings together gitleaks, semgrep, trivy, jest --coverage, and a CI-config linter — each installed separately, version-pinned separately, and emitting a different output format. greengate runs all of those gates from the same binary, one config file, one exit code. Breadth is what keeps it in your pipeline; the supply-chain gate is why it earns a place there.

Key properties:

  • Catches install-time supply-chain attacks — the phantom-dropper / malicious-lifecycle-script class that CVE scanners and lock-file diffing structurally cannot detect, across npm/yarn/pnpm/bun/pip/cargo.
  • Single static binary — ships as a single musl-linked executable for Linux, macOS, and Windows. Copy it to /usr/local/bin and it works. Every release binary is keyless-signed with Sigstore (verify it).
  • AST-accurate SAST — uses tree-sitter to parse real ASTs instead of fragile regex over source text. No false positives from comments or string literals.
  • CI-native output — emits SARIF, JUnit XML, GitLab SAST JSON, and GitHub Check Run annotations with per-line findings. Plugs into Code Scanning, GitLab Security Dashboard, and SonarQube out of the box.
  • Zero configuration required — sensible defaults work on any repo; .greengate.toml is optional.

Where greengate is intentionally not the strongest tool: on any single classic axis in isolation — raw secret-pattern count, deep inter-procedural taint, or a rules marketplace — a dedicated incumbent (gitleaks, Semgrep Pro) will go deeper. Our own reproducible secret-detection benchmark publishes the numbers honestly, false positives included. See Known Limitations for exactly where the boundaries are. greengate's bet is the supply-chain gate plus good-enough coverage of everything else in a single self-contained binary (a few features — image scanning, SBOM signing, registry checks — additionally need Docker, cosign, or network access).


What it does

Commands are grouped by concern. Each runs standalone or as a step in greengate run. The supply-chain gate is listed first because it's the one incumbent tools don't provide.

Supply Chain Protection

Command What it does
greengate watch-install <pm> <args> Wrap any npm/yarn/pnpm/bun install. Slopsquat guard on every requested package, pre-flight lifecycle script scan (entropy + network/eval patterns), runtime phantom-file detection (files created then deleted by postinstall), and post-install exec-drop detection.
greengate pip-install <args> Wrap pip install. Slopsquat guard + typosquat detection against the 60 most-downloaded PyPI packages, post-install .dist-info/RECORD Python source scan, executable-drop detection. Halts before pip runs.
greengate cargo-add <args> Wrap cargo add. Slopsquat guard + typosquat detection against the 60 most-downloaded crates.io crates, build.rs static analysis for every newly added crate (network access, subprocess spawning, env exfiltration), transitive dependency explosion guard (> 50 new transitive deps triggers a warning).

What is the slopsquat guard? See Slopsquat / hallucinated-package guard below — the AI-era defense that catches invented package names typosquat detection can't.

Scope of the runtime check: phantom-file detection polls the install tree while the package manager runs, so it catches droppers that touch disk. A payload that exfiltrates purely over the network without writing a file is not caught by this layer — pair it with the pre-flight script scan (which flags network/eval patterns) and CI-runner egress controls. Full network isolation is on the roadmap as sandbox-install.

Secrets & Code Security

Command What it does
greengate scan Scan for hardcoded secrets, PII, and SAST issues. 26 built-in secret patterns (AWS keys, GitHub tokens, private keys, JWTs, etc.) plus Shannon entropy detection for unrecognised credentials. AST-based SAST for unsafe patterns in JS/TS/Python/Go/Rust.
greengate scan --history Scan the full git commit history for secrets (catches credentials that were committed then deleted).
greengate scan --staged Scan only staged files — ideal as a pre-commit hook.
greengate scan --format sarif Emit SARIF 2.1.0 for GitHub Code Scanning / SonarQube upload. Also supports json, junit, gitlab.
greengate scan --triage Call an LLM to classify each finding as likely-real, likely-false-positive, or uncertain. Shows confidence % and a one-sentence reason. Supports Claude (default), any OpenAI-compatible API, or a local model via Ollama.
greengate scan --dry-run / --fix Preview (--dry-run), then optionally auto-redact detected secrets in-place, replacing matched values with <REDACTED>. Always run --dry-run first — --fix rewrites your source. See Auto-redact.

Dependencies

Command What it does
greengate audit Query the OSV database for known vulnerabilities in your project's dependencies. Supports package-lock.json, yarn.lock, pnpm-lock.yaml, Cargo.lock, requirements.txt, go.sum, pom.xml, and *.csproj.
greengate sbom Generate a CycloneDX 1.5 SBOM from the project's lock file.
greengate sbom --attest Generate a SBOM and sign it with Sigstore keyless signing. Produces a bundle file anchored in the Rekor public transparency log. No private key required.
greengate sbom --verify <file> Verify a SBOM against a cosign bundle. Fails if the signature is invalid or the bundle is missing from the Rekor log.

Code Quality & Review

Command What it does
greengate review Analyse a PR diff: Complexity Score (cyclomatic + cognitive) and new-code coverage gaps. Posts a GitHub Check Run with per-line annotations. Fails if complexity exceeds complexity_budget or new code coverage falls below min_new_code_coverage.
greengate coverage Parse LCOV or Cobertura XML and fail if total coverage is below a configurable threshold.
greengate tia Test Impact Analysis — use AST import parsing to determine exactly which test files are affected by a diff. Pipe the output directly into pytest, jest, or go test to skip unaffected tests.
greengate reassure Parse a Reassure performance report and fail if any React component's mean render time regresses beyond a threshold.
greengate provenance Report the AI-authored vs human-authored split of a commit range (from commit trailers + author identity) and optionally fail when AI-generated code exceeds a share of new lines (--max-ai-lines-pct). Emits --format text|json|sarif. Differentiated governance for the AI-coding era.

Infrastructure & CI

Command What it does
greengate ci-lint Lint GitHub Actions workflow files for security misconfigurations: unpinned action refs (actions/checkout@v4 instead of a commit SHA), pull_request_target + PR checkout (script injection), expression injection in run: steps, and secrets leaked via environment variables. Emits SARIF for Code Scanning.
greengate image-scan <image> Scan a Docker/OCI image for secrets baked into its layers. Calls docker save, unpacks every layer tar, and runs the full regex + entropy scan on all text files — including .env, /etc/environment, .ssh/, .pem, and docker-compose files. Findings include layer digest and path inside the layer.
greengate lint Kubernetes manifest linting — validates resource limits, security contexts, latest image tags, and missing probes.
greengate docker-lint Dockerfile best-practice checks — latest base images, running as root, missing HEALTHCHECK, ADD instead of COPY, etc.
greengate lighthouse Gate on Google PageSpeed Insights scores (Performance, Accessibility, Best Practices, SEO).

Workflow

Command What it does
greengate run Run all pipeline steps defined in [pipeline] of .greengate.toml in order. One command to run every gate.
greengate init Interactive wizard that generates .greengate.toml. Pass --ci github-actions to also scaffold a ready-to-use workflow and dependabot.yml.
greengate install-hooks Install greengate as a git pre-commit hook (runs scan --staged on every commit).
greengate watch Re-run scan automatically whenever source files change (file-watcher mode for local development).
greengate check-config Validate .greengate.toml and print all resolved configuration values.

Observability

Feature How it works
OTLP metrics export After each command, greengate POSTs structured metrics to any OpenTelemetry-compatible collector (Grafana, Datadog, Honeycomb, etc.) via OTLP HTTP/JSON. Configure otlp_endpoint in [telemetry].
Prometheus text format Writes a .prom file after each command. Point node_exporter --collector.textfile.directory at the same directory and metrics are scraped automatically. Configure metrics_file in [telemetry].

Supported languages

Secret scanning

Secret scanning uses regex patterns and Shannon entropy — it is language-agnostic and runs on any file type, including config files, .env files, shell scripts, and binary-adjacent formats.

SAST (AST-based)

SAST uses tree-sitter to parse a real AST, so findings are accurate even when the pattern appears inside a comment or a string in another language.

Language File extensions SAST rules
JavaScript .js, .jsx, .mjs, .cjs eval(), innerHTML, dangerouslySetInnerHTML, document.write, setTimeout(string), child_process.exec
TypeScript .ts, .tsx Same as JavaScript
Python .py exec(), eval(), subprocess with shell=True, pickle.loads, yaml.load (unsafe), os.system
Go .go os/exec command injection, fmt.Sprintf in SQL, http.ListenAndServe without TLS
Rust .rs unsafe {} blocks, std::process::Command, .unwrap() and .expect() in library code

Test Impact Analysis

TIA walks the import graph using AST parsing to find which test files transitively import a changed source file.

Language Import resolution
JavaScript / TypeScript import / require (relative paths, bare specifiers)
Python import / from … import
Go import "…" (full package path)

Dependency audit (OSV)

Ecosystem Lock file
Node.js (npm) package-lock.json
Node.js (Yarn) yarn.lock
Node.js (pnpm) pnpm-lock.yaml
Rust Cargo.lock
Python requirements.txt
Go go.sum
Java (Maven) pom.xml
.NET *.csproj

Supply chain wrapping

Package manager Command Typosquat detection Script scanning Exec-drop detection
npm / yarn / pnpm / bun greengate watch-install postinstall scripts
pip greengate pip-install 60 popular PyPI packages .py files via RECORD venv bin/
Cargo greengate cargo-add 60 popular crates.io crates build.rs

CI config linting

Platform File pattern
GitHub Actions .github/workflows/*.yml

Installation

macOS (Apple Silicon):

curl -sL https://github.com/thinkgrid-labs/greengate/releases/latest/download/greengate-macos-arm64 \
  -o /usr/local/bin/greengate && chmod +x /usr/local/bin/greengate

macOS (Intel):

curl -sL https://github.com/thinkgrid-labs/greengate/releases/latest/download/greengate-macos-amd64 \
  -o /usr/local/bin/greengate && chmod +x /usr/local/bin/greengate

Linux (x64, musl — works in Alpine/Docker):

curl -sL https://github.com/thinkgrid-labs/greengate/releases/latest/download/greengate-linux-amd64 \
  -o /usr/local/bin/greengate && chmod +x /usr/local/bin/greengate

Windows (x64) — PowerShell:

Invoke-WebRequest `
  -Uri "https://github.com/thinkgrid-labs/greengate/releases/latest/download/greengate-windows-amd64.exe" `
  -OutFile "$env:USERPROFILE\.local\bin\greengate.exe"

Build from source (Rust 1.85+):

cargo install --git https://github.com/thinkgrid-labs/greengate

Verifying a release binary

Every release binary is signed with Sigstore keyless signing in the release workflow — the same mechanism greengate sbom --attest gives your own artifacts. Each asset ships with a .sig (signature) and .pem (signing certificate) alongside it. Verify a download before trusting it:

VERSION=v0.3.2   # the release you downloaded
BASE="https://github.com/thinkgrid-labs/greengate/releases/download/${VERSION}"

# Fetch the signature and certificate for your platform's asset
curl -sLO "${BASE}/greengate-linux-amd64.sig"
curl -sLO "${BASE}/greengate-linux-amd64.pem"

cosign verify-blob \
  --certificate greengate-linux-amd64.pem \
  --signature   greengate-linux-amd64.sig \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp '^https://github.com/[Tt]hink[Gg]rid-[Ll]abs/greengate/.github/workflows/release.yml@refs/tags/' \
  greengate-linux-amd64

A successful verification proves the binary was produced by greengate's own tagged release workflow and has not been modified since.


Quick start

# Generate a .greengate.toml and a GitHub Actions workflow in one step
greengate init --ci github-actions

# Scan the current directory for secrets, PII, and SAST issues
greengate scan

# Preview what --fix would change (no files written) — always do this first
greengate scan --dry-run

# Auto-redact detected secrets in-place (rewrites source; review the diff after)
greengate scan --fix && git diff

# Audit dependencies for known CVEs (reads any supported lock file)
greengate audit

# Zero-trust npm install (static scan + runtime phantom-file watcher)
greengate watch-install npm ci

# Zero-trust pip install with typosquat detection
greengate pip-install -r requirements.txt

# Zero-trust cargo add with build.rs analysis
greengate cargo-add serde tokio

# Lint GitHub Actions workflows for security misconfigurations
greengate ci-lint

# Test impact analysis — run only affected tests
pytest $(greengate tia --base main)
npx jest $(greengate tia --base main --format newline)

# PR complexity score + coverage gaps (posts GitHub Check Run annotations)
greengate review --base main --coverage-file coverage/lcov.info --annotate

# Gate on 80% minimum coverage
greengate coverage --file coverage/lcov.info --min 80

# Lint Kubernetes manifests
greengate lint --dir ./k8s

# Triage findings with an LLM (requires ANTHROPIC_API_KEY)
greengate scan --triage

# Generate a CycloneDX SBOM
greengate sbom --output sbom.json

# Generate a SBOM and sign it with Sigstore keyless signing
greengate sbom --attest --output sbom.json

# Verify a SBOM against its cosign bundle
greengate sbom --verify sbom.json --bundle sbom.json.bundle.json

# Scan a Docker image for secrets baked into its layers (requires Docker)
greengate image-scan nginx:latest
greengate image-scan ghcr.io/my-org/my-app:sha-abc123

# Run all configured gates in order
greengate run

# Install as a git pre-commit hook
greengate install-hooks

Suppressing false positives

Inline suppression

Add // greengate: ignore on the same line as a finding to suppress it:

const TEST_KEY = "AKIAIOSFODNN7EXAMPLE"; // greengate: ignore

Next-line suppression

Put the comment on the line above to suppress the next line:

# greengate: ignore
AWS_KEY = load_from_env("AWS_ACCESS_KEY_ID")

Auto-redact

--fix rewrites your working tree, replacing each detected secret with <REDACTED> in place. It is a convenience for cleaning up an accidental commit, not a gate to run unattended in CI — a false positive means real source gets overwritten. Always preview with --dry-run, run it on a clean tree, and review git diff before committing:

greengate scan --dry-run   # preview changes — no files written
greengate scan --fix       # apply redactions (rewrites source)
git diff                   # review every change before committing

For most workflows, prefer inline suppression or a baseline over --fix: they leave the (often still-needed) value in place and simply stop it failing the build.

Baseline

Save current findings as an accepted baseline and only fail on new findings:

greengate scan --update-baseline   # save current state
greengate scan --since-baseline    # fail only on new findings

AI triage

False positives are the #1 reason developers disable security tools. --triage sends each finding to an LLM along with its surrounding source code and asks: "Is this a real issue or a false positive, and why?"

⚠️  Found 3 potential issue(s) — 1 triaged as false positive(s) and auto-suppressed, 2 require attention:

  - [HIGH] [SAST/CommandInjection] ./src/admin/shell.rs:88
    → Triage: LIKELY REAL (94%) — User-controlled cmd flows into Command::new with no sanitization.

  - [HIGH] [Secret/AwsAccessKey] ./tests/fixtures/creds.rs:12 [AUTO-SUPPRESSED]
    → Triage: LIKELY FALSE POSITIVE (91%) — Matches canonical AWS example key in test fixture.

  - [MEDIUM] [SAST/EvalUsage] ./src/utils/eval.rs:44
    → Triage: UNCERTAIN (55%) — eval() usage but surrounding context does not clarify intent.

  Triage summary: 1 likely real · 1 uncertain · 1 false positive (suppressed: 1) · 0 unavailable

Setup

Set the API key for your chosen model:

export ANTHROPIC_API_KEY=sk-ant-...   # Claude (default)
export OPENAI_API_KEY=sk-...          # OpenAI-compatible

Then run:

greengate scan --triage

No additional dependencies — greengate calls the API directly using its built-in HTTP client.

Configuration

[triage]
model                   = "claude-haiku-4-5-20251001"  # fast and cheap; accurate enough for triage
api_key_env             = "ANTHROPIC_API_KEY"           # env var that holds the key
auto_suppress_threshold = 0.0                           # 0 = annotate only; 0.9 = auto-suppress high-confidence FPs
context_lines           = 10                            # source lines before/after the finding sent as context
# endpoint = "http://localhost:11434/v1/chat/completions"  # Ollama or any OpenAI-compatible API

auto_suppress_threshold

When set above 0.0, findings where the LLM is at least that confident they are false positives are automatically suppressed — they appear in output tagged [AUTO-SUPPRESSED] but do not cause a non-zero exit code.

Value Behaviour
0.0 (default) Annotate all findings; never suppress. Safe for first use.
0.90 Suppress findings the LLM is ≥ 90% confident are false positives.
1.0 Only suppress when the LLM is 100% confident — effectively off.

Start at 0.0 to see the triage output, then raise the threshold once you trust the results for your codebase.

Local models via Ollama

Run triage entirely offline — no data leaves your machine:

ollama pull mistral
[triage]
model       = "mistral"
api_key_env = "UNUSED"                                           # Ollama doesn't require a key
endpoint    = "http://localhost:11434/v1/chat/completions"

Any model that supports the OpenAI /v1/chat/completions format works: Mistral, Llama 3, Phi-3, Gemma, etc.

How triage errors are handled

  • API key missing — prints a warning and falls through to normal scan output. Findings are never silently swallowed.
  • API call fails for one finding — that finding shows Triage: unavailable and is counted as non-suppressed. The scan continues.
  • Triage errors never affect the scan exit code — if LLM calls all fail, the exit code is determined entirely by the raw findings.

Slopsquat / hallucinated-package guard

AI coding assistants confidently suggest packages that don't exist. Attackers harvest these hallucinated names from public prompts and model outputs, register them on npm / PyPI / crates.io, and ship malware to whoever installs them next — a supply-chain attack class known as slopsquatting. Because the invented name isn't a near-miss of any real package, classic edit-distance typosquat detection can't see it.

greengate's install wrappers score registry metadata for every package an install introduces (packages already pinned in your lock file are trusted and skipped):

Signal Why it matters
Does it exist? A name that 404s on the registry is very likely a pure hallucination.
How new is it? Slopsquat packages are registered after the model starts suggesting the name — a package a few days old is suspect.
How adopted is it? Near-zero downloads on a package your agent just reached for is a red flag.
Any source repo? Legitimate packages almost always declare one.

Signals combine into a suspicion level: HIGH blocks the install, MEDIUM warns. If the registry is unreachable the check fails open — it never breaks an offline or air-gapped build.

# An agent suggested a plausible-but-invented crate:
$ greengate cargo-add fast-json-parser-turbo
  [SLOPSQUAT] 'fast-json-parser-turbo' — High suspicion
       · does not exist on the registry (possible hallucinated name)
  If an AI assistant suggested this name, verify the exact crate at https://crates.io before adding.
Error: 1 high-suspicion slopsquat(s) — halting before add.

Works across all three install wrappers:

greengate watch-install npm install <pkg>   # npm / yarn / pnpm / bun
greengate pip-install <pkg>                  # PyPI
greengate cargo-add <pkg>                    # crates.io

Dependency confusion

The same pre-flight also catches dependency-confusion attacks — where an attacker publishes a package to a public registry using the name of one of your private/internal packages, hoping a misconfigured install resolves the public (malicious) one. Declare what's yours and greengate flags any internal name that unexpectedly resolves on the public registry:

[supply_chain]
internal_packages = ["@mycorp", "acme-internal-*", "acme-secret-db"]

Patterns may be an npm scope (@mycorp → matches @mycorp/anything), an exact name, or a prefix-* wildcard. Empty (default) disables the check.

[DEP-CONFUSION] '@mycorp/auth' — High suspicion
     · declared internal, but '@mycorp/auth' also resolves on the public npm registry — dependency-confusion risk

Configuration

[supply_chain]
slopsquat_check         = true    # enable the guard (default: true)
slopsquat_min_age_days  = 90      # registered more recently than this = suspicious
slopsquat_min_downloads = 1000    # fewer total downloads than this = suspicious
internal_packages       = []      # your private names/scopes/prefixes (dependency-confusion)

Add known-good internal package names to the relevant allow_* list (allow_cargo_crates, allow_pip_packages, allow_postinstall) to exempt them from the guard.


SBOM attestation

Software Bill of Materials attestation lets you prove to customers, auditors, and your own security team that a published SBOM was generated by a specific, unmodified build — and that it hasn't been tampered with since.

greengate uses Sigstore keyless signing via the cosign CLI. There is no private key to manage or rotate: in CI the signing identity comes from the OIDC token issued by your CI provider (GitHub Actions, GitLab CI, etc.). The resulting signature is anchored in the Rekor public transparency log, making it auditable by anyone.

Requirements

Install cosign once:

# macOS
brew install sigstore/tap/cosign

# GitHub Actions (add before the greengate step)
- uses: sigstore/cosign-installer@v3

Sign a SBOM

# Generates sbom.json then signs it → writes sbom.json.bundle.json
greengate sbom --attest

# Custom paths
greengate sbom --attest --output dist/sbom.json --bundle dist/sbom.bundle.json

In GitHub Actions with id-token: write permission this runs fully non-interactively. Locally it opens a browser for a one-time OAuth flow.

The bundle file (sbom.json.bundle.json) contains:

  • The signing certificate (your CI identity, e.g. https://github.com/acme/repo/.github/workflows/release.yml@refs/heads/main)
  • The ECDSA signature over the SBOM content
  • The Rekor transparency log entry ID and inclusion proof

Distribute both sbom.json and sbom.json.bundle.json alongside your release artifacts.

Verify a SBOM

# Basic verification — validates the certificate chain and Rekor log entry
greengate sbom --verify sbom.json --bundle sbom.json.bundle.json

# Strict: require the SBOM to have been signed by your GitHub Actions workflow
greengate sbom --verify sbom.json \
  --bundle sbom.json.bundle.json \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity https://github.com/acme/repo/.github/workflows/release.yml@refs/heads/main

Without --certificate-oidc-issuer / --certificate-identity, greengate accepts any valid Sigstore identity. For production pipelines, always pin both to your specific workflow URL.

GitHub Actions — full release example

permissions:
  contents: read
  id-token: write   # required for keyless Sigstore signing

steps:
  - uses: actions/checkout@v4

  - uses: sigstore/cosign-installer@v3

  - name: Install greengate
    run: |
      curl -sL https://github.com/thinkgrid-labs/greengate/releases/latest/download/greengate-linux-amd64 \
        -o /usr/local/bin/greengate && chmod +x /usr/local/bin/greengate

  - name: Generate and attest SBOM
    run: greengate sbom --attest --output sbom.json

  - name: Upload SBOM and bundle as release assets
    uses: softprops/action-gh-release@v2
    with:
      files: |
        sbom.json
        sbom.json.bundle.json

Any consumer can then verify the SBOM without trusting you:

greengate sbom --verify sbom.json \
  --bundle sbom.json.bundle.json \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity https://github.com/acme/repo/.github/workflows/release.yml@refs/heads/main

Config defaults

Set your expected issuer and identity once in .greengate.toml to avoid repeating them on every --verify call:

[sbom]
default_output    = "sbom.json"
expected_issuer   = "https://token.actions.githubusercontent.com"
expected_identity = "https://github.com/acme/repo/.github/workflows/release.yml@refs/heads/main"

Telemetry

greengate emits metrics after every command run. Both backends are opt-in, optional, and independent — telemetry errors are printed as warnings and never cause a command to fail.

OTLP (OpenTelemetry)

[telemetry]
enabled       = true
otlp_endpoint = "http://localhost:4318"   # OTLP HTTP/JSON (port 4318, not gRPC 4317)
service_name  = "my-api"                  # attached as service.name resource attribute

Metrics are posted to {otlp_endpoint}/v1/metrics after each run. Compatible with:

Backend How to connect
OpenTelemetry Collector Set otlp_endpoint to the collector's OTLP HTTP receiver
Grafana Cloud Use the Grafana Agent OTLP endpoint, or the Cloud OTLP gateway URL
Datadog Enable OTLP intake in the Datadog Agent (otlp_config.receiver.protocols.http)
Honeycomb https://api.honeycomb.io with api-key header (set via collector)
Self-hosted Grafana + Prometheus Use the Prometheus text format below instead

Prometheus text format

[telemetry]
enabled      = true
service_name = "my-api"
metrics_file = "/var/lib/node_exporter/textfile/greengate.prom"

Writes a .prom file with standard # HELP / # TYPE headers after each command. Add the containing directory to node_exporter --collector.textfile.directory and Prometheus will scrape it automatically.

Metrics reference

Metric Type Labels Emitted by
greengate_scan_findings_total counter severity, service scan
greengate_scan_duration_ms gauge service scan
greengate_command_duration_ms gauge command, success, service all commands
greengate_command_success gauge command, service all commands

Example Grafana queries:

# Finding rate over time
rate(greengate_scan_findings_total{severity="critical"}[7d])

# Commands failing in CI
greengate_command_success == 0

# Slowest commands
topk(5, greengate_command_duration_ms)

Container image scanning

Most applications ship as Docker images. Secrets baked in at build time — via COPY .env /app/, ARG values written to config files, or leaked into the shell environment — don't appear in your source repo but live in every deployed container. greengate image-scan catches them.

How it works

  1. docker save <image> -o image.tar — exports the full image to a local tar archive. The image is pulled from the registry if not already cached.
  2. Unpack the outer tar — extracts the OCI manifest and layer archives.
  3. Unpack each layer — extracts every layer.tar individually. OCI images contain one tar per filesystem layer.
  4. Scan all text files — runs the same 26 secret patterns and Shannon entropy scan used by greengate scan on every text file in every layer.
  5. Deduplicate — files that are unchanged across layers (a common OCI pattern) are reported only once.

Findings are reported as <image>::layer:<digest>::<path>, e.g.:

nginx:latest::layer:a1b2c3d4e5f6::etc/environment:3  HIGH  AWS_ACCESS_KEY_ID

Sensitive path promotion

Files in sensitive locations have their severity automatically promoted one level (medium → high) to surface credentials that are more likely to be exploitable:

Promoted paths
.env, *.env.*
.aws/credentials
.ssh/ directories
etc/environment
*.pem, *.key, *.p12, *.pfx
docker-compose*.yml

Usage

# Scan a public image
greengate image-scan nginx:latest

# Scan a private registry image (must be accessible via `docker pull`)
greengate image-scan ghcr.io/my-org/my-app:sha-abc123

# Output as JSON (for CI integration)
greengate image-scan my-app:v1.2.3 --format json

# Report findings but don't fail the build (audit mode)
greengate image-scan my-app:v1.2.3 --no-fail

GitHub Actions — scan image before push

- name: Build image
  run: docker build -t my-app:${{ github.sha }} .

- name: Scan image layers for secrets
  run: greengate image-scan my-app:${{ github.sha }}

Requirements

  • Docker must be running and docker must be in PATH. greengate image-scan will print a helpful error with installation instructions if Docker is not found.
  • The image must be accessible locally (already pulled) or in a registry that the Docker daemon can pull from.

GitHub Actions

The fastest setup: greengate init --ci github-actions writes a ready-to-use workflow. Or wire it in manually:

name: greengate

on: [push, pull_request]

permissions:
  contents: read
  checks: write
  security-events: write

jobs:
  greengate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install greengate
        run: |
          curl -sL https://github.com/thinkgrid-labs/greengate/releases/latest/download/greengate-linux-amd64 \
            -o /usr/local/bin/greengate && chmod +x /usr/local/bin/greengate

      # Scan for secrets/SAST and upload results to GitHub Code Scanning
      - name: Scan (SARIF)
        run: greengate scan --format sarif > greengate.sarif || true

      - name: Upload to Code Scanning
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: greengate.sarif

      # Lint CI workflows for security misconfigurations
      - name: CI lint
        run: greengate ci-lint

      # Zero-trust install — catches supply chain attacks at install time
      - name: Zero-trust install
        run: greengate watch-install npm ci

      # Dependency CVE audit
      - name: Audit
        run: greengate audit

      # Coverage gate
      - name: Coverage
        run: greengate coverage --file coverage/lcov.info --min 80

      # PR complexity + coverage gaps (pull_request only)
      - name: Review
        if: github.event_name == 'pull_request'
        run: |
          greengate review \
            --base "${{ github.event.pull_request.base.sha }}" \
            --coverage-file coverage/lcov.info \
            --annotate
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          GITHUB_SHA: ${{ github.sha }}

Or use the composite action (zero download boilerplate):

- uses: ThinkGrid-Labs/greengate@v0
  with:
    profile: ci
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

See CI/CD Integration for GitLab CI, Bitbucket Pipelines, and CircleCI examples.


Configuration

Run greengate init for an interactive wizard, or create .greengate.toml manually. Every field is optional — greengate works with zero config.

[scan]
entropy             = true
entropy_threshold   = 4.5          # higher = fewer false positives
exclude_patterns    = ["tests/**", "*.test.ts", "fixtures/**"]

[sast]
enabled = true

[supply_chain]
block_phantom_scripts   = true
enforce_sandbox         = true
allow_postinstall       = ["esbuild", "prisma", "@swc/core"]  # npm
allow_pip_packages      = ["grpcio"]                           # pip
allow_cargo_crates      = ["openssl"]                          # cargo
slopsquat_check         = true    # hallucinated-package guard (all install wrappers)
slopsquat_min_age_days  = 90      # registered more recently than this = suspicious
slopsquat_min_downloads = 1000    # fewer total downloads than this = suspicious
internal_packages       = ["@mycorp", "acme-internal-*"]  # dependency-confusion guard

[coverage]
file = "coverage/lcov.info"
min  = 80.0

[review]
min_new_code_coverage = 80    # new lines must be covered at this % (PR gate)
complexity_budget     = 0     # 0 = warn only; > 0 = hard fail threshold

[tia]
test_patterns = [
  "**/*.test.ts",
  "**/*.test.js",
  "**/test_*.py",
  "**/*_test.go",
]

[pipeline]
steps = [
  "scan",
  "ci-lint",
  "audit",
  "coverage",
  "review --base main --coverage-file coverage/lcov.info",
]

[triage]
model                   = "claude-haiku-4-5-20251001"
api_key_env             = "ANTHROPIC_API_KEY"
auto_suppress_threshold = 0.0      # raise to 0.9 once you've reviewed triage output
context_lines           = 10
# endpoint = "http://localhost:11434/v1/chat/completions"  # Ollama

[sbom]
default_output    = "sbom.json"
# expected_issuer   = "https://token.actions.githubusercontent.com"
# expected_identity = "https://github.com/acme/repo/.github/workflows/release.yml@refs/heads/main"

[telemetry]
enabled       = true
otlp_endpoint = "http://localhost:4318"   # OTLP HTTP — remove if not using
# metrics_file = "/var/lib/node_exporter/textfile/greengate.prom"  # Prometheus
service_name  = "my-service"

Full reference → Configuration Reference


Documentation


Contributing

greengate is open source under the MIT License. Contributions welcome — especially new secret patterns, SAST rules, and CI platform support.

cargo test          # 255 unit + integration tests
cargo clippy        # lint
cargo fmt --check   # formatting

See CONTRIBUTING.md for how to add patterns, SAST rules, and run the full test suite.

Releases

Packages

Contributors

Languages