Blog
June 2, 2026 / 8 min read

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.

github actions security scanning sarif github code scanning pre-commit hook python security ekmire build guard ci github security tab findings

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: true on 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

  1. Install the pre-commit hook: ekmire hook install — catches findings before they reach CI at all.
  2. 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.
  3. Keep the daily cron: the retroactive coverage is in the schedule block. 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.

Sources and further reading