Skip to main content

Supply-chain dependency pinning enforcement for every ecosystem

Project description

pinstack

Supply-chain dependency pinning enforcement for every ecosystem.

pinstack scans a project directory and reports unpinned or loosely-pinned dependencies across the most common package managers, container formats, and infrastructure tools. It is designed to drop into any CI pipeline with a single command and zero setup beyond pip install pinstack.


Project Goals

1. Broad ecosystem coverage

pinstack understands as many project and language types as possible. A single invocation on a monorepo surfaces pinning issues in Python packages, Node modules, Docker images, Go modules, Rust crates, Ruby gems, Terraform providers, Helm charts, and more.

2. Zero dependencies

pinstack has no runtime dependencies outside the Python standard library. It runs on any system's python3 (3.9 or later) without a virtual environment or any other setup step. This makes it practical to adopt in non-Python projects where adding a Python dependency tree would be unwelcome.

3. CLI-only configuration

There are no config files to maintain. Every behaviour is controlled by flags passed on the command line. What you see in the command is what it does, making pinstack easy to audit, reproduce, and reason about in CI logs.


Installation

pip install pinstack

Because pinstack has zero runtime dependencies, it can also be run directly from a checkout without installation:

python -m pinstack .

Quick Start

Scan the current directory:

pinstack .

Only check Python and Docker dependency files:

pinstack . --check requirements,pyproject,dockerfile

Output as SARIF for GitHub Code Scanning integration:

pinstack . --format sarif > results.sarif

Supported Ecosystems

