Skip to content
This repository was archived by the owner on Aug 22, 2026. It is now read-only.
Open
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
52 changes: 52 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Dependabot configuration for automated dependency updates
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates

version: 2
updates:
# Enable version updates for Python packages
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
reviewers:
- "j-kemble"
assignees:
- "j-kemble"
# Labels for pull requests
labels:
- "dependencies"
- "python"
# Group related updates together
groups:
dev-dependencies:
patterns:
- "pytest*"
- "bandit*"
- "mypy*"
- "pre-commit*"
- "pip-audit*"
production-dependencies:
patterns:
- "cryptography*"
- "platformdirs*"
- "pyperclip*"
commit-message:
prefix: "deps"
include: "scope"
# Security updates are handled separately by Dependabot alerts

# Enable version updates for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "ci"
include: "scope"
36 changes: 0 additions & 36 deletions .github/workflows/ci.yml

This file was deleted.

80 changes: 70 additions & 10 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,90 @@ on:
tags:
- "v*"

permissions:
contents: read

jobs:
build:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0

- name: Validate tag is on main (production publish branch)
run: |
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
echo "Tag ${GITHUB_REF} points to a commit not reachable from main."
echo "PyPI publishes only from main. Refusing to continue."
exit 1
fi
echo "Tag is reachable from main: ${GITHUB_REF}"

- name: Validate tag matches project version
run: |
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
PROJECT_VERSION=$(grep '^version' pyproject.toml | head -1 | sed 's/.*= *"\(.*\)"/\1/')
if [ "$TAG_VERSION" != "$PROJECT_VERSION" ]; then
echo "Tag version ($TAG_VERSION) does not match pyproject.toml version ($PROJECT_VERSION)"
exit 1
fi
echo "Version validated: $TAG_VERSION"

test:
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v6
- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: "3.12"

- name: Install dependencies
run: |
python -m pip install --require-hashes -r constraints/ci.txt
python -m pip install --no-deps -e .

- name: Run tests
run: pytest -p no:cacheprovider -q

- name: Security scan
run: bandit -c pyproject.toml -r generate_it/ -ll

- name: Dependency vulnerability scan
run: pip-audit

build-and-publish:
needs: test
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write # Required for OIDC trusted publishing

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false

- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: "3.12"

- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build twine
python -m pip install --require-hashes -r constraints/release.txt
python -m pip install --no-deps -e .

- name: Build distribution
run: python -m build
run: python -m build --no-isolation

- name: Check distribution
run: twine check dist/*

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0
184 changes: 184 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
name: Security & Quality

on:
push:
branches: [main, development]
pull_request:
branches: [main, development]

# Least-privilege: read-only access to repository contents.
# No write access to issues, PRs, deployments, or other resources.
permissions:
contents: read

jobs:
# Security linting with Bandit (BLOCKING: fails CI on low+ severity findings).
# Uses pyproject.toml for bandit configuration; -ll gates at LOW severity and above.
bandit:
name: Security linting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --require-hashes -r constraints/ci.txt
python -m pip install --no-deps -e .
- name: Run Bandit security scan (blocking)
run: bandit -c pyproject.toml -r generate_it/ -ll
- name: Generate Bandit JSON report
if: always()
run: bandit -c pyproject.toml -r generate_it/ -f json -o bandit-report.json -ll
- name: Upload Bandit report
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
if: always()
with:
name: bandit-report
path: bandit-report.json
if-no-files-found: ignore

# Dependency vulnerability scanning with pip-audit (BLOCKING: fails CI on any vulnerability).
pip-audit:
name: Dependency vulnerability scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --require-hashes -r constraints/ci.txt
python -m pip install --no-deps -e .
- name: Run pip-audit
run: pip-audit --format=json --output=pip-audit-report.json
- name: Display pip-audit report
if: always()
run: pip-audit

# Secrets detection with TruffleHog (BLOCKING: fails CI on verified secrets).
#
# POLICY: This scan runs with --only-verified, meaning only secrets that TruffleHog
# can confirm as valid (by attempting authentication against the provider) will cause
# a CI failure. Unverified matches (heuristic-only) are suppressed. This balances
# security with signal-to-noise: verified secrets are a real breach and must block
# the pipeline; unverified matches are informational and can be reviewed in the job
# log without blocking merges. To audit for unverified findings, re-run locally:
# trufflehog git file://. --since-commit=main --no-verification
# and review the results manually as part of the pre-commit or PR review process.
secrets-scan:
name: Secrets detection
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5
with:
fetch-depth: 0
persist-credentials: false
- name: Run TruffleHog secret scanner
uses: trufflesecurity/trufflehog@27b0417c16317ca9a472a9a8092acce143b49c55 # v3.95.9
with:
path: ./
base: main
head: HEAD
extra_args: --only-verified

# Static type checking with mypy (blocking).
typecheck:
name: Static type check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5
with:
fetch-depth: 0
persist-credentials: false
- name: Check for trailing whitespace violations
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
git diff --check "${{ github.event.pull_request.base.sha }}...${{ github.sha }}"
elif [ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ]; then
git diff --check "$(git hash-object -t tree /dev/null)..${{ github.sha }}"
else
git diff --check "${{ github.event.before }}...${{ github.sha }}"
fi
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --require-hashes -r constraints/ci.txt
python -m pip install --no-deps -e .
- name: Run mypy type checker
run: mypy generate_it

# Test suite with pytest.
test:
name: Test suite
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10", "3.12", "3.13"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: ${{ matrix.python-version }}
- name: Install Unix dependencies
if: matrix.os != 'windows-latest'
run: python -m pip install --require-hashes -r constraints/ci.txt
- name: Install Windows dependencies
if: matrix.os == 'windows-latest'
run: python -m pip install --require-hashes -r constraints/ci-windows.txt
- name: Install package
run: python -m pip install --no-deps -e .
- name: Run tests
run: pytest tests/ -v --cov=generate_it --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
with:
file: ./coverage.xml
fail_ci_if_error: false

# Package build and validation.
build:
name: Package validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.12"
- name: Install build tools
run: |
python -m pip install --require-hashes -r constraints/release.txt
python -m pip install --no-deps -e .
- name: Build package
run: |
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) python -m build --no-isolation
- name: Validate package
run: |
python -m twine check dist/*
- name: Smoke-test built wheel
run: |
python -m venv /tmp/generate-it-wheel-smoke
/tmp/generate-it-wheel-smoke/bin/python -m pip install --require-hashes -r constraints/ci.txt
/tmp/generate-it-wheel-smoke/bin/python -m pip install --no-deps dist/*.whl
cd /tmp
/tmp/generate-it-wheel-smoke/bin/python -c "import generate_it, generate_it.storage, generate_it.tui, generate_it.__main__; from importlib.resources import files; path = str(files('generate_it').joinpath('wordlist.txt')); assert 'site-packages' in generate_it.__file__; assert path.endswith('wordlist.txt'); print(generate_it.__file__)"
- name: Upload build artifacts
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: dist
path: dist/
Loading
Loading