diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..e4ccac2b
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,29 @@
+version: 2
+updates:
+ # Go module dependencies.
+ - package-ecosystem: gomod
+ directory: "/"
+ schedule:
+ interval: weekly
+ day: monday
+ open-pull-requests-limit: 5
+ commit-message:
+ prefix: "deps"
+ groups:
+ go-deps:
+ patterns:
+ - "*"
+
+ # Versions of the actions pinned in our workflows.
+ - package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: weekly
+ day: monday
+ open-pull-requests-limit: 5
+ commit-message:
+ prefix: "ci"
+ groups:
+ actions:
+ patterns:
+ - "*"
diff --git a/.github/workflows/bpfcompat-example-hosted.yml b/.github/workflows/bpfcompat-example-hosted.yml
new file mode 100644
index 00000000..95c9e010
--- /dev/null
+++ b/.github/workflows/bpfcompat-example-hosted.yml
@@ -0,0 +1,94 @@
+name: bpfcompat-example-hosted
+
+# Zero-infrastructure on-ramp: this runs the full QEMU VM compatibility gate on a
+# stock GitHub-hosted runner. No self-hosted runner is required. GitHub-hosted
+# Linux runners now expose /dev/kvm, so VM validation is hardware-accelerated;
+# if a runner ever lacks KVM, bpfcompat degrades to TCG software emulation
+# (correct results, slower) instead of failing.
+
+on:
+ workflow_dispatch:
+ pull_request:
+ paths:
+ - "cmd/**"
+ - "internal/**"
+ - "validator/**"
+ - "examples/**"
+ - "matrices/**"
+ - "vm/**"
+ - "action.yml"
+ - "Makefile"
+ - "go.mod"
+ - "go.sum"
+ - ".github/workflows/bpfcompat-example-hosted.yml"
+
+permissions:
+ contents: read
+
+jobs:
+ compatibility-gate:
+ name: Compatibility Gate (hosted runner)
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Report KVM acceleration status
+ shell: bash
+ run: |
+ set -euo pipefail
+ if [[ -e /dev/kvm ]]; then
+ echo "::notice::/dev/kvm present - VM validation is hardware-accelerated."
+ else
+ echo "::warning::/dev/kvm not found - falling back to TCG software emulation (slower, still correct)."
+ fi
+
+ - name: Install host dependencies
+ shell: bash
+ run: |
+ set -euo pipefail
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends \
+ qemu-system-x86 qemu-utils clang llvm libbpf-dev libelf-dev zlib1g-dev \
+ pkg-config jq
+
+ - name: Build validator and fixture artifacts
+ shell: bash
+ run: |
+ set -euo pipefail
+ make validator-static
+ make examples
+
+ - name: Fetch VM image for dev-one profile
+ shell: bash
+ run: |
+ set -euo pipefail
+ make vm-ubuntu-22
+
+ - name: Run bpfcompat action
+ uses: ./
+ with:
+ suite: suites/dev-functional.yaml
+ suite-out: reports/ci-dev-functional-suite.json
+ suite-markdown: reports/ci-dev-functional-suite.md
+ timeout: 12m
+ concurrency: "1"
+ build: "true"
+
+ - name: Upload reports
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: bpfcompat-reports-${{ github.run_id }}
+ if-no-files-found: warn
+ path: |
+ reports/ci-dev-functional-suite.json
+ reports/ci-dev-functional-suite.md
+ reports/suites/dev-functional/*.json
+ reports/suites/dev-functional/*.md
+ .bpfcompat/runs/**/targets/**/serial.log
+ .bpfcompat/runs/**/targets/**/libbpf.log
+ .bpfcompat/runs/**/targets/**/validator-result.json
+ .bpfcompat/runs/**/targets/**/validator.stderr
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 00000000..0a5427ca
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,49 @@
+name: codeql
+
+# Static analysis for the Go code (cmd/, internal/) via GitHub CodeQL.
+# build-mode: none analyzes the source directly, so this does not need the
+# C/libbpf validator toolchain. Results surface under Security > Code scanning.
+#
+# Security note: no untrusted ${{ github.event.* }} value is interpolated into
+# any run: shell here, so workflow-injection via PR titles/branches does not
+# apply to this file.
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: "27 4 * * 1"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ analyze:
+ name: Analyze (Go)
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ permissions:
+ contents: read
+ security-events: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: go
+ build-mode: none
+ queries: security-and-quality
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:go"
diff --git a/.github/workflows/firecracker-preflight.yml b/.github/workflows/firecracker-preflight.yml
index ec25e450..c78431e1 100644
--- a/.github/workflows/firecracker-preflight.yml
+++ b/.github/workflows/firecracker-preflight.yml
@@ -1,7 +1,9 @@
name: firecracker-preflight
-# Firecracker executable proof. This requires a self-hosted x64 KVM runner
-# because GitHub-hosted runners do not expose /dev/kvm.
+# Firecracker executable proof. This uses a self-hosted x64 KVM runner for the
+# full Firecracker toolchain (Firecracker binary, busybox, cpio, uncompressed
+# guest kernel). The default QEMU lane runs on stock GitHub-hosted runners,
+# which now expose /dev/kvm (see bpfcompat-example-hosted.yml).
on:
workflow_dispatch:
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
new file mode 100644
index 00000000..8f18659a
--- /dev/null
+++ b/.github/workflows/scorecard.yml
@@ -0,0 +1,57 @@
+name: scorecard
+
+# OpenSSF Scorecard supply-chain risk assessment. Publishes results to the
+# public Scorecard API (so the README badge resolves) and uploads SARIF to
+# GitHub code scanning.
+#
+# Security note: no untrusted ${{ github.event.* }} value is interpolated into
+# any run: shell here, so workflow-injection does not apply to this file.
+
+on:
+ branch_protection_rule:
+ schedule:
+ - cron: "41 5 * * 1"
+ push:
+ branches: [main]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ analysis:
+ name: Scorecard analysis
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ permissions:
+ security-events: write # upload SARIF to code scanning
+ id-token: write # publish results to the Scorecard API
+ contents: read
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Run analysis
+ uses: ossf/scorecard-action@v2
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ publish_results: true
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: scorecard-results
+ path: results.sarif
+ retention-days: 5
+
+ - name: Upload to code scanning
+ uses: github/codeql-action/upload-sarif@v3
+ with:
+ sarif_file: results.sarif
diff --git a/CHANGELOG.md b/CHANGELOG.md
index da8d475d..9a978f1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,19 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once a
## [Unreleased]
### Added
+- Supply-chain trust signals: GitHub CodeQL static analysis
+ (`.github/workflows/codeql.yml`), OpenSSF Scorecard
+ (`.github/workflows/scorecard.yml`), and Dependabot
+ (`.github/dependabot.yml`, Go modules + pinned actions). README gains CI,
+ CodeQL, Scorecard, and license badges; `docs/supply-chain.md` documents the
+ controls and the maintainer-side repo settings (branch protection, secret
+ scanning, OpenSSF Best Practices registration). SBOM + cosign keyless signing
+ already shipped in `release-artifacts.yml`.
+- Zero-infrastructure CI on-ramp: `.github/workflows/bpfcompat-example-hosted.yml`
+ runs the full QEMU VM compatibility gate on a stock GitHub-hosted
+ `ubuntu-latest` runner. GitHub-hosted Linux runners now expose `/dev/kvm`, so
+ no self-hosted runner is required for the default lane. The README now leads
+ with this path and with the Falco `modern_bpf` 5-kernel proof.
- `bpfcompat kernel-freshness`: compares the kernel release each VM profile
last validated (`vm/kernel-baselines.yaml`) against the per-distro kernel
inventory published weekly by falcosecurity/kernel-crawler, flagging
@@ -27,6 +40,12 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once a
--count N` generates the derived profiles and matrix from the
kernel-crawler inventory. Ubuntu only for now.
+### Changed
+- QEMU executor falls back to TCG software emulation when `/dev/kvm` is absent
+ (`-accel tcg -cpu max`) instead of failing the launch, so VM validation stays
+ correct on runners without hardware virtualization (just slower). Hosted-KVM
+ runners keep `-enable-kvm -cpu host`.
+
## [0.1.5] - 2026-06-11
### Fixed
diff --git a/README.md b/README.md
index be19187f..6056d48a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,10 @@
# bpfcompat
+[](https://github.com/Kernel-Guard/bpfcompat/actions/workflows/ci.yml)
+[](https://github.com/Kernel-Guard/bpfcompat/actions/workflows/codeql.yml)
+[](https://scorecard.dev/viewer/?uri=github.com/Kernel-Guard/bpfcompat)
+[](LICENSE)
+
`bpfcompat` is an open-source compatibility validator for compiled eBPF
artifacts. It runs real libbpf load/attach checks against Linux kernel profiles
and produces JSON/Markdown reports that can fail CI when an artifact regresses.
@@ -9,6 +14,47 @@ The core question is simple:
> Will this `.bpf.o` load and attach on the kernels I care about, and if not,
> what failed?
+## Why not just rely on CO-RE / BTFHub?
+
+CO-RE makes a `.bpf.o` *portable in principle*; it does not guarantee it will
+*load* on a given kernel. Real-world failures that CO-RE does not prevent:
+
+- missing or partial kernel BTF,
+- CO-RE relocation errors against a divergent kernel,
+- unsupported map types (e.g. ringbuf before 5.8),
+- unsupported program/attach types,
+- capability and kernel-config differences.
+
+`bpfcompat` answers the empirical question CO-RE leaves open: *does it actually
+load and attach here?* — by running the artifact in a real kernel.
+
+## Try it in CI — no self-hosted runner
+
+GitHub-hosted Linux runners now expose `/dev/kvm`, so the full QEMU VM
+compatibility gate runs on a stock `ubuntu-latest` runner. No KVM box of your
+own, no self-hosted runner. See
+[`.github/workflows/bpfcompat-example-hosted.yml`](.github/workflows/bpfcompat-example-hosted.yml)
+for a copy-paste workflow; if a runner ever lacks KVM, validation degrades to
+TCG software emulation (correct, just slower) instead of failing.
+
+## Proof: a real Falco probe catches a real regression
+
+`bpfcompat` validates Falco's `modern_bpf` probe (`bpf_probe.o`, ~64 programs)
+exactly as Falco's own loader runs it, across a 5-kernel matrix:
+
+| Profile | Host kernel | Status | Why |
+|---|---|---|---|
+| `ubuntu-20.04-5.4` | `5.4.0-216` | ❌ fail | `UNSUPPORTED_MAP_TYPE` — ringbuf needs ≥ 5.8 |
+| `ubuntu-22.04-5.15` | `5.15.0-173` | ✅ pass | loads; selects `*_old_x` syscall variants |
+| `debian-12-6.1` | `6.1.0-47` | ✅ pass | loads; full variant set |
+| `ubuntu-23.10-6.5` | `6.5.0-44` | ✅ pass | loads; full variant set |
+| `ubuntu-24.04-6.8` | `6.8.0-106` | ✅ pass | loads; full variant set |
+
+The red `5.4` row is the point: a kernel below Falco's real floor is flagged
+*before* shipping, with the exact mechanism (`ringbuf_maps` create returns
+`-EINVAL`) and remediation — not a generic "it broke." Reproduce this matrix
+locally; see [`docs/falco-parity.md`](docs/falco-parity.md).
+
## Current Status
The project is a serious MVP for compatibility evidence and CI gating. It is
@@ -51,9 +97,11 @@ does not remove them from git history.
## Prerequisites
-For the main QEMU/KVM path:
+For the main QEMU path:
-- Linux host with `/dev/kvm`
+- Linux host (a GitHub-hosted `ubuntu-latest` runner works; `/dev/kvm`
+ enables hardware acceleration, and bpfcompat falls back to TCG software
+ emulation when it is absent)
- Go 1.22+
- `make`
- `clang`
@@ -247,7 +295,10 @@ native ARM64 KVM runner.
This repository includes a composite action that runs `bpfcompat` and appends
the Markdown report to the GitHub Actions job summary. VM-backed validation
-requires a self-hosted Linux runner with KVM access (`/dev/kvm`).
+runs on a stock GitHub-hosted `ubuntu-latest` runner (which now exposes
+`/dev/kvm`); a self-hosted KVM runner is only needed for wide matrices, ARM64,
+or the Firecracker lane. See
+[`.github/workflows/bpfcompat-example-hosted.yml`](.github/workflows/bpfcompat-example-hosted.yml).
Suite mode (recommended — gates the whole collection):
@@ -356,6 +407,7 @@ planning notes — useful for contributors, not needed to use the tool):
- [`docs/acceptance-tests.md`](docs/acceptance-tests.md)
- [`docs/falco-parity.md`](docs/falco-parity.md)
+- [`docs/supply-chain.md`](docs/supply-chain.md) — supply-chain controls and maintainer repo settings
- [`docs/backend-execution-proof.md`](docs/backend-execution-proof.md)
- [`docs/external-ci-proof.md`](docs/external-ci-proof.md)
- remaining `docs/*.md` proof, runbook, and checklist documents
@@ -385,6 +437,30 @@ Operator guidance:
- do not enable internal-host or `file://` fetches outside controlled tests;
- run host-loading flows only through a local policy-gated agent path.
+### Supply-chain posture
+
+- **Static analysis:** GitHub CodeQL (`codeql.yml`) plus `govulncheck` and
+ `golangci-lint` in CI on every PR.
+- **Dependency updates:** Dependabot (`dependabot.yml`) for Go modules and
+ pinned GitHub Actions, grouped weekly.
+- **Risk scoring:** OpenSSF Scorecard (`scorecard.yml`), published to the
+ public Scorecard API (badge above).
+- **Signed releases:** tag builds produce a CycloneDX SBOM and cosign keyless
+ (Sigstore OIDC) signatures over the binaries, `SHA256SUMS`, and SBOM
+ (`release-artifacts.yml`). Verify with:
+
+ ```bash
+ cosign verify-blob \
+ --certificate SHA256SUMS.crt --signature SHA256SUMS.sig \
+ --certificate-identity-regexp 'https://github.com/Kernel-Guard/bpfcompat/.*' \
+ --certificate-oidc-issuer https://token.actions.githubusercontent.com \
+ SHA256SUMS
+ ```
+
+Maintainer-side repo settings (branch protection, secret-scanning push
+protection, OpenSSF Best Practices registration) are tracked in
+[`docs/supply-chain.md`](docs/supply-chain.md).
+
## License
Apache-2.0. See [`LICENSE`](LICENSE).
diff --git a/docs/falco-parity.md b/docs/falco-parity.md
index 3a792bdd..5f545310 100644
--- a/docs/falco-parity.md
+++ b/docs/falco-parity.md
@@ -10,6 +10,7 @@ multi-architecture runners, and release matrix publication.
| Capability | Current implementation |
|---|---|
| CI/release gate | `action.yml`, `.github/workflows/bpfcompat-example.yml`, and external proof repo. |
+| Zero-infra hosted-runner gate | `.github/workflows/bpfcompat-example-hosted.yml` runs the full QEMU VM gate on stock `ubuntu-latest` (GitHub-hosted runners now expose `/dev/kvm`; TCG software-emulation fallback when absent). |
| Multi-artifact input | `bpfcompat suite` and `suites/dev-functional.yaml`. |
| Manifest-described test steps | Manifest `functional_tests` are converted into validator functional plans. |
| Behavioral event proof | `examples/functional-execve` attaches to execve, triggers `/bin/true`, and observes the trace marker while the BPF link is alive. |
diff --git a/docs/supply-chain.md b/docs/supply-chain.md
new file mode 100644
index 00000000..9dd111a7
--- /dev/null
+++ b/docs/supply-chain.md
@@ -0,0 +1,66 @@
+# Supply-Chain & Trust Posture
+
+This document records the supply-chain controls that ship as code in this
+repository and the maintainer-side settings that must be configured in the
+GitHub repository UI/API (they cannot be set from committed files).
+
+## In-repo controls (automated)
+
+| Control | Where | Trigger |
+|---|---|---|
+| CodeQL static analysis (Go, security-and-quality) | `.github/workflows/codeql.yml` | push/PR to `main`, weekly, manual |
+| OpenSSF Scorecard | `.github/workflows/scorecard.yml` | push to `main`, weekly, `branch_protection_rule`, manual |
+| Dependency updates (gomod + github-actions) | `.github/dependabot.yml` | weekly (Mondays) |
+| Vulnerability scan | `govulncheck` in `.github/workflows/ci.yml` | every PR |
+| Lint | `golangci-lint` in `.github/workflows/ci.yml` | every PR |
+| SBOM (CycloneDX) | `.github/workflows/release-artifacts.yml` | push to `main`, tags, manual |
+| Keyless signing (cosign / Sigstore OIDC) | `.github/workflows/release-artifacts.yml` | tag releases (`v*`) |
+| SHA256 checksums | `.github/workflows/release-artifacts.yml` | all builds |
+
+### Verifying a signed release
+
+```bash
+cosign verify-blob \
+ --certificate SHA256SUMS.crt --signature SHA256SUMS.sig \
+ --certificate-identity-regexp 'https://github.com/Kernel-Guard/bpfcompat/.*' \
+ --certificate-oidc-issuer https://token.actions.githubusercontent.com \
+ SHA256SUMS
+sha256sum -c SHA256SUMS
+```
+
+## Maintainer-side settings (configure once, in the GitHub UI/API)
+
+These are not files in the repo. Track their status here.
+
+- [x] **Branch protection on `main`** (wired 2026-06-14): blocks force-pushes
+ and deletions, requires conversation resolution, and requires the CI
+ checks `Test (-race)`, `golangci-lint`, `govulncheck`, `Build (Go side)`.
+ `enforce_admins` is off (solo-maintainer bypass) and no review count is
+ required. Add `Analyze (Go)` to the required contexts once `codeql.yml`
+ is on `main`. Set via:
+ `gh api -X PUT repos/Kernel-Guard/bpfcompat/branches/main/protection --input -`
+- [x] **Secret scanning + push protection** (wired 2026-06-14): enabled via
+ `gh api -X PATCH repos/Kernel-Guard/bpfcompat` with
+ `security_and_analysis.secret_scanning` + `secret_scanning_push_protection`.
+- [x] **Dependabot alerts + security updates** (wired 2026-06-14): enabled via
+ `gh api -X PUT .../vulnerability-alerts` and `.../automated-security-fixes`.
+- [ ] **Code scanning default setup OFF if CodeQL workflow is used** (avoid
+ double analysis): Settings → Code security → Code scanning. Do this after
+ `codeql.yml` lands on `main`.
+- [ ] **OpenSSF Best Practices badge**: register the project at
+ , then add the returned badge:
+ `[](https://www.bestpractices.dev/projects/)`
+ to the README badge row. (Scorecard is automated; this one needs a
+ one-time self-assessment.)
+- [ ] **Scorecard badge publishing**: the first successful `scorecard.yml` run
+ on `main` populates
+ and resolves the README badge.
+
+## Notes
+
+- `publish_results: true` in the Scorecard workflow requires the repository to
+ be public; it is a no-op signal otherwise.
+- CodeQL uses `build-mode: none`, so it does not need the C/libbpf validator
+ toolchain (the validator is a separate non-Go component).
+- The `Kernel-Guard` GitHub org and the "KernelGuard" site branding should be
+ reconciled before a wider public launch; see the project roadmap.
diff --git a/internal/vm/qemu.go b/internal/vm/qemu.go
index 6b685469..5fd45844 100644
--- a/internal/vm/qemu.go
+++ b/internal/vm/qemu.go
@@ -631,12 +631,37 @@ func qemuSystemBinary(profile Profile) string {
}
func qemuMachineArgs(profile Profile) []string {
- switch normalizeArch(profile.Arch) {
+ return machineArgsFor(normalizeArch(profile.Arch), kvmAvailable())
+}
+
+// machineArgsFor is the pure acceleration decision: with KVM it pins -cpu host
+// for speed; without it (e.g. some hosted runners) it degrades to TCG software
+// emulation with -cpu max so results stay correct rather than the launch
+// failing outright.
+func machineArgsFor(arch string, kvm bool) []string {
+ switch arch {
case "aarch64":
- return []string{"-machine", "virt,accel=kvm", "-cpu", "host"}
+ if kvm {
+ return []string{"-machine", "virt,accel=kvm", "-cpu", "host"}
+ }
+ return []string{"-machine", "virt,accel=tcg", "-cpu", "max"}
default:
- return []string{"-enable-kvm", "-cpu", "host"}
+ if kvm {
+ return []string{"-enable-kvm", "-cpu", "host"}
+ }
+ return []string{"-accel", "tcg", "-cpu", "max"}
+ }
+}
+
+// kvmAvailable reports whether hardware-accelerated virtualization is usable on
+// this host. When /dev/kvm is missing (some CI runners), callers degrade to TCG
+// software emulation rather than failing the QEMU launch outright.
+func kvmAvailable() bool {
+ info, err := os.Stat("/dev/kvm")
+ if err != nil {
+ return false
}
+ return info.Mode()&os.ModeCharDevice != 0
}
func normalizeArch(arch string) string {
diff --git a/internal/vm/qemu_test.go b/internal/vm/qemu_test.go
index 5081f118..995e40e4 100644
--- a/internal/vm/qemu_test.go
+++ b/internal/vm/qemu_test.go
@@ -409,3 +409,25 @@ func TestProgVariantArgs(t *testing.T) {
t.Fatalf("unexpected args:\n got %q\nwant %q", got, want)
}
}
+
+func TestMachineArgsForAccelFallback(t *testing.T) {
+ cases := []struct {
+ name string
+ arch string
+ kvm bool
+ want []string
+ }{
+ {"x86 kvm", "x86_64", true, []string{"-enable-kvm", "-cpu", "host"}},
+ {"x86 tcg", "x86_64", false, []string{"-accel", "tcg", "-cpu", "max"}},
+ {"arm kvm", "aarch64", true, []string{"-machine", "virt,accel=kvm", "-cpu", "host"}},
+ {"arm tcg", "aarch64", false, []string{"-machine", "virt,accel=tcg", "-cpu", "max"}},
+ }
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ got := machineArgsFor(tc.arch, tc.kvm)
+ if strings.Join(got, " ") != strings.Join(tc.want, " ") {
+ t.Fatalf("machineArgsFor(%q, %v) = %v, want %v", tc.arch, tc.kvm, got, tc.want)
+ }
+ })
+ }
+}