Secure Your CI: GitHub Actions Security Scanning and SARIF
Most CI security only scans new PRs. Here's how GitHub Actions security scanning with SARIF catches secrets committed months before any rule existed.
Most teams add security scanning to CI the wrong way: triggered by an incident, bolted onto an existing workflow, checking only the files changed in the current PR. A scan that only runs on new code is a scan that misses three months of committed secrets.
This post covers ekmire Build Guard as a CI gate — three ways to run it, a full walkthrough of the GitHub Actions workflow, and the architectural reason why a daily scheduled scan retroactively catches problems that a PR-only scan would never find.
What Build Guard catches
Build Guard runs two engines on every file it scans.
The regex engine is language-agnostic and fast. It catches hardcoded secrets — AWS access keys starting with AKIA, GCP service account keys, Stripe live keys, GitHub personal access tokens, SSH private keys, and high-entropy strings in assignment patterns. It also flags static prompt injection phrases and unpinned MCP server configs that are vulnerable to rug pulls.
The AST engine goes deeper into Python files. It catches semantic patterns that regex cannot: subprocess.run with shell=True and user-controlled input, f-string interpolation inside SQL execute() calls, dangerous deserialization via pickle.loads, and hidden instruction injection inside code comments — the pattern behind CVE-2025-53773, the GitHub Copilot RCE via poisoned comments. These are exactly the classes of vulnerability that AI-generated code introduces without a developer noticing.
When a finding is intentional and not a real risk, suppression is one line added inline:
# ekmire-ignore:HARDCODED_SECRET
Suppressed findings still appear in the dashboard as build.suppressed events, giving the team a record of what was deliberately skipped — not a silent exception that disappears from history.
Three ways to run Build Guard
ekmire is designed to meet developers at the right point in their workflow. There are three natural integration points, each with a different scope.
1. Pre-commit hook (local gate)
ekmire hook install
Installs a git pre-commit hook that runs both engines on staged files before every commit. The commit is blocked if high or critical findings are present. Nothing leaves the machine until the scan passes. This is the fastest feedback loop — findings surface at the moment the developer is still thinking about the code.
2. Manual deep scan (ad-hoc review)
ekmire scan --deep . --output sarif
--deep scans the entire directory tree, not just staged or git-tracked files. Useful for auditing a legacy codebase, a cloned repo you did not write, or any directory of scripts that lives outside a git repository.
3. GitHub Actions (automated CI)
This is where findings become visible to the entire team in the GitHub Security tab — and where the daily scheduled scan that retroactively catches old code becomes possible.
The GitHub Actions workflow walkthrough
ekmire ships a ready-made workflow at templates/ekmire-sarif.yml. Drop it into .github/workflows/ and it runs without further configuration.
name: ekmire Security Scan
on:
push:
branches: [main, master]
pull_request:
schedule:
- cron: "0 3 * * *" # daily at 03:00 UTC
jobs:
mire-scan:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Install ekmire CLI
run: pip install ekmire
- name: Run deep scan (SARIF output)
env:
EKMIRE_API_KEY: ${{ secrets.EKMIRE_API_KEY }}
run: |
mire scan --deep . --output sarif --fail-on high > ekmire-results.sarif
continue-on-error: true
- name: Upload to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: ekmire-results.sarif
category: ekmire
A few design decisions worth understanding:
--fail-on high— exits with code 1 when any high or critical finding is present. This makes the CI job fail, blocking a merge. Remove it if you want findings surfaced without blocking the build while the team works through the backlog.security-events: write— the minimum GitHub permission needed to upload SARIF. Without it, the upload step fails silently, with no error message, and findings never appear in the Security tab.continue-on-error: trueon the scan step — lets the SARIF upload proceed even when the scan exits with code 1. Without this, a failing scan prevents findings from reaching the Security tab entirely, which defeats the purpose.if: always()on the upload step — uploads SARIF regardless of whether previous steps succeeded or failed.EKMIRE_API_KEY— optional. Omit it and the scan runs entirely offline against the locally cached rule bundle. Add it to also forward events to your cloud dashboard for the full event feed and alerting.
Why the daily cron is the most important line in the file
Most teams configure their CI scanner to run on push and pull request. That is correct but incomplete. A PR-only scan catches problems in code being written today. It does not catch problems in code committed three months ago when no detection rule existed for them yet.
ekmire's threat feed is live. A daily pipeline fetches new CVEs from the NVD database, generates candidate detection rules using an LLM, puts them through admin review, and publishes Ed25519-signed rule bundles to every connected CLI within 60 seconds. When a new rule ships today, it describes a pattern that may already exist in your repository history.
The cron: "0 3 * * *" line runs a full --deep scan against the entire repository every morning at 03:00 UTC. If a rule for a newly discovered AWS credential format ships overnight, the morning scan finds every instance of it — including the one committed in February by a developer who has since left the team. A PR-only scanner would never have seen that file again.
This is why the template includes the schedule trigger. Do not remove it to simplify the workflow.
What findings look like in the GitHub Security tab
After SARIF is uploaded, findings appear under Security → Code scanning → ekmire in your repository. Each finding includes:
- the rule ID — e.g.
HARDCODED_AWS_KEY,CMD_INJECTION_FSTRING,HIDDEN_INSTRUCTION_IN_COMMENT - severity: critical, high, or medium
- exact file and line number
- a description of the pattern and a remediation hint
To dismiss a finding from the Security tab, add the inline suppression comment and let the next scheduled scan run:
# ekmire-ignore:HARDCODED_AWS_KEY
The suppression appears in your ekmire dashboard as a build.suppressed event — not silently discarded. Your team retains an audit trail of what was skipped, when, and in which file.
SARIF is a widely supported format. The same output ekmire produces for GitHub can be consumed by any SIEM, security platform, or tool that accepts SARIF 2.1.0. You are not locked into GitHub as the only destination for findings.
Three actions to take today
- Install the pre-commit hook:
ekmire hook install— catches findings before they reach CI at all. - Drop the template into your workflows directory: one file at
.github/workflows/ekmire-sarif.yml, zero configuration required beyond the optional API key for dashboard forwarding. - Keep the daily cron: the retroactive coverage is in the
scheduleblock. Removing it to "simplify" the workflow trades away the most valuable part of the integration.
Once commit-time scanning is in place, the natural next layer is runtime protection. The two-line WAF post covers how to add the ekmire proxy in front of your app without touching a line of application code — closing the loop from what you commit to what reaches users.