Thanks for your interest in bpfcompat. This document is the short, opinionated version of how to work with the codebase.
By participating, you agree to follow the Code of Conduct. Project roles and decision-making are described in GOVERNANCE.md.
- Treat other contributors professionally. Follow the Code of Conduct in project spaces and when representing the project elsewhere.
- Security disclosures go through
SECURITY.md, not public issues. If you've found a vulnerability, please email the address listed there and wait for a coordinated disclosure window before publishing. - Tests are mandatory for behavior changes. The CI gate runs
go test -race,go vet,golangci-lint, andgovulncheck. Bench/fuzz additions are welcome but not required for every PR. - No
panic()in production code. The only acceptable process-terminating call isos.Exitfromcmd/bpfcompat/main.go. - Don't loosen the JSON body cap or DisallowUnknownFields. They're load
bearing for the API version contract (see
docs/openapi.yamland the/api/v1/move). Changes that need a richer wire shape should ride av2route, not relax the v1 one.
make build # produces bin/bpfcompat with version ldflags
make validator-static # builds the C validator (needs libbpf-dev + clang)
make test # go test ./...
make openapi-sync # copies docs/openapi.yaml into the embed locationRun the API server locally:
BPFCOMPAT_API_WRITE_KEY=dev-key \
BPFCOMPAT_API_ENABLE_METRICS=true \
BPFCOMPAT_LOG_LEVEL=debug \
BPFCOMPAT_LOG_FORMAT=text \
./bin/bpfcompat serve --addr 127.0.0.1:8080For VM-backed validation you'll need qemu-system-x86_64, qemu-img,
ssh, scp, jq, pkg-config, libbpf-dev, and /dev/kvm. make doctor
checks for them.
- Open an issue (or comment on an existing one) describing the change before you start a non-trivial PR. Security-adjacent changes especially benefit from an early design conversation.
- Branch from
main. Keep PRs focused — one logical change per PR. - Run
golangci-lint run --new-from-rev=origin/mainlocally before pushing. Pre-existing lint debt is tracked separately; new code should be clean. - Update
CHANGELOG.mdunder the[Unreleased]heading with a one-line summary of your change. Categorize as Added / Changed / Fixed / Security. - If you touch
docs/openapi.yaml, runmake openapi-syncand commit the updatedinternal/api/openapi_spec.yaml. CI fails on drift.
gofmt+goimportsenforced viagolangci-lint.- Public types and exported functions need at least a one-line doc comment.
- Comments explain why, not what. Don't repeat the code in English. Surrounding context (related security finding, historical bug, related design decision) is what we want to capture.
- Tests live next to the code (
foo.go↔foo_test.go). Integration tests that span packages can live underinternal/<pkg>/integration. - Errors: wrap with
%wand surface domain sentinels (registry.ErrNotFound,cloudregistry.ErrUnauthorized) so handlers can map to status codes without string matching.
Imperative mood, capitalized first line under ~70 characters, optional body wrapped at 80. Reference the issue number when relevant. Example:
api: refuse new validate submissions during shutdown drain (#42)
Bind the global shutting flag at the top of handleValidateStart so callers
get a clean 503 instead of starting a job we're about to cancel mid-flight.
Goroutines launched before the flag flipped continue to a normal terminal
state and drain via inflight.WaitGroup.
- Register via
registerAPIRoute(mux, "/your/route", handler)so both/api/v1/your/routeand the legacy/api/your/routeget the same handler. - Pick the right auth gate:
- State change →
requireWriteAuthorizationForAction(w, r, "your_action") - Read-only →
requireReadAuthorizationForAction(w, r, "your_action")
- State change →
- Use
decodeJSONBody(w, r, &req)for JSON bodies — neverjson.NewDecoderdirectly (the helper enforces size/unknown-field/smuggling rules). - Record the route in
docs/openapi.yamland re-runmake openapi-sync. - Add a tests file that exercises the auth gate, the happy path, and at least one error path.
- If the handler triggers a runtime side effect (fetch, execute, registry
change), bump the corresponding metric counter via the helpers in
internal/api/metrics.go.
- Define the constant in the package that consumes it (don't centralize — the consumer documents the semantics).
- Surface the default + description in the env reference (
docs/env-reference.md) and viabpfcompat env. - Document any operational risk (e.g.
BPFCOMPAT_FETCH_ALLOW_INTERNAL_HOSTScarries an SSRF unlock) inSECURITY.mdunder the hardening guidance section.
By submitting a contribution you agree to license it under the terms in
LICENSE (Apache-2.0). See the appendix in that file for the
required boilerplate header on new source files.