diff --git a/.github/workflows/agent-shield.yml b/.github/workflows/agent-shield.yml index 8704981d..3bbd6375 100644 --- a/.github/workflows/agent-shield.yml +++ b/.github/workflows/agent-shield.yml @@ -30,4 +30,4 @@ permissions: jobs: agent-shield: - uses: petry-projects/.github/.github/workflows/agent-shield-reusable.yml@v1 + uses: petry-projects/.github/.github/workflows/agent-shield-reusable.yml@ae9709f4466dec60a5733c9e7487f69dcd004e05 # v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 785b7c8d..f5358a1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,3 +95,34 @@ jobs: - name: Run AgentShield run: | npx ecc-agentshield scan --path . --format json --min-severity high + + secret-scan: + name: Secret scan (gitleaks) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout (full history) + # Pin to SHA per Action Pinning Policy (ci-standards.md#action-pinning-policy). + # Look up current SHA: gh api repos/actions/checkout/git/refs/tags/v4 --jq '.object.sha' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Install gitleaks + # Install gitleaks CLI via verified download (pinned version + checksum). + # Using the CLI directly avoids the gitleaks-action org license requirement. + # To update: gh api repos/gitleaks/gitleaks/releases/assets/ -H "Accept: application/octet-stream" | grep linux_x64 + # Checksum source: gitleaks_8.24.0_checksums.txt (release asset ID 230829272) + run: | + GITLEAKS_VERSION="8.24.0" + GITLEAKS_SHA="cb49b7de5ee986510fe8666ca0273a6cc15eb82571f2f14832c9e8920751f3a4" + curl -sLo gitleaks.tar.gz \ + "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + echo "${GITLEAKS_SHA} gitleaks.tar.gz" | sha256sum -c - + tar xzf gitleaks.tar.gz gitleaks + chmod +x gitleaks + rm gitleaks.tar.gz + + - name: Run gitleaks + run: ./gitleaks detect --source . --redact --verbose --exit-code 1 diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 8f7c686d..4eb6053f 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -20,7 +20,7 @@ permissions: {} jobs: claude-code: - uses: petry-projects/.github/.github/workflows/claude-code-reusable.yml@main + uses: petry-projects/.github/.github/workflows/claude-code-reusable.yml@ae9709f4466dec60a5733c9e7487f69dcd004e05 # v1 secrets: inherit permissions: contents: write diff --git a/.github/workflows/dependency-audit.yml b/.github/workflows/dependency-audit.yml index a8cc76d9..bc764198 100644 --- a/.github/workflows/dependency-audit.yml +++ b/.github/workflows/dependency-audit.yml @@ -1,14 +1,22 @@ -# Dependency vulnerability audit. -# Detects ecosystems and runs appropriate audit tools (npm, pnpm, Go, Rust, Python). -# Standard: https://github.com/petry-projects/.github/blob/main/standards/dependabot-policy.md#vulnerability-audit-ci-check +# ───────────────────────────────────────────────────────────────────────────── +# SOURCE OF TRUTH: petry-projects/.github/standards/workflows/dependency-audit.yml +# Standard: petry-projects/.github/standards/ci-standards.md#5-dependency-audit-dependency-auditym +# Reusable: petry-projects/.github/.github/workflows/dependency-audit-reusable.yml # -# Auto-detects ecosystems present in the repository and runs the appropriate -# audit tool. Fails the build if any dependency has a known security advisory. +# AGENTS — READ BEFORE EDITING: +# • This file is a THIN CALLER STUB. All ecosystem-detection and audit logic +# lives in the reusable workflow above. +# • You MAY change: nothing in this file in normal use. Adopt verbatim. +# • You MUST NOT change: trigger events, the `uses:` line, or job name +# (used as a required status check). +# • If you need different behaviour (new ecosystem, tool version bump), +# open a PR against the reusable in the central repo. +# ───────────────────────────────────────────────────────────────────────────── # -# Add "dependency-audit" as a required status check in branch protection. -# -# Pinned tool versions (update deliberately): -# govulncheck v1.1.4 | cargo-audit 0.22.1 | pip-audit 2.9.0 +# Dependency vulnerability audit — thin caller for the org-level reusable. +# To adopt: copy this file to .github/workflows/dependency-audit.yml in your repo. +# Add "dependency-audit / Detect ecosystems" as a required status check +# in branch protection. name: Dependency audit on: @@ -21,198 +29,5 @@ permissions: contents: read jobs: - detect: - name: Detect ecosystems - runs-on: ubuntu-latest - outputs: - npm: ${{ steps.check.outputs.npm }} - pnpm: ${{ steps.check.outputs.pnpm }} - gomod: ${{ steps.check.outputs.gomod }} - cargo: ${{ steps.check.outputs.cargo }} - pip: ${{ steps.check.outputs.pip }} - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - - name: Detect package ecosystems - id: check - run: | - # npm — look for package-lock.json anywhere (excluding node_modules) - if find . -name 'package-lock.json' -not -path '*/node_modules/*' | grep -q .; then - echo "npm=true" >> "$GITHUB_OUTPUT" - else - echo "npm=false" >> "$GITHUB_OUTPUT" - fi - - # pnpm — look for pnpm-lock.yaml anywhere - if find . -name 'pnpm-lock.yaml' -not -path '*/node_modules/*' | grep -q .; then - echo "pnpm=true" >> "$GITHUB_OUTPUT" - else - echo "pnpm=false" >> "$GITHUB_OUTPUT" - fi - - # Go modules — detect via go.mod (not go.sum, which may not exist) - if find . -name 'go.mod' -not -path '*/vendor/*' | grep -q .; then - echo "gomod=true" >> "$GITHUB_OUTPUT" - else - echo "gomod=false" >> "$GITHUB_OUTPUT" - fi - - # Cargo — detect via Cargo.toml anywhere (lockfile may not exist for libraries) - if find . -name 'Cargo.toml' -not -path '*/target/*' | grep -q .; then - echo "cargo=true" >> "$GITHUB_OUTPUT" - else - echo "cargo=false" >> "$GITHUB_OUTPUT" - fi - - # Python — detect pyproject.toml or requirements.txt anywhere - if find . -name 'pyproject.toml' -not -path '*/.venv/*' -not -path '*/venv/*' | grep -q . || \ - find . -name 'requirements.txt' -not -path '*/.venv/*' -not -path '*/venv/*' | grep -q .; then - echo "pip=true" >> "$GITHUB_OUTPUT" - else - echo "pip=false" >> "$GITHUB_OUTPUT" - fi - - audit-npm: - name: npm audit - needs: detect - if: needs.detect.outputs.npm == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "lts/*" - - - name: Audit npm dependencies - run: | - # Audit each package-lock.json found in the repo - status=0 - while IFS= read -r dir; do - echo "::group::npm audit $dir" - if ! (cd "$dir" && npm audit --audit-level=low); then - status=1 - fi - echo "::endgroup::" - done < <(find . -name 'package-lock.json' -not -path '*/node_modules/*' -exec dirname {} \;) - exit $status - - audit-pnpm: - name: pnpm audit - needs: detect - if: needs.detect.outputs.pnpm == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - - uses: pnpm/action-setup@91ab88e2619ed1f46221f0ba42d1492c02baf788 # v4 - - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "lts/*" - - - name: Audit pnpm dependencies - run: | - # Audit each pnpm-lock.yaml found in the repo - status=0 - while IFS= read -r dir; do - echo "::group::pnpm audit $dir" - if ! (cd "$dir" && pnpm audit --audit-level low); then - status=1 - fi - echo "::endgroup::" - done < <(find . -name 'pnpm-lock.yaml' -not -path '*/node_modules/*' -exec dirname {} \;) - exit $status - - audit-go: - name: govulncheck - needs: detect - if: needs.detect.outputs.gomod == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v5 - with: - go-version: "stable" - - - name: Install govulncheck - run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 - - - name: Audit Go dependencies - run: | - status=0 - while IFS= read -r dir; do - echo "::group::govulncheck $dir" - if ! (cd "$dir" && govulncheck ./...); then - status=1 - fi - echo "::endgroup::" - done < <(find . -name 'go.mod' -not -path '*/vendor/*' -exec dirname {} \;) - exit $status - - audit-cargo: - name: cargo audit - needs: detect - if: needs.detect.outputs.cargo == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - - uses: dtolnay/rust-toolchain@stable - - - name: Install cargo-audit - run: cargo install cargo-audit@0.22.1 --locked - - - name: Audit Cargo dependencies - run: | - # cargo audit operates on Cargo.lock at workspace root - # For workspaces, a single audit at root covers all crates - status=0 - while IFS= read -r dir; do - echo "::group::cargo audit $dir" - if ! (cd "$dir" && cargo generate-lockfile 2>/dev/null; cargo audit); then - status=1 - fi - echo "::endgroup::" - done < <(find . -name 'Cargo.toml' -not -path '*/target/*' -exec dirname {} \; | sort -u) - exit $status - - audit-pip: - name: pip-audit - needs: detect - if: needs.detect.outputs.pip == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.x" - - - name: Install pip-audit - run: pip install pip-audit==2.9.0 - - - name: Audit Python dependencies - run: | - status=0 - # Audit each Python project found in the repo - while IFS= read -r dir; do - echo "::group::pip-audit $dir" - if [ -f "$dir/pyproject.toml" ]; then - if ! pip-audit "$dir"; then - status=1 - fi - elif [ -f "$dir/requirements.txt" ]; then - if ! pip-audit -r "$dir/requirements.txt"; then - status=1 - fi - fi - echo "::endgroup::" - done < <( - { - find . -name 'pyproject.toml' -not -path '*/.venv/*' -not -path '*/venv/*' -exec dirname {} \; - find . -name 'requirements.txt' -not -path '*/.venv/*' -not -path '*/venv/*' -exec dirname {} \; - } | sort -u - ) - exit $status + dependency-audit: + uses: petry-projects/.github/.github/workflows/dependency-audit-reusable.yml@ae9709f4466dec60a5733c9e7487f69dcd004e05 # v1 diff --git a/standards/ci-standards.md b/standards/ci-standards.md index d6ef37aa..01e564f2 100644 --- a/standards/ci-standards.md +++ b/standards/ci-standards.md @@ -22,7 +22,7 @@ where to send a fix when behavior needs to change. | Tier | Examples | What lives in `standards/workflows/` | Where logic lives | Edits allowed in adopting repo | |---|---|---|---|---| -| **1. Stub** | `claude.yml`, `dependency-audit.yml`, `dependabot-automerge.yml`, `dependabot-rebase.yml`, `agent-shield.yml`, `feature-ideation.yml` | A thin caller stub that delegates via `uses: petry-projects/.github/.github/workflows/-reusable.yml@v1` | The matching `*-reusable.yml` in this repo (single source of truth) | **None** in normal use. May tune `with:` inputs where the reusable exposes them (e.g. `agent-shield` accepts `min-severity`, `required-files`; `feature-ideation` requires `project_context`). To change behavior, open a PR against the reusable in this repo — repos on `@v1` pick it up after the `v1` tag is bumped; repos on `@main` pick it up on their next run. | +| **1. Stub** | `claude.yml`, `dependency-audit.yml`, `dependabot-automerge.yml`, `dependabot-rebase.yml`, `agent-shield.yml`, `feature-ideation.yml` | A thin caller stub that delegates via `uses: petry-projects/.github/.github/workflows/-reusable.yml@ # v1` (SHA-pinned per Action Pinning Policy). **Exception:** `claude.yml` uses `@v1` (not a SHA) due to the Anthropic OIDC token constraint — see the OIDC exemption note in `standards/workflows/claude.yml`. | The matching `*-reusable.yml` in this repo (single source of truth) | **None** in normal use. May tune `with:` inputs where the reusable exposes them (e.g. `agent-shield` accepts `min-severity`, `required-files`; `feature-ideation` requires `project_context`). To change behavior, open a PR against the reusable in this repo — the change propagates everywhere on next run. | | **2. Per-repo template** | `ci.yml`, `sonarcloud.yml` | _(no template — see the patterns documented below)_ | In each repo, because the workflow is tech-stack-specific (language matrix, build tool, test framework) | **Limited.** Each adopting repo carries its own copy. Stay within the patterns in this document; do not change action SHAs, permission scopes, trigger events, or job names without raising a standards PR first. | | **GitHub-managed** | CodeQL default setup | _(no workflow file — managed via repo Settings → Code security)_ | GitHub | None. Configured via `apply-repo-settings.sh`; per-repo `codeql.yml` files are treated as drift by the compliance audit. See [§2 CodeQL Analysis](#2-codeql-analysis-github-managed-default-setup). | | **3. Free per-repo** | `release.yml`, project-specific automation | _(out of scope for this standard)_ | Per-repo | Free, but must still comply with the [Action Pinning Policy](#action-pinning-policy) and the [Required Workflows](#required-workflows) constraints. | @@ -33,11 +33,15 @@ file with that header, **stop and read the header first** — if the change isn't allowed by the contract, the right move is a PR against the central reusable, not a local edit. -> **Why pin to `@v1`?** Stubs reference reusables by tag, not `@main`, so a -> bad commit on the central repo's `main` branch cannot break every -> downstream repo simultaneously. The `v1` tag is bumped deliberately when -> a backward-compatible release is ready; breaking changes will publish a -> `v2` tag that downstream repos opt into explicitly. +> **Why SHA-pin reusable workflow refs?** The [Action Pinning Policy](#action-pinning-policy) +> requires all `uses:` references to be pinned to a commit SHA, including +> reusable workflow references. The SHA in the template corresponds to the `v1` +> annotated tag commit — the version comment `# v1` documents this for human +> readability. Dependabot keeps the SHA current via the `github-actions` ecosystem +> entry in `dependabot.yml`. Using a SHA instead of `@main` also ensures that a +> bad commit on the central repo's `main` branch cannot break every downstream +> repo simultaneously; breaking changes are published as a new `v2` tag, and +> downstream repos opt in explicitly when Dependabot proposes the bump. ### Available templates diff --git a/standards/workflows/agent-shield.yml b/standards/workflows/agent-shield.yml index 8704981d..3bbd6375 100644 --- a/standards/workflows/agent-shield.yml +++ b/standards/workflows/agent-shield.yml @@ -30,4 +30,4 @@ permissions: jobs: agent-shield: - uses: petry-projects/.github/.github/workflows/agent-shield-reusable.yml@v1 + uses: petry-projects/.github/.github/workflows/agent-shield-reusable.yml@ae9709f4466dec60a5733c9e7487f69dcd004e05 # v1 diff --git a/standards/workflows/dependency-audit.yml b/standards/workflows/dependency-audit.yml index f1173774..1bfeaf61 100644 --- a/standards/workflows/dependency-audit.yml +++ b/standards/workflows/dependency-audit.yml @@ -1,6 +1,6 @@ # ───────────────────────────────────────────────────────────────────────────── # SOURCE OF TRUTH: petry-projects/.github/standards/workflows/dependency-audit.yml -# Standard: petry-projects/.github/standards/ci-standards.md#5-dependency-audit-dependency-auditym +# Standard: petry-projects/.github/standards/ci-standards.md#6-dependency-audit-dependency-audityml # Reusable: petry-projects/.github/.github/workflows/dependency-audit-reusable.yml # # AGENTS — READ BEFORE EDITING: @@ -30,4 +30,4 @@ permissions: jobs: dependency-audit: - uses: petry-projects/.github/.github/workflows/dependency-audit-reusable.yml@v1 + uses: petry-projects/.github/.github/workflows/dependency-audit-reusable.yml@ae9709f4466dec60a5733c9e7487f69dcd004e05 # v1