Skip to content
Merged

Dev #13

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .greengate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ entropy = true
entropy_threshold = 4.5
entropy_min_length = 20

[sast]
# Disable rules that generate expected false positives in greengate's own source:
# - RustUnwrap/RustExpect: used on compiled regexes and in test helpers where
# panic is the correct behaviour (LOW severity, too noisy to suppress inline).
# - RustCommandNew: suppressed inline with `// greengate: ignore` on each
# intentional Command::new call so intent is documented at the call site.
disabled_rules = [
"SAST/RustUnwrap",
"SAST/RustExpect",
]

[coverage]
file = "coverage/lcov.info"
min = 80.0
Expand Down
17 changes: 14 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "greengate"
version = "0.3.1"
version = "0.3.2"
edition = "2024"

[dependencies]
Expand All @@ -20,6 +20,7 @@ tree-sitter-javascript = "0.23"
tree-sitter-typescript = "0.23"
tree-sitter-python = "0.23"
tree-sitter-go = "0.23"
tree-sitter-rust = "0.23"
ureq = { version = "2", features = ["json"] }
roxmltree = "0.20"
globset = "0.4"
Expand Down
763 changes: 673 additions & 90 deletions README.md

Large diffs are not rendered by default.

98 changes: 98 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: greengate
description: >-
Zero-trust DevOps security gate — scans for secrets, SAST issues,
vulnerable dependencies, coverage gaps, CI misconfigurations, and more
in one fast Rust binary. No Docker required.
author: ThinkGrid Labs
branding:
icon: shield
color: green

inputs:
version:
description: >-
greengate release tag to download, e.g. "v0.3.2".
Use "latest" to always pull the newest release
(pin to a specific version for reproducible CI).
required: false
default: latest
profile:
description: >-
Quality profile to apply: "strict" (90 % coverage, tighter entropy),
"relaxed" (70 % coverage), or "ci" (default — 80 % + SAST enabled,
code-smell rules silenced to reduce noise).
required: false
default: ci
args:
description: Extra arguments forwarded verbatim to `greengate run`.
required: false
default: ''

outputs:
findings:
description: Total number of findings reported across all pipeline steps.
value: ${{ steps.run.outputs.findings }}

runs:
using: composite
steps:
# ── Download the correct binary for the runner platform ───────────────────
- name: Install greengate
id: install
shell: bash
env:
GREENGATE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail

VERSION="$GREENGATE_VERSION"
if [ "$VERSION" = "latest" ]; then
VERSION=$(curl -sSfL \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/ThinkGrid-Labs/greengate/releases/latest" \
| grep '"tag_name"' | head -1 | cut -d'"' -f4)
if [ -z "$VERSION" ]; then
echo "::error::Could not resolve latest greengate version. Set inputs.version explicitly."
exit 1
fi
fi

OS="$(uname -s)"
ARCH="$(uname -m)"
case "${OS}-${ARCH}" in
Linux-x86_64) TRIPLE="x86_64-unknown-linux-musl" ;;
Darwin-arm64) TRIPLE="aarch64-apple-darwin" ;;
Darwin-x86_64) TRIPLE="x86_64-apple-darwin" ;;
*)
echo "::error::greengate action: unsupported platform ${OS}-${ARCH}"
exit 1
;;
esac

INSTALL_DIR="${HOME}/.local/bin"
mkdir -p "$INSTALL_DIR"

URL="https://github.com/ThinkGrid-Labs/greengate/releases/download/${VERSION}/greengate-${VERSION}-${TRIPLE}"
echo "Downloading greengate ${VERSION} (${TRIPLE})..."
curl -sSfL "$URL" -o "${INSTALL_DIR}/greengate"
chmod +x "${INSTALL_DIR}/greengate"
echo "${INSTALL_DIR}" >> "$GITHUB_PATH"

echo "greengate ${VERSION} installed to ${INSTALL_DIR}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

# ── Execute the pipeline defined in .greengate.toml ───────────────────────
- name: Run greengate
id: run
shell: bash
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail

PROFILE_ARG=""
if [ -n "${{ inputs.profile }}" ]; then
PROFILE_ARG="--profile ${{ inputs.profile }}"
fi

greengate run $PROFILE_ARG ${{ inputs.args }}
30 changes: 23 additions & 7 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { defineConfig } from 'vitepress'

export default defineConfig({
title: 'GreenGate',
description: 'Rust CLI for zero-trust supply chain protection, secret scanning, AST-based SAST, test impact analysis, Kubernetes linting, coverage gates, and dependency auditing — single zero-dependency binary.',
description: 'Rust CLI for zero-trust supply chain protection (npm/pip/cargo), secret scanning, AST-based SAST, AI-assisted triage, SBOM attestation, CI config linting, test impact analysis, coverage gates, and OTLP metrics — single zero-dependency binary.',
base: '/greengate/',

head: [
Expand Down Expand Up @@ -45,31 +45,47 @@ export default defineConfig({
],
},
{
text: 'Commands',
text: 'Commands — Security',
items: [
{ text: '🔒 watch-install', link: '/commands/watch-install' },
{ text: '🎯 tia', link: '/commands/tia' },
{ text: '🐍 pip-install', link: '/commands/pip-install' },
{ text: '🦀 cargo-add', link: '/commands/cargo-add' },
{ text: 'scan', link: '/commands/scan' },
{ text: 'ci-lint', link: '/commands/ci-lint' },
{ text: 'image-scan', link: '/commands/image-scan' },
{ text: 'audit', link: '/commands/audit' },
{ text: 'sbom', link: '/commands/sbom' },
],
},
{
text: 'Commands — Quality',
items: [
{ text: '🎯 tia', link: '/commands/tia' },
{ text: 'review', link: '/commands/review' },
{ text: 'coverage', link: '/commands/coverage' },
{ text: 'lint', link: '/commands/lint' },
{ text: 'docker-lint', link: '/commands/docker-lint' },
{ text: 'coverage', link: '/commands/coverage' },
{ text: 'install-hooks', link: '/commands/install-hooks' },
{ text: 'lighthouse', link: '/commands/lighthouse' },
{ text: 'reassure', link: '/commands/reassure' },
{ text: 'sbom', link: '/commands/sbom' },
],
},
{
text: 'Commands — Workflow',
items: [
{ text: 'run', link: '/commands/run' },
{ text: 'init', link: '/commands/init' },
{ text: 'install-hooks', link: '/commands/install-hooks' },
{ text: 'watch', link: '/commands/watch' },
{ text: 'run', link: '/commands/run' },
{ text: 'check-config', link: '/commands/check-config' },
],
},
{
text: 'Reference',
items: [
{ text: 'Configuration', link: '/reference/config' },
{ text: 'Secret Patterns', link: '/reference/secret-patterns' },
{ text: 'SAST Rules', link: '/reference/sast-rules' },
{ text: 'Telemetry & Metrics', link: '/reference/telemetry' },
{ text: 'Exit Codes', link: '/reference/exit-codes' },
{ text: 'Output Formats', link: '/reference/output-formats' },
{ text: 'Limitations', link: '/reference/limitations' },
Expand Down
134 changes: 134 additions & 0 deletions docs/commands/cargo-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
---
title: 'cargo-add — Zero-Trust Rust Supply Chain Gate'
description: 'Wrap cargo add with three-layer supply chain protection: typosquat detection against 60 popular crates, build.rs static analysis for every new crate, and transitive dependency explosion guard.'
---

# cargo-add — Zero-Trust Rust Supply Chain Gate

> **Intercept malicious crates before they execute build scripts on your machine.**

`greengate cargo-add` wraps `cargo add` with three independent layers of supply-chain security. Unlike npm, Cargo crates run `build.rs` at compile time — giving a malicious crate arbitrary code execution on every `cargo build`. This command blocks that attack vector.

## Usage

```bash
greengate cargo-add [OPTIONS] [CARGO_ARGS...]
```

All arguments are forwarded verbatim to `cargo add`:

```bash
# Add a single crate
greengate cargo-add serde

# Add with features
greengate cargo-add serde --features derive

# Add multiple crates
greengate cargo-add tokio anyhow clap

# Add a dev-dependency
greengate cargo-add --dev mockall
```

## Options

| Flag | Default | Description |
|---|---|---|
| `--no-fail` | — | Report findings to stderr but exit 0. Useful for audit-only pipelines. |

## Three-layer architecture

### Layer 1 — Typosquat detection (pre-add)

Before `cargo add` runs, each crate name is compared against the 60 most-downloaded crates.io crates using Levenshtein distance. If the edit distance is ≤ 2, the operation is **halted before cargo runs**.

```
greengate cargo-add serd

Error: Possible typosquat: "serd" is 1 edit(s) away from "serde".
Verify the crate name is correct before adding.
```

This catches attacks like `serd`, `tokio_`, `anyhoww`, `reqwest2`, etc.

### Layer 2 — `build.rs` static analysis (post-add)

After `cargo add` and `cargo fetch`, greengate locates the `build.rs` file for each newly added crate in `~/.cargo/registry/src/` and scans it for 25 suspicious signals:

| Category | Signals |
|---|---|
| Network access | `TcpStream::connect`, `reqwest`, `ureq`, `curl_sys`, `hyper` |
| Subprocess spawning | `Command::new`, `std::process::Command`, `process::exit` |
| Environment exfiltration | `std::env::var`, `env::vars()`, `HOME`, `AWS_`, `GITHUB_TOKEN` |
| Filesystem writes outside target | `std::fs::write`, `File::create` with absolute path |
| Dynamic code loading | `dlopen`, `libloading`, `unsafe extern "C"` |
| High entropy strings | Shannon entropy > 4.8 over any 64-char window |

### Layer 3 — Dependency explosion guard

After the add, greengate diffs `Cargo.lock` before and after. If more than **50 new transitive dependencies** were added, a warning is emitted. Adding a simple utility crate that pulls in 80 dependencies is a significant attack surface expansion.

```
⚠️ Dependency explosion: adding "fancy-logger" introduced 67 new transitive dependencies.
Review Cargo.lock carefully before committing.
```

## Example output

**Typosquat detected (Layer 1):**

```
Error: Possible typosquat: "tokio_" is 1 edit(s) away from "tokio".
Verify the crate name is correct before adding.
```

**Suspicious build.rs (Layer 2):**

```
⚠️ Supply chain scan: suspicious build.rs in "malicious-crate@0.1.0":
Signals: TcpStream::connect, std::env::var("GITHUB_TOKEN"), Command::new

Error: Supply chain gate: suspicious build.rs detected — halting.
```

**Clean add:**

```
✅ cargo-add: no typosquats, suspicious build.rs, or dependency explosion detected.
```

## Configuration

```toml
[supply_chain]
# Crate names exempted from typosquat and build.rs scanning.
# Use for internal crates or crates with legitimately complex build scripts.
allow_cargo_crates = [
# "openssl", # has complex build.rs for system lib detection
# "ring", # cryptography crate with intentionally complex build
]
```

## CI usage

```yaml
# In a workflow that adds new dependencies
- name: Zero-trust cargo add
run: greengate cargo-add serde tokio anyhow

# Audit-only (non-blocking)
- name: Supply chain audit
run: greengate cargo-add --no-fail serde
```

## Why `build.rs` matters

Unlike npm postinstall scripts (which run at install time), Rust's `build.rs` runs at **every `cargo build`** — including in CI, on every developer's machine, and during release builds. A malicious `build.rs` can:

- Exfiltrate CI secrets (`GITHUB_TOKEN`, `AWS_*`, `CARGO_REGISTRY_TOKEN`)
- Modify generated code to introduce backdoors
- Make outbound network requests before your firewall rules can stop them
- Write files outside the build directory

`greengate cargo-add` scans the `build.rs` before you commit the `Cargo.lock`, when it's still easy to remove the dependency.
Loading
Loading