Metadata-Version: 2.4
Name: pipeguard-cli
Version: 0.1.1
Summary: Pre-commit security scanner for GitHub Actions workflows
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: click>=8.0
Requires-Dist: pytest>=9.0.2
Requires-Dist: pyyaml>=6.0
Requires-Dist: requests>=2.31
Requires-Dist: rich>=13.0
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
Description-Content-Type: text/markdown

# PipeGuard

<div align="center">

[![CI](https://github.com/alex-jung/pipeguard-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/alex-jung/pipeguard-cli/actions/workflows/ci.yml)
[![PyPI Version](https://img.shields.io/pypi/v/pipeguard-cli)](https://pypi.org/project/pipeguard-cli/)
[![Downloads](https://img.shields.io/pypi/dm/pipeguard-cli)](https://pypi.org/project/pipeguard-cli/)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)

</div>

<div align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
    <img src="assets/logo-light.svg" alt="PipeGuard" width="200">
  </picture>
</div>

> Catch GitHub Actions security issues before they reach your runners.

Pre-commit security scanner for GitHub Actions workflows. Catches supply-chain risks, unpinned actions, known CVEs, and secret leaks — before you push.

---

## Demo

![Demo](assets/demo.gif)

The recording shows a scan of a real workflow file with multiple security issues.
PipeGuard detects them all in under a second — no API key, no network call, fully offline:

- No top-level `permissions:` block — GitHub grants write access to all scopes by default
- `tj-actions/changed-files@v35` and `reviewdog/action-setup@v1` match known CVEs in the bundled database
- All actions are pinned to tags instead of commit SHAs — a supply-chain risk
- `echo ${{ secrets.DEPLOY_TOKEN }}` leaks a secret value to the workflow log
- `8398a7/action-slack` is a third-party action from an unverified publisher

---

## PipeGuard vs. the alternatives

| Feature | PipeGuard | actionlint | StepSecurity | act |
|---------|:---------:|:----------:|:------------:|:---:|
| SHA-pinning check | ✅ | ❌ | ✅ | ❌ |
| CVE database (offline) | ✅ | ❌ | ✅ (online) | ❌ |
| Permissions analysis | ✅ | ⚠️ syntax only | ✅ | ❌ |
| Secrets-leak detection | ✅ | ❌ | ✅ (runtime) | ❌ |
| Supply-chain audit | ✅ | ❌ | ✅ | ❌ |
| Syntax / type checks | ✅ via actionlint | ✅ | ❌ | ❌ |
| Run workflows locally | ❌ | ❌ | ❌ | ✅ |
| Runtime hardening | ❌ | ❌ | ✅ | ❌ |
| Pre-commit hook | ✅ | ✅ | ❌ | ❌ |
| SARIF output | ✅ | ✅ | ❌ | ❌ |
| No API key required | ✅ | ✅ | ❌ | ✅ |
| Fully offline | ✅ | ✅ | ❌ | ⚠️ needs images |
| Open source | ✅ | ✅ | ✅ | ✅ |
| Free | ✅ core | ✅ | ⚠️ freemium | ✅ |

PipeGuard fills the gap between authoring and execution: static security analysis, offline, before you push, without any external service.

---

## Installation

**Prerequisites:** Python 3.11+

```bash
pip install pipeguard-cli
```

**Install actionlint** (required for syntax checks, optional for other checks):

```bash
# Linux
curl -fsSL https://github.com/rhysd/actionlint/releases/latest/download/actionlint_linux_amd64.tar.gz \
  | tar xz actionlint && sudo mv actionlint /usr/local/bin/

# macOS
brew install actionlint

# Ubuntu/Debian (snap)
sudo snap install actionlint
```

---

## Usage

```bash
pipeguard scan [PATH]
```

`PATH` can be a single workflow file or a directory. If omitted, defaults to `.github/workflows`.

### Examples

```bash
# Scan entire .github/workflows directory (default)
pipeguard scan

# Scan a specific directory
pipeguard scan .github/workflows/

# Scan a single file
pipeguard scan .github/workflows/ci.yml

# JSON output (for scripts / CI pipelines)
pipeguard scan --format json

# SARIF output (for IDE integration, GitHub Code Scanning)
pipeguard scan --format sarif

# Use a custom config file
pipeguard scan --config path/to/.pipeguard.yml
```

When scanning a directory, pipeguard prints a per-file header and a summary at the end:

```
Scanning: .github/workflows/ci.yml
  ✓ No issues found

Scanning: .github/workflows/deploy.yml
  ╭─────────┬──────────────────┬──────────────────────────┬──────────────╮
  │ Severity│ Rule             │ Location                 │ Message      │
  ├─────────┼──────────────────┼──────────────────────────┼──────────────┤
  │ error   │ cve-cve-2025-... │ deploy.yml:12            │ Action ...   │
  │ error   │ sha-pinning      │ deploy.yml:15            │ Action ...   │
  ╰─────────┴──────────────────┴──────────────────────────┴──────────────╯

Scanned 2 file(s) — 2 error(s), 1 warning(s), 3 info(s) total.
```

### Exit codes

| Code | Meaning |
|------|---------|
| `0`  | No errors or warnings |
| `1`  | One or more errors or warnings found |

Info-level findings (action inventory) do not affect the exit code.

---

## Security checks

### Errors & Warnings

| Rule | Severity | Description |
|------|----------|-------------|
| `sha-pinning` | error | Action pinned to a tag or branch instead of a full commit SHA — supply-chain risk (cf. [CVE-2025-30066](https://www.cve.org/CVERecord?id=CVE-2025-30066)) |
| `sha-pinning-reusable` | error | Reusable workflow called with a tag or branch ref instead of a full commit SHA — same supply-chain risk as unpinned actions |
| `cve-<id>` | error | Action matches a known CVE in the local database (offline, no API) |
| `supply-chain` | warning | Third-party action from an unverified publisher — suppress per publisher or action via `.pipeguard.yml` |
| `secrets-leak` | error | Secret value echoed or logged in a `run:` step |
| `secrets-leak-debug` | error / warning | `set -x` or `set -o xtrace` in a `run:` step — shell debug mode prints every expanded command; error when secrets are in env scope, warning otherwise |
| `permissions-missing` | error | No `permissions:` block — GitHub grants write access to most scopes by default |
| `permissions-write-all` | error | `permissions: write-all` at top-level or per-job |
| `permissions-excessive` | warning | Sensitive scope (e.g. `secrets`, `workflows`) set to `write` |
| `permissions-id-token-unused` | warning | `id-token: write` is set but no OIDC-consuming action is present — allows minting cloud credentials unnecessarily |
| `permissions-invalid` | error | Unknown permission level (not `read`, `write`, or `none`) |
| `pull-request-target` | warning | `pull_request_target` trigger runs with base-repo write permissions and secret access — even for fork PRs |
| `pull-request-target-pwn` | error | `pull_request_target` combined with checkout of the PR head ref — allows untrusted fork code to run with secrets (Pwn Request) |
| `actionlint` | error | Syntax and type errors (requires actionlint) |

### Info

| Rule | Severity | Description |
|------|----------|-------------|
| `action-inventory` | info | Inventory of all third-party actions used — ref(s) and occurrence count |

---

## CVE database

PipeGuard ships with a built-in offline CVE database — no API key, no network call during scan. The database is updated daily via NVD and shipped with each new weekly release.

<!-- cve-updated-start -->
> [!NOTE]
> Last updated: 2026-03-12
<!-- cve-updated-end -->

<!-- cve-table-start -->
| CVE | Action | Description |
|-----|--------|-------------|
| [CVE-2025-30066](https://www.cve.org/CVERecord?id=CVE-2025-30066) | `tj-actions/changed-files` | tj-actions/changed-files was compromised in a supply-chain attack. All tag-based refs are potentially affected — pin to a known-good SHA. |
| [CVE-2025-30154](https://www.cve.org/CVERecord?id=CVE-2025-30154) | `reviewdog/action-setup` | reviewdog/action-setup was compromised in the same supply-chain campaign as tj-actions/changed-files. Pin to a vetted SHA. |
| [CVE-2023-49291](https://www.cve.org/CVERecord?id=CVE-2023-49291) | `tj-actions/branch-names` | tj-actions/branch-names improperly references github.event.pull_request.head.ref and github.head_ref in a run step, allowing a specially crafted branch name to execute arbitrary code and steal secrets or abuse GITHUB_TOKEN permissions. |
<!-- cve-table-end -->

SHA-pinned refs are not flagged — another reason to always pin to a commit SHA.

To get the latest CVEs, upgrade to the newest release:

```bash
pip install --upgrade pipeguard-cli
```

---

## Configuration

PipeGuard looks for a `.pipeguard.yml` (or `.pipeguard.yaml`, `pipeguard.yml`, `pipeguard.yaml`) in the current directory and its parents.

### Suppress `supply-chain` warnings for trusted publishers

If you trust a specific publisher or action, add them to the allowlist:

```yaml
# .pipeguard.yml
trusted_publishers:
  - my-org           # suppresses all warnings for my-org/*
  - some-vendor      # suppresses all warnings for some-vendor/*

trusted_actions:
  - other-org/specific-action  # suppresses warning for this exact action only
```

- `trusted_publishers` matches as a prefix — every action from `my-org/*` is considered trusted.
- `trusted_actions` requires an exact match on the action name (without the `@ref`).
- A trailing `/` in publisher names is optional; PipeGuard normalises it automatically.

The built-in allowlist (GitHub, AWS, Azure, Google, Docker, HashiCorp, etc.) is always active and cannot be removed.

---

## CI integration

Add pipeguard as a pre-push check or CI step:

```yaml
# .github/workflows/pipeguard.yml
- name: Scan workflows
  run: |
    pip install pipeguard-cli
    pipeguard scan .github/workflows/
```

Or as a pre-commit hook (`.pre-commit-config.yaml`):

```yaml
repos:
  - repo: local
    hooks:
      - id: pipeguard
        name: PipeGuard workflow scan
        entry: pipeguard scan
        language: python
        additional_dependencies: [pipeguard-cli]
        files: \.github/workflows/.*\.ya?ml$
```

---

## Output formats

**Terminal** (default) — human-readable table with rule, location, and fix suggestions.

**JSON** — machine-readable, one object per finding:

```json
[
  {
    "rule": "cve-cve-2025-30066",
    "severity": "error",
    "message": "Action 'tj-actions/changed-files@v35' is affected by CVE-2025-30066: ...",
    "file": ".github/workflows/ci.yml",
    "line": 12,
    "fix_suggestion": "Immediately pin 'tj-actions/changed-files' to a verified safe SHA."
  }
]
```

**SARIF** — compatible with GitHub Code Scanning.

---

## License

Apache 2.0 — see [LICENSE](LICENSE).

## Status

In active development. All Free-tier scanners are functional.
Planned: auto-fix PRs (Pro), Maintainer Trust Score (Pro), cloud sandbox testing, IDE plugin.

Free-tier checks: SHA-pinning, CVE database, permissions audit (incl. `id-token: write` without OIDC), secrets-leak detection, action inventory, actionlint syntax checks.
