Skip to content

fix: solve bundler mismatch #466

fix: solve bundler mismatch

fix: solve bundler mismatch #466

Workflow file for this run

name: Security Scan
on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]
schedule:
# Run weekly on Monday at 9am UTC
- cron: '0 9 * * 1'
permissions:
contents: read
pull-requests: write
issues: write
jobs:
brakeman:
name: Brakeman Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Ruby
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1
with:
ruby-version: 3.4.5
bundler-cache: true
- name: Run Brakeman
run: |
bundle exec brakeman --rails7 \
--format json \
--output brakeman-report.json \
--no-exit-on-warn \
--no-exit-on-error
- name: Parse Results
id: parse
run: |
WARNINGS=$(jq '.warnings | length' brakeman-report.json)
HIGH=$(jq '[.warnings[] | select(.confidence == "High")] | length' brakeman-report.json)
echo "warnings=$WARNINGS" >> "$GITHUB_OUTPUT"
echo "high=$HIGH" >> "$GITHUB_OUTPUT"
- name: Upload Report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: brakeman-report
path: brakeman-report.json
- name: Comment PR
if: github.event_name == 'pull_request'
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
with:
script: |
const warnings = '${{ steps.parse.outputs.warnings }}';
const high = '${{ steps.parse.outputs.high }}';
const body = `## 🔒 Brakeman Security Scan
- Total warnings: ${warnings}
- High confidence: ${high}
${high > 0 ? '⚠️ High confidence issues found! Please review.' : '✅ No high confidence issues found.'}
`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
- name: Fail on High Confidence Issues
if: steps.parse.outputs.high > 0
run: |
echo "::error::Found ${{ steps.parse.outputs.high }} high confidence security issues!"
exit 1
dependency-check:
name: Dependency Vulnerability Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Ruby
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1
with:
ruby-version: 3.4.5
bundler-cache: true
- name: Update Vulnerability Database
run: bundle exec bundler-audit update
- name: Run Bundle Audit
id: audit
run: |
if ! bundle exec bundler-audit check; then
echo "vulnerabilities=true" >> "$GITHUB_OUTPUT"
bundle exec bundler-audit check > bundle-audit.txt || true
else
echo "vulnerabilities=false" >> "$GITHUB_OUTPUT"
bundle exec bundler-audit check > bundle-audit.txt
fi
- name: Upload Report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: bundle-audit-report
path: bundle-audit.txt
- name: Comment PR
if: github.event_name == 'pull_request'
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
with:
script: |
const fs = require('fs');
const report = fs.existsSync('bundle-audit.txt')
? fs.readFileSync('bundle-audit.txt', 'utf8')
: 'No report generated.';
const hasVulns = '${{ steps.audit.outputs.vulnerabilities }}' === 'true';
const body = `## 📦 Dependency Security Check
${hasVulns ? '⚠️ Vulnerabilities found in dependencies!' : '✅ No known vulnerabilities found.'}
<details>
<summary>View Report</summary>
\`\`\`
${report}
\`\`\`
</details>
`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
- name: Fail on Vulnerabilities
if: steps.audit.outputs.vulnerabilities == 'true'
run: |
echo "::error::Vulnerable dependencies found!"
exit 1
semgrep:
name: Semgrep Static Analysis
runs-on: ubuntu-latest
container:
image: returntocorp/semgrep
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Run Semgrep
run: |
semgrep scan \
--config=auto \
--json \
--output=semgrep-report.json \
--exclude 'scripts/' \
--exclude 'load_tests/' \
--exclude 'security_tests/' \
--exclude 'vendor/' \
--verbose \
|| true
- name: Parse Results
id: parse
run: |
TOTAL=$(jq '.results | length' semgrep-report.json)
ERRORS=$(jq '.results | map(select(.extra.severity == "ERROR")) | length' semgrep-report.json)
WARNINGS=$(jq '.results | map(select(.extra.severity == "WARNING")) | length' semgrep-report.json)
CRITICAL=$(jq '.results | map(select(.extra.metadata.confidence == "HIGH" and (.extra.metadata.subcategory // "vuln") != "audit")) | length' semgrep-report.json)
echo "errors=$ERRORS" >> "$GITHUB_OUTPUT"
echo "warnings=$WARNINGS" >> "$GITHUB_OUTPUT"
echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT"
echo "::notice::Total findings: $TOTAL — errors: $ERRORS, warnings: $WARNINGS, critical: $CRITICAL"
if [ "$ERRORS" -gt 0 ]; then
echo "::group::ERROR Severity Issues"
jq -r '.results[] | select(.extra.severity == "ERROR") | " - \(.path):\(.start.line) — \(.check_id)"' semgrep-report.json
echo "::endgroup::"
fi
- name: Upload Report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: semgrep-report
path: semgrep-report.json
- name: Comment PR
if: github.event_name == 'pull_request'
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
with:
script: |
const errors = '${{ steps.parse.outputs.errors }}';
const warnings = '${{ steps.parse.outputs.warnings }}';
const critical = '${{ steps.parse.outputs.critical }}';
const body = `## 🔍 Semgrep Static Analysis
| Severity | Count |
|----------|-------|
| Errors | ${errors} |
| Critical (HIGH confidence) | ${critical} |
| Warnings | ${warnings} |
${errors > 0 ? '❌ Security errors found! Please fix before merging.'
: critical > 0 ? '⚠️ High confidence issues found. Please review.'
: warnings > 0 ? '⚠️ Warnings found (non-blocking).'
: '✅ No issues found.'}
`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});
- name: Fail on Critical Errors
if: steps.parse.outputs.errors > 0
run: |
echo "::error::Semgrep found ${{ steps.parse.outputs.errors }} ERROR severity issues."
exit 1
secret-scan:
name: Secret Detection
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: TruffleHog Secret Scan
uses: trufflesecurity/trufflehog@6961f2bace57ab32b23b3ba40f8f420f6bc7e004 # main
with:
path: ./
extra_args: --only-verified
# Dynamic Application Security Testing (DAST)
ssrf-protection:
name: SSRF Protection Test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: prostaff_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Ruby
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1
with:
ruby-version: 3.4.5
bundler-cache: true
- name: Setup Database
env:
RAILS_ENV: test
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/prostaff_test
REDIS_URL: redis://127.0.0.1:6379/0
run: bundle exec rails db:create db:migrate RAILS_ENV=test
- name: Start Rails Server
env:
RAILS_ENV: test
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/prostaff_test
REDIS_URL: redis://127.0.0.1:6379/0
JWT_SECRET_KEY: test_jwt_secret_key_for_ci
RIOT_API_KEY: ${{ secrets.RIOT_API_KEY || 'dummy_key' }}
run: |
bundle exec rails server -p 3333 -d
timeout 60 bash -c 'until curl -sf http://localhost:3333/up; do sleep 2; done'
- name: Run SSRF Protection Tests
run: |
chmod +x .pentest/test-ssrf-quick.sh
.pentest/test-ssrf-quick.sh
- name: Upload Results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ssrf-test-results
path: security_tests/reports/ssrf/
authentication-test:
name: Authentication Security Test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: prostaff_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Ruby
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1
with:
ruby-version: 3.4.5
bundler-cache: true
- name: Setup Database
env:
RAILS_ENV: test
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/prostaff_test
REDIS_URL: redis://127.0.0.1:6379/0
run: bundle exec rails db:create db:migrate RAILS_ENV=test
- name: Start Rails Server
env:
RAILS_ENV: test
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/prostaff_test
REDIS_URL: redis://127.0.0.1:6379/0
JWT_SECRET_KEY: test_jwt_secret_key_for_ci
RIOT_API_KEY: ${{ secrets.RIOT_API_KEY || 'dummy_key' }}
run: |
bundle exec rails server -p 3333 -d
timeout 60 bash -c 'until curl -sf http://localhost:3333/up; do sleep 2; done'
- name: Run Authentication Tests
run: |
chmod +x .pentest/test-authentication-quick.sh
.pentest/test-authentication-quick.sh
sql-injection-test:
name: SQL Injection Protection Test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: prostaff_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Ruby
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1
with:
ruby-version: 3.4.5
bundler-cache: true
- name: Setup Database
env:
RAILS_ENV: test
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/prostaff_test
REDIS_URL: redis://127.0.0.1:6379/0
run: bundle exec rails db:create db:migrate RAILS_ENV=test
- name: Start Rails Server
env:
RAILS_ENV: test
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/prostaff_test
REDIS_URL: redis://127.0.0.1:6379/0
JWT_SECRET_KEY: test_jwt_secret_key_for_ci
RIOT_API_KEY: ${{ secrets.RIOT_API_KEY || 'dummy_key' }}
run: |
bundle exec rails server -p 3333 -d
timeout 60 bash -c 'until curl -sf http://localhost:3333/up; do sleep 2; done'
- name: Run SQL Injection Tests
run: |
chmod +x .pentest/test-sql-injection-quick.sh
.pentest/test-sql-injection-quick.sh
secrets-scan-enhanced:
name: Secrets Scan (Enhanced)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Run Secrets Check
run: |
chmod +x .pentest/test-secrets-quick.sh
.pentest/test-secrets-quick.sh
security-summary:
name: Security Summary
runs-on: ubuntu-latest
needs:
- brakeman
- dependency-check
- semgrep
- ssrf-protection
- authentication-test
- sql-injection-test
- secrets-scan-enhanced
if: always()
steps:
- name: Check Results
run: |
echo "Brakeman: ${{ needs.brakeman.result }}"
echo "Dependency Check: ${{ needs.dependency-check.result }}"
echo "Semgrep: ${{ needs.semgrep.result }}"
echo "SSRF Protection: ${{ needs.ssrf-protection.result }}"
echo "Authentication: ${{ needs.authentication-test.result }}"
echo "SQL Injection: ${{ needs.sql-injection-test.result }}"
echo "Secrets Scan: ${{ needs.secrets-scan-enhanced.result }}"
- name: Write Step Summary
run: |
status() {
case "$1" in
success) echo "✅" ;;
failure) echo "❌" ;;
*) echo "⚠️" ;;
esac
}
cat >> "$GITHUB_STEP_SUMMARY" << EOF
## 🔐 Security Scan Summary
### Static Analysis (SAST)
| Check | Status |
|-------|--------|
| Brakeman | $(status "${{ needs.brakeman.result }}") ${{ needs.brakeman.result }} |
| Dependencies | $(status "${{ needs.dependency-check.result }}") ${{ needs.dependency-check.result }} |
| Semgrep | $(status "${{ needs.semgrep.result }}") ${{ needs.semgrep.result }} |
| Secrets | $(status "${{ needs.secrets-scan-enhanced.result }}") ${{ needs.secrets-scan-enhanced.result }} |
### Dynamic Analysis (DAST)
| Check | Status |
|-------|--------|
| SSRF Protection | $(status "${{ needs.ssrf-protection.result }}") ${{ needs.ssrf-protection.result }} |
| Authentication | $(status "${{ needs.authentication-test.result }}") ${{ needs.authentication-test.result }} |
| SQL Injection | $(status "${{ needs.sql-injection-test.result }}") ${{ needs.sql-injection-test.result }} |
EOF
- name: Comment PR
if: github.event_name == 'pull_request'
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
with:
script: |
const s = (r) => ({ success: '✅', failure: '❌' }[r] ?? '⚠️');
const brakeman = '${{ needs.brakeman.result }}';
const deps = '${{ needs.dependency-check.result }}';
const semgrep = '${{ needs.semgrep.result }}';
const ssrf = '${{ needs.ssrf-protection.result }}';
const auth = '${{ needs.authentication-test.result }}';
const sqli = '${{ needs.sql-injection-test.result }}';
const secrets = '${{ needs.secrets-scan-enhanced.result }}';
const allPassed = [brakeman, deps, semgrep, ssrf, auth, sqli, secrets]
.every(r => r === 'success');
const body = `## 🔐 Security Scan Summary
### Static Analysis (SAST)
| Check | Status |
|-------|--------|
| Brakeman | ${s(brakeman)} ${brakeman} |
| Dependencies | ${s(deps)} ${deps} |
| Semgrep | ${s(semgrep)} ${semgrep} |
| Secrets | ${s(secrets)} ${secrets} |
### Dynamic Analysis (DAST)
| Check | Status |
|-------|--------|
| SSRF Protection | ${s(ssrf)} ${ssrf} |
| Authentication | ${s(auth)} ${auth} |
| SQL Injection | ${s(sqli)} ${sqli} |
${allPassed ? '✅ All security checks passed!' : '⚠️ Some checks failed — review the details above.'}
`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});