Metadata-Version: 2.4
Name: pipeguard-scanner-cli
Version: 1.0.0
Summary: Like a linter, but for CI/CD and DevOps mistakes.
Author-email: Liem Tran <rin@rilab.space>
License: MIT
License-File: LICENSE
Keywords: cicd,cli,devops,docker,github-actions,scanner,security
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.10
Requires-Dist: pyyaml>=6.0.0
Requires-Dist: rich>=13.7.0
Requires-Dist: tomli>=2.0.0; python_version < '3.11'
Requires-Dist: typer>=0.12.0
Provides-Extra: dev
Requires-Dist: build>=1.2.0; extra == 'dev'
Requires-Dist: pip-audit>=2.7.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.5.0; extra == 'dev'
Requires-Dist: twine>=5.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# PipeGuard

**Like a linter, but for CI/CD and DevOps mistakes**

[![Python](https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
[![Typer](https://img.shields.io/badge/CLI-Typer-00A67E)](https://typer.tiangolo.com/)
[![Rich](https://img.shields.io/badge/Terminal-Rich-7A5FFF)](https://rich.readthedocs.io/)
[![PyYAML](https://img.shields.io/badge/YAML-PyYAML-CB171E)](https://pyyaml.org/)
[![Pytest](https://img.shields.io/badge/Tests-Pytest-0A9EDC?logo=pytest&logoColor=white)](https://docs.pytest.org/)
[![Ruff](https://img.shields.io/badge/Lint-Ruff-D7FF64?logo=ruff&logoColor=black)](https://docs.astral.sh/ruff/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

PipeGuard is a lightweight Python CLI that scans your repository for risky CI/CD workflows, insecure Dockerfiles, and common DevOps configuration mistakes

It helps catch issues such as:

- Unpinned GitHub Actions
- Over-permissive workflow tokens
- Unsafe `pull_request_target` usage
- Dangerous `curl | bash` commands
- Docker images running as root
- Dockerfiles using `latest` tags
- Missing Docker healthchecks
- Committed `.env` files
- Committed tokens and private keys
- Unpinned Python dependencies
- Python packages with known vulnerabilities from `pip-audit`
- Docker OS packages that cannot be reliably image-audited

## Installation

From the project directory:

```bash
pip install -e .
```

Or install with development tools:

```bash
pip install -e ".[dev]"
```

## Built With

| Tool | Purpose |
| --- | --- |
| ![Python](https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white) | Core language and packaging runtime |
| ![Typer](https://img.shields.io/badge/Typer-CLI-00A67E) | Command-line interface for `pipeguard scan` |
| ![Rich](https://img.shields.io/badge/Rich-Terminal-7A5FFF) | Pretty terminal tables and scan reports |
| ![PyYAML](https://img.shields.io/badge/PyYAML-YAML-CB171E) | Workflow and config file parsing |
| `pip-audit` | Python package vulnerability advisories |
| ![Pytest](https://img.shields.io/badge/Pytest-Tests-0A9EDC?logo=pytest&logoColor=white) | Automated test suite |
| ![Ruff](https://img.shields.io/badge/Ruff-Lint-D7FF64?logo=ruff&logoColor=black) | Fast Python linting |

## Usage

Scan the current repository:

```bash
pipeguard scan .
```

Scan a specific repository:

```bash
pipeguard scan /path/to/project
```

Scan a public Git repository:

```bash
pipeguard scan https://github.com/owner/repo --ref main
```

### Supported commands

| Command | Purpose |
| --- | --- |
| `pipeguard --version` | Show the installed PipeGuard version |
| `pipeguard scan .` | Scan the current directory |
| `pipeguard scan <path>` | Scan a specific project folder |
| `pipeguard scan https://github.com/owner/repo` | Clone and scan a public Git repository in a temporary directory |
| `pipeguard scan <path> --category cicd` | Scan only GitHub Actions workflows |
| `pipeguard scan <path> --category docker` | Scan only Dockerfiles |
| `pipeguard scan <path> --category iac` | Scan Docker Compose and Kubernetes manifests |
| `pipeguard scan <path> --category repo` | Scan only repository health checks |
| `pipeguard scan <path> --category secrets` | Scan only committed secret patterns |
| `pipeguard scan <path> --category vuln` | Scan dependency and Docker package audit readiness |
| `pipeguard scan <path> --exclude "dist/**"` | Exclude paths from scanning and reporting |
| `pipeguard scan <path> --docker-image image:tag` | Scan a Docker image with Trivy or Grype, bootstrapped automatically when missing |
| `pipeguard scan <path> --image-scanner trivy` | Select the Docker image vulnerability scanner |
| `pipeguard scan <path> --output report.md` | Export a Markdown report |
| `pipeguard scan <path> --output report.json` | Export a JSON report |
| `pipeguard scan <path> --output report.sarif` | Export a SARIF report for code scanning |
| `pipeguard scan <path> --fail-on high` | Exit with code 1 when high or critical findings exist |
| `pipeguard scan <path> --ignore RULE_ID` | Ignore a specific rule ID |
| `pipeguard scan <path> --config pipeguard.yml` | Load a specific PipeGuard config file |

### Category examples

Scan only GitHub Actions workflows:

```bash
pipeguard scan . --category cicd
```

Scan only Dockerfiles:

```bash
pipeguard scan . --category docker
```

Scan only Docker Compose and Kubernetes manifests:

```bash
pipeguard scan . --category iac
```

Scan only repository health checks:

```bash
pipeguard scan . --category repo
```

Scan only committed secret patterns:

```bash
pipeguard scan . --category secrets
```

Scan only Python package vulnerabilities and Docker package audit readiness:

```bash
pipeguard scan . --category vuln
```

Scan a Docker image with an installed scanner:

```bash
pipeguard scan . --category vuln --docker-image python:3.12 --image-scanner auto
```

When `--docker-image` is used, PipeGuard first looks for an installed Trivy or Grype binary.
If neither is available, it downloads the official scanner installer into a temporary tools
directory, runs the scan, and deletes the temporary directory afterward.

### Sample output

PipeGuard prints tracking logs, a progress bar, a summary panel, and findings grouped by severity.
The examples below are abbreviated from real category scans.

GitHub Actions:

```text
track Scanning GitHub Actions workflows
track Scan complete
Scan complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%

PipeGuard Scan Report
Scanned path: /path/to/your-project
Findings: 9 | Critical: 0 | High: 2 | Medium: 7 | Low: 0

HIGH
GH_ACTION_JOB_WRITE_PERMISSION    cicd  .github/workflows/release-tag.yml      24   Job grants write token permissions
GH_ACTION_SECRET_IN_PULL_REQUEST  cicd  .github/workflows/python-security.yml  120  Workflow references secrets in pull request context

MEDIUM
GH_ACTION_UNPINNED_ACTION         cicd  .github/workflows/aws.yml              22   GitHub Action is not pinned to a commit SHA
GH_ACTION_WORKFLOW_WRITE_PERMISSION cicd .github/workflows/tag-codebuild-gated.yml 33 Workflow grants write token permissions
```

Docker:

```text
track Scanning Dockerfiles
track Scan complete
Scan complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%

PipeGuard Scan Report
Scanned path: /path/to/your-project
Findings: 3 | Critical: 0 | High: 0 | Medium: 2 | Low: 1

MEDIUM
DOCKER_LATEST_TAG       docker  side-jobs/delete-junk-provider-redis-queue/Dockerfile  1  Docker image uses latest or implicit latest tag
DOCKER_RUNNING_AS_ROOT  docker  side-jobs/delete-junk-provider-redis-queue/Dockerfile  -  Dockerfile does not switch to a non-root user

LOW
DOCKER_MISSING_HEALTHCHECK docker side-jobs/delete-junk-provider-redis-queue/Dockerfile - Dockerfile does not define a healthcheck
```

Repository health:

```text
track Scanning repository health
track Scan complete
Scan complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%

PipeGuard Scan Report
Scanned path: /path/to/your-project
Findings: 31 | Critical: 0 | High: 0 | Medium: 0 | Low: 31

LOW
REPO_LARGE_FILE             repo  swagger-codegen/swagger-codegen-cli.jar  -   Large file found in repository
REPO_MISSING_ENV_EXAMPLE    repo  .                                        -   Repository is missing .env.example
REPO_REQUIREMENTS_UNPINNED  repo  requirements.txt                         2   Python dependency is not fully pinned
```

Secrets:

```text
track Scanning committed secret patterns
track Scan complete
Scan complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%

PipeGuard Scan Report
Scanned path: /path/to/your-project
Findings: 61 | Critical: 61 | High: 0 | Medium: 0 | Low: 0

CRITICAL
SECRET_AWS_ACCESS_KEY  secrets  app/config.py      25  AWS access key appears to be committed
SECRET_PRIVATE_KEY     secrets  certs/badkey.pem   1   Private key appears to be committed
```

Vulnerability scan:

```text
track Scanning dependency vulnerabilities
track Collecting Python dependency manifests
track Preparing isolated pip-audit virtual environments
track Virtualenv isolation unavailable, using temporary target directories
track Installing pip-audit into temporary tools directory
track Installing target dependencies from requirements.txt
track Running pip-audit against temporary target directory
track Parsing pip-audit advisory results
track Checking Dockerfile package installs
track Scan complete
Scan complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%

PipeGuard Scan Report
Scanned path: /path/to/your-project
Findings: 28 | Critical: 0 | High: 28 | Medium: 0 | Low: 0

HIGH
VULN_PYTHON_PACKAGE_VULNERABLE  vuln  requirements.txt  18  Python package has known vulnerability CVE-2024-37891
VULN_PYTHON_PACKAGE_VULNERABLE  vuln  requirements.txt  28  Python package has known vulnerability CVE-2025-68616
```

Docker image vulnerability scan:

```text
track Scanning Docker image python:3.9-slim
track Scan complete
Scan complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%

PipeGuard Scan Report
Scanned path: /path/to/your-project
Findings: 199 | Critical: 6 | High: 44 | Medium: 85 | Low: 64

CRITICAL
VULN_DOCKER_IMAGE_PACKAGE_VULNERABLE  vuln  python:3.9-slim  -  Docker image package has known vulnerability CVE-2025-15467

HIGH
VULN_DOCKER_IMAGE_PACKAGE_VULNERABLE  vuln  python:3.9-slim  -  Docker image package has known vulnerability CVE-2026-0861
```

All checks:

```text
track Scanning GitHub Actions workflows
track Scanning Dockerfiles
track Scanning repository health
track Scanning committed secret patterns
track Scanning dependency vulnerabilities
track Scan complete
Scan complete ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%

PipeGuard Scan Report
Scanned path: /path/to/your-project
Findings: 132 | Critical: 61 | High: 30 | Medium: 9 | Low: 32
```

### Export examples

Export a Markdown report:

```bash
pipeguard scan . --output report.md
```

Export JSON:

```bash
pipeguard scan . --output report.json
```

Export JSON for another project:

```bash
pipeguard scan /path/to/flask-app --output flask-app-pipeguard.json
```

Export SARIF for code scanning:

```bash
pipeguard scan . --output pipeguard.sarif
```

### Visible project examples

Scan the bundled unsafe example project:

```bash
pipeguard scan examples
```

Export the bundled unsafe example project to JSON:

```bash
pipeguard scan examples --output examples-pipeguard.json
```

Scan `slynk-api`:

```bash
pipeguard scan /path/to/slynk-api
```

Export `slynk-api` to JSON:

```bash
pipeguard scan /path/to/slynk-api --output slynk-api-pipeguard.json
```

Scan `Syncraft`:

```bash
pipeguard scan /path/to/Syncraft
```

Export `Syncraft` to JSON:

```bash
pipeguard scan /path/to/Syncraft --output syncraft-pipeguard.json
```

### CI and config examples

Fail CI when high-risk issues are found:

```bash
pipeguard scan . --fail-on high
```

Ignore a specific rule:

```bash
pipeguard scan . --ignore GH_ACTION_UNPINNED_ACTION
```

You can also create `pipeguard.yml` in the repository root:

```yaml
ignore:
  - GH_ACTION_UNPINNED_ACTION

exclude:
  - examples/**
  - tests/fixtures/**
  - .security-target/**
  - .security-tools/**

fail_on: high

rules:
  DOCKER_MISSING_HEALTHCHECK:
    severity: medium
  REPO_MISSING_ENV_EXAMPLE: off
```

PipeGuard also looks for `pipeguard.yaml`, `.pipeguard.yml`, and `.pipeguard.yaml`
Use `--config path/to/file.yml` to load a specific config file

See [RULES.md](RULES.md) for rule IDs, severities, and fixes.

## Current checks

### GitHub Actions

| Rule | Severity | Description |
| --- | --- | --- |
| `GH_ACTION_UNPINNED_ACTION` | Medium | Action is pinned to a tag or branch instead of a full SHA |
| `GH_ACTION_MISSING_PERMISSIONS` | Medium | Workflow does not define least-privilege permissions |
| `GH_ACTION_WORKFLOW_WRITE_PERMISSION` | Medium | Workflow grants at least one write token permission |
| `GH_ACTION_WRITE_ALL_PERMISSION` | High | Workflow grants `write-all` token permissions |
| `GH_ACTION_JOB_WRITE_PERMISSION` | High | Job grants at least one write token permission |
| `GH_ACTION_PULL_REQUEST_TARGET` | High | Workflow uses `pull_request_target` |
| `GH_ACTION_PULL_REQUEST_TARGET_CHECKOUT_HEAD` | Critical | Workflow appears to use PR head code with `pull_request_target` |
| `GH_ACTION_SECRET_IN_PULL_REQUEST` | High | Workflow references secrets in pull request context |
| `GH_ACTION_WORKFLOW_RUN_TRIGGER` | Medium | Workflow uses `workflow_run` trigger |
| `GH_ACTION_CHECKOUT_PERSIST_CREDENTIALS` | Medium | Checkout persists GitHub credentials |
| `GH_ACTION_CURL_BASH` | High | Workflow pipes remote script directly into shell |
| `GH_ACTION_PIP_INSTALL_REMOTE_URL` | Medium | Workflow installs Python package from a remote URL |
| `GH_ACTION_DOCKER_LOGIN_PASSWORD` | Medium | Workflow performs Docker login with a password/secret |
| `GH_ACTION_PYPI_TOKEN_PUBLISH` | Medium | Workflow appears to use a long-lived PyPI token |

### Dockerfile

| Rule | Severity | Description |
| --- | --- | --- |
| `DOCKER_LATEST_TAG` | Medium | Base image uses `latest` or implicit latest tag |
| `DOCKER_RUNNING_AS_ROOT` | Medium | Dockerfile does not switch to non-root user |
| `DOCKER_MISSING_HEALTHCHECK` | Low | Dockerfile does not define a healthcheck |
| `DOCKER_APT_NO_CLEANUP` | Low | apt package lists are not cleaned |
| `DOCKER_COPY_ENTIRE_CONTEXT` | Low | Dockerfile copies the whole context |
| `DOCKER_SECRET_ENV` | Critical | Dockerfile contains a likely secret in ARG/ENV |

### Docker Compose and Kubernetes

| Rule | Severity | Description |
| --- | --- | --- |
| `COMPOSE_PRIVILEGED_SERVICE` | High | Compose service runs with privileged mode |
| `COMPOSE_HOST_NETWORK` | Medium | Compose service uses host networking |
| `COMPOSE_RUNS_AS_ROOT` | Medium | Compose service runs as root |
| `K8S_PRIVILEGED_CONTAINER` | High | Kubernetes container runs privileged |
| `K8S_HOST_PATH_VOLUME` | High | Kubernetes workload mounts a hostPath volume |
| `K8S_CONTAINER_RUNS_AS_ROOT` | Medium | Kubernetes container runs as UID 0 |
| `K8S_MISSING_RESOURCE_LIMITS` | Low | Kubernetes container is missing resource limits |

### Repository health

| Rule | Severity | Description |
| --- | --- | --- |
| `REPO_MISSING_README` | Low | Repository is missing a README |
| `REPO_MISSING_GITIGNORE` | Low | Repository is missing `.gitignore` |
| `REPO_ENV_FILE_COMMITTED` | Critical | `.env` style file appears committed |
| `REPO_MISSING_ENV_EXAMPLE` | Low | Repository is missing `.env.example` |
| `REPO_REQUIREMENTS_UNPINNED` | Low | Python dependency is not fully pinned |
| `REPO_MISSING_PYPROJECT` | Low | Python project may lack modern packaging config |
| `REPO_LARGE_FILE` | Low | Large file found in repository |

### Vulnerability

| Rule | Severity | Description |
| --- | --- | --- |
| `VULN_PYTHON_PACKAGE_VULNERABLE` | High | `pip-audit` reported a known vulnerability for a pinned Python package |
| `VULN_PIP_AUDIT_UNAVAILABLE` | Medium | Isolated `pip-audit` environment setup or execution failed |
| `VULN_DOCKER_OS_PACKAGE_UNPINNED` | Medium | Dockerfile installs an OS package without an exact version |
| `VULN_DOCKER_IMAGE_PACKAGE_VULNERABLE` | Low-Critical | Trivy or Grype reported a vulnerable package in a Docker image |
| `VULN_DOCKER_IMAGE_SCANNER_UNAVAILABLE` | Medium | Docker image scan was requested but Trivy or Grype could not be found or bootstrapped |

### Secrets

| Rule | Severity | Description |
| --- | --- | --- |
| `SECRET_PRIVATE_KEY` | Critical | Private key appears to be committed |
| `SECRET_GITHUB_TOKEN` | Critical | GitHub token appears to be committed |
| `SECRET_AWS_ACCESS_KEY` | Critical | AWS access key appears to be committed |
| `SECRET_STRIPE_KEY` | Critical | Stripe live secret key appears to be committed |
| `SECRET_SLACK_TOKEN` | High | Slack token appears to be committed |
| `SECRET_JWT_TOKEN` | Medium | JWT appears to be committed |

## Why PipeGuard?

Most teams review application code carefully, but CI/CD workflows, Dockerfiles, and repository setup often receive less attention

PipeGuard gives your repository a quick DevOps health check before small configuration mistakes become production or supply-chain problems

## Development

Run tests:

```bash
pytest
```

Run linting:

```bash
ruff check .
```

## License

PipeGuard is released under the [MIT License](LICENSE)

Copyright (c) 2026 Liem Tran
