Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/semgrep.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Semgrep static analysis
on:
pull_request:
jobs:
semgrep:
permissions:
contents: read
pull-requests: write
# User definable name of this GitHub Actions job.
name: semgrep-oss/scan
# If you are self-hosting, change the following `runs-on` value:
runs-on: ubuntu-latest
container:
# A Docker image with Semgrep installed. Do not change this.
image: semgrep/semgrep:1.152.0
steps:
# Fetch project source with GitHub Actions Checkout.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

# Fetch org-wide custom Semgrep rules from the central repository.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Comment thread
eloymg marked this conversation as resolved.
with:
repository: grafana/security-github-actions
ref: ${{ github.repository == 'grafana/security-github-actions' && (github.head_ref || github.ref_name) || '' }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, the action is pinned but the content isn't. When the workflow runs in grafana/security-github-actions: ref is the PR branch or current branch (so it allows testing rule changes in the repo etc).

When the workflow runs in any other repo (e.g. grafana/some-app): the condition is false, so ref is '' (empty string). With ref: '', actions/checkout uses the default branch of grafana/security-github-actions (main). So:

  • There is no built-in rollback: you can’t point “all consumers” at a “last known good” without changing something (e.g. the ref in the workflow).
  • Any push to the main branch of security-github-actions (mistake or compromise) is immediately what every other repo’s PRs use. No separate “release” or review in the consuming repos.

These points are even more painful if this workflow is ever used in an enforcing/block mode.

It is a good idea to pin the ref to a release tag or commit for consuming repos. Then updates are intentional, you change the tag/SHA in the workflow (or in a shared template) when you want to roll out a new version. Additionally, a bad push to main does not automatically affect consumers until something explicitly updates that ref.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sense. I can do this in a future PR with the config file in place

sparse-checkout: |
semgrep/custom-rules.yaml
semgrep/format-results.sh
path: security-github-actions
- id: semgrep
env:
GITHUB_REPOSITORY: ${{github.repository}}
GITHUB_BRANCH: ${{github.head_ref || github.ref_name}}
run: |
set +e
semgrep scan --error --json --config security-github-actions/semgrep/custom-rules.yaml > /tmp/semgrep-results.json
EXIT_CODE=$?
set -e
if [ $EXIT_CODE -eq 1 ]; then
echo "has_findings=true" >> "$GITHUB_OUTPUT"
{
echo 'SEMGREP_OUTPUT<<SEMGREP_EOF'
bash security-github-actions/semgrep/format-results.sh /tmp/semgrep-results.json
echo 'SEMGREP_EOF'
} >> "$GITHUB_ENV"
fi

if [ $EXIT_CODE -gt 1 ]; then
echo "::error::Semgrep run encounters an error"
cat /tmp/semgrep-results.json
exit 1
fi

HIGH_CRITICAL=$(jq '[.results[] | select(.extra.severity == "HIGH" or .extra.severity == "CRITICAL")] | length' /tmp/semgrep-results.json)
if [ "$HIGH_CRITICAL" -gt 0 ]; then
echo "has_high_critical=true" >> "$GITHUB_OUTPUT"
fi

- if: steps.semgrep.outputs.has_findings == 'true'
uses: int128/comment-action@66317511bc86c47bd51e03059040e8a460a167b8
with:
update-if-exists: recreate
post: |
${{ env.SEMGREP_OUTPUT }}

Comment on lines +58 to +64
- if: steps.semgrep.outputs.has_high_critical == 'true'
run: |
echo "::error::Semgrep found HIGH or CRITICAL severity findings."
exit 1
13 changes: 13 additions & 0 deletions semgrep/custom-rules.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
rules:
- id: deny-actions-create-github-app-token
patterns:
- pattern-regex: "uses:\\s*actions/create-github-app-token"
paths:
include:
- "*.yaml"
- "*.yml"
message: >
Do not use actions/create-github-app-token. Use the organization's
approved [alternative for generating GitHub App tokens](https://enghub.grafana-ops.net/docs/default/component/deployment-tools/platform/vault/github-app-token-broker-migration-guide/).
languages: [generic]
severity: LOW
38 changes: 38 additions & 0 deletions semgrep/format-results.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Format semgrep JSON results into a GitHub-flavored markdown comment.
set -euo pipefail

INPUT_FILE="$1"

RESULTS_COUNT=$(jq '.results | length' "$INPUT_FILE")

if [ "$RESULTS_COUNT" -eq 0 ]; then
exit 0
fi

echo "## Semgrep Findings"
echo ""
echo "**${RESULTS_COUNT}** finding(s) detected."
echo ""
echo "| Severity | Rule | File | Message |"
echo "|----------|------|------|---------|"

jq -r --arg repo "$GITHUB_REPOSITORY" --arg sha "$GITHUB_SHA" '.results[] | {
sev: .extra.severity,
rule: (.check_id | split(".")[-1]),
path: .path,
line: .start.line,
msg: (.extra.message | gsub("\n"; " ") | ltrimstr(" ") | rtrimstr(" ") | gsub("\\|"; "\\|") | gsub("`"; "\\`"))
} | {
icon: (if .sev == "CRITICAL" then "🔴"
elif .sev == "HIGH" then "🟠"
elif .sev == "MEDIUM" then "🟡"
elif .sev == "LOW" then "🔵"
elif .sev == "INFO" then "⚪"
else "⚪" end),
sev: .sev,
rule: .rule,
path: .path,
line: .line,
msg: .msg
} | "| \(.icon) \(.sev) | `\(.rule)` | [`\(.path):\(.line)`](https://github.com/\($repo)/blob/\($sha)/\(.path)#L\(.line)) | \(.msg) |"' "$INPUT_FILE"
Loading