A blazing-fast DevOps CLI built in Rust — secret scanning, AST-based SAST for JS/TS/Python/Go, Kubernetes linting, Dockerfile linting, coverage gates, dependency auditing with offline cache, web performance auditing, React component regression detection, CycloneDX SBOM generation, scan baselines, a pipeline runner, config profiles, and an interactive setup wizard — in a single zero-dependency binary.
- Why GreenGate?
- Features
- Installation
- Quick Start
- Commands
- Secret Detection Patterns
- SAST Rules Reference
- Suppressing Findings
- Configuration File
- Config Profiles
- Output Formats
- Exit Codes
- CI/CD Integration
- Architecture
- React Native
- Troubleshooting
- Contributing
Most DevOps quality tools are either slow, require a runtime (Node, Python, Java), or solve only one problem. GreenGate packages essential CI gates into a single compiled Rust binary:
| Problem | GreenGate command |
|---|---|
| Hardcoded secrets pushed to git | greengate scan |
| XSS, eval, command injection in JS/TS/Python/Go | greengate scan (SAST) |
| Kubernetes manifests missing resource limits | greengate lint |
| Dockerfile best-practice violations | greengate docker-lint |
| Test coverage silently dropping | greengate coverage |
| Vulnerable dependencies shipping to production | greengate audit |
| Secrets committed before anyone notices | greengate install-hooks |
| Web Lighthouse score regressing between deploys | greengate lighthouse |
| React component render performance regressing | greengate reassure |
| Existing findings flooding CI noise | greengate scan --since-baseline |
| Unknown who introduced a secret | greengate scan --blame |
| Need an SBOM for compliance | greengate sbom |
| Config file has a syntax error | greengate check-config |
| Running all gates with one command | greengate run |
| Getting started without reading docs | greengate init |
Key advantages:
- Zero runtime dependencies — drop a single binary into any CI pipeline, Docker image, or developer machine. No Node, Python, or JVM required.
- Blazing fast — parallel file scanning via
rayonacross all CPU cores. Typical repos scan in under a second. - AST-based SAST — tree-sitter parses JS/TS/TSX/JSX/Python/Go into a real AST before pattern matching. Secrets are only flagged when they appear inside string literals, eliminating comment noise and false positives.
- Cloud-provider agnostic — detects secrets across AWS, Azure, GCP, DigitalOcean, Alibaba Cloud, Stripe, GitHub, Twilio, Expo, Sentry, Mapbox, and more.
- gitignore-aware — uses the
ignorecrate to automatically skip files in.gitignore, so you never scannode_modules/ortarget/by accident. - CI-native output —
--format sarifproduces SARIF 2.1.0 output that GitHub Advanced Security displays as inline PR annotations with zero extra config. - Configurable — a single
.greengate.tomlfile sets defaults for all commands; CLI flags always override it.
| Feature | Status |
|---|---|
| Secret & PII scanning (26 built-in patterns) | ✅ |
| Severity levels on every finding (critical / high / medium / low) | ✅ |
| AST-based SAST for JS/TS/TSX/JSX | ✅ |
| AST-based SAST for Python (eval, exec, pickle, subprocess, yaml.load) | ✅ |
| AST-based SAST for Go (unsafe, exec.Command, panic) | ✅ |
| Tier-1 intra-procedural taint tracking for JS/TS | ✅ |
| Sanitizer-aware XSS suppression (DOMPurify, he, JSON.stringify, …) | ✅ |
Confirmed taint findings labelled [tainted] for high-confidence triage |
✅ |
| String-literal scoping (no comment / JSX-text noise) | ✅ |
| Dangerous pattern detection (XSS, eval, command injection) | ✅ |
| Code smell / complexity rules (long functions, deep nesting, too many params) | ✅ |
| Custom SAST rules via config (tree-sitter S-expression queries) | ✅ |
| Custom extra patterns via config | ✅ |
| Exclude paths via glob patterns | ✅ |
Inline suppression (// greengate: ignore) |
✅ |
| Git diff / staged-only / full history scanning | ✅ |
Git blame enrichment (--blame) |
✅ |
| JSON, SARIF 2.1.0, JUnit XML, GitLab SAST output formats | ✅ |
GitHub Check Run annotations + rich PR summary comment (--annotate) |
✅ |
| Kubernetes manifest linting (7 rules) | ✅ |
| Dockerfile linting (8 rules) | ✅ |
| LCOV & Cobertura XML coverage threshold gate | ✅ |
| Dependency audit via OSV API (6 ecosystems) | ✅ |
Suppress known-acceptable advisories via ignore_advisories |
✅ |
| Offline OSV audit cache (24-hour TTL, air-gap friendly) | ✅ |
CycloneDX 1.5 SBOM generation (greengate sbom) |
✅ |
| Git pre-commit hook installer | ✅ |
| Web performance audit via PageSpeed Insights | ✅ |
| React component performance gate (Reassure) | ✅ |
| Scan baseline (suppress known findings in CI) | ✅ |
Config profiles (--profile strict|relaxed|ci) |
✅ |
Config validation (greengate check-config) |
✅ |
Pipeline runner (greengate run) |
✅ |
Interactive setup wizard (greengate init) |
✅ |
Live file watcher (greengate watch) |
✅ |
.greengate.toml config file |
✅ |
Respects .gitignore |
✅ |
macOS (Apple Silicon / M1+):
curl -sL https://github.com/ThinkGrid-Labs/greengate/releases/latest/download/greengate-macos-arm64 \
-o /usr/local/bin/greengate && chmod +x /usr/local/bin/greengatemacOS (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/greengateLinux (x64):
curl -sL https://github.com/ThinkGrid-Labs/greengate/releases/latest/download/greengate-linux-amd64 \
-o /usr/local/bin/greengate && chmod +x /usr/local/bin/greengateWindows (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"
# Add $env:USERPROFILE\.local\bin to your PATH if not already presentcargo install --git https://github.com/ThinkGrid-Labs/greengate$ greengate --version
greengate 0.2.6
$ greengate --help
A high-performance DevOps CLI tool in Rust
Usage: greengate [--profile <PROFILE>] <COMMAND>
Commands:
scan Scans the current directory for hardcoded secrets and PII
lint Validates Kubernetes YAML manifests for resource limits and security issues
docker-lint Lints a Dockerfile for best-practice violations
coverage Parses an LCOV or Cobertura XML coverage file and fails if below threshold
audit Audits project dependencies for known vulnerabilities via the OSV database
install-hooks Installs greengate as a git pre-commit hook
lighthouse Audits web performance via Google PageSpeed Insights (Lighthouse)
reassure Parses a Reassure performance report and gates on regressions
sbom Generates a CycloneDX 1.5 SBOM from the project's lock file
check-config Validates .greengate.toml and prints all resolved configuration values
init Interactive wizard that generates a .greengate.toml config file
watch Re-runs scan automatically whenever source files change
run Runs all pipeline steps defined in .greengate.toml in order
help Print this message or the help of the given subcommand(s)
# Scan for secrets in current repo (includes SAST for JS/TS files)
greengate scan
# Lint all Kubernetes YAML files
greengate lint --dir ./k8s
# Enforce 80% minimum coverage
greengate coverage --file coverage/lcov.info --min 80
# Audit dependencies for CVEs
greengate audit
# Install as a git hook (runs on every commit)
greengate install-hooks
# Gate on Lighthouse web performance scores
greengate lighthouse --url https://yourapp.com
# Gate on React component performance regressions (after `reassure measure`)
greengate reassureRecursively scans every file in the current directory for hardcoded secrets, credentials, and PII using 26 built-in regex patterns. Respects .gitignore automatically.
For JavaScript, TypeScript, Python, and Go files the scanner automatically uses an AST-based pass (SAST) instead of plain regex. This eliminates false positives from comments and non-string content, and additionally flags dangerous API patterns. See SAST for JS/TS/Python/Go below.
greengate scan [OPTIONS]
Options:
--format <FORMAT> Output format: text (default), json, sarif, junit, gitlab
--staged Only scan git-staged files (git diff --cached)
--since <COMMIT> Only scan files changed since the given commit
--history Scan the entire git commit history (slow on large repos)
--annotate Post findings as a GitHub Check Run with per-line annotations
and a rich PR summary comment. Requires GITHUB_TOKEN,
GITHUB_REPOSITORY, and GITHUB_SHA env vars. No-op when absent.
--update-baseline Save current findings as the baseline (.greengate-baseline.json)
--since-baseline Only fail on findings not present in the saved baseline
--blame Enrich each finding with git blame info (author + commit)
-h, --help Print help
Examples:
# Full scan, human-readable output
greengate scan
# Only scan what you're about to commit (fast, perfect for pre-commit)
greengate scan --staged
# Only scan files changed in the last commit
greengate scan --since HEAD~1
# Output SARIF for GitHub Advanced Security PR annotations
greengate scan --format sarif > results.sarif
# JUnit XML for Jenkins / Azure DevOps
greengate scan --format junit > results.xml
# GitLab SAST Security Scanner JSON
greengate scan --format gitlab > gl-sast-report.json
# Output JSON for custom tooling
greengate scan --format json | jq '.findings[].rule'
# Enrich findings with git blame info (author + commit)
greengate scan --blame
# Post findings directly to the GitHub Checks tab and a rich PR summary comment
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
GITHUB_REPOSITORY=owner/repo \
GITHUB_SHA=${{ github.sha }} \
greengate scan --annotateSample output (text):
ℹ️ Starting secret and PII scan...
ℹ️ Running SAST checks...
ℹ️ SAST: scanning 14 file(s)...
⚠️ Found 3 potential issue(s):
- [CRITICAL] [AWS Access Key] src/config.ts:14
- [CRITICAL] [SAST/EvalUsage] src/utils/parser.js:42
- [HIGH] [SAST/InnerHTMLAssignment] src/components/Widget.tsx:88
Error: Scan failed: 3 secret(s)/PII found. Review the findings above.
Sample output (--format json):
{
"total": 3,
"findings": [
{ "rule": "AWS Access Key", "file": "src/config.ts", "line": 14, "severity": "critical" },
{ "rule": "SAST/EvalUsage", "file": "src/utils/parser.js", "line": 42, "severity": "critical" },
{ "rule": "SAST/InnerHTMLAssignment", "file": "src/components/Widget.tsx", "line": 88, "severity": "high" }
]
}When scanning .js, .jsx, .ts, .tsx, .py, or .go files, greengate uses tree-sitter to parse each file into an AST before running any checks. This gives two important improvements over plain regex:
1. String-literal scoping for secrets
Secret and PII regex patterns are applied only against string literal values — not against comments, import declarations, or JSX text content. This means:
// AKIAIOSFODNN7EXAMPLE123 ← comment: NOT flagged ✅
const key = "AKIAIOSFODNN7EXAMPLE123"; // ← string literal: flagged ⚠️
const msg = `Contact us at admin@example.com`; // ← template literal: flagged ⚠️// JSX text content is never flagged — only string attributes are:
<p>contact@example.com</p> // ← JSX text: NOT flagged ✅
<Input placeholder="name@example.com" /> // ← string attribute: flagged ⚠️2. Dangerous pattern detection with Tier-1 taint tracking
SAST runs structural rules that detect dangerous API calls regardless of whether their arguments are string literals. For JS/TS, these rules are enhanced by intra-procedural taint tracking — the engine traces where each sink value originates within the enclosing function body, then:
- Suppresses findings where the value is provably safe (static string/number literal, or flows through a known sanitizer like
DOMPurify.sanitize,JSON.stringify,he.encode). - Labels confirmed taint chains with
[tainted]— e.g.SAST/InnerHTMLAssignment [tainted]— for high-confidence triage. - Emits normally when the origin is unknown (unchanged conservative behaviour).
const raw = req.body.comment; // taint source
const html = `<div>${raw}</div>`; // propagated through template literal
el.innerHTML = html;
// → SAST/InnerHTMLAssignment [tainted] (confirmed injection path)
const clean = DOMPurify.sanitize(raw); // sanitizer call
el.innerHTML = clean;
// → suppressed (provably safe)
const label = "Enter your name:"; // static literal
el.innerHTML = label;
// → suppressed (provably safe)JavaScript / TypeScript:
| Rule ID | What it flags |
|---|---|
SAST/DangerouslySetInnerHTML |
<div dangerouslySetInnerHTML={{__html: ...}} /> in TSX/JSX |
SAST/InnerHTMLAssignment |
element.innerHTML = expr |
SAST/OuterHTMLAssignment |
element.outerHTML = expr |
SAST/EvalUsage |
eval(expr) |
SAST/FunctionConstructor |
new Function(...) |
SAST/SetTimeoutString |
setTimeout("code", delay) — string as first arg |
SAST/SetIntervalString |
setInterval("code", delay) — string as first arg |
SAST/ChildProcessExec |
child_process.exec(cmd) |
SAST/ChildProcessExecSync |
child_process.execSync(cmd) |
SAST/ChildProcessSpawn |
child_process.spawn(cmd, args) |
SAST/ChildProcessExecFile |
child_process.execFile(cmd) |
SAST/DocumentWrite |
document.write(expr) |
SAST/DocumentWriteln |
document.writeln(expr) |
Python:
| Rule ID | What it flags |
|---|---|
SAST/PythonEval |
eval(expr) |
SAST/PythonExec |
exec(code) |
SAST/PythonPickle |
pickle.load(f) / pickle.loads(data) — unsafe deserialization |
SAST/PythonSubprocessShell |
Any call with shell=True — command injection risk |
SAST/PythonYamlLoad |
yaml.load(data) — use yaml.safe_load instead |
Go:
| Rule ID | What it flags |
|---|---|
SAST/GoUnsafe |
import "unsafe" — direct memory manipulation |
SAST/GoExecCommand |
exec.Command(cmd, ...) — possible command injection |
SAST/GoPanic |
panic(...) — unexpected process termination |
3. Code smell / complexity rules (JS/TS only)
Three additional rules flag maintainability issues at the function level:
| Rule ID | Trigger | Default threshold |
|---|---|---|
SMELL/LongFunction |
Function body exceeds max_function_lines lines |
50 lines |
SMELL/TooManyParameters |
Function has more than max_parameters parameters |
5 params |
SMELL/DeepNesting |
Control-flow depth exceeds max_nesting_depth inside a function |
4 levels |
Rule IDs include the measured value for immediate context — e.g. SMELL/LongFunction (63 lines, max 50). Thresholds are configurable in .greengate.toml:
[sast]
max_function_lines = 60 # default 50
max_parameters = 6 # default 5
max_nesting_depth = 5 # default 44. Custom SAST rules
Define project-specific rules using tree-sitter S-expression queries. Each rule must include a @match capture that marks the outermost node to report:
[sast]
custom_rules = [
# Flag any direct call to eval()
{ id = "CUSTOM/EvalCall", query = "(call_expression function: (identifier) @_fn (#eq? @_fn \"eval\") arguments: (_) @match)" },
# Flag fetch() calls (useful for auditing outbound requests)
{ id = "CUSTOM/FetchCall", query = "(call_expression function: (identifier) @_fn (#eq? @_fn \"fetch\") @match)" },
]Custom rules are validated at startup — invalid queries are skipped with a warning and never cause greengate scan to crash. Custom rule IDs can be disabled with disabled_rules like any built-in rule.
Note: SAST runs automatically when
greengate scanencounters a supported file — there is no separate command. Unsupported file types (.rs,.yaml,.env, etc.) use the regex scanner.
Disabling SAST or individual rules:
# .greengate.toml
[sast]
# Set to false to disable SAST entirely and fall back to regex for all files
enabled = true
# Suppress specific rules that generate noise in your codebase
disabled_rules = [
"SAST/ChildProcessExec", # if you intentionally shell out in a Node script
"SAST/DocumentWrite", # if you have a legacy codebase that uses it
"SAST/PythonSubprocessShell", # if subprocess with shell=True is intentional
"SMELL/LongFunction", # if you have intentionally large generated files
]GitHub Check Run annotations + PR summary (--annotate):
When running in GitHub Actions, pass --annotate to have greengate post findings directly to the Checks tab with per-line annotations and a rich markdown summary comment on the pull request. The comment includes a severity breakdown and a per-finding table (capped at 20 rows). No SARIF upload step required.
- name: Secret, PII & SAST Scan
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: greengate scan --annotateRequired env vars (automatically set in GitHub Actions): GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_SHA. When any env var is absent, --annotate is a no-op and the scan runs normally. A clean scan (no findings) posts a "No issues found" pass comment.
Validates Kubernetes workload YAML files (Deployment, DaemonSet, StatefulSet, Job, CronJob) against security and reliability best practices. Supports multi-document YAML files (--- separator).
greengate lint [OPTIONS]
Options:
-d, --dir <DIR> Directory to scan for Kubernetes manifests [default: . or lint.target_dir from config]
-h, --help Print help
Rules enforced:
| Rule ID | Description | Applies to |
|---|---|---|
no-latest-image |
Container image uses :latest tag or no tag at all |
All workloads |
no-resource-limits |
resources.limits block is entirely missing |
All workloads |
no-cpu-limit |
resources.limits.cpu is not set |
All workloads |
no-memory-limit |
resources.limits.memory is not set |
All workloads |
run-as-root |
securityContext.runAsUser is 0 |
All workloads |
no-readiness-probe |
readinessProbe is not defined |
Deployment, DaemonSet, StatefulSet |
no-liveness-probe |
livenessProbe is not defined |
Deployment, DaemonSet, StatefulSet |
Note:
JobandCronJobare intentionally exempt from probe checks — they run to completion and don't need readiness/liveness probes.
Examples:
# Lint manifests in the current directory
greengate lint
# Lint a specific directory
greengate lint --dir ./infrastructure/k8sSample output:
ℹ️ Linting Kubernetes manifests in './k8s'...
⚠️ Found 3 issue(s) across 2 file(s):
[no-latest-image] k8s/api.yaml (container: api) — Image 'myapp:latest' uses an unpinned or :latest tag
[no-memory-limit] k8s/api.yaml (container: api) — resources.limits.memory is not set
[no-readiness-probe] k8s/worker.yaml (container: worker) — readinessProbe is not defined
Error: K8s lint failed: 3 issue(s) found.
Example of a fully compliant manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
containers:
- name: api
image: myapp:1.4.2 # pinned tag
resources:
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /health
port: 8080
livenessProbe:
httpGet:
path: /health
port: 8080Parses a coverage report and fails with exit code 1 if the total line coverage is below the specified minimum. Shows per-file breakdown of files below the threshold.
Two formats are supported and auto-detected by file extension (or by peeking at the file contents for ambiguous extensions):
| Format | Extension | Used by |
|---|---|---|
| LCOV | .info or no extension |
Rust (cargo-llvm-cov), Jest, pytest-cov, Go |
| Cobertura XML | .xml |
Python (coverage.py), Java (JaCoCo), .NET |
greengate coverage [OPTIONS]
Options:
-f, --file <FILE> Path to the coverage file [default: coverage/lcov.info or coverage.file from config]
-m, --min <MIN> Minimum coverage threshold percentage [default: 80 or coverage.min from config]
-h, --help Print help
Examples:
# LCOV (Rust, Jest, pytest)
greengate coverage --file coverage/lcov.info --min 80
# Cobertura XML (Python coverage.py, JaCoCo)
greengate coverage --file coverage.xml --min 80
# Enforce a stricter 90% gate
greengate coverage --file coverage/lcov.info --min 90
# Read defaults from .greengate.toml
greengate coverageGenerating coverage reports by language:
# Rust (cargo-llvm-cov) → LCOV
cargo llvm-cov --lcov --output-path coverage/lcov.info
# JavaScript / TypeScript (Jest) → LCOV
jest --coverage --coverageReporters=lcov
# Python (pytest-cov) → LCOV or Cobertura
pytest --cov=. --cov-report=lcov:coverage/lcov.info
pytest --cov=. --cov-report=xml:coverage.xml
# Go (go test) → LCOV
go test ./... -coverprofile=coverage/lcov.info
# Java (JaCoCo) → Cobertura XML
# Configure your build tool to output Cobertura format and pass the .xml fileSample output:
ℹ️ Analyzing coverage file: coverage/lcov.info (threshold: 80.0%)
Files below threshold (80.0%):
61.2% src/handlers/auth.rs
72.4% src/utils/parser.rs
⚠️ Coverage 74.8% is below threshold 80.0% (12 files, 748/1000 lines covered)
Error: Coverage gate failed: 74.8% < 80.0%
Automatically detects your project's lock file, parses all pinned dependencies, and queries the OSV (Open Source Vulnerabilities) database in a single batch request. Works across six ecosystems.
greengate audit
Options:
-h, --help Print help
Supported lock files (checked in order):
| Lock file | Ecosystem | Notes |
|---|---|---|
Cargo.lock |
crates.io |
All registry packages |
package-lock.json |
npm |
v2/v3 format (packages map) |
yarn.lock |
npm |
v1 (classic) and v2/Berry |
pnpm-lock.yaml |
npm |
v5–v9 (slash and no-slash formats) |
requirements.txt |
PyPI |
Only == pinned versions |
go.sum |
Go |
All module checksums |
Examples:
# Rust project
greengate audit
# Node.js project (npm, yarn, or pnpm — auto-detected)
greengate audit
# Python project (auto-detected)
greengate audit
# Go project (auto-detected)
greengate auditSample output:
ℹ️ Auditing 312 packages from Cargo.lock (crates.io) via OSV...
⚠️ [suppressed] GHSA-2G4F-4PWH-QVX6 — ajv 6.12.6 (known acceptable transitive dep)
⚠️ Found 2 actionable vulnerability/-ies in 312 packages:
[GHSA-jfh8-c2jp-hdmh] openssl@0.10.55 — Use-after-free in X.509 certificate verification
[CVE-2023-26964] h2@0.3.15 — Denial of Service via CONTINUATION frames
Error: Audit failed: 2 known vulnerability/-ies found.
Note: Results are cached to
~/.cache/greengate/osv/with a 24-hour TTL. Subsequent runs reuse the cached response, making the audit fast and air-gap friendly after the first run. On network errors greengate warns and exits 0, so it won't block CI pipelines with no outbound access.
Suppressing unfixable transitive advisories:
Some transitive dependency CVEs cannot be fixed because the vulnerable package is pulled in by a build tool (webpack, jest, turbo) that has not yet released a compatible upgrade. Rather than disabling the audit entirely, suppress individual advisory IDs in .greengate.toml:
[audit]
ignore_advisories = [
# ajv@6.x — used internally by webpack. No direct dep; upgrade blocked.
"GHSA-2G4F-4PWH-QVX6",
]Suppressed advisories appear as [suppressed] warnings — not errors — so new advisories still fail the build.
Installs greengate as a git pre-commit hook that automatically runs scan --staged before every git commit, catching secrets before they ever reach the remote.
greengate install-hooks [OPTIONS]
Options:
--force Overwrite an existing hook without prompting
-h, --help Print help
What it installs (written to .git/hooks/pre-commit):
#!/bin/sh
# greengate pre-commit hook (auto-installed)
# Scans only staged files for secrets and PII before every commit.
greengate scan --stagedExamples:
# Install (safe — will not overwrite an existing hook)
greengate install-hooks
# Overwrite an existing hook
greengate install-hooks --forceSample output:
✅ Pre-commit hook installed at /your/repo/.git/hooks/pre-commit
ℹ️ greengate scan --staged will now run before every commit.
Tip: Combine with
greengate scan --stagedin CI for a two-layer defence: developers catch issues locally before pushing, and CI catches anything that slips through.
Fetches your deployed URL from the Google PageSpeed Insights API v5 (which runs a real Lighthouse audit server-side) and gates on scores for four categories: Performance, Accessibility, Best Practices, and SEO. No Node.js required — it's a pure HTTPS call via the same HTTP client used by audit.
greengate lighthouse [OPTIONS]
Options:
--url <URL> URL to audit (overrides config)
--strategy <STRATEGY> Device strategy: mobile (default) or desktop (overrides config)
--min-performance <N> Minimum Performance score 0–100 [default: 80]
--min-accessibility <N> Minimum Accessibility score 0–100 [default: 90]
--min-best-practices <N> Minimum Best Practices score 0–100 [default: 80]
--min-seo <N> Minimum SEO score 0–100 [default: 80]
--key <KEY> Google PageSpeed Insights API key (overrides PAGESPEED_API_KEY env var)
-h, --help Print help
Examples:
# Audit with default thresholds (mobile strategy)
greengate lighthouse --url https://yourapp.com
# Desktop audit with a stricter performance threshold
greengate lighthouse --url https://yourapp.com --strategy desktop --min-performance 90
# Use an API key for higher quota (unauthenticated quota: a few requests/day)
greengate lighthouse --url https://yourapp.com --key AIza...
# Read URL and thresholds from .greengate.toml
greengate lighthouseAPI key: The PageSpeed Insights API works without a key for occasional runs (development, infrequent CI). For production CI pipelines that run on every PR, create a free key in the Google Cloud Console and pass it via the PAGESPEED_API_KEY environment variable:
export PAGESPEED_API_KEY=AIza...
greengate lighthouse --url https://yourapp.comSample output:
ℹ️ Running Lighthouse audit: https://yourapp.com (mobile)
Performance: 87 ✅ (min: 80)
Accessibility: 95 ✅ (min: 90)
Best Practices: 75 ❌ (min: 80)
SEO: 98 ✅ (min: 80)
Error: Lighthouse failed: 1 category/-ies below threshold.
Configuration (.greengate.toml):
[lighthouse]
url = "https://yourapp.com"
strategy = "mobile" # mobile | desktop
min_performance = 80
min_accessibility = 90
min_best_practices = 80
min_seo = 80
# api_key = "" # prefer PAGESPEED_API_KEY env varNote:
lighthouseaudits a live, publicly reachable URL. It is best placed in a post-deploy CI step, not in a PR build where the URL may not yet be reachable. For PR-level feedback, consider running it against a preview/staging URL.
Parses the JSON performance report produced by Reassure (reassure measure) and fails CI if any component's mean render time regresses beyond a configurable threshold, or if the number of renders increases.
Reassure measures real component performance by running each test scenario many times (default 10 iterations × 5 runs). The output is a .perf JSON file that greengate reads directly — no Node.js required at gate time.
greengate reassure [OPTIONS]
Options:
--current <PATH> Path to current.perf file [default: output/current.perf]
--baseline <PATH> Path to baseline.perf file [default: output/baseline.perf]
--threshold <N> % mean-time increase allowed before failure [default: 15]
-h, --help Print help
Typical CI workflow:
# 1. In your frontend project, run Reassure to produce current.perf
npx reassure measure
# 2. Gate on regressions with greengate
greengate reassure
# 3. To compare against a saved baseline, first save it:
cp output/current.perf output/baseline.perf # after a known-good run
# Then on subsequent runs, greengate compares current vs baseline automaticallyRegression rules:
| Condition | Result |
|---|---|
meanTime increased by more than threshold% |
❌ fail |
renders count increased vs baseline |
❌ fail |
No baseline.perf found |
Report-only (informational, no failure) |
| New component with no baseline entry | Listed as new, not flagged |
Sample output (with baseline):
ℹ️ Parsing Reassure report: output/current.perf
ℹ️ Baseline found: output/baseline.perf
Component Mean (ms) Renders Δ Mean Δ Renders
────────────────────────────────────────────────────────────────────────────────────
ProductList render 15.4 2.3 +2.1% ─
HeavyList render 42.1 3.0 +21.3% ❌ ─
SearchBox render 8.9 1.0 -5.2% ─
NewComponent render 6.3 1.0 new ─
Error: Reassure failed: 1 component(s) exceed the 15.0% regression threshold.
Sample output (no baseline — report-only mode):
ℹ️ Parsing Reassure report: output/current.perf
⚠️ Baseline file not found — running in report-only mode.
Component Mean (ms) Renders
─────────────────────────────────────────────────────────────
ProductList render 15.4 2.3
HeavyList render 42.1 3.0
SearchBox render 8.9 1.0
ℹ️ No baseline provided — metrics reported above (no gating applied).
Configuration (.greengate.toml):
[reassure]
current = "output/current.perf"
baseline = "output/baseline.perf"
threshold = 15.0 # % mean-time regression allowedSetting up Reassure in a React project:
# Install
npm install --save-dev reassure
# Write a performance test (e.g. __perf__/ProductList.perf.tsx)
import { measureRenders } from 'reassure';
import { ProductList } from '../ProductList';
test('ProductList render', async () => {
await measureRenders(<ProductList items={mockItems} />);
});
# Run measurement (generates output/current.perf)
npx reassure measure
# Then gate with greengate
greengate reassure --threshold 10Tip: Store
output/baseline.perfin your repository (or as a CI artifact) after a confirmed good release. On every PR, greengate compares the freshly measuredcurrent.perfagainst it and fails if performance has regressed.
Generates a CycloneDX 1.5 Software Bill of Materials from your project's lock file. No internet access required.
greengate sbom [OPTIONS]
Options:
-o, --output <FILE> Write SBOM to a file instead of stdout
Supported lock files (checked in order): Cargo.lock, package-lock.json, requirements.txt, go.sum
# Print to stdout
greengate sbom
# Write to a file
greengate sbom --output sbom.jsonEach component includes name, version, purl (Package URL), and scope: required. CycloneDX SBOMs are accepted by Dependency-Track, FOSSA, Grype, Trivy, and most enterprise compliance platforms.
Reads .greengate.toml, validates it, and prints all resolved configuration values. Useful before running CI to confirm that overrides are applied correctly.
greengate check-config
# Preview merged values with a profile applied
greengate --profile strict check-configExits 0 on valid config (or when no file exists, printing built-in defaults). Exits 1 if the file has a parse error.
Checks a Dockerfile for best-practice violations across 8 rules.
greengate docker-lint [--file <path>]| Rule | What it catches |
|---|---|
no-latest-image |
FROM node:latest or untagged FROM node |
prefer-copy-over-add |
ADD where COPY is sufficient |
no-user-directive |
No USER instruction — runs as root by default |
no-root-user |
Explicit USER root or USER 0 |
no-healthcheck |
Missing HEALTHCHECK instruction |
secret-in-env |
ENV API_KEY=…, ENV PASSWORD=… baked into the image layer |
apt-no-recommends |
apt-get install without --no-install-recommends |
apt-stale-cache |
apt-get update and apt-get install in separate RUN layers |
Configure the default path in .greengate.toml:
[docker]
dockerfile = "Dockerfile"In repositories with existing findings, baseline mode lets CI gate only on new findings introduced by a PR — without failing on pre-existing issues the team hasn't fixed yet.
Step 1: Save a baseline (run once on your main branch, commit the output)
greengate scan --update-baseline
# writes .greengate-baseline.json
git add .greengate-baseline.json && git commit -m "chore: add greengate scan baseline"Step 2: Use the baseline in CI (PRs only fail on new findings)
greengate scan --since-baselineThe baseline stores (file, rule, line) fingerprints. A secret that moves to a different line is treated as a new finding and re-reviewed.
Generates a tailored .greengate.toml by asking a few questions. The fastest way to get started.
greengate init # guided setup
greengate init --force # overwrite existing configExample session:
greengate init — generating .greengate.toml
[ Secret & SAST Scanning ]
Enable entropy-based secret detection? [Y/n]:
Entropy threshold [4.5]:
[ Coverage Gate ]
LCOV file path [coverage/lcov.info]:
Minimum coverage % [80]:
[ Docker Lint ]
Dockerfile path (leave blank to skip) []:
[ Pipeline Runner ]
Generate a default pipeline (greengate run)? [Y/n]:
✅ .greengate.toml written successfully.
Re-runs the scan automatically every time a source file changes. Useful during active development.
greengate watch # watch full working tree
greengate watch --staged # only scan staged files on change
greengate watch --interval 500 # poll every 500msUnlike CI mode, watch does not exit on findings — it reports them and keeps watching so you can fix in-place.
Runs all quality gates in sequence using the steps defined in .greengate.toml. First failure stops the pipeline.
greengate runDefine your pipeline:
[pipeline]
steps = [
"scan",
"audit",
"coverage --min 80",
"lint --dir ./k8s",
"docker-lint",
"lighthouse",
]Each step maps directly to an greengate subcommand. Inline flags override config values for that step. Use greengate init to generate a starter pipeline automatically.
GreenGate ships with 26 built-in patterns covering the most common cloud providers and services. All patterns are applied per-line for non-JS/TS files, and scoped to string literals for JS/TS/TSX/JSX files (no comment noise).
| Rule ID | What it detects |
|---|---|
AWS Access Key |
IAM access key IDs (AKIA…16 chars) |
AWS Secret Key |
aws_secret_access_key = …40 chars in config files |
| Rule ID | What it detects |
|---|---|
Azure Storage Connection String |
Full connection strings containing DefaultEndpointsProtocol + AccountKey |
Azure SAS Token |
Shared Access Signature URLs containing sv=20XX-XX-XX + &sig= |
| Rule ID | What it detects |
|---|---|
Google API Key |
Browser/server API keys (AIza…35 chars); also catches Firebase API keys |
GCP Service Account Key |
Service account JSON files ("type": "service_account"); also catches google-services.json |
GCP OAuth2 Token |
Short-lived access tokens (ya29.…) |
| Rule ID | What it detects |
|---|---|
DigitalOcean PAT |
Personal access tokens (dop_v1_…64 chars) |
| Rule ID | What it detects |
|---|---|
Alibaba Cloud Access Key ID |
Access key IDs (LTAI…14-20 chars) |
| Rule ID | What it detects |
|---|---|
GitHub PAT (classic) |
Classic personal access tokens (ghp_…36 chars) |
GitHub PAT (fine-grained) |
Fine-grained personal access tokens (github_pat_…82 chars) |
| Rule ID | What it detects |
|---|---|
Slack Webhook |
Incoming webhook URLs (hooks.slack.com/services/…) |
Stripe Secret Key |
Live secret keys (sk_live_…24 chars) |
Stripe Publishable Key |
Live publishable keys (pk_live_…24 chars) |
SendGrid API Key |
API keys (SG.22chars.43chars) |
Mailgun API Key |
API keys (key-…32 chars) |
Twilio Account SID |
Account SIDs (AC + 32 lowercase hex chars) |
| Rule ID | What it detects |
|---|---|
HashiCorp Vault Token |
Service tokens (hvs.…90+ chars) |
PEM Private Key |
RSA, EC, DSA, OPENSSH private key headers |
JWT Token |
Three-part base64url tokens (eyJ…) |
| Rule ID | What it detects |
|---|---|
Expo Access Token |
EAS CLI robot/personal tokens (expa_…40+ chars) |
Sentry DSN |
Error reporting DSNs (https://key@o123.ingest.sentry.io/project) |
Mapbox Secret Token |
Secret tokens (sk.eyJ…); public tokens (pk.eyJ…) are not flagged |
| Rule ID | What it detects |
|---|---|
Generic PII (SSN) |
US Social Security Numbers (XXX-XX-XXXX) |
Generic PII (Email) |
Email addresses |
In addition to the named patterns, greengate flags high-entropy tokens that don't match any known pattern — catching unrecognised API keys, random secrets, and opaque credentials:
[scan]
entropy = true # enable/disable (default: true)
entropy_threshold = 4.5 # Shannon entropy score; lower = more sensitive (default: 4.5)
entropy_min_length = 20 # minimum token length before entropy is checked (default: 20)Use extra_patterns in .greengate.toml to add your own patterns without forking:
[scan]
extra_patterns = [
{ name = "Internal API Token", regex = "myapp_[a-z0-9]{32}" },
{ name = "Database URL", regex = "postgres://[^@]+@[^/]+" },
]Rules are checked on .js, .jsx, .ts, .tsx, .py, and .go files using tree-sitter AST queries. They fire on the structure of the code, not on string content.
| Rule ID | Trigger | Risk |
|---|---|---|
SAST/DangerouslySetInnerHTML |
<El dangerouslySetInnerHTML={{__html: v}} /> |
XSS via React bypass |
SAST/InnerHTMLAssignment |
el.innerHTML = expr |
DOM XSS |
SAST/OuterHTMLAssignment |
el.outerHTML = expr |
DOM XSS |
SAST/DocumentWrite |
document.write(expr) |
Legacy DOM XSS |
SAST/DocumentWriteln |
document.writeln(expr) |
Legacy DOM XSS |
| Rule ID | Trigger | Risk |
|---|---|---|
SAST/EvalUsage |
eval(expr) |
Arbitrary code execution |
SAST/FunctionConstructor |
new Function(...) |
Arbitrary code execution |
SAST/SetTimeoutString |
setTimeout("string", delay) |
eval-equivalent |
SAST/SetIntervalString |
setInterval("string", delay) |
eval-equivalent |
setTimeout/setIntervalare only flagged when the first argument is a string or template literal. Passing an arrow function or function reference is safe and not flagged.
| Rule ID | Trigger | Risk |
|---|---|---|
SAST/ChildProcessExec |
cp.exec(cmd) |
OS command injection |
SAST/ChildProcessExecSync |
cp.execSync(cmd) |
OS command injection |
SAST/ChildProcessSpawn |
cp.spawn(cmd, args) |
OS command injection |
SAST/ChildProcessExecFile |
cp.execFile(path) |
OS command injection |
These rules fire on any
.exec()/.spawn()/.execFile()/.execSync()member-expression call, regardless of which object it's called on. Adjust withdisabled_rulesif you have a legitimate use case.
| Rule ID | Trigger | Severity |
|---|---|---|
SAST/PythonEval |
eval(expr) |
critical |
SAST/PythonExec |
exec(code) |
critical |
SAST/PythonPickle |
pickle.load(f) / pickle.loads(data) |
high |
SAST/PythonSubprocessShell |
Any call with shell=True keyword argument |
high |
SAST/PythonYamlLoad |
yaml.load(data) — use yaml.safe_load instead |
high |
| Rule ID | Trigger | Severity |
|---|---|---|
SAST/GoUnsafe |
import "unsafe" |
high |
SAST/GoExecCommand |
exec.Command(cmd, ...) |
high |
SAST/GoPanic |
panic(...) |
medium |
| Rule ID | Trigger | Default |
|---|---|---|
SMELL/LongFunction |
Function body exceeds max_function_lines lines |
50 lines |
SMELL/TooManyParameters |
Function has more than max_parameters parameters |
5 params |
SMELL/DeepNesting |
Control-flow nesting depth exceeds max_nesting_depth |
4 levels |
Rule IDs embed the measured value — e.g. SMELL/LongFunction (63 lines, max 50) — so findings are self-explanatory without additional context. Thresholds are configurable per project in .greengate.toml. See Configuration File for details.
Add // greengate: ignore on the same line as a finding to suppress it:
const legacyKey = "AKIAIOSFODNN7EXAMPLE123"; // greengate: ignore
el.innerHTML = sanitizedHtml; // greengate: ignore
eval(trustedAdminScript); // greengate: ignoreSuppression works for both secret/PII findings and SAST dangerous-pattern findings. It is applied per-line — only the finding on that exact line is suppressed.
Place .greengate.toml in the root of your repository. CLI flags always override config file values.
[scan]
# Glob patterns for paths to skip during scanning
exclude_patterns = [
"tests/**",
"*.test.ts",
"fixtures/**",
"vendor/**",
]
# Extra patterns on top of the 26 built-ins
extra_patterns = [
{ name = "Internal Service Token", regex = "svc_[a-z0-9]{40}" },
]
# Shannon entropy detection (flags high-entropy tokens like unrecognized API keys)
entropy = true
entropy_threshold = 4.5 # lower = more sensitive
entropy_min_length = 20 # ignore tokens shorter than this
[sast]
# Set to false to disable SAST entirely for JS/TS files (falls back to plain regex)
enabled = true
# Suppress specific SAST rule IDs that generate noise in your codebase
disabled_rules = [
# "SAST/ChildProcessExec",
# "SAST/EvalUsage",
# "SMELL/LongFunction",
]
# Code smell thresholds (defaults shown)
max_function_lines = 50 # flag functions longer than this many lines
max_parameters = 5 # flag functions with more than this many parameters
max_nesting_depth = 4 # flag control-flow nesting deeper than this inside a function
# Custom rules using tree-sitter S-expression queries
# Each rule must contain a @match capture marking the outermost node to report.
custom_rules = [
# { id = "CUSTOM/FetchCall", query = "(call_expression function: (identifier) @_fn (#eq? @_fn \"fetch\") @match)" },
]
[coverage]
# Default LCOV file path (overridden by --file)
file = "coverage/lcov.info"
# Default minimum threshold (overridden by --min)
min = 85.0
[lint]
# Default directory to scan for Kubernetes manifests (overridden by --dir)
target_dir = "./infrastructure/k8s"
[lighthouse]
# URL of the deployed app to audit (overridden by --url)
url = "https://yourapp.com"
# Device strategy: mobile (default) or desktop (overridden by --strategy)
strategy = "mobile"
# Per-category minimum scores 0–100 (overridden by --min-* flags)
min_performance = 80
min_accessibility = 90
min_best_practices = 80
min_seo = 80
# API key (optional; prefer PAGESPEED_API_KEY env var)
# api_key = ""
[reassure]
# Path to Reassure current measurement file (overridden by --current)
current = "output/current.perf"
# Path to Reassure baseline file (overridden by --baseline)
# If absent, greengate runs in report-only mode (no failure)
baseline = "output/baseline.perf"
# Maximum mean-time regression % before failing (overridden by --threshold)
threshold = 15.0All fields are optional. Omitted values fall back to safe defaults. CLI flags always take precedence over config file values.
The scan command supports five output formats via --format:
Human-readable output to stderr with severity prefix on each finding.
- [CRITICAL] [AWS Access Key] src/config.ts:14
- [HIGH] [SAST/InnerHTMLAssignment] src/components/Widget.tsx:88
Machine-readable JSON written to stdout. Includes severity field on every finding.
{
"total": 1,
"findings": [
{ "rule": "SAST/EvalUsage", "file": "./src/utils.js", "line": 42, "severity": "critical" }
]
}SARIF 2.1.0 JSON written to stdout. Upload directly to GitHub Advanced Security for inline PR annotations.
greengate scan --format sarif > results.sarifJUnit XML written to stdout. Compatible with Jenkins, Azure DevOps, and CircleCI test report parsers.
greengate scan --format junit > results.xmlGitLab SAST Security Scanner JSON (schema v15.0.6) written to stdout. Use as a GitLab security artifact to display findings in Merge Request security reports.
# .gitlab-ci.yml
secret-scan:
script: greengate scan --format gitlab > gl-sast-report.json
artifacts:
reports:
sast: gl-sast-report.jsonApply a named quality profile on top of your loaded config without editing .greengate.toml. Pass --profile as a global flag before the subcommand:
greengate --profile strict scan
greengate --profile ci scan --staged
greengate --profile relaxed coverage| Profile | Effect |
|---|---|
strict |
Coverage ≥ 90%, entropy threshold 3.5 (more sensitive), Lighthouse performance ≥ 90 & accessibility ≥ 95, SAST enabled |
relaxed |
Coverage ≥ 70%, entropy threshold 5.0 (fewer false positives) |
ci |
Coverage ≥ 80%, SAST enabled, SMELL/* rules disabled to reduce noise in CI |
Profiles only modify the in-memory config — they never write to .greengate.toml.
| Code | Meaning |
|---|---|
0 |
All checks passed — safe to proceed |
1 |
Check failed (secrets found, SAST issues, lint issues, coverage below threshold, vulnerabilities detected) or tool error |
CI pipelines can rely on the exit code directly — no parsing required.
name: GreenGate Quality Gate
on: [push, pull_request]
permissions:
contents: read
security-events: write # required for SARIF upload and Check Runs
checks: write # required for --annotate (GitHub Check Runs)
pull-requests: write # required for --annotate (PR review comment)
jobs:
# ── Security & code-quality gates ──────────────────────────────────────────
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
# Option A: Native GitHub Check Run annotations (no SARIF upload step needed)
- name: Secret, PII & SAST Scan
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: greengate scan --annotate
# Option B: SARIF upload to GitHub Advanced Security (alternative to --annotate)
# - name: Scan (SARIF for PR annotations)
# run: greengate scan --format sarif > results.sarif
# if: always()
# continue-on-error: true
# - name: Upload SARIF
# uses: github/codeql-action/upload-sarif@v4
# if: always()
# with:
# sarif_file: results.sarif
# continue-on-error: true
- name: Kubernetes Lint
run: greengate lint --dir ./k8s
- name: Coverage Gate
run: greengate coverage --file coverage/lcov.info --min 80
- name: Dependency Audit
run: greengate audit
# ── Performance gates (post-deploy) ────────────────────────────────────────
perf:
runs-on: ubuntu-latest
# needs: [deploy] # uncomment and set your deploy job name
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
# Lighthouse — gates on PageSpeed scores for the deployed URL.
# Set LIGHTHOUSE_URL as a repository variable (Settings → Variables).
# Set PAGESPEED_API_KEY as a repository secret for higher quota.
- name: Lighthouse audit
if: ${{ vars.LIGHTHOUSE_URL != '' }}
env:
PAGESPEED_API_KEY: ${{ secrets.PAGESPEED_API_KEY }}
run: |
greengate lighthouse \
--url "${{ vars.LIGHTHOUSE_URL }}" \
--strategy mobile \
--min-performance 80 \
--min-accessibility 90
# Reassure — gates on React component render regressions.
# Your frontend test job should run `reassure measure` and upload
# output/current.perf as an artifact, then download it here.
- name: Download Reassure report
uses: actions/download-artifact@v4
with:
name: reassure-report
path: output/
continue-on-error: true # skip gracefully if artifact not found
- name: Reassure performance gate
if: hashFiles('output/current.perf') != ''
run: greengate reassure --threshold 15stages:
- security
- quality
variables:
OXIDE_CI_URL: https://github.com/ThinkGrid-Labs/greengate/releases/latest/download/greengate-linux-amd64
.install_oxide: &install_oxide
before_script:
- curl -sL $OXIDE_CI_URL -o /usr/local/bin/greengate
- chmod +x /usr/local/bin/greengate
secret-and-sast-scan:
stage: security
<<: *install_oxide
script:
- greengate scan
k8s-lint:
stage: security
<<: *install_oxide
script:
- greengate lint --dir ./k8s
coverage-gate:
stage: quality
<<: *install_oxide
script:
- greengate coverage --file coverage/lcov.info --min 80
dependency-audit:
stage: security
<<: *install_oxide
script:
- greengate audit
allow_failure: true # optional: don't block pipeline on network issuesimage: ubuntu:22.04
pipelines:
default:
- step:
name: GreenGate Security & Quality Gates
script:
- apt-get update -qq && apt-get install -y curl
- 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
- greengate scan
- greengate lint --dir ./k8s
- greengate coverage --file coverage/lcov.info --min 80
- greengate auditversion: 2.1
jobs:
greengate:
docker:
- image: cimg/base:stable
steps:
- checkout
- run:
name: Install GreenGate
command: |
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
- run:
name: Secret, PII & SAST Scan
command: greengate scan
- run:
name: Kubernetes Lint
command: greengate lint --dir ./k8s
- run:
name: Coverage Gate
command: greengate coverage --file coverage/lcov.info --min 80
- run:
name: Dependency Audit
command: greengate audit
workflows:
quality:
jobs:
- greengateThe fastest way to enforce secrets scanning locally — runs automatically on every git commit:
greengate install-hooksTo remove the hook:
rm .git/hooks/pre-commitgreengate/
├── src/
│ ├── main.rs # CLI entry point (clap)
│ ├── modules/
│ │ ├── scanner.rs # Secret/PII scanning (rayon parallel) + SAST orchestration
│ │ ├── sast.rs # AST-based SAST for JS/TS/TSX/JSX (tree-sitter)
│ │ ├── github.rs # GitHub Check Runs + PR review comment (--annotate)
│ │ ├── k8s_lint.rs # Kubernetes manifest linter (serde_yaml)
│ │ ├── coverage.rs # LCOV & Cobertura XML parser and threshold gate
│ │ ├── audit.rs # OSV dependency audit (ureq)
│ │ ├── hooks.rs # Git hook installer
│ │ ├── perf_lighthouse.rs # PageSpeed Insights Lighthouse gate (ureq)
│ │ └── reassure.rs # Reassure .perf report parser and gate
│ └── utils/
│ ├── config.rs # .greengate.toml loader (toml + serde)
│ ├── files.rs # File walker (ignore crate)
│ └── terminal.rs # Styled output + progress bars (indicatif)
└── tests/
└── integration_test.rs # End-to-end binary tests
Scan pipeline for JS/TS files:
collect_findings()
│
├── non-JS/TS files ──→ run_regex_scan() (rayon parallel, per-line regex + entropy)
│
└── JS/TS files ──────→ run_sast_scan() (rayon parallel, tree-sitter per file)
│
├── parse AST (tree-sitter)
├── scan_string_literals() ← secrets/PII scoped to string nodes
├── scan_dangerous_patterns() ← 13 structural rules + custom rules
└── scan_complexity() ← 3 code smell rules
(long function, params, nesting)
emit_findings() ← text / JSON / SARIF
│
└── --annotate ──→ github::annotate() ← GitHub Check Run + PR comment (no-op if env absent)
Dependencies:
| Crate | Purpose |
|---|---|
clap |
CLI argument parsing |
rayon |
CPU-bound parallelism (file scanning) |
ignore |
gitignore-aware file walking |
regex |
Secret pattern matching |
tree-sitter |
AST parsing engine |
tree-sitter-javascript |
JS/JSX grammar |
tree-sitter-typescript |
TS/TSX grammar |
streaming-iterator |
Required by tree-sitter 0.24 QueryMatches API |
serde + serde_json |
JSON output (SARIF, audit, GitHub API bodies) |
serde_yaml |
Kubernetes YAML parsing |
roxmltree |
Zero-copy Cobertura XML parsing |
toml |
Config file parsing |
ureq |
HTTP client (OSV audit, PageSpeed, GitHub APIs) |
indicatif |
Progress bars |
anyhow |
Error handling and propagation |
greengate works with React Native and Expo projects out of the box. npm, Yarn, and pnpm lock files are all supported for dependency auditing, SAST runs automatically on all JS/TS/TSX files, and Reassure — which was originally built for React Native by Callstack — is a first-class citizen.
| Command | React Native support | Notes |
|---|---|---|
scan |
✅ Full | Secrets + PII + SAST on JS/TS/TSX files; gitignore-aware (skips node_modules/ automatically) |
audit |
✅ Full | Reads package-lock.json, yarn.lock, and pnpm-lock.yaml; queries OSV for npm CVEs |
coverage |
✅ Full | Reads Jest LCOV output (--coverageReporters=lcov) |
install-hooks |
✅ Full | Pre-commit hook works in any git repo |
reassure |
✅ Full | Built for React Native; parses output/current.perf from reassure measure |
lint |
Only useful if the project has a Kubernetes backend | |
lighthouse |
Audits a public URL; applicable if you ship a React Native Web build or marketing site |
Note on Bun:
bun.lockbis a binary format that greengate cannot parse. If you use Bun, runbun install --save-text-lockfileto generate abun.locktext file alongside it, or use thepackage-lock.jsonfallback (bun install --backend=npm).
In addition to the 23 built-in cloud patterns, greengate detects these patterns common in RN projects (scoped to string literals in JS/TS files):
| Rule | What it detects |
|---|---|
Expo Access Token |
EAS CLI robot/personal tokens (expa_…) |
Sentry DSN |
Sentry error-reporting DSNs (quota exhaustion + event read risk) |
Mapbox Secret Token |
Mapbox secret tokens (sk.eyJ…); public tokens (pk.eyJ…) are not flagged |
Google API Key |
Firebase API keys (AIza…) — same prefix as GCP, already built-in |
GCP Service Account Key |
google-services.json leaks — already built-in |
SAST is especially valuable in RN/Expo codebases because:
dangerouslySetInnerHTML—SAST/DangerouslySetInnerHTMLcatches this in TSX/JSX files. React Native itself doesn't render HTML, but React Native Web builds do.eval—SAST/EvalUsageflags dynamic code evaluation in JS bundles. Metro bundler includes all JS in the final bundle, making eval a supply-chain risk.- Node.js build scripts —
SAST/ChildProcessExec/SAST/ChildProcessSpawnflags shell commands in custom Metro config, build scripts, and Expo plugins.
[scan]
exclude_patterns = [
# Build artifacts
"android/build/**",
"android/.gradle/**",
"ios/build/**",
"ios/Pods/**",
# Expo cache and generated files
".expo/**",
".expo-shared/**",
# Metro bundler cache
".metro-cache/**",
# Test fixtures that intentionally contain fake patterns
"__tests__/**",
"__mocks__/**",
# React Native generated bundle
"android/app/src/main/assets/index.android.bundle",
]
# Shannon entropy catches random API keys not matched by explicit rules.
entropy = true
entropy_threshold = 4.5
entropy_min_length = 20
[sast]
enabled = true
# disabled_rules = [] # uncomment to suppress specific rules
[coverage]
file = "coverage/lcov.info"
min = 80.0
[reassure]
current = "output/current.perf"
baseline = "output/baseline.perf"
threshold = 15.0name: React Native CI
on: [push, pull_request]
jobs:
quality:
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
- name: Secret, PII & SAST scan
run: greengate scan
- name: Dependency audit (npm CVEs via OSV)
run: greengate audit # reads package-lock.json / yarn.lock / pnpm-lock.yaml
- name: Coverage gate
run: greengate coverage # reads coverage/lcov.info (generated by jest --coverage)
perf:
runs-on: ubuntu-latest
needs: quality
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run Reassure measurement
run: npx reassure measure
# Produces output/current.perf
- 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: Reassure performance gate
run: greengate reassure --threshold 15
# Fails if any component regresses > 15% vs baseline.perfError: Resource not accessible by integration
This is expected behaviour for private repositories. Uploading SARIF results to the GitHub Security tab requires GitHub Advanced Security (GHAS), which is:
- Free for public repositories
- Paid add-on for private repositories (part of GitHub Enterprise)
The greengate scan step itself still runs and will fail the job if real secrets or SAST issues are found — your security gate is intact. The SARIF upload is only needed for inline PR annotations in the Security tab.
Options:
| Option | When to use |
|---|---|
Keep continue-on-error: true (default) |
Private repo, no GHAS — scan still blocks on findings |
| Enable GitHub Advanced Security | You have GitHub Enterprise or want inline PR annotations |
| Remove the SARIF upload steps | You don't need the Security tab integration |
Package managers store sha512 integrity hashes in lockfiles (pnpm-lock.yaml, yarn.lock, package-lock.json). These look like high-entropy secrets and will generate hundreds of false positives.
Fix: Exclude lockfiles in .greengate.toml:
[scan]
exclude_patterns = [
"pnpm-lock.yaml",
"yarn.lock",
"package-lock.json",
"Cargo.lock", # sha256 checksums
]Compiled or generated files commonly contain base64-encoded data and high-entropy strings that are not secrets:
| File type | Reason flagged |
|---|---|
favicon.svg |
Embedded base64 image data |
pdf.worker.min.mjs / *.min.js |
Minified third-party library code |
*.chunk.js |
Webpack/Next.js build output |
Fix: Exclude public assets and minified files:
[scan]
exclude_patterns = [
"**/public/**", # static assets directory
"**/*.min.js",
"**/*.min.mjs",
".next/**", # Next.js build output
"dist/**",
"build/**",
]Sentry DSN URLs contain an @ symbol (e.g. https://abc123@o123.ingest.sentry.io/456), which triggers the Generic PII (Email) pattern. In properly configured projects the DSN is loaded from an environment variable (NEXT_PUBLIC_SENTRY_DSN), so the source files contain no hardcoded secret.
Fix: Exclude Sentry and instrumentation config files:
[scan]
exclude_patterns = [
"**/sentry.client.config.ts",
"**/sentry.server.config.ts",
"**/sentry.edge.config.ts",
"**/instrumentation.ts",
]Note: If your Sentry DSN is hardcoded in these files, greengate is correctly flagging it as a secret. Move it to an environment variable before excluding the file.
Test files commonly use placeholder addresses like user@example.com as input fixtures. These are not real PII.
Fix: Exclude test files and directories:
[scan]
exclude_patterns = [
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/__tests__/**",
]The Generic PII (Email) pattern catches any email-shaped string. Contact addresses in footers, documentation, form placeholders, or help pages are intentional — not secrets.
Fix: Exclude the specific files or directories:
[scan]
exclude_patterns = [
"docs/**",
"*.md",
"**/components/footer/**",
"**/components/forms/**", # input placeholder emails
"**/app/**/help/**", # contact emails on help pages
]If your config file contains example emails in comments (e.g. user@example.com), greengate will flag the config file itself. Exclude it:
[scan]
exclude_patterns = [
".greengate.toml",
]Some SAST rules (e.g. SAST/ChildProcessExec) fire on any usage of a pattern, even when it's intentional in a build script or CLI tool. Two ways to handle this:
Option 1 — Inline suppression (preferred for isolated occurrences):
child_process.exec(buildCmd, callback); // greengate: ignoreOption 2 — Disable the rule globally (preferred when the pattern is widespread):
[sast]
disabled_rules = ["SAST/ChildProcessExec"]Copy this as a starting point and remove exclusions that don't apply to your project:
[scan]
exclude_patterns = [
# Package manager lockfiles — sha512 integrity hashes, not secrets
"pnpm-lock.yaml",
"yarn.lock",
"package-lock.json",
# Public assets and minified files — base64 image data and bundled libraries
"**/public/**",
"**/*.min.js",
"**/*.min.mjs",
# Sentry / OpenTelemetry config — DSN URLs contain @ but are loaded from env vars
"**/sentry.client.config.ts",
"**/sentry.server.config.ts",
"**/sentry.edge.config.ts",
"**/instrumentation.ts",
# Test fixtures — placeholder emails used as test input, not real PII
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/__tests__/**",
# The config file itself — comments may contain example emails like user@example.com
".greengate.toml",
# Docs and markdown — example snippets and editorial emails
"docs/**",
"*.md",
# Build output
".next/**",
"dist/**",
"build/**",
".git/**",
]
entropy = true
entropy_threshold = 4.5
entropy_min_length = 20
[sast]
enabled = true
# disabled_rules = ["SAST/ChildProcessExec"]Warning: CodeQL Action v3 will be deprecated in December 2026.
Update the SARIF upload action in your workflow from @v3 to @v4:
# Before
uses: github/codeql-action/upload-sarif@v3
# After
uses: github/codeql-action/upload-sarif@v4GreenGate is open source under the MIT License. Contributions are welcome.
Adding a new secret pattern:
- Add a
(&str, &str)tuple toBUILTIN_PATTERNSin src/modules/scanner.rs inside the appropriate cloud provider section - Add a matching
#[test]for both a positive match and a false-positive check - Run
cargo testto verify
Adding a new SAST rule:
- Add a
SastRule { id, query }entry toRULESin src/modules/sast.rs - Write the tree-sitter S-expression query — the
@matchcapture marks the outermost node for location reporting - Add a unit test in the
#[cfg(test)]block that asserts the rule fires and (if applicable) that a safe variant does not fire - Run
cargo testto verify
Adding a new lint rule:
- Add a check in
check_manifest()in src/modules/k8s_lint.rs - Add a unit test in the
#[cfg(test)]block
Running tests:
cargo test # all unit + integration tests
cargo test scanner # only scanner tests
cargo test sast # only SAST tests
cargo clippy # lintIssues & feature requests: github.com/ThinkGrid-Labs/greengate/issues