Skip to content

chore(deps): bump github/codeql-action from cb4e075f119f8bccbc942d49655b2cd4dc6e615a to a899987af240c0578ed84ce13c02319a693e168f #93

chore(deps): bump github/codeql-action from cb4e075f119f8bccbc942d49655b2cd4dc6e615a to a899987af240c0578ed84ce13c02319a693e168f

chore(deps): bump github/codeql-action from cb4e075f119f8bccbc942d49655b2cd4dc6e615a to a899987af240c0578ed84ce13c02319a693e168f #93

Workflow file for this run

name: Security
on:
workflow_dispatch:
push:
branches: [main]
paths-ignore:
- '**.md'
- 'docs/**'
- '.github/*.md'
pull_request:
branches: [main]
paths-ignore:
- '**.md'
- 'docs/**'
- '.github/*.md'
schedule:
- cron: '0 6 * * 3' # Weekly on Wednesday 6am UTC
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false # Security scans should always complete
permissions: {}
jobs:
# Scan dependencies for known vulnerabilities on PRs
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
timeout-minutes: 10
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: block
allowed-endpoints: >
api.github.com:443
api.securityscorecards.dev:443
github.com:443
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Dependency Review
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.7.1
with:
config-file: .github/dependency-review-config.yml
# Secret scanning using Gitleaks
secret-scanning:
name: Secret Scanning
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
security-events: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: block
allowed-endpoints: >
api.github.com:443
github.com:443
github-releases.githubusercontent.com:443
objects.githubusercontent.com:443
release-assets.githubusercontent.com:443
uploads.github.com:443
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0 # Full history for comprehensive secret scanning
- name: Install Gitleaks
run: |
GITLEAKS_VERSION="8.24.0"
GITLEAKS_CHECKSUM="cb49b7de5ee986510fe8666ca0273a6cc15eb82571f2f14832c9e8920751f3a4"
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -o gitleaks.tar.gz
echo "${GITLEAKS_CHECKSUM} gitleaks.tar.gz" | sha256sum -c -
tar -xzf gitleaks.tar.gz
chmod +x gitleaks
sudo mv gitleaks /usr/local/bin/
rm gitleaks.tar.gz
- name: Run Gitleaks
run: |
set +e
gitleaks detect --source . --report-format sarif --report-path gitleaks-results.sarif --exit-code 1
exit_code=$?
set -e
if [[ $exit_code -eq 1 ]]; then
echo "::error::Gitleaks detected secrets in the repository"
exit 1
elif [[ $exit_code -ne 0 ]]; then
echo "::error::Gitleaks encountered an error (exit code: $exit_code)"
exit $exit_code
fi
echo "No secrets detected"
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@34950e1b113b30df4edee1a6d3a605242df0c40b # v4
if: always()
with:
sarif_file: gitleaks-results.sarif
category: gitleaks
continue-on-error: true # Don't fail if SARIF upload fails (e.g., on forks)
# Static analysis for bash scripts
script-security:
name: Script Security Check
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
security-events: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: block
allowed-endpoints: >
api.github.com:443
github.com:443
uploads.github.com:443
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Initialize findings directory
run: mkdir -p findings
- name: Check for hardcoded secrets
id: hardcoded-secrets
shell: bash
run: |
set -euo pipefail
declare -A patterns=(
["password\s*=\s*['\"][^'\"]+['\"]"]="Hardcoded password"
["api[_-]?key\s*=\s*['\"][^'\"]+['\"]"]="Hardcoded API key"
["secret\s*=\s*['\"][^'\"]+['\"]"]="Hardcoded secret"
["token\s*=\s*['\"][^'\"]+['\"]"]="Hardcoded token"
["AKIA[0-9A-Z]{16}"]="AWS Access Key ID"
["-----BEGIN\s+(RSA|DSA|EC|OPENSSH|PGP)?\s*PRIVATE KEY-----"]="Private key"
["ghp_[0-9a-zA-Z]{36}"]="GitHub personal access token"
["gho_[0-9a-zA-Z]{36}"]="GitHub OAuth access token"
["ghu_[0-9a-zA-Z]{36}"]="GitHub user-to-server token"
["ghs_[0-9a-zA-Z]{36}"]="GitHub server-to-server token"
["ghr_[0-9a-zA-Z]{36}"]="GitHub refresh token"
["github_pat_[0-9a-zA-Z]{22}_[0-9a-zA-Z]{59}"]="GitHub fine-grained PAT"
["xox[baprs]-[0-9a-zA-Z-]+"]="Slack token"
["https://[^/]*\.webhook\.office\.com/webhookb2/[a-zA-Z0-9-]+"]="Teams webhook URL"
)
issues=0
findings="[]"
for pattern in "${!patterns[@]}"; do
description="${patterns[$pattern]}"
# Search in workflow files, excluding this security workflow's pattern definitions
while IFS=: read -r file line_num _; do
if [[ -n "$file" && -n "$line_num" ]]; then
# Skip this file's pattern definitions
if [[ "$file" == ".github/workflows/security.yml" ]]; then
continue
fi
echo "::warning file=$file,line=$line_num::$description"
((issues++)) || true
# Add to findings
findings=$(echo "$findings" | jq --arg file "$file" \
--arg line "$line_num" \
--arg desc "$description" \
--arg rule "hardcoded-secret" \
'. += [{
"ruleId": $rule,
"level": "error",
"message": {"text": $desc},
"locations": [{
"physicalLocation": {
"artifactLocation": {"uri": $file},
"region": {"startLine": ($line | tonumber)}
}
}]
}]')
fi
done < <(grep -rEn "$pattern" .github/workflows/ --include="*.yml" --include="*.yaml" 2>/dev/null || true)
done
echo "$findings" > findings/hardcoded-secrets.json
echo "issues=$issues" >> "$GITHUB_OUTPUT"
if [[ $issues -gt 0 ]]; then
echo "::error::Found $issues potential hardcoded secret(s)"
else
echo "No hardcoded secrets detected"
fi
- name: Check for unsafe bash patterns
id: unsafe-patterns
shell: bash
run: |
set -euo pipefail
# Patterns that indicate potentially unsafe bash code
# Note: eval pattern matches only when eval is a command, not a subcommand (e.g., 'yq eval' is safe)
declare -A unsafe_patterns=(
['(^|[;|&(]|\$\()\s*eval\s']="eval can execute arbitrary code"
['\bcurl\s.*\|\s*(bash|sh)']="Piping curl to shell is dangerous"
['\bwget\s.*\|\s*(bash|sh)']="Piping wget to shell is dangerous"
['source\s+<\(']="Process substitution with source can be unsafe"
['\$\(\s*cat\s']="Command substitution with cat may indicate code injection"
['rm\s+-rf\s+/[^.]']="Dangerous recursive delete from root"
['chmod\s+777']="World-writable permissions are insecure"
['\bdd\s+.*of=/dev/']="Direct device writes are dangerous"
)
# Allow-list: file:pattern pairs for known-safe usages
declare -A allow_list=(
# Add exceptions here as needed
# [".github/workflows/example.yml:eval"]="1"
)
issues=0
findings="[]"
for pattern in "${!unsafe_patterns[@]}"; do
description="${unsafe_patterns[$pattern]}"
while IFS=: read -r file line_num _; do
if [[ -n "$file" && -n "$line_num" ]]; then
# Check allow-list
allow_key="$file:$(echo "$pattern" | sed 's/\\b//g' | cut -c1-20)"
if [[ -n "${allow_list[$allow_key]:-}" ]]; then
continue
fi
# Skip this file's pattern definitions
if [[ "$file" == ".github/workflows/security.yml" ]]; then
continue
fi
echo "::error file=$file,line=$line_num::$description"
((issues++)) || true
# Add to findings
findings=$(echo "$findings" | jq --arg file "$file" \
--arg line "$line_num" \
--arg desc "$description" \
--arg rule "unsafe-bash-pattern" \
'. += [{
"ruleId": $rule,
"level": "warning",
"message": {"text": $desc},
"locations": [{
"physicalLocation": {
"artifactLocation": {"uri": $file},
"region": {"startLine": ($line | tonumber)}
}
}]
}]')
fi
done < <(grep -rEn "$pattern" .github/workflows/ --include="*.yml" --include="*.yaml" 2>/dev/null || true)
done
echo "$findings" > findings/unsafe-patterns.json
echo "issues=$issues" >> "$GITHUB_OUTPUT"
if [[ $issues -gt 0 ]]; then
echo "::error::Found $issues potentially unsafe pattern(s)"
echo "If these are intentional, add them to the allow-list in this workflow."
else
echo "No unsafe patterns detected"
fi
- name: Run ShellCheck
id: shellcheck
run: |
set -euo pipefail
echo "Running ShellCheck on shell scripts..."
shopt -s nullglob globstar
scripts=(./**/*.sh)
if [[ ${#scripts[@]} -eq 0 ]]; then
echo "No standalone shell scripts found"
echo "[]" > findings/shellcheck.json
echo "issues=0" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Found ${#scripts[@]} shell script(s)"
# Run shellcheck with JSON output, capture results even on findings
set +e
shellcheck --severity=warning --format=json "${scripts[@]}" > shellcheck-raw.json 2>/dev/null
set -e
# Convert ShellCheck JSON to our findings format
if [[ -s shellcheck-raw.json ]]; then
jq '[.[] | {
ruleId: ("SC" + (.code | tostring)),
level: (if .level == "error" then "error" elif .level == "warning" then "warning" else "note" end),
message: {text: .message},
locations: [{
physicalLocation: {
artifactLocation: {uri: .file},
region: {
startLine: .line,
startColumn: .column,
endLine: .endLine,
endColumn: .endColumn
}
}
}]
}]' shellcheck-raw.json > findings/shellcheck.json
issues=$(jq 'length' findings/shellcheck.json)
else
echo "[]" > findings/shellcheck.json
issues=0
fi
echo "issues=$issues" >> "$GITHUB_OUTPUT"
rm -f shellcheck-raw.json
if [[ $issues -gt 0 ]]; then
echo "::warning::ShellCheck found $issues issue(s)"
else
echo "ShellCheck analysis complete - no issues"
fi
- name: Generate combined SARIF report
if: always()
run: |
set -euo pipefail
# Combine all findings from individual checks
all_results="[]"
for f in findings/*.json; do
if [[ -f "$f" ]]; then
all_results=$(jq -s '.[0] + .[1]' <(echo "$all_results") "$f")
fi
done
# Generate final SARIF with all rules and results
jq -n --argjson results "$all_results" '{
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "Bash Security Scanner",
"version": "1.0.0",
"informationUri": "https://github.com/koalaman/shellcheck",
"rules": [
{
"id": "hardcoded-secret",
"name": "HardcodedSecret",
"shortDescription": {"text": "Potential hardcoded secret detected"},
"defaultConfiguration": {"level": "error"},
"helpUri": "https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password"
},
{
"id": "unsafe-bash-pattern",
"name": "UnsafeBashPattern",
"shortDescription": {"text": "Potentially unsafe bash pattern detected"},
"defaultConfiguration": {"level": "warning"}
}
]
}
},
"results": $results
}]
}' > security-results.sarif
echo "Generated SARIF with $(echo "$all_results" | jq 'length') finding(s)"
- name: Check for failures
if: always()
run: |
# Fail the job if any security issues were found
secrets="${{ steps.hardcoded-secrets.outputs.issues }}"
patterns="${{ steps.unsafe-patterns.outputs.issues }}"
if [[ "${secrets:-0}" -gt 0 ]]; then
echo "::error::Hardcoded secrets check failed with $secrets issue(s)"
exit 1
fi
if [[ "${patterns:-0}" -gt 0 ]]; then
echo "::error::Unsafe patterns check failed with $patterns issue(s)"
exit 1
fi
echo "All security checks passed"
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@34950e1b113b30df4edee1a6d3a605242df0c40b # v4
if: always()
with:
sarif_file: security-results.sarif
category: bash-security
continue-on-error: true # Don't fail if SARIF upload fails (e.g., on forks)
- name: Upload security report artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: always()
with:
name: bash-security-report
path: security-results.sarif
if-no-files-found: ignore