Contributing to AgentFence
June 24, 2026 · View on GitHub
Thanks for your interest in AgentFence. This document covers everything you need to build, test, and submit changes.
AgentFence is a security tool. Changes that affect policy evaluation, audit logging, or redaction receive extra scrutiny.
Prerequisites
- Go: 1.22 or newer (the module declares
go 1.22; newer toolchains work). - make: any reasonably recent GNU make.
- No other runtime dependencies. The project uses the Go standard library plus
gopkg.in/yaml.v3and nothing else.
Verify:
go version # go1.22 or newer
make help # lists available targets
Build
make build # produces ./agentfence
./agentfence version
To embed a release version at build time:
make build VERSION=0.1.0
./agentfence version
# agentfence 0.1.0 linux/amd64
Test
make test # plain go test ./...
make test-race # go test -race with coverage profile (used in CI)
make cover # runs test-race then opens an HTML coverage report
To run a single package or test:
go test ./internal/engine/...
go test ./internal/policy -run TestParsePolicy
Test style
- Use the standard library
testingpackage only — no testify, no helper libraries. - Prefer table-driven tests for branching logic (see
internal/engine/engine_test.gofor examples). - Tests must fail without the change and pass with it.
- Do not use real credentials, even in fixtures. Use clearly fake values
such as
sk-demo-secretorghp_fake_token_for_tests.
Fuzz tests
Go native fuzz targets cover the security-critical parsers — policy YAML,
tool-call JSONL, the glob matcher, and the redactor. They are kept under the
same _test.go files (internal/{policy,engine,redact}/fuzz_test.go).
The seed corpora run as part of go test ./... (and therefore make ci).
To actually fuzz, use the fuzz target:
make fuzz # 30s per target (default)
make fuzz FUZZTIME=2s # quick smoke
make fuzz FUZZTIME=5m # overnight-ish
Go's native fuzzer only fuzzes one target per go test invocation, so make fuzz iterates them sequentially. Any newly discovered failing inputs are
written to testdata/fuzz/<TargetName>/… under the affected package; commit
them as regression fixtures alongside the fix.
Lint
make lint # fmt-check + vet + golangci-lint + gosec (the full gate)
make fmt # apply gofmt in place
make fmt-check # fail if anything needs reformatting (used in CI)
make golangci # golangci-lint only (config: .golangci.yml)
make sec # gosec security analysis only
CI rejects any code that is not gofmt-clean. Run make fmt before committing
or wire your editor to format on save.
make lint also runs golangci-lint (v2) and
gosec; install both once so the target
works locally:
# golangci-lint v2 — prefer the official installer (see
# https://golangci-lint.run/welcome/install/), e.g.:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh \
| sh -s -- -b "$(go env GOPATH)/bin" v2.5.0
# gosec:
go install github.com/securego/gosec/v2/cmd/gosec@latest
The lint configuration lives in .golangci.yml. Security findings that are
intentional for a tool that launches operator-specified commands and reads
operator-specified files are annotated inline with
// #nosec <rule> -- <reason> at the call site rather than disabled globally,
so genuinely new findings still fail the build.
Dependency and vulnerability hygiene
make vuln # govulncheck against the module's dependencies
Dependencies and pinned GitHub Actions are updated automatically by Dependabot
(.github/dependabot.yml), and CI runs govulncheck on every change. See
SECURITY.md for the supply-chain posture.
Examples and docs checks
make examples # run every file under examples/ through the binary (#181)
make doc-check # documented commands exist + internal doc links resolve (#165)
Both run in CI, so a drifted example or a broken doc link fails the build. Keep
examples/ and the README/docs/ in sync with the CLI.
Demo
make demo
Expected output is shown in the README under Demo output. If you change the demo, update the README to match.
Pre-push gate
Before opening a pull request, run:
make ci
make ci performs fmt-check, vet, and test-race with coverage — the same
gate the project has always used locally. In CI the cross-platform test job
runs go test -race ./... directly (Go-native so it works on the Windows
runner; coverage is collected on Linux), and the fmt-check half runs in the
lint job. CI additionally runs the lint, security, examples, and docs
checks above on Linux and the test job on a Linux/macOS/Windows matrix. If
make ci is green locally, the test matrix should pass.
Release artifacts
We use GoReleaser for cross-platform release artifacts. To validate the configuration locally without producing a release:
# Install goreleaser once (see https://goreleaser.com/install/)
make release-check
CI also runs goreleaser check on every PR.
PR guidelines
Branch naming
Use a short prefix that describes the change:
feat/— new feature or capabilityfix/— bug fixdocs/— documentation onlychore/— tooling, CI, deps, refactorstest/— tests only
Example: feat/policy-imports, fix/redact-nested-arrays.
Commit and PR titles
Follow Conventional Commits:
feat: add policy imports for reusable packs
fix(engine): handle empty path argument
docs(threat-model): add MCP proxy attack surface
chore(ci): add coverage summary step
The ^docs:, ^test:, and ^chore: prefixes are excluded from auto-generated
release changelogs (see .goreleaser.yml).
A non-blocking PR-title check (.github/workflows/pull-request-triage.yml)
flags titles that do not follow Conventional Commits. Automated tooling (and AI
agents) should set the branch prefix above and a Conventional-Commit title.
Auto-labeling
New PRs are labeled automatically by changed path using the existing area:*
and documentation/developer-experience taxonomy
(.github/labeler.yml) — e.g. touching internal/policy/** adds
area:policy. Labels are additive; add or adjust labels manually as needed.
PR scope
- One PR = one concern. Smaller PRs ship faster and review better.
- If you find an unrelated bug while working on something else, open a separate issue rather than bundling fixes.
- Use the PR template. Every PR must
reference the issue it addresses with
Fixes #NorRefs #N.
Required checks
Every PR must:
- Pass
make cilocally before push. - Update or add tests for any behavior change.
- Update README,
docs/, or inline documentation if user-visible behavior changes. - Reference an issue number.
Adding a new package
- Packages live under
internal/unless they are intentionally part of the public Go API (none are today). - Each package keeps its tests next to the code (
foo.go+foo_test.go). - Avoid cross-package import cycles. If two packages need each other, extract the shared types into a third package.
Policy schema changes
The policy schema (internal/policy/policy.go) is user-facing. Any change to
it must:
- Be backward-compatible with existing policies where possible, or include a clear deprecation path.
- Update
docs/policy-language.md. - Update
examples/policy.yamlto demonstrate the new field where useful. - Add cases to
internal/policy/policy_test.gocovering valid, invalid, and omitted forms. - If the change affects the audit event format, bump the audit schema version (see issue #31 once implemented).
Security-sensitive changes
If your change touches internal/policy, internal/engine, internal/audit,
or internal/redact:
- Call it out in the PR description.
- Add explicit test cases for the security-critical behavior (e.g., deny precedence, redaction of nested structures, escape attempts).
- Do not relax existing default-deny behavior without explicit discussion in an issue first.
Reporting bugs
Use the bug report issue template.
Include the AgentFence version (./agentfence version), Go version, OS, and
a minimal policy + tool-call input that reproduces the problem.
Reporting vulnerabilities
Do not open a public issue for security vulnerabilities. See the project README for current contact channels.
License
By contributing, you agree that your contributions will be licensed under the same license as the project (see LICENSE).