From e876b6cc3e8cd1ce0a9dfa7f2ccf73cd9945a794 Mon Sep 17 00:00:00 2001 From: Gerald Fruhmann Date: Wed, 10 Jun 2026 21:39:59 +0200 Subject: [PATCH] security: harden repository security to match PulseBase setup - Pin all GitHub Actions to commit digests - Add permissions: contents: read at workflow level - Add concurrency with cancel-in-progress - Add check-pr-size job (400 LOC limit, excludes lockfiles/CHANGELOG) - Move gitleaks into dedicated security job - Add ci-ok gate job as single required status check - Add pre-commit-hooks (trailing-whitespace, check-yaml/json/toml, check-merge-conflict, large-files, no-commit-to-branch) - Add detect-secrets hook with .secrets.baseline - Add .github/pull_request_template.md for docs/plugin workflow Co-Authored-By: Claude Sonnet 4.6 --- .github/pull_request_template.md | 18 +++++++ .github/workflows/ci.yml | 80 +++++++++++++++++++++++++++----- .pre-commit-config.yaml | 29 ++++++++++++ .secrets.baseline | 43 +++++++++++++++++ 4 files changed, 158 insertions(+), 12 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 .secrets.baseline diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..049c69c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +## What does this PR change? + + + +## Type of change + +- [ ] New skill +- [ ] Skill update / bugfix +- [ ] Rule change (`claude/` or `reference/`) +- [ ] Docs / Config +- [ ] Meta (`plugin.json`, `commands/`, `validate-skills.sh`) + +## Checklist + +- [ ] `pre-commit run --all-files` passes +- [ ] `bash scripts/validate-skills.sh` passes +- [ ] Mirror up to date: `cp claude/*.md plugins/dev/rules/` run (for rule changes) +- [ ] `CHANGELOG.md` updated (for new skills or breaking changes) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4059f80..aac5015 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,50 +4,106 @@ on: push: branches: [master] pull_request: - branches: [master] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +permissions: + contents: read jobs: + check-pr-size: + name: PR Size Check + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + - name: Fail if PR adds more than 400 lines + env: + BASE_REF: ${{ github.base_ref }} + run: | + ADDED=$(git diff "origin/$BASE_REF"...HEAD \ + -- . ':(exclude)*.lock' ':(exclude)*-lock.json' \ + ':(exclude)CHANGELOG.md' \ + | grep -cE '^\+[^+]' || echo 0) + echo "Lines added (excl. lockfiles/changelog): $ADDED" + if [ "$ADDED" -gt 400 ]; then + echo "::error::PR too large (${ADDED} lines added, limit: 400) — please split." + exit 1 + fi + lint: + name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Markdown Lint - uses: DavidAnson/markdownlint-cli2-action@v16 + uses: DavidAnson/markdownlint-cli2-action@b4c9feab76d8025d1e83c653fa3990936df0e6c8 # v16 with: globs: "**/*.md" - name: Link Check - uses: lycheeverse/lychee-action@v2 + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2 with: - args: --no-progress --exclude-loopback --exclude 'report-uri\.com' --exclude 'github\.com/.*/compare/' --exclude 'github\.com/.*/releases/tag/' --exclude 'github\.com/.*/actions/' --exclude 'pdos\.csail\.mit\.edu' --exclude 'css\.csail\.mit\.edu' '**/*.md' + args: >- + --no-progress --exclude-loopback + --exclude 'report-uri\.com' + --exclude 'github\.com/.*/compare/' + --exclude 'github\.com/.*/releases/tag/' + --exclude 'github\.com/.*/actions/' + --exclude 'pdos\.csail\.mit\.edu' + --exclude 'css\.csail\.mit\.edu' + '**/*.md' fail: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} validate-skills: + name: Validate Skills runs-on: ubuntu-latest needs: lint steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Validate plugin skills run: bash scripts/validate-skills.sh - name: Check rule mirror sync run: | - diff -r claude/ plugins/dev/rules/ && echo "✅ Mirror in sync" || { - echo "❌ plugins/dev/rules/ is out of sync with claude/ — run: cp claude/*.md plugins/dev/rules/" + diff -r claude/ plugins/dev/rules/ && echo "Mirror in sync" || { + echo "plugins/dev/rules/ is out of sync with claude/ — run: cp claude/*.md plugins/dev/rules/" exit 1 } - secrets: + security: + name: Security runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 - - - uses: gitleaks/gitleaks-action@v2 + - name: gitleaks + uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ci-ok: + name: CI OK (All Green Gate) + runs-on: ubuntu-latest + if: always() + needs: [check-pr-size, lint, validate-skills, security] + steps: + - name: All checks passed + run: | + if echo '${{ toJSON(needs.*.result) }}' | grep -qE '"failure"|"cancelled"'; then + echo "One or more required jobs failed or were cancelled." + exit 1 + fi + echo "All CI jobs passed." diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af664ef..989a58f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,38 @@ repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.27.2 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-json + - id: check-yaml + - id: check-toml + - id: check-merge-conflict + - id: check-added-large-files + args: [--maxkb=500] + - id: no-commit-to-branch + args: [--branch, master] + - repo: https://github.com/DavidAnson/markdownlint-cli2 rev: v0.17.2 hooks: - id: markdownlint-cli2 + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets + args: + - --baseline + - .secrets.baseline + - --exclude-files + - (^|/)(pnpm-lock\.yaml|package-lock\.json|yarn\.lock|uv\.lock)$ + - repo: local hooks: - id: validate-skills diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 0000000..949cb74 --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,43 @@ +{ + "version": "1.5.0", + "plugins_used": [ + {"name": "ArtifactoryDetector"}, + {"name": "AWSKeyDetector"}, + {"name": "AzureStorageKeyDetector"}, + {"name": "Base64HighEntropyString", "limit": 4.5}, + {"name": "BasicAuthDetector"}, + {"name": "CloudantDetector"}, + {"name": "DiscordBotTokenDetector"}, + {"name": "GitHubTokenDetector"}, + {"name": "HexHighEntropyString", "limit": 3.0}, + {"name": "IbmCloudIamDetector"}, + {"name": "IbmCosHmacDetector"}, + {"name": "JwtTokenDetector"}, + {"name": "KeywordDetector", "keyword_exclude": ""}, + {"name": "MailchimpDetector"}, + {"name": "NpmDetector"}, + {"name": "PrivateKeyDetector"}, + {"name": "SendGridDetector"}, + {"name": "SlackDetector"}, + {"name": "SoftlayerDetector"}, + {"name": "SquareOAuthDetector"}, + {"name": "StripeDetector"}, + {"name": "TwilioKeyDetector"} + ], + "filters_used": [ + {"path": "detect_secrets.filters.allowlist.is_line_allowlisted"}, + {"path": "detect_secrets.filters.common.is_baseline_file", "filename": ".secrets.baseline"}, + {"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", "min_level": 2}, + {"path": "detect_secrets.filters.heuristic.is_indirect_reference"}, + {"path": "detect_secrets.filters.heuristic.is_likely_id_secret"}, + {"path": "detect_secrets.filters.heuristic.is_lock_file"}, + {"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"}, + {"path": "detect_secrets.filters.heuristic.is_potential_uuid"}, + {"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"}, + {"path": "detect_secrets.filters.heuristic.is_sequential_string"}, + {"path": "detect_secrets.filters.heuristic.is_swagger_file"}, + {"path": "detect_secrets.filters.heuristic.is_templated_secret"} + ], + "results": {}, + "generated_at": "2026-06-10T00:00:00Z" +}