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
- Fork the repository at https://github.com/a4899e/pinstack
- Create a branch from
develop(notmain) - Add tests for any new checker or behaviour
- 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pinstack-0.1.3.tar.gz.
File metadata
- Download URL: pinstack-0.1.3.tar.gz
- Upload date:
- Size: 67.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c17d413a7486a20f289c09888fb7e10ac7cb0c2c20f70ca5a320647fcfc90c20
|
|
| MD5 |
9e7581c606250ed5acebdc9636f2f6a0
|
|
| BLAKE2b-256 |
1631f32c33f29a4f3e13792cff47192438123e811b5b0836cfe4a4926cc08342
|
File details
Details for the file pinstack-0.1.3-py3-none-any.whl.
File metadata
- Download URL: pinstack-0.1.3-py3-none-any.whl
- Upload date:
- Size: 48.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44fee2de2d962f96d8cf0ed4629fc9bae84625051f739fbc40e3ac91084eca9f
|
|
| MD5 |
7fe0bfa101c8538964aca793bb057c38
|
|
| BLAKE2b-256 |
d3e7ff17e4ca8c729c688ae4b5f76dd35ffed8db4feea9bdfba5d568f3ccfc96
|