Skip to content
Merged

Dev #11

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
15 changes: 15 additions & 0 deletions .greengate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,18 @@ target_dir = "."
# current = "output/current.perf"
# baseline = "output/baseline.perf"
# threshold = 15.0 # % mean-time regression allowed

# ── Supply chain protection ───────────────────────────────────────────────────
# Drives `greengate watch-install <pm> install`.
# block_phantom_scripts — exit non-zero if a postinstall script creates and
# then deletes a file inside node_modules/ (dropper signature).
# enforce_sandbox — also flag new executables dropped in the project root
# that were not present before the install began.
# allow_postinstall — packages whose postinstall scripts legitimately create
# temp files (e.g. native build tools that compile .node addons).
# Findings from these are reported as warnings, not errors.
#
# [supply_chain]
# block_phantom_scripts = true
# enforce_sandbox = true
# allow_postinstall = ["esbuild", "prisma", "@swc/core"]
6 changes: 3 additions & 3 deletions Cargo.lock

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

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

[dependencies]
Expand Down
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@

| Command | Purpose |
|---|---|
| `greengate watch-install` | **Supply-chain protection** — wraps npm/yarn/pnpm/bun and halts on phantom postinstall droppers |
| `greengate scan` | Secrets, PII & AST-based SAST for JS/TS/Python/Go |
| `greengate audit` | OSV dependency vulnerability audit |
| `greengate review` | PR Complexity Score + new-code coverage gaps |
| `greengate lint` | Kubernetes manifest linting |
| `greengate docker-lint` | Dockerfile best-practice checks |
| `greengate coverage` | LCOV / Cobertura coverage threshold gate |
| `greengate audit` | OSV dependency vulnerability audit |
| `greengate lighthouse` | PageSpeed Insights performance gate |
| `greengate reassure` | React component render regression gate |
| `greengate sbom` | CycloneDX 1.5 SBOM generation |
Expand Down Expand Up @@ -68,18 +69,21 @@ cargo install --git https://github.com/thinkgrid-labs/greengate
## Quick start

```bash
# Supply-chain safe install — detects postinstall droppers in real time
greengate watch-install npm ci

# Scan for secrets and run SAST
greengate scan

# Audit dependencies for known CVEs
greengate audit

# Analyze a PR: complexity score + new-code coverage gaps
greengate review --base main --coverage-file coverage/lcov.info

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

# Audit dependencies for known CVEs
greengate audit

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

Expand All @@ -100,11 +104,18 @@ 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

# Replaces plain `npm ci` — halts if a postinstall script drops and deletes a binary
- name: Supply-chain safe install
run: greengate watch-install npm ci

- name: Secret, PII & SAST scan
run: greengate scan --annotate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Dependency audit (OSV)
run: greengate audit

- name: PR review (complexity + coverage gaps)
if: github.event_name == 'pull_request'
run: |
Expand All @@ -120,9 +131,6 @@ greengate run

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

- name: Dependency audit
run: greengate audit
```

> See [CI/CD Integration](https://thinkgrid-labs.github.io/greengate/guide/ci-integration) for full GitHub Actions, GitLab CI, Bitbucket, and CircleCI examples.
Expand All @@ -134,6 +142,11 @@ greengate run
Create `.greengate.toml` in your repo root. All fields are optional:

```toml
[supply_chain]
block_phantom_scripts = true
enforce_sandbox = true
allow_postinstall = ["esbuild", "prisma", "@swc/core"]

[scan]
exclude_patterns = ["tests/**", "*.test.ts", "vendor/**"]
entropy = true
Expand Down Expand Up @@ -162,8 +175,8 @@ Full guides, command references, and CI examples live in the **[docs site](https
- [Getting Started](https://thinkgrid-labs.github.io/greengate/guide/getting-started)
- [CI/CD Integration](https://thinkgrid-labs.github.io/greengate/guide/ci-integration)
- [Use Cases](https://thinkgrid-labs.github.io/greengate/guide/use-cases)
- **Commands:** [scan](https://thinkgrid-labs.github.io/greengate/commands/scan) · [review](https://thinkgrid-labs.github.io/greengate/commands/review) · [coverage](https://thinkgrid-labs.github.io/greengate/commands/coverage) · [audit](https://thinkgrid-labs.github.io/greengate/commands/audit) · [lint](https://thinkgrid-labs.github.io/greengate/commands/lint) · [docker-lint](https://thinkgrid-labs.github.io/greengate/commands/docker-lint) · [lighthouse](https://thinkgrid-labs.github.io/greengate/commands/lighthouse) · [reassure](https://thinkgrid-labs.github.io/greengate/commands/reassure) · [sbom](https://thinkgrid-labs.github.io/greengate/commands/sbom) · [run](https://thinkgrid-labs.github.io/greengate/commands/run)
- **Reference:** [Config](https://thinkgrid-labs.github.io/greengate/reference/config) · [Secret Patterns](https://thinkgrid-labs.github.io/greengate/reference/secret-patterns) · [SAST Rules](https://thinkgrid-labs.github.io/greengate/reference/sast-rules) · [Output Formats](https://thinkgrid-labs.github.io/greengate/reference/output-formats) · [Exit Codes](https://thinkgrid-labs.github.io/greengate/reference/exit-codes)
- **Commands:** [watch-install](https://thinkgrid-labs.github.io/greengate/commands/watch-install) · [scan](https://thinkgrid-labs.github.io/greengate/commands/scan) · [audit](https://thinkgrid-labs.github.io/greengate/commands/audit) · [review](https://thinkgrid-labs.github.io/greengate/commands/review) · [coverage](https://thinkgrid-labs.github.io/greengate/commands/coverage) · [lint](https://thinkgrid-labs.github.io/greengate/commands/lint) · [docker-lint](https://thinkgrid-labs.github.io/greengate/commands/docker-lint) · [lighthouse](https://thinkgrid-labs.github.io/greengate/commands/lighthouse) · [reassure](https://thinkgrid-labs.github.io/greengate/commands/reassure) · [sbom](https://thinkgrid-labs.github.io/greengate/commands/sbom) · [run](https://thinkgrid-labs.github.io/greengate/commands/run)
- **Reference:** [Config](https://thinkgrid-labs.github.io/greengate/reference/config) · [Secret Patterns](https://thinkgrid-labs.github.io/greengate/reference/secret-patterns) · [SAST Rules](https://thinkgrid-labs.github.io/greengate/reference/sast-rules) · [Output Formats](https://thinkgrid-labs.github.io/greengate/reference/output-formats) · [Exit Codes](https://thinkgrid-labs.github.io/greengate/reference/exit-codes) · [Roadmap](https://thinkgrid-labs.github.io/greengate/reference/roadmap)

---

Expand Down
6 changes: 4 additions & 2 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 DevOps CLI: secret scanning, AST-based SAST, Kubernetes linting, coverage gates, dependency auditing, and web performance — single zero-dependency binary.',
description: 'Rust DevOps CLI: supply-chain protection, secret scanning, AST-based SAST, Kubernetes linting, coverage gates, dependency auditing, and web performance — single zero-dependency binary.',
base: '/greengate/',

head: [
Expand Down Expand Up @@ -36,11 +36,12 @@ export default defineConfig({
{
text: 'Commands',
items: [
{ text: '🔒 watch-install', link: '/commands/watch-install' },
{ text: 'scan', link: '/commands/scan' },
{ text: 'audit', link: '/commands/audit' },
{ text: 'lint', link: '/commands/lint' },
{ text: 'docker-lint', link: '/commands/docker-lint' },
{ text: 'coverage', link: '/commands/coverage' },
{ text: 'audit', link: '/commands/audit' },
{ text: 'install-hooks', link: '/commands/install-hooks' },
{ text: 'lighthouse', link: '/commands/lighthouse' },
{ text: 'reassure', link: '/commands/reassure' },
Expand All @@ -57,6 +58,7 @@ export default defineConfig({
{ text: 'Exit Codes', link: '/reference/exit-codes' },
{ text: 'Output Formats', link: '/reference/output-formats' },
{ text: 'Limitations', link: '/reference/limitations' },
{ text: 'Roadmap', link: '/reference/roadmap' },
],
},
],
Expand Down
166 changes: 166 additions & 0 deletions docs/commands/watch-install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# watch-install

> **Supply-chain protection for npm, yarn, pnpm, and bun installs.**

`greengate watch-install` wraps your package manager and monitors `node_modules/` in real time during the install. If a postinstall script creates a file and then deletes it before the install finishes — the classic dropper signature used in attacks like the [2025 axios compromise](#background) — the install is halted and the offending package is named.

---

## Usage

```bash
greengate watch-install <PACKAGE_MANAGER> [ARGS...]
```

All arguments after the package manager name are forwarded verbatim:

```bash
# Drop-in for npm install
greengate watch-install npm install

# Frozen lockfile (CI)
greengate watch-install npm ci

# pnpm with flags
greengate watch-install pnpm install --frozen-lockfile

# yarn
greengate watch-install yarn install --immutable

# bun
greengate watch-install bun install
```

---

## Flags

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

---

## What it detects

### 1. Phantom files (`PHANTOM`)

A file is created inside `node_modules/` during a postinstall script and deleted before the install completes. This is the primary dropper signature:

```
postinstall → write binary to disk → execute → unlink to hide evidence
```

GreenGate polls `node_modules/` every 250 ms while the package manager runs. Any file that appears in one poll and disappears in a later poll is flagged.

### 2. Executable drops (`EXEC_DROP`)

A new executable file (one with the execute bit set on Unix, or a `.exe/.bat/.cmd/.ps1` extension on Windows) appears in the project root after the install completes that was not there before. Legitimate package managers never place executables outside `node_modules/`.

---

## Example output

**Phantom detected:**

```
🚨 greengate watch-install: 1 suspicious event(s) detected:

[PHANTOM ] evil-pkg
path: node_modules/evil-pkg/.postinstall

Tip: if this package is a known native build tool (e.g. esbuild, swc),
add it to [supply_chain] allow_postinstall in .greengate.toml to suppress.

Error: watch-install: 1 blocking event(s) detected — halting.
```

**Clean install:**

```
✅ watch-install: clean — no phantom files or executable drops detected.
```

---

## Configuration

All options live under `[supply_chain]` in `.greengate.toml`:

```toml
[supply_chain]
# Halt the install when a phantom or exec-drop is detected (default: true).
block_phantom_scripts = true

# Also monitor the project root for new executables (default: true).
enforce_sandbox = true

# Packages whose postinstall scripts legitimately create temp files.
# Native build tools (esbuild, @swc/core, prisma) compile .node addons
# and may create intermediate files during the build. List them here to
# downgrade their findings to warnings instead of errors.
allow_postinstall = ["esbuild", "prisma", "@swc/core"]
```

### allow_postinstall behaviour

Packages on the allowlist still appear in the output with `[allowlisted — warning only]` so you have a full audit trail, but they do not cause `block_phantom_scripts` to trigger a non-zero exit.

---

## CI usage

Replace your existing `npm install` / `npm ci` step with `greengate watch-install`:

```yaml
- 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: Supply-chain safe install
run: greengate watch-install npm ci
```

For teams not yet ready to block on findings, start in audit-only mode:

```yaml
- name: Supply-chain audit (non-blocking)
run: greengate watch-install --no-fail npm ci
```

---

## Layered defence with `audit`

`watch-install` and `greengate audit` are complementary, not redundant:

| Tool | When it runs | What it catches |
|---|---|---|
| `greengate audit` | Pre/post install | Known CVEs in OSV database for your lock file |
| `greengate watch-install` | During install | Runtime dropper behaviour that CVE databases cannot see |

Run both:

```yaml
- run: greengate watch-install npm ci # catches runtime behaviour
- run: greengate audit # catches known CVEs
```

---

## Background

In early 2025, the [axios](https://github.com/axios/axios) npm package was the subject of a supply-chain compromise discussion where attackers targeted postinstall hooks to execute and then self-delete malicious payloads. This attack pattern — write, execute, unlink — leaves no trace in `node_modules/` after the install finishes, making it invisible to static scanners and lock-file diffing tools.

`watch-install` catches it because the file system events happen _during_ the install window, not after.

---

## Limitations

- **Pure network exfiltration** — if a postinstall script sends data over the network without writing any file, there is no filesystem event to observe. Pair with network egress controls in CI for defence in depth.
- **Windows** — phantom detection works on Windows via `std::fs` polling, but exec-drop detection uses file-extension heuristics (`.exe`, `.bat`, `.cmd`, `.ps1`) rather than the execute bit.
- **Very fast droppers** — files created and deleted within a single 250 ms poll window may be missed. This is the theoretical lower bound; real-world payloads take longer to download and execute.

See also: [Roadmap](/reference/roadmap) for planned sandbox-level isolation (`greengate sandbox-install`).
10 changes: 7 additions & 3 deletions docs/guide/ci-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,18 @@ jobs:
-o /usr/local/bin/greengate
chmod +x /usr/local/bin/greengate

# Replaces plain `npm ci` — halts if a postinstall script drops and deletes a binary
- name: Supply-chain safe install
run: greengate watch-install npm ci

- name: Secret, PII & SAST Scan
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: greengate scan --annotate

- name: Dependency Audit (OSV)
run: greengate audit

- name: PR Review (Complexity + Coverage Gaps)
if: github.event_name == 'pull_request'
env:
Expand All @@ -51,9 +58,6 @@ jobs:

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

- name: Dependency Audit
run: greengate audit
```

## GitHub Actions — SARIF upload (alternative)
Expand Down
Loading
Loading