Skip to content

scan — Secret, PII & SAST Scanning

Recursively scans every file in the current directory for hardcoded secrets, credentials, and PII using 26 built-in regex patterns plus Shannon entropy detection. Respects .gitignore automatically.

For JavaScript, TypeScript, Python, Go, and Rust files the scanner additionally runs an AST-based SAST pass using tree-sitter, eliminating false positives from comments and string non-literals.

Usage

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.
  --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)
  --fix                Auto-redact detected secrets in-place (replaces matched values with <REDACTED>)
  --dry-run            Preview what --fix would change without writing to disk
  --triage             Call an LLM to classify each finding as likely-real,
                       likely-false-positive, or uncertain
  -h, --help           Print help

Examples

bash
# Full scan — human-readable output
greengate scan

# Only scan what you're about to commit (fast, ideal for pre-commit)
greengate scan --staged

# Only scan files changed since the last commit
greengate scan --since HEAD~1

# Output SARIF for GitHub Advanced Security PR annotations
greengate scan --format sarif > results.sarif

# Output JSON for custom tooling
greengate scan --format json | jq '.findings[].rule'

# 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

# Enrich findings with git blame (author + commit hash)
greengate scan --blame

# Post findings directly to the GitHub Checks tab + PR summary comment
GITHUB_TOKEN=... GITHUB_REPOSITORY=owner/repo GITHUB_SHA=abc123 \
greengate scan --annotate

# Preview what auto-redaction would change (no files written)
greengate scan --dry-run

# Auto-redact detected secrets in-place
greengate scan --fix

# Triage each finding with an LLM (requires ANTHROPIC_API_KEY by default)
greengate scan --triage

Auto-fix (--fix / --dry-run)

--fix rewrites files in-place, replacing the matched secret value with <REDACTED>. SAST findings are listed but not modified — only secret/PII patterns that have a clearly bounded matched value are redacted.

Workflow:

bash
# 1. Preview changes (no writes)
greengate scan --dry-run

# 2. Review the diff output, then apply
greengate scan --fix

# 3. Verify the result
git diff

--dry-run prints a unified diff-style preview showing exactly which characters would be replaced. No files are written.

AI triage (--triage)

When enabled, each finding is sent to an LLM along with ~10 lines of surrounding context. The LLM classifies it as likely-real, likely-false-positive, or uncertain and gives a one-sentence reason.

⚠️  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:

bash
export ANTHROPIC_API_KEY=sk-ant-...   # Claude (default)
greengate scan --triage

Configure model, endpoint, and auto-suppression threshold in .greengate.toml:

toml
[triage]
model                   = "claude-haiku-4-5-20251001"
api_key_env             = "ANTHROPIC_API_KEY"
auto_suppress_threshold = 0.90   # suppress when ≥ 90% confident it's a false positive
context_lines           = 10
# endpoint = "http://localhost:11434/v1/chat/completions"  # Ollama / any OpenAI-compat

See the triage configuration section for full options and local model setup.

AST-based SAST

When scanning .js, .jsx, .ts, .tsx, .py, .go, or .rs files, greengate uses tree-sitter to parse a real AST. This provides:

String-literal scoping — secret patterns only fire inside string and template literals, not comments or JSX text:

ts
// AKIAIOSFODNN7EXAMPLE123          ← comment: NOT flagged
const key = "AKIAIOSFODNN7EXAMPLE123";  // ← string literal: flagged

Dangerous pattern detection — structural rules that catch XSS, eval, command injection, unsafe deserialization, and more. See SAST Rules for the full list.

Code smell rules — flags long functions, too many parameters, and deep nesting (JS/TS only).

Baseline mode

If your repository already has existing findings you can't fix immediately, the baseline workflow lets you suppress known findings and only fail on new ones introduced by a PR.

Step 1: Save a baseline (typically on your main branch)

bash
greengate scan --update-baseline
# Writes .greengate-baseline.json — commit this file

Step 2: Gate on new findings only (in CI, on every PR)

bash
greengate scan --since-baseline
# Only fails if new findings are introduced vs the baseline
yaml
# GitHub Actions example
- name: Save baseline (main branch only)
  if: github.ref == 'refs/heads/main'
  run: greengate scan --update-baseline && git add .greengate-baseline.json

- name: Gate on new secrets only (PRs)
  if: github.event_name == 'pull_request'
  run: greengate scan --since-baseline

Suppressing findings

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

ts
const legacyKey = "AKIAIOSFODNN7EXAMPLE123"; // greengate: ignore
el.innerHTML = sanitizedHtml;                 // greengate: ignore

Or on the line above to suppress the next line:

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

Suppress an entire rule class across the repo via [sast] in .greengate.toml:

toml
[sast]
disabled_rules = ["SAST/RustUnwrap", "SMELL/LongFunction"]

Sample output

ℹ️  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.

With --blame:

  - [CRITICAL] [AWS Access Key] src/config.ts:14
    blame: Alice Smith <alice@example.com> (abc12345)

Released under the MIT License.