Checker Files Examined What Is Checked
requirements requirements*.txt == pinning and --hash verification
pyproject pyproject.toml == exact pins in [project.dependencies] and [project.optional-dependencies]
package_json package.json No ^, ~, or range specifiers in any dependency section
package_lock package-lock.json integrity hashes present on all packages
yarn_lock yarn.lock Integrity checksums present
pnpm_lock pnpm-lock.yaml integrity hashes present
dockerfile Dockerfile* @sha256: digest on every FROM
github_actions .github/workflows/*.yml 40-char commit SHA after @ in uses:
go go.mod, go.sum go.sum exists alongside go.mod; h1: hashes present
cargo Cargo.lock checksum field on each registry [[package]]
gemfile Gemfile, Gemfile.lock Lock file exists alongside Gemfile
terraform .terraform.lock.hcl h1: hashes present per provider
helm Chart.yaml, Chart.lock Lock file exists when Chart.yaml has dependencies; digest: present
compose docker-compose*.yml, compose*.yml @sha256: digest on image: references

CLI Reference

pinstack [PATH] [OPTIONS]
Option Description
PATH Directory to scan. Defaults to . (current directory).
--check NAME,NAME Only run these checkers (comma-separated). Mutually exclusive with --exclude.
--exclude NAME,NAME Skip these checkers (comma-separated). Mutually exclusive with --check.
--format FORMAT Output format: text (default) or sarif.
--exit-zero Always exit 0, even when findings are present. Useful for advisory-only runs.
--max-depth N Maximum directory depth to recurse into. Default: 4. Increase for deep monorepos.
--max-files N Maximum number of dependency files to index. Default: 384. Increase for large monorepos. A warning is printed to stderr if the limit is reached.
--exclude-dir DIR,DIR Additional directory names to skip during traversal (comma-separated).
--list-checkers Print all available checkers and exit.
--version Print the pinstack version and exit.

Exit Codes

Code Meaning
0 No findings (or --exit-zero was set).
1 One or more findings reported.
2 Runtime error.

Output Formats

text (default)

Human-readable output suitable for local development and CI logs:

FAIL  Dockerfile:3  FROM without @sha256: digest: python:3.11-slim
FAIL  requirements.txt:7  not pinned with ==: requests>=2.28
FAIL  go.mod  go.sum missing alongside go.mod

3 errors in 3 files

sarif

SARIF 2.1.0 JSON, consumable by GitHub Code Scanning, GitLab SAST, and other tools that support the standard.


CI Integration

pinstack is a standalone CLI tool, not a library. It works with any project regardless of language. Python 3.9+ is the only requirement, and it ships on virtually every CI runner out of the box.

GitHub Actions

name: Supply Chain Check
on: [push, pull_request]

jobs:
  pinstack:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29

      - name: Set up Python
        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
        with:
          python-version: "3.x"

      - name: Install pinstack
        run: pip install pinstack

      - name: Check dependency pinning
        run: pinstack .

To upload results to GitHub Code Scanning:

      - name: Check dependency pinning (SARIF)
        run: pinstack . --format sarif > pinstack.sarif
        continue-on-error: true

      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d
        with:
          sarif_file: pinstack.sarif

GitLab CI

pinstack:
  stage: test
  image: python:3-slim
  before_script:
    - pip install pinstack
  script:
    - pinstack .

To integrate with GitLab SAST, output SARIF and use the SAST report artifact:

pinstack:
  stage: test
  image: python:3-slim
  before_script:
    - pip install pinstack
  script:
    - pinstack . --format sarif > gl-sast-report.json || true
  artifacts:
    reports:
      sast: gl-sast-report.json

Python Projects (Invoke)

Add a task to your tasks.py:

from invoke import task

@task
def supply_chain(c):
    """Check dependency pinning."""
    c.run("pinstack .")

Then run it with invoke supply-chain or include it in your build task:

@task(pre=[lint, typecheck, test, supply_chain])
def build(c):
    """Full build pipeline."""
    pass

Any Project

pinstack runs anywhere Python 3.9+ is available. No virtual environment needed, no dependencies to install beyond pinstack itself.

With pip:

pip install pinstack
pinstack .

Without pip (clone and run directly):

git clone https://github.com/a4899e/pinstack.git /tmp/pinstack
python3 /tmp/pinstack/pinstack .

Check only specific ecosystems:

# Java project -- only check Maven and Gradle
pinstack . --check maven,gradle

# Node project -- only check JS ecosystem
pinstack . --check package_json,package_lock,yarn_lock,pnpm_lock

# Docker-only check
pinstack . --check dockerfile,compose

Skip directories that contain third-party code:

pinstack . --exclude-dir vendor,third_party,checkouts

Contributing

  1. Fork the repository at https://github.com/a4899e/pinstack
  2. Create a branch from develop (not main)
  3. Add tests for any new checker or behaviour
  4. Open a pull request against develop

Development setup:

./scripts/setup-dev.sh
source .venv/bin/activate

Or manually:

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
git config core.hooksPath .githooks

Running Tests

After running ./scripts/setup-dev.sh (or the manual steps above):

source .venv/bin/activate
invoke test                              # run the full test suite
python -m pytest tests/ -v               # or run pytest directly
python -m pytest tests/test_checker_go.py # run a single test file

Build Pipeline

The pre-commit hook runs invoke build before each commit. You can also run it manually:

invoke build     # tests + ruff + pyright + bandit
invoke clean     # remove .pyc, caches, build artifacts

License

Apache 2.0. See LICENSE for the full text.

Copyright 2024 Trevor T. trevort@scantonomous.ai

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pinstack-0.1.1.tar.gz (65.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pinstack-0.1.1-py3-none-any.whl (48.4 kB view details)

Uploaded Python 3

File details

Details for the file pinstack-0.1.1.tar.gz.

File metadata

  • Download URL: pinstack-0.1.1.tar.gz
  • Upload date:
  • Size: 65.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for pinstack-0.1.1.tar.gz
Algorithm Hash digest
SHA256 59950add66640bc1cd87c380c923bb5280bc1ac56b7afb45a90ad27272844db8
MD5 d53acaf73af96306ff7c8669e319a5cf
BLAKE2b-256 614e2675fdc11d24615ab38302a6b5a1d402135d4580dd78ecf61e7ea95da75a

See more details on using hashes here.

File details

Details for the file pinstack-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: pinstack-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 48.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for pinstack-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 aea6a88a3259de8b697a71ee74c6ee17a17e6061c387ae760c0082e02c4aa179
MD5 e3661b56562b9eb931d9f91c7ac28cb0
BLAKE2b-256 5395d4d846f45e20c72095dfa5160572293b31ec21e4fe216656d2734e222859

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page