diff --git a/.github/workflows/auto-merge-deps.yml b/.github/workflows/auto-merge-deps.yml index bccf7ca..5066a75 100644 --- a/.github/workflows/auto-merge-deps.yml +++ b/.github/workflows/auto-merge-deps.yml @@ -1,8 +1,10 @@ name: Auto-merge dependency PRs + on: - pull_request_target: - types: [opened, synchronize, reopened] + pull_request: + permissions: {} + jobs: auto-merge: uses: netresearch/typo3-ci-workflows/.github/workflows/auto-merge-deps.yml@main diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5fc762..fef80d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,78 @@ name: CI + on: push: pull_request: + schedule: + - cron: '0 6 * * 1' + permissions: {} + jobs: ci: uses: netresearch/typo3-ci-workflows/.github/workflows/ci.yml@main permissions: contents: read with: - php-versions: '["8.2", "8.3", "8.4", "8.5"]' - typo3-versions: '["^13.4", "^14.0"]' - typo3-packages: '["typo3/cms-core", "typo3/cms-backend"]' - php-extensions: intl, mbstring, xml, sodium, json - run-functional-tests: true - upload-coverage: true + php-versions: '["8.2","8.3","8.4","8.5"]' + typo3-versions: '["^13.4","^14.0"]' + run-functional-tests: true + upload-coverage: true + php-extensions: intl, mbstring, xml, sodium, json secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + security: + uses: netresearch/typo3-ci-workflows/.github/workflows/security.yml@main + permissions: + contents: read + security-events: write + secrets: + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} + + fuzz: + uses: netresearch/typo3-ci-workflows/.github/workflows/fuzz.yml@main + permissions: + contents: read + + license-check: + uses: netresearch/typo3-ci-workflows/.github/workflows/license-check.yml@main + permissions: + contents: read + + codeql: + uses: netresearch/typo3-ci-workflows/.github/workflows/codeql.yml@main + permissions: + contents: read + security-events: write + actions: read + + scorecard: + if: github.event_name == 'schedule' || (github.event_name == 'push' && github.ref_name == github.event.repository.default_branch) + uses: netresearch/typo3-ci-workflows/.github/workflows/scorecard.yml@main + permissions: + contents: read + security-events: write + id-token: write + actions: read + + dependency-review: + if: github.event_name == 'pull_request' + uses: netresearch/typo3-ci-workflows/.github/workflows/dependency-review.yml@main + permissions: + contents: read + pull-requests: write + + pr-quality: + if: github.event_name == 'pull_request' + uses: netresearch/typo3-ci-workflows/.github/workflows/pr-quality.yml@main + permissions: + contents: read + pull-requests: write + + labeler: + if: github.event_name == 'pull_request' + uses: netresearch/typo3-ci-workflows/.github/workflows/labeler.yml@main + permissions: + contents: read + pull-requests: write diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 250554a..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: CodeQL -on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - - cron: '0 6 * * 1' -permissions: {} -jobs: - analyze: - uses: netresearch/typo3-ci-workflows/.github/workflows/codeql.yml@main - permissions: - contents: read - security-events: write - actions: read diff --git a/.github/workflows/community.yml b/.github/workflows/community.yml new file mode 100644 index 0000000..511f0f7 --- /dev/null +++ b/.github/workflows/community.yml @@ -0,0 +1,34 @@ +name: Community + +on: + schedule: + - cron: '0 0 * * *' + issues: + types: [opened] + pull_request_target: + types: [opened] + workflow_dispatch: + +permissions: {} + +jobs: + stale: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + uses: netresearch/typo3-ci-workflows/.github/workflows/stale.yml@main + permissions: + issues: write + pull-requests: write + + lock: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + uses: netresearch/typo3-ci-workflows/.github/workflows/lock.yml@main + permissions: + issues: write + pull-requests: write + + greetings: + if: github.event_name == 'issues' || github.event_name == 'pull_request_target' + uses: netresearch/typo3-ci-workflows/.github/workflows/greetings.yml@main + permissions: + issues: write + pull-requests: write diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml deleted file mode 100644 index dc30377..0000000 --- a/.github/workflows/dependency-review.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Dependency Review -on: - pull_request: -permissions: {} -jobs: - review: - uses: netresearch/typo3-ci-workflows/.github/workflows/dependency-review.yml@main - permissions: - contents: read - pull-requests: write diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml deleted file mode 100644 index b508eab..0000000 --- a/.github/workflows/fuzz.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Fuzzing - -on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - # Run weekly on Sunday at 3:00 AM UTC - - cron: '0 3 * * 0' - -# Restrict default permissions - jobs declare what they need -permissions: {} - -jobs: - fuzz-tests: - name: Fuzz Tests - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Harden Runner - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Setup PHP - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0 - with: - php-version: '8.2' - extensions: sodium, json, pdo, pdo_sqlite - tools: composer:v2 - - - name: Get Composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache Composer dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-fuzz-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer-fuzz- - - - name: Install dependencies - run: composer install --no-progress --prefer-dist - - - name: Run Fuzz Tests - run: .Build/bin/phpunit -c Build/phpunit.xml --testsuite Fuzz --colors=never - - mutation-testing: - name: Mutation Testing (Infection) - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Harden Runner - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Setup PHP - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0 - with: - php-version: '8.2' - extensions: sodium, json, pdo, pdo_sqlite - tools: composer:v2 - coverage: xdebug - - - name: Get Composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache Composer dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-mutation-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer-mutation- - - - name: Install dependencies - run: composer install --no-progress --prefer-dist - - - name: Run Mutation Tests - # Thresholds set to achievable levels; raise incrementally as coverage improves - run: composer ci:test:php:mutation -- --min-msi=50 --min-covered-msi=60 - continue-on-error: true diff --git a/.github/workflows/pr-quality.yml b/.github/workflows/pr-quality.yml deleted file mode 100644 index 747a8be..0000000 --- a/.github/workflows/pr-quality.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: PR Quality Gates - -on: - pull_request: - branches: [main] - types: [opened, synchronize, reopened, ready_for_review] - -# Restrict default permissions - jobs declare what they need -permissions: {} - -jobs: - quality-gate: - name: Quality Gate - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - if: github.event.pull_request.draft == false - - steps: - - name: Harden Runner - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: PR Size Check - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const { data: files } = await github.rest.pulls.listFiles({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }); - const total = files.reduce((sum, f) => sum + f.additions + f.deletions, 0); - console.log(`PR Size: ${total} lines changed`); - if (total > 1000) { - core.warning(`Large PR with ${total} changes. Consider breaking into smaller PRs.`); - } - - auto-approve: - name: Auto-Approve (Solo Maintainer) - runs-on: ubuntu-latest - permissions: - pull-requests: write - needs: quality-gate - if: github.event.pull_request.draft == false - - steps: - - name: Harden Runner - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 - with: - egress-policy: audit - - - name: Auto-approve PR - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - await github.rest.pulls.createReview({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - event: 'APPROVE', - body: '**Automated approval for solo maintainer project**\n\nAll CI checks passed. See SECURITY.md for compensating controls.' - }); diff --git a/.github/workflows/publish-to-ter.yml b/.github/workflows/publish-to-ter.yml deleted file mode 100644 index 4aa0c78..0000000 --- a/.github/workflows/publish-to-ter.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Publish to TER -on: - release: - types: [published] -permissions: {} -jobs: - publish: - uses: netresearch/typo3-ci-workflows/.github/workflows/publish-to-ter.yml@main - permissions: - contents: read - secrets: - TYPO3_EXTENSION_KEY: ${{ secrets.TYPO3_EXTENSION_KEY }} - TYPO3_TER_ACCESS_TOKEN: ${{ secrets.TYPO3_TER_ACCESS_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ae49c4..742508b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,156 +5,33 @@ on: tags: - 'v*' -# Restrict default permissions - jobs declare what they need permissions: {} jobs: release: - name: Create Release - runs-on: ubuntu-latest + uses: netresearch/typo3-ci-workflows/.github/workflows/release.yml@main permissions: contents: write id-token: write attestations: write - outputs: - version: ${{ steps.version.outputs.version }} - steps: - - name: Harden Runner - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 - with: - egress-policy: audit + with: + archive-prefix: nr-vault + package-name: netresearch/nr-vault - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - - name: Get version from tag - id: version - run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - - - name: Setup PHP - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0 - with: - php-version: '8.5' - extensions: sodium, json - tools: composer:v2 - - - name: Install Cosign - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - - - name: Install dependencies - run: composer install --no-dev --no-progress --prefer-dist --optimize-autoloader - - - name: Create release archive - run: | - # Create clean archive without dev files - mkdir -p dist - tar --exclude='.git' \ - --exclude='.github' \ - --exclude='.ddev' \ - --exclude='.Build' \ - --exclude='Build' \ - --exclude='Tests' \ - --exclude='dist' \ - --exclude='.php-cs-fixer.dist.php' \ - --exclude='phpstan.neon' \ - --exclude='phpat.neon' \ - --exclude='infection.json5' \ - --exclude='rector.php' \ - -czvf dist/nr-vault-${{ steps.version.outputs.version }}.tar.gz . - - # Create zip as well - zip -r dist/nr-vault-${{ steps.version.outputs.version }}.zip . \ - -x '.git/*' \ - -x '.github/*' \ - -x '.ddev/*' \ - -x '.Build/*' \ - -x 'Build/*' \ - -x 'Tests/*' \ - -x 'dist/*' \ - -x '.php-cs-fixer.dist.php' \ - -x 'phpstan.neon' \ - -x 'phpat.neon' \ - -x 'infection.json5' \ - -x 'rector.php' - - - name: Generate SBOM - uses: anchore/sbom-action@17ae1740179002c89186b61233e0f892c3118b11 # v0.23.0 - with: - artifact-name: sbom-${{ steps.version.outputs.version }}.spdx.json - output-file: dist/sbom-${{ steps.version.outputs.version }}.spdx.json - format: spdx-json - - - name: Generate checksums - run: | - cd dist - sha256sum * > checksums-${{ steps.version.outputs.version }}.txt - - - name: Sign artifacts with Cosign (keyless) - run: | - cd dist - for file in *.tar.gz *.zip *.spdx.json checksums-*.txt; do - cosign sign-blob --yes "$file" --bundle "${file}.bundle" - done - - - name: Generate build provenance attestation - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 - with: - subject-path: dist/nr-vault-${{ steps.version.outputs.version }}.tar.gz - - - name: Generate build provenance attestation (zip) - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 - with: - subject-path: dist/nr-vault-${{ steps.version.outputs.version }}.zip - - - name: Create GitHub Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 - with: - generate_release_notes: true - files: | - dist/*.tar.gz - dist/*.zip - dist/*.spdx.json - dist/checksums-*.txt - dist/*.bundle - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - ter: - name: Upload to TER - needs: release - runs-on: ubuntu-latest + publish-to-ter: + uses: netresearch/typo3-ci-workflows/.github/workflows/publish-to-ter.yml@main permissions: contents: read - if: ${{ !contains(github.ref_name, '-') }} # Skip pre-releases (e.g., v0.1.0-beta) - - steps: - - name: Harden Runner - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Setup PHP - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0 - with: - php-version: '8.2' + secrets: + TYPO3_EXTENSION_KEY: ${{ secrets.TYPO3_EXTENSION_KEY }} + TYPO3_TER_ACCESS_TOKEN: ${{ secrets.TYPO3_TER_ACCESS_TOKEN }} - - name: Install tailor - run: composer global require typo3/tailor --no-progress - - - name: Upload to TER - env: - TYPO3_API_TOKEN: ${{ secrets.TYPO3_TER_ACCESS_TOKEN }} - VERSION: ${{ needs.release.outputs.version }} - run: | - # Remove dev files before packaging (tailor will create the archive) - rm -rf .git .github Tests Build .Build .ddev .php-cs-fixer* phpstan* phpunit* grumphp* rector* infection* Makefile *.dist - # Strip 'v' prefix from version for TER - TER_VERSION="${VERSION#v}" - # Publish to TER (tailor creates the package from current directory) - ~/.composer/vendor/bin/tailor ter:publish "$TER_VERSION" \ - --comment="Release $VERSION" + slsa-provenance: + needs: release + uses: netresearch/typo3-ci-workflows/.github/workflows/slsa-provenance.yml@main + permissions: + actions: read + contents: write + id-token: write + with: + version: ${{ github.ref_name }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml deleted file mode 100644 index 454795d..0000000 --- a/.github/workflows/scorecard.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: OpenSSF Scorecard -on: - push: - branches: [main] - schedule: - - cron: '25 6 * * 1' -permissions: {} -jobs: - scorecard: - uses: netresearch/typo3-ci-workflows/.github/workflows/scorecard.yml@main - permissions: - contents: read - security-events: write - id-token: write - actions: read diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml deleted file mode 100644 index fa078bd..0000000 --- a/.github/workflows/security.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Security - -on: - push: - branches: - - main - pull_request: - branches: - - main - -permissions: - contents: read - -jobs: - # Note: CodeQL does not support PHP. Using PHPStan for static analysis in tests workflow. - # OpenSSF Scorecard is handled by the dedicated scorecard.yml workflow. - # Dependency review handled by dependency-review.yml (centralized workflow). - - security-audit: - name: Security Audit - runs-on: ubuntu-latest - steps: - - name: Harden Runner - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 - with: - egress-policy: audit - - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Setup PHP - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0 - with: - php-version: '8.2' - extensions: sodium, json - tools: composer:v2 - - - name: Install dependencies - run: composer install --no-progress --prefer-dist - - - name: Check for vulnerable dependencies - run: composer audit --format=plain diff --git a/.github/workflows/slsa-provenance.yml b/.github/workflows/slsa-provenance.yml deleted file mode 100644 index 27b717e..0000000 --- a/.github/workflows/slsa-provenance.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: SLSA Provenance - -on: - workflow_run: - workflows: ["Release"] - types: [completed] - workflow_dispatch: - inputs: - version: - description: 'Release version (e.g., v0.3.1)' - required: true - type: string - -# Restrict default permissions - jobs declare what they need -permissions: {} - -jobs: - provenance: - name: Generate SLSA Provenance - runs-on: ubuntu-latest - if: (github.event_name == 'workflow_dispatch') || (github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'v')) - permissions: - actions: read - id-token: write - contents: write - outputs: - subjects: ${{ steps.subjects.outputs.subjects }} - has_subjects: ${{ steps.subjects.outputs.has_subjects }} - version: ${{ steps.version.outputs.version }} - steps: - - name: Harden Runner - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 - with: - egress-policy: audit - - - name: Get version - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - VERSION="${{ inputs.version }}" - else - VERSION="${{ github.event.workflow_run.head_branch }}" - fi - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "Processing release: $VERSION" - - - name: Download release assets - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - mkdir -p release-assets - # Wait a moment for release to be fully available - sleep 5 - gh release download "${{ steps.version.outputs.version }}" \ - --repo "${{ github.repository }}" \ - -D release-assets \ - --pattern "*.tar.gz" \ - --pattern "*.zip" || echo "Some assets may not be available" - ls -la release-assets/ - - - name: Generate provenance subjects - id: subjects - run: | - cd release-assets - if ls *.tar.gz *.zip 1> /dev/null 2>&1; then - # sha256sum output format: HASH FILENAME (two spaces) - sha256sum *.tar.gz *.zip > ../provenance-subjects.txt - cat ../provenance-subjects.txt - # base64-subjects expects sha256sum format, not JSON - SUBJECTS_BASE64=$(cat ../provenance-subjects.txt | base64 -w0) - echo "subjects=$SUBJECTS_BASE64" >> "$GITHUB_OUTPUT" - echo "has_subjects=true" >> "$GITHUB_OUTPUT" - else - echo "No release archives found" - echo "has_subjects=false" >> "$GITHUB_OUTPUT" - fi - - slsa-provenance: - name: SLSA Level 3 Provenance - needs: provenance - if: needs.provenance.outputs.has_subjects == 'true' - permissions: - actions: read - id-token: write - contents: write - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0 - with: - base64-subjects: ${{ needs.provenance.outputs.subjects }} - upload-assets: true - upload-tag-name: ${{ needs.provenance.outputs.version }} - # Compile from source to avoid binary fetch issues - compile-generator: true - # Workaround: generator incorrectly detects public repo as private - # Safe for public repos - just allows posting to Rekor transparency log - private-repository: true