diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..60bfd59 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,32 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + groups: + github-actions: + patterns: + - "*" + schedule: + interval: weekly + cooldown: + default-days: 7 + + - package-ecosystem: bundler + directory: "/" + schedule: + interval: weekly + cooldown: + semver-major-days: 7 + semver-minor-days: 3 + semver-patch-days: 2 + default-days: 7 + + - package-ecosystem: docker + directory: "/" + schedule: + interval: weekly + cooldown: + semver-major-days: 7 + semver-minor-days: 3 + semver-patch-days: 2 + default-days: 7 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ac6d9e..a3ed857 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,16 +6,22 @@ on: pull_request: branches: [ main ] +permissions: {} + jobs: scan: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 with: ruby-version: .ruby-version bundler-cache: true @@ -25,12 +31,16 @@ jobs: lint: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 with: ruby-version: .ruby-version bundler-cache: true @@ -38,11 +48,32 @@ jobs: - name: Lint code for consistent style run: bin/rubocop + lint-actions: + name: GitHub Actions audit + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false + + - name: Run actionlint + uses: rhysd/actionlint@393031adb9afb225ee52ae2ccd7a5af5525e03e8 # v1.7.11 + + - name: Run zizmor + uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 + with: + advanced-security: false + test: runs-on: ubuntu-latest + permissions: + contents: read services: redis: - image: redis + image: redis # zizmor: ignore[unpinned-images] -- version tag is fine for service containers ports: - 6379:6379 options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 @@ -51,10 +82,12 @@ jobs: run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libsqlite3-0 libvips curl ffmpeg - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 env: REDIS_URL: redis://localhost:6379/0 with: @@ -66,9 +99,11 @@ jobs: test_system: runs-on: ubuntu-latest + permissions: + contents: read services: redis: - image: redis + image: redis # zizmor: ignore[unpinned-images] -- version tag is fine for service containers ports: - 6379:6379 options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 @@ -77,10 +112,12 @@ jobs: run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libsqlite3-0 libvips curl ffmpeg - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 env: REDIS_URL: redis://localhost:6379/0 with: diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index eb19134..477652e 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -13,11 +13,7 @@ concurrency: group: publish-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -permissions: - contents: read - packages: write - id-token: write - attestations: write +permissions: {} env: IMAGE_DESCRIPTION: Campfire is a web-based chat application with multiple rooms, direct messages, file attachments with previews, search, web push notifications, @mentions, and bot integrations. Single-tenant; production-ready image with web app, background jobs, caching, file serving, and SSL. @@ -27,6 +23,11 @@ jobs: build: name: Build and push image (${{ matrix.arch }}) runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + id-token: write + attestations: write timeout-minutes: 45 strategy: fail-fast: false @@ -43,13 +44,15 @@ jobs: IMAGE_NAME: ${{ github.repository }} steps: - name: Checkout - uses: actions/checkout@v5.0.0 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.11.1 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Log in to GHCR - uses: docker/login-action@v3.5.0 + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -66,7 +69,7 @@ jobs: - name: Extract Docker metadata (tags, labels) with arch suffix id: meta - uses: docker/metadata-action@v5.8.0 + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 with: images: ${{ steps.vars.outputs.canonical }} tags: | @@ -84,7 +87,7 @@ jobs: - name: Build and push (${{ matrix.platform }}) id: build - uses: docker/build-push-action@v6.18.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . file: Dockerfile @@ -102,7 +105,7 @@ jobs: - name: Attest image provenance (per-arch) if: github.event_name != 'pull_request' - uses: actions/attest-build-provenance@v3.0.0 + uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0 with: subject-name: ${{ steps.vars.outputs.canonical }} subject-digest: ${{ steps.build.outputs.digest }} @@ -113,16 +116,20 @@ jobs: needs: build if: github.event_name != 'pull_request' runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write timeout-minutes: 20 env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} steps: - name: Set up Docker Buildx (for imagetools) - uses: docker/setup-buildx-action@v3.11.1 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Log in to GHCR - uses: docker/login-action@v3.5.0 + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -139,7 +146,7 @@ jobs: - name: Compute base tags (no suffix) id: meta - uses: docker/metadata-action@v5.8.0 + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 with: images: ${{ steps.vars.outputs.canonical }} tags: | @@ -157,9 +164,11 @@ jobs: - name: Create multi-arch manifests shell: bash + env: + TAGS: ${{ steps.meta.outputs.tags }} run: | set -eu - tags="${{ steps.meta.outputs.tags }}" + tags="$TAGS" echo "Creating manifests for tags:" printf '%s\n' "$tags" while IFS= read -r tag; do @@ -185,13 +194,15 @@ jobs: done <<< "$tags" - name: Install Cosign - uses: sigstore/cosign-installer@v3.9.2 + uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 - name: Cosign sign all tags (keyless OIDC) shell: bash + env: + TAGS: ${{ steps.meta.outputs.tags }} run: | set -eu - tags="${{ steps.meta.outputs.tags }}" + tags="$TAGS" printf '%s\n' "$tags" while IFS= read -r tag; do [ -z "$tag" ] && continue diff --git a/Gemfile.lock b/Gemfile.lock index 60e9fb2..04f4f08 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -137,7 +137,7 @@ GEM bcrypt (3.1.20) benchmark (0.5.0) bigdecimal (3.3.1) - brakeman (7.1.2) + brakeman (8.0.4) racc builder (3.3.0) capybara (3.40.0) @@ -165,10 +165,7 @@ GEM erubi (1.13.1) faker (3.5.2) i18n (>= 1.8.11, < 2) - ffi (1.17.2-aarch64-linux-gnu) - ffi (1.17.2-arm64-darwin) - ffi (1.17.2-x86_64-darwin) - ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2) geared_pagination (1.2.0) activesupport (>= 5.0) addressable (>= 2.5.0) @@ -212,6 +209,7 @@ GEM mini_magick (5.3.1) logger mini_mime (1.1.5) + mini_portile2 (2.8.9) minitest (5.26.2) mocha (2.7.1) ruby2_keywords (>= 0.0.5) @@ -231,13 +229,8 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.5) - nokogiri (1.18.10-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-gnu) + nokogiri (1.18.10) + mini_portile2 (~> 2.8.2) racc (~> 1.4) openssl (3.3.0) ostruct (0.6.3) @@ -306,11 +299,11 @@ GEM rake (>= 10.0, < 14.0) resque (>= 1.22, < 3) rexml (3.4.1) - rqrcode (3.1.0) + rqrcode (3.2.0) chunky_png (~> 1.0) rqrcode_core (~> 2.0) - rqrcode_core (2.0.0) - rubocop (1.80.0) + rqrcode_core (2.1.0) + rubocop (1.80.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -364,10 +357,8 @@ GEM rack-protection (= 4.1.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - sqlite3 (2.7.3-aarch64-linux-gnu) - sqlite3 (2.7.3-arm64-darwin) - sqlite3 (2.7.3-x86_64-darwin) - sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.7.3) + mini_portile2 (~> 2.8.0) stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.1.8) @@ -381,9 +372,9 @@ GEM tsort (0.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (3.1.5) - unicode-emoji (~> 4.0, >= 4.0.4) - unicode-emoji (4.0.4) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) uri (1.1.1) useragent (0.16.11) web-push (3.0.2) diff --git a/bin/setup b/bin/setup index 5905fe9..9bf981b 100755 --- a/bin/setup +++ b/bin/setup @@ -100,6 +100,20 @@ if ! redis_running; then fi fi +# Install GitHub Actions linting tools +for tool in actionlint shellcheck zizmor; do + if ! command -v "$tool" &> /dev/null; then + if command -v brew &> /dev/null; then + step "Installing $tool" brew install "$tool" + elif command -v pacman &> /dev/null; then + step "Installing $tool" sudo pacman -S --noconfirm "$tool" + else + echo "Error: install $tool manually" >&2 + exit 1 + fi + fi +done + step "Cleaning up logs and tempfiles" rails log:clear tmp:clear step "Restarting services" rails restart diff --git a/config/ci.rb b/config/ci.rb index 37081af..ac4242d 100644 --- a/config/ci.rb +++ b/config/ci.rb @@ -4,6 +4,8 @@ CI.run do step "Setup", "bin/setup --skip-server" step "Style: Ruby", "bin/rubocop" + step "Style: GitHub Actions (actionlint)", "actionlint" + step "Style: GitHub Actions (zizmor)", "zizmor ." step "Security: Gem audit", "bin/bundler-audit" step "Security: Importmap vulnerability audit", "bin/importmap audit"