Skip to content

security: add permissions blocks to CI workflows #56

security: add permissions blocks to CI workflows

security: add permissions blocks to CI workflows #56

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
env:
# Each job detects Python locally to handle multi-runner setups
FIND_PY: |
for candidate in python3.12 python3.13 python3; do
if command -v "$candidate" >/dev/null 2>&1; then
version=$("$candidate" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
major=$(echo "$version" | cut -d. -f1)
minor=$(echo "$version" | cut -d. -f2)
if [ "$major" -ge 3 ] && [ "$minor" -ge 12 ]; then
PY="$candidate"
echo "PY=$candidate" >> "$GITHUB_ENV"
echo "Found $candidate ($version)"
break
fi
fi
done
if [ -z "${PY:-}" ]; then
echo "::error::No Python 3.12+ found on this runner"
exit 1
fi
jobs:
lint:
runs-on: [self-hosted, Linux]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Detect Python 3.12+
run: eval "$FIND_PY"
- name: Lint with ruff
run: $PY -m ruff check src/ tests/
- name: Check formatting
run: $PY -m ruff format --check src/ tests/
test:
runs-on: [self-hosted, Linux]
needs: lint
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Detect Python 3.12+
run: eval "$FIND_PY"
- name: Install and test
run: |
$PY -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install -e ".[test]"
.venv/bin/python -m pytest tests/ -v --tb=short --cov=project_forge --cov-fail-under=40 --cov-report=term-missing
build:
runs-on: [self-hosted, Linux]
needs: test
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Detect Python 3.12+
run: eval "$FIND_PY"
- name: Build and verify wheel
run: |
$PY -m venv .build-venv
.build-venv/bin/pip install --upgrade pip
.build-venv/bin/pip install build
.build-venv/bin/python -m build --outdir dist/
WHEEL=$(ls dist/*.whl)
echo "Built: $WHEEL ($(stat -c%s "$WHEEL") bytes)"
$PY -c "
import zipfile, sys
with zipfile.ZipFile('$WHEEL') as zf:
names = zf.namelist()
required = ['project_forge/models.py', 'project_forge/web/', 'templates/', 'static/']
for r in required:
if not any(r in n for n in names):
print(f'MISSING: {r}'); sys.exit(1)
print(f'FOUND: {r}')
print(f'Wheel OK: {len(names)} files')
"
- name: Test install in clean venv
run: |
$PY -m venv /tmp/forge-install-test
/tmp/forge-install-test/bin/pip install --upgrade pip
/tmp/forge-install-test/bin/pip install dist/*.whl
/tmp/forge-install-test/bin/python -c "import project_forge; print('OK:', project_forge.__file__)"
test -f /tmp/forge-install-test/bin/forge-generate && echo "PASS: forge-generate"
test -f /tmp/forge-install-test/bin/forge-serve && echo "PASS: forge-serve"
test -f /tmp/forge-install-test/bin/forge-self-improve && echo "PASS: forge-self-improve"
rm -rf /tmp/forge-install-test
security:
runs-on: [self-hosted, Linux]
needs: lint
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Detect Python 3.12+
run: eval "$FIND_PY"
- name: Security lint
run: $PY -m ruff check src/ --select S --ignore S101,S311,S314,S603,S607,S701
- name: Dependency audit (pip-audit)
run: |
$PY -m venv .audit-venv
.audit-venv/bin/pip install --upgrade pip
.audit-venv/bin/pip install -e ".[dev]"
.audit-venv/bin/pip-audit --strict --progress-spinner=off || echo "::warning::pip-audit found vulnerabilities"
rm -rf .audit-venv
issue-test-ratio:
runs-on: [self-hosted, Linux]
needs: test
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Detect Python 3.12+
run: eval "$FIND_PY"
- name: Check issue-to-test ratio
env:
GH_REPO: rayketcham-lab/project-forge
run: |
ISSUE_COUNT=$(gh issue list -R "$GH_REPO" --state all --label feature --json number -q 'length' 2>/dev/null || echo 0)
TEST_COUNT=$($PY -m pytest tests/ --collect-only -q 2>/dev/null | tail -1 | grep -oP '\d+(?= test)' || echo 0)
echo "Feature issues: $ISSUE_COUNT, Tests: $TEST_COUNT"
if [ "$TEST_COUNT" -lt "$ISSUE_COUNT" ]; then
echo "::warning::Test count ($TEST_COUNT) is below feature issue count ($ISSUE_COUNT)"
fi
- name: Detect untested source modules
run: |
echo "=== Gap Detection: checking for untested source modules ==="
UNTESTED=""
for src in $(find src/project_forge -name '*.py' ! -name '__init__.py' ! -path '*/templates/*' ! -path '*/static/*'); do
MODULE=$(basename "$src" .py)
if ! find tests/ -name "*.py" -exec grep -l "$MODULE" {} + >/dev/null 2>&1; then
UNTESTED="$UNTESTED $MODULE"
echo "::warning::No test file references module: $MODULE ($src)"
fi
done
if [ -n "$UNTESTED" ]; then
echo "Untested modules:$UNTESTED"
else
echo "All source modules have corresponding test coverage."
fi
self-improvement-queue:
runs-on: [self-hosted, Linux]
needs: [test]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Check for open ci-queue items
env:
GH_REPO: rayketcham-lab/project-forge
run: |
set -euo pipefail
QUEUE=$(gh issue list -R "$GH_REPO" --state open --label ci-queue --json number,title -q '.[] | "#\(.number) \(.title)"' 2>/dev/null || true)
COUNT=$(echo "$QUEUE" | grep -c '^#' || true)
echo "Open ci-queue items: $COUNT"
if [ "$COUNT" -gt 0 ]; then
echo ""
echo "=== Self-improvement items awaiting action ==="
echo "$QUEUE"
echo ""
echo "::warning::$COUNT open self-improvement item(s) in the ci-queue."
else
echo "No pending self-improvement items. Queue is clear."
fi