Skip to content
Merged
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
21 changes: 16 additions & 5 deletions .github/verify_badges.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@

import requests

# Trusted badge hostnames (scheme must be https)
_TRUSTED_HOSTS = frozenset({"github.com", "img.shields.io", "codecov.io"})


def _is_trusted_host(url: str, hostname: str) -> bool:
"""Validate that a URL uses HTTPS and matches an exact trusted hostname."""
parsed = urlparse(url)
return parsed.scheme == "https" and parsed.hostname == hostname and hostname in _TRUSTED_HOSTS


class BadgeVerifier:
"""Verifies GitHub badges and provides status reports."""
Expand Down Expand Up @@ -47,8 +56,9 @@ def check_badge(self, name: str, url: str, expected_status: int = 200) -> dict:
}

# Additional checks for GitHub Actions badges
parsed = urlparse(url)
if parsed.hostname == "github.com" and "actions/workflows" in parsed.path:
if _is_trusted_host(url, "github.com") and urlparse(url).path.startswith(
"/docdyhr/versiontracker/actions/workflows/"
):
# These might return different status codes when no runs exist
result["success"] = status in [200, 404] # 404 is OK for new workflows

Expand Down Expand Up @@ -254,10 +264,11 @@ def print_summary(self) -> bool:
github_actions = [
r
for r in self.results
if urlparse(r["url"]).hostname == "github.com" and "/actions" in urlparse(r["url"]).path
if _is_trusted_host(r["url"], "github.com")
and urlparse(r["url"]).path.startswith("/docdyhr/versiontracker/actions/")
]
shields_io = [r for r in self.results if urlparse(r["url"]).hostname == "img.shields.io"]
codecov = [r for r in self.results if urlparse(r["url"]).hostname == "codecov.io"]
shields_io = [r for r in self.results if _is_trusted_host(r["url"], "img.shields.io")]
codecov = [r for r in self.results if _is_trusted_host(r["url"], "codecov.io")]

print("\n📋 BADGE CATEGORIES:")
print("-" * 40)
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
test:
name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -136,6 +138,8 @@ jobs:
name: Build Package
runs-on: ubuntu-latest
needs: [test]
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v6
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
lint:
name: Code Linting and Formatting
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v6
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
smoke:
name: Performance Smoke Check
runs-on: ubuntu-latest
permissions: {}
if: github.event_name == 'pull_request' || github.event_name == 'push'
steps:
- name: No-op performance status
Expand All @@ -42,6 +43,8 @@ jobs:
performance-test:
name: Performance Testing on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
# Only run full performance suite on schedule or manual dispatch
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
strategy:
Expand Down Expand Up @@ -116,6 +119,7 @@ jobs:
name: Performance Analysis
runs-on: ubuntu-latest
needs: performance-test
permissions: {}
# Only analyze when full performance-test runs
if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && always()

Expand All @@ -141,6 +145,7 @@ jobs:
name: Performance Testing
runs-on: ubuntu-latest
needs: [performance-test, performance-analysis]
permissions: {}
# Only report final status when full suite runs
if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && always()

Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
validate-release:
name: Validate Release
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
version: ${{ steps.version.outputs.version }}
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
Expand Down Expand Up @@ -85,6 +87,8 @@ jobs:
name: Pre-Release Quality Checks
runs-on: ubuntu-latest
needs: validate-release
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v6
Expand Down Expand Up @@ -203,6 +207,8 @@ jobs:
name: Build and Test Package
runs-on: ${{ matrix.os }}
needs: [validate-release, pre-release-checks]
permissions:
contents: read
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
Expand Down Expand Up @@ -343,6 +349,7 @@ jobs:
name: Verify Release
runs-on: ubuntu-latest
needs: [validate-release, publish-package]
permissions: {}
if: always() && needs.publish-package.result == 'success'
steps:
- name: Set up Python
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:
security:
name: Security Analysis
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v6
Expand Down
Loading