Skip to content

fix(git-id-switcher): replace broken Snyk badge with static shield (#… #1427

fix(git-id-switcher): replace broken Snyk badge with static shield (#…

fix(git-id-switcher): replace broken Snyk badge with static shield (#… #1427

Workflow file for this run

name: Security Checks
on:
push:
branches: [main]
# Removed paths filter to improve SAST coverage (Scorecard check)
# All commits now trigger security checks including CodeQL
pull_request:
branches: [main]
# Removed paths filter to improve SAST coverage (Scorecard check)
schedule:
# Run daily at 00:00 UTC
- cron: '0 0 * * *'
branch_protection_rule:
workflow_dispatch:
permissions: {}
jobs:
security-audit:
name: Security Audit
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: extensions/git-id-switcher
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: package-lock.json
# Install at root level for npm workspaces
- name: Install dependencies
working-directory: .
run: npm ci
# 2-tier dev audit: high → CI fail, moderate → warn only
# Prevents Dependabot PRs from being blocked by moderate-level advisories
# while ensuring high/critical vulnerabilities are never missed
- name: Run npm audit (dev, high+critical = fail)
run: npm audit --audit-level=high
- name: Run npm audit (dev, moderate = warn only)
run: npm audit --audit-level=moderate
continue-on-error: true
- name: Run npm audit (production only)
run: npm audit --omit=dev --audit-level=high
lint-security:
name: Security Linting
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: extensions/git-id-switcher
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: package-lock.json
# Install at root level for npm workspaces
- name: Install dependencies
working-directory: .
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Check for dangerous patterns
run: |
echo "Checking for dangerous exec() usage..."
if grep -rn "exec(" src/ --include="*.ts" | grep -v "execFile" | grep -v "secureExec" | grep -v "\.exec(" | grep -v ".test.ts"; then
echo "❌ Found potentially dangerous exec() calls"
exit 1
fi
echo "✅ No dangerous exec() patterns found"
echo "Checking for string interpolation in commands..."
if grep -rn 'execAsync.*`' src/ --include="*.ts" | grep -v ".test.ts"; then
echo "❌ Found string interpolation in command execution"
exit 1
fi
echo "✅ No string interpolation in commands"
security-tests:
name: Security Tests
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: extensions/git-id-switcher
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: package-lock.json
# Install at root level for npm workspaces
- name: Install dependencies
working-directory: .
run: npm ci
- name: Compile TypeScript
run: npm run compile
- name: Run security tests (includes fuzzing)
run: npm run test:security
codeql-analysis:
name: CodeQL Analysis
runs-on: ubuntu-latest
# Only run when results can be uploaded (skip forked PRs)
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Initialize CodeQL
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4
with:
languages: typescript
config-file: ./.github/codeql-config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4
with:
category: "/language:typescript"
semgrep:
name: Semgrep SAST
runs-on: ubuntu-latest
# Skip forked PRs (no SARIF upload permission)
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: read
security-events: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Semgrep
uses: semgrep/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d # v1 (v0.58.0, verified 2026-03-17)
with:
config: >-
p/typescript
p/security-audit
p/secrets
socket-security:
name: Socket.dev Security
runs-on: ubuntu-latest
# Only run on pull requests (analyzes dependency changes)
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Socket Security Review
uses: SocketDev/action@ba6de6cc0565af1f42295590380973573297e31f # v1.3.2
with:
mode: firewall
scorecard:
name: OpenSSF Scorecard
runs-on: ubuntu-latest
# Only run on main branch (push, schedule) - not on PRs
if: github.event_name != 'pull_request'
permissions:
contents: read
security-events: write
id-token: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run Scorecard analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload Scorecard results to Security tab
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4
with:
sarif_file: results.sarif
# Separate job: scorecard-action requires all steps in its job to use `uses` only.
# Shell script steps cause workflow verification failure on result publication.
scorecard-threshold:
name: Scorecard Threshold Gate
runs-on: ubuntu-latest
needs: scorecard
# Only run on main branch (push, schedule) - not on PRs
if: github.event_name != 'pull_request'
permissions:
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Check Scorecard score threshold
env:
SCORECARD_THRESHOLD: ${{ vars.SCORECARD_THRESHOLD || '7.0' }}
run: |
REPO="${{ github.repository }}"
# Fetch latest published Scorecard results from REST API
HTTP_CODE=$(curl -s -o scorecard-api.json -w "%{http_code}" \
"https://api.scorecard.dev/projects/github.com/${REPO}")
if [ "$HTTP_CODE" != "200" ]; then
echo "::warning::Could not fetch Scorecard results (HTTP ${HTTP_CODE}). Skipping threshold check."
echo "This may happen on the first run before results are published."
exit 0
fi
SCORE=$(jq -r '.score // empty' scorecard-api.json)
if [ -z "$SCORE" ]; then
echo "::warning::No score found in API response. Skipping threshold check."
exit 0
fi
echo "## OpenSSF Scorecard Results" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**Overall score: ${SCORE} / 10.0** (threshold: ${SCORECARD_THRESHOLD})" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Check | Score | Status |" >> "$GITHUB_STEP_SUMMARY"
echo "|-------|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
jq -r '.checks[] | "| \(.name) | \(if .score < 0 then "N/A" else "\(.score)/10" end) | \(if .score < 0 then "➖" elif .score >= 7 then "✅" elif .score >= 4 then "⚠️" else "❌" end) |"' \
scorecard-api.json | sort >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Overall score: ${SCORE} / 10.0 (threshold: ${SCORECARD_THRESHOLD})"
echo ""
echo "Individual check scores:"
jq -r '.checks[] | " \(if .score < 0 then "➖" elif .score >= 7 then "✅" elif .score >= 4 then "⚠️" else "❌" end) \(.name): \(if .score < 0 then "N/A" else "\(.score)/10" end)"' \
scorecard-api.json | sort
echo ""
# Compare scores using awk (bash does not support float comparison)
BELOW=$(echo "${SCORE} ${SCORECARD_THRESHOLD}" | awk '{print ($1 < $2) ? "1" : "0"}')
if [ "$BELOW" = "1" ]; then
echo "::error::Scorecard score ${SCORE} is below threshold ${SCORECARD_THRESHOLD}"
echo ""
echo "Checks needing attention (score < 7):"
jq -r '.checks[] | select(.score >= 0 and .score < 7) | " ❌ \(.name): \(.score)/10 — \(.reason)"' scorecard-api.json
exit 1
fi
echo "✅ Scorecard score ${SCORE} meets threshold ${SCORECARD_THRESHOLD}"