← Back to blog

Blocking Unsafe Code: Security Audits in GitHub Actions

September 2025
6 min read
GitHub Actions
Ruby
Security
Supply Chain
DevSecOps

The Problem: When a Deploy Becomes a Risk

Picture this: tests are green, code merged to main, deployed to production — and a week later a critical vulnerability pops up in one of your gems. It’s already being exploited, and you learn about it from the news. Sounds familiar? This guide is for you.

The Solution: Security-First CI/CD

We’ll add automated checks to your pipeline that fail the build if vulnerabilities or license issues are found. The deploy won’t start until the audit is green.

What We Check (and With What)

  • Dependency vulnerabilitiesbundler-audit (RubySec database).
  • Licenseslicense_finder (e.g., block GPL in a commercial app).
  • OSV database — Google OSV Scanner (works well in mixed repos).
1# Install tools locally (example)
2gem install bundler-audit license_finder
3
4# Vulnerability check
5bundle audit check --update
6
7# License check
8license_finder --quiet

Option 1: All-in-One Workflow (Recommended)

If your deploy.yml isn’t huge, add an audit job right there and make the deploy depend on it.

1name: Deploy
2
3on:
4  push:
5    branches: [ main ]
6  pull_request:
7    paths: ['Gemfile*', '*.gemspec']
8  schedule:
9    - cron: '0 8 * * MON'  # Weekly security check
10
11jobs:
12  audit:
13    name: Security & License Audit
14    runs-on: ubuntu-latest
15    steps:
16      - name: Checkout code
17        uses: actions/checkout@v4
18
19      - name: Setup Ruby
20        uses: ruby/setup-ruby@v1
21        with:
22          ruby-version: .ruby-version
23          bundler-cache: true
24
25      - name: Install audit tools
26        run: |
27          gem install bundler-audit license_finder
28
29      - name: Vulnerability scan
30        run: bundle audit check --update
31
32      - name: License compliance
33        run: license_finder --quiet
34
35      # --- OSV: way 1 (via action) ---
36      - name: OSV scan (action)
37        # If you use the official action — set a valid ref (check the README).
38        # If GitHub can't resolve the action, use way 2 below.
39        uses: google/osv-scanner-action@v1
40        with:
41          scan-args: '--recursive --skip-git .'
42
43      # --- OSV: way 2 (fallback, without action) ---
44      # - name: OSV scan (binary)
45      #   run: |
46      #     curl -sSL https://raw.githubusercontent.com/google/osv-scanner/main/scripts/install.sh | sh -s -- -b /usr/local/bin
47      #     osv-scanner --recursive --skip-git .
48
49  deploy:
50    needs: [audit] # ← deploy won't start until audit is green
51    runs-on: ubuntu-latest
52    steps:
53      - uses: actions/checkout@v4
54      # ... your deploy steps
55

If you see “Unresolved action/workflow reference”

Either use a correct action ref (check the action’s README) or install OSV as a binary (fallback above). It’s reliable and not dependent on the marketplace.

Option 2: Split Audit and Deploy (For Larger Repos)

Put the audit in a separate workflow and trigger the deploy after it finishes successfully.

1# .github/workflows/audit.yml
2name: Security Audit
3
4on:
5  pull_request:
6    paths: ['Gemfile*', '*.gemspec']
7  push:
8    branches: [ main ]
9  schedule:
10    - cron: '0 8 * * MON'
11
12jobs:
13  audit:
14    runs-on: ubuntu-latest
15    steps:
16      - uses: actions/checkout@v4
17      - uses: ruby/setup-ruby@v1
18        with:
19          ruby-version: .ruby-version
20          bundler-cache: true
21      - name: Install tools
22        run: gem install bundler-audit license_finder
23      - name: Vulnerability scan
24        run: bundle audit check --update
25      - name: License compliance
26        run: license_finder --quiet
27      - name: OSV scan
28        uses: google/osv-scanner-action@v1
29        with:
30          scan-args: '--recursive --skip-git .'
31
1# .github/workflows/deploy.yml
2name: Deploy
3
4on:
5  workflow_run:
6    workflows: ["Security Audit"]
7    types: [completed]
8
9jobs:
10  deploy:
11    if: ${{ github.event.workflow_run.conclusion == 'success' }}
12    runs-on: ubuntu-latest
13    steps:
14      - uses: actions/checkout@v4
15      # ... your deploy steps
16

When to Run It

1pull_request:
2  paths: ['Gemfile*', '*.gemspec']  # audit only when dependencies change
3
4schedule:
5  - cron: '0 8 * * MON'             # Monday Morning Security Check
6
7push:
8  branches: [ main ]                 # for the split audit.yml approach
9

What Happens on Issues

  1. Vulnerability found: audit fails → deploy doesn’t start → production stays safe.
  2. License conflict: license_finder flags GPL etc. → deploy is blocked → team gets notified.
  3. All clear: everything is green → deploy starts automatically.

Quick PR checklist

  • New dependency: why this one? Any alternatives?
  • Is it actively maintained (releases/downloads)?
  • Supply chain risk covered: pinned versions, trusted source, commit SHA for git dependencies?
  • Did the PR pass bundle audit and license checks?

Helpful Bundler Settings & OSV Alternatives

Bundler settings

1# Protect against mixed sources (source substitution)
2bundle config set disable_multisource true
3
4# Cache and cleanup
5bundle config set cache_all true
6bundle config set clean 'true'

Alternatives & add-ons

  • Snyk / Trivy as an extra layer next to OSV.
  • If the OSV action isn’t available — install the CLI binary and run it.
  • Run a quarterly “dependency hygiene day” with your team.

About gem signatures

Signed releases are nice, but still rare. Keep defense in depth: audits + process + version pinning.

Wrap-up

Adding one mandatory audit step dramatically lowers the chance vulnerable deps reach production. If anything goes red, the release won’t ship until it’s fixed.

Personally, I turn on GitHub alerts, automate bundle audit and license checks, add OSV as a second layer, and schedule regular dependency hygiene with the team.

Want to strengthen your Ruby project’s supply chain security?
Need help rolling out DevSecOps practices and dependency audits? I can set up CI, processes, and train the team.