Muninn is an all-in-one open-source security scanner for GitHub Actions pipelines and self-hosted CI, built by Skald Lab.
Named after Odin's raven of Memory from Norse mythology, Muninn remembers every vulnerability it has ever seen β and reports them all back in one unified format.
Muninn orchestrates eight best-in-class open-source scanners, normalizes their output into a single finding schema, and delivers results as GitHub PR comments, SARIF uploads, and structured JSON β with a single uses: line.
Add this to any GitHub Actions workflow:
- name: Muninn Security Scan
uses: skaldlab/muninn@v0.3.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
fail-on: highMuninn runs all eight scanners, posts a summary comment on your pull request, uploads SARIF to the GitHub Security tab, and fails the check when findings meet or exceed your fail-on threshold.
| Scanner | What it finds | Upstream |
|---|---|---|
| gitleaks | Secrets, API keys, and credentials in source | gitleaks/gitleaks |
| zizmor | CI/CD pipeline misconfigurations and injection risks | woodruffw/zizmor |
| actionlint | GitHub Actions syntax errors and security anti-patterns | rhysd/actionlint |
| poutine | Supply chain risks in CI/CD (unpinned actions, risky patterns) | boostsecurityio/poutine |
| semgrep | Application SAST (injection, insecure patterns, secrets in code) | semgrep.dev |
| osv-scanner | Known CVEs in dependencies (Go, npm, pip, and more) | google/osv-scanner |
| trivy | Container image and filesystem vulnerabilities | aquasecurity/trivy |
| checkov | Infrastructure-as-Code misconfigurations (Terraform, K8s, Docker) | bridgecrewio/checkov |
Running multiple dependency scanners means the same CVE can surface more than
once β OSV-Scanner flags it in a lockfile while Trivy flags it in a container
layer. Muninn collapses these into a single finding keyed on the advisory id
(a CVE-β¦ is preferred over GHSA-β¦ so the same vulnerability converges across
scanners) scoped to the affected package, so distinct packages sharing a CVE
stay separate.
Because an aggregated finding no longer belongs to one tool, dependency findings
render under a neutral [dependency] heading (rather than [osv-scanner]) with
structured detail, and the scanners are surfaced via attribution instead:
- PR comment β
Package,Advisory(with the shared CVE in parentheses),Detected by, and aSourceslist showing where each scanner saw it (e.g.package-lock.json (osv-scanner),node:18 (trivy)). - JSON report β
detected_by(the scanner list) andsources(per-scannertool+filepairs). - SARIF β a
detectedByproperty on the result.
The severity summary counts these aggregated unique findings, not raw scanner hits, because deduplication runs before any report is written.
Create a muninn.yml at the root of your repository to customize scanner behavior, severity thresholds, and suppressions.
version: 1
# Minimum severity to fail the run.
# Options: critical | high | medium | low | info
fail-on: critical
scanners:
gitleaks:
enabled: true
zizmor:
enabled: true
actionlint:
enabled: true
poutine:
enabled: true
semgrep:
enabled: true
rulesets:
- p/security-audit
- p/secrets
exclude-paths:
- tests/
- fixtures/
osv-scanner:
enabled: true
trivy:
enabled: true
# Optional: narrow what Trivy reports (default: all levels).
# severity: [CRITICAL, HIGH]
ignore-unfixed: true
checkov:
enabled: true
skip-checks: []
# Suppress specific findings. Every entry must include a reason.
suppressions:
- id: fixtures/
reason: "Intentional test fixtures, not real secrets or vulnerabilities"
expires: ""
- fingerprint: abc123def456
reason: "Known false positive in generated code"
expires: "2026-12-31T23:59:59Z"
- tool: gitleaks
rule-id: generic-api-key
reason: "Test fixture file, not a real secret"
expires: ""| Field | Type | Default | Description |
|---|---|---|---|
version |
int |
1 |
Config schema version. Must be 1. |
fail-on |
string |
critical |
Minimum severity that causes a non-zero exit code |
Each key under scanners matches a scanner name. All scanners support enabled (default true).
| Scanner | Additional fields | Description |
|---|---|---|
semgrep |
rulesets, exclude-paths |
Semgrep rule packs and path prefixes to skip |
trivy |
severity, ignore-unfixed |
Trivy severity filter (default: UNKNOWN through CRITICAL; omit to scan all levels) and whether to omit unfixed CVEs |
checkov |
skip-checks |
Checkov check IDs to skip |
| Field | Description |
|---|---|
id |
Suppress findings whose file path contains this substring |
fingerprint |
Suppress a specific finding by its Muninn fingerprint |
tool |
Scanner name; with rule-id, both must match. Alone, suppresses all findings from that scanner |
rule-id |
Scanner-native rule identifier; with tool, both must match. Alone, suppresses that rule from any scanner |
reason |
Required. Human-readable justification |
expires |
Optional RFC 3339 UTC timestamp; omit for permanent suppressions |
| Input | Default | Description |
|---|---|---|
token |
${{ github.token }} |
GitHub token for PR comments and SARIF upload |
fail-on |
critical |
Minimum severity to exit non-zero |
config |
muninn.yml |
Path to config file relative to repository root |
format |
sarif,comment |
Comma-separated output formats: sarif, json, comment |
output |
muninn.sarif |
SARIF output path override |
target |
. |
Path to scan (repository root) |
| Output | Description |
|---|---|
findings-count |
Total non-suppressed findings across all scanners |
critical-count |
Critical-severity finding count |
high-count |
High-severity finding count |
medium-count |
Medium-severity finding count |
low-count |
Low-severity finding count |
sarif-path |
Path to the generated SARIF report |
json-path |
Path to the generated JSON report |
| Muninn | Snyk | GitHub Advanced Security | Manual tool chain | |
|---|---|---|---|---|
| License | AGPL-3.0 (open source) | Proprietary SaaS | Proprietary (Enterprise or public repos) | Mixed |
| Data leaves your repo | No β runs on your runner | Yes β code uploaded to Snyk | Yes β processed by GitHub | Depends on tool |
| CI/CD pipeline security | Yes (zizmor, actionlint, poutine) | Limited | No | Requires assembly |
| Supply chain / pipeline risks | Yes (poutine) | Partial | No | Requires assembly |
| Self-hostable | Yes β Docker image or binary | No | No | Yes, with effort |
| Unified findings | Yes β one schema, one comment | Partial | Partial | No β N different formats |
| Setup | One uses: line |
Account + integration | Org licensing | Install and wire each tool |
Muninn is not a replacement for every security program β it is a practical default for teams that want broad coverage without stitching together eight separate tools.
The official image bundles Muninn and all eight scanner binaries:
docker run --rm \
-v "$(pwd):/github/workspace" \
-w /github/workspace \
ghcr.io/skaldlab/muninn:0.3.3 \
--target . \
--output json,sarif \
--fail-on highDownload a release binary from GitHub Releases or install with Go:
go install github.com/skaldlab/muninn@v0.3.3Scanner binaries (gitleaks, semgrep, checkov, and the rest) must be on PATH. The Docker image includes everything pre-installed.
Every release is signed with cosign using keyless (OIDC) signing β there are no long-lived keys to trust. The container image also ships with an SBOM and a max-mode SLSA provenance attestation.
Verify the container image:
cosign verify \
--certificate-identity-regexp '^https://github.com/skaldlab/muninn/\.github/workflows/release\.yml@' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
ghcr.io/skaldlab/muninn:0.3.3Verify release binaries via the signed checksums file (download checksums.txt and the Sigstore bundle checksums.txt.sigstore.json from the release):
cosign verify-blob \
--bundle checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github.com/skaldlab/muninn/\.github/workflows/release\.yml@' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
checksums.txt
# Then confirm the binary matches the verified checksums:
shasum -a 256 -c checksums.txtInspect the image's SBOM and SLSA provenance attestations (attached by BuildKit):
docker buildx imagetools inspect ghcr.io/skaldlab/muninn:0.3.3 \
--format '{{ json .SBOM }}'
docker buildx imagetools inspect ghcr.io/skaldlab/muninn:0.3.3 \
--format '{{ json .Provenance }}'| Flag | Env var | Default | Description |
|---|---|---|---|
--config |
CONFIG_PATH |
muninn.yml |
Path to configuration file |
--target |
SCAN_TARGET |
. |
Repository root to scan |
--fail-on |
FAIL_ON |
from config | Minimum severity to exit non-zero |
--output |
OUTPUT_FORMATS |
json |
Comma-separated formats: json, sarif, comment |
--version |
β | β | Print version and exit |
Contributions are welcome. See CONTRIBUTING.md for scanner development guidelines, test commands, and pull request conventions.
Muninn is licensed under the GNU Affero General Public License v3.0. You are free to use, modify, and distribute Muninn β including running it as a service β but modifications must remain open source under AGPL-3.0.
For commercial licensing or enterprise support, contact hello@skaldlab.dev.
Built with π¦ββ¬ by Skald Lab