Contributing to Pipelock#
Thanks for your interest in making AI agents more secure.
Prerequisites#
- Go 1.25+ (
go version) - golangci-lint v2
- gofumpt (
go install mvdan.cc/gofumpt@latest)
Quick Start#
git clone https://github.com/luckyPipewrench/pipelock.git
cd pipelock
make build
make test
make lint
Development Workflow#
- Fork the repository on GitHub
- Clone your fork and create a feature branch
- Make changes with tests
- Run the pre-commit checklist (below)
- Open a PR against
main
Branch naming:
feat/for new featuresfix/for bug fixeschore/for maintenancedocs/for documentation
Pre-Commit Checklist#
These match exactly what CI checks. Both must pass with zero issues.
golangci-lint run ./... # Full lint (19 linters, see .golangci.yml)
go test -race -count=1 ./... # All tests with race detector
Pull Requests#
- Fill in a clear description of what changed and why
- CI runs 6 required checks: test (Go 1.25 + 1.26 matrix), lint, build, govulncheck, CodeQL, pipelock (self-scan)
- Address reviewer feedback and bot comments. Automated AI review (e.g. CodeRabbit) is advisory only — maintainers make all security decisions, and a bot's passing status or summary does not by itself mean a change was security-reviewed.
- PRs are squash-merged
Review Standard#
Every change to main goes through a pull request and requires approval from a
human code owner other than the author. The main-branch ruleset dismisses stale
approvals after a push, requires approval of the latest revision, requires all
review threads to be resolved, and blocks merging until the required status
checks pass.
Reviewers check:
- whether the change is useful, scoped, and compatible with documented behavior;
- correctness at normal, boundary, malformed, concurrent, and failure inputs;
- fail-open versus fail-closed behavior at security boundaries;
- tests for changed behavior and regression coverage for fixed defects;
- documentation and example-config accuracy, including transport-specific limits;
- secret handling, authorization, path/network trust boundaries, and dependency risk;
- backward compatibility, operator lifecycle, and recovery behavior where applicable.
A change is acceptable only when the reviewer can explain the behavior, required
tests and checks pass, security-relevant claims are supported by code or
reproduction, no unresolved blocking feedback remains, and the latest revision
has a human approval. Bot review may provide evidence but cannot replace that
approval.
Testing#
Requirements#
- All tests run with
-race -count=1 - Target 95%+ coverage on new code (
make test-coverfor local report) - Table-driven tests where there are 3+ cases
Patterns#
Disable SSRF in unit tests to avoid DNS lookups:
cfg := config.Defaults()
cfg.Internal = nil // disables SSRF checks
CLI tests capture output via SetOut, never os.Pipe:
var buf strings.Builder
cmd.SetOut(&buf)
Build fake credentials at runtime to avoid gitleaks false positives:
key := "sk-ant-" + "api03-" + "XXXXXXXXXXXX"
Benchmarks#
make bench
See docs/benchmarks.md for methodology and results.
Code Style#
- gofumpt formatting, not just gofmt (CI enforces this)
- Error wrapping:
fmt.Errorf("context: %w", err) - No stutter:
proxy.Optionnotproxy.ProxyOption cmd.OutOrStdout()for CLI output,cmd.ErrOrStderr()for diagnostics- File permissions:
0o600not0600 - HTTP methods:
http.MethodGetnot"GET" - See .golangci.yml for all 19 enabled linters
Python dependencies#
A small amount of Python lives in the repo: the cross-implementation
verifier fixture at testdata/python_verifier_fixture/ and the
pr-review.yaml workflow's runtime deps at
.github/requirements-pr-review.txt.
Rule: Generated Python lockfiles (requirements*.txt) in this repo
MUST be ==-pinned with --hash lines. requirements*.in is the
source-manifest exception and may carry loose bounds; the lockfile
generated from it must be strict. Loose >=, <=, ~=, >, <, and
=== operators are forbidden in lockfiles. Reasons:
- OSV-Scanner (run by the OpenSSF Scorecard workflow) over-reports on
range pins. A>=46.0.7,<47.0.0cryptography line has triggered
six-CVE Scorecard alerts on main even though the floor was clean. - Pinned hashes give reproducible installs across developer machines,
CI runners, and downstream agent containers. pip install --require-hashesrejects unpinned or hash-missing
inputs at install time, so a tampered wheel can not silently
substitute.
The lockfile pattern, modeled on .github/requirements-pr-review.txt:
package==X.Y.Z \
--hash=sha256:<hash-1> \
--hash=sha256:<hash-2>
For larger sets of deps with transitive resolution (e.g. the verifier
fixture), use pip-compile from pip-tools with a source .in file.
See testdata/python_verifier_fixture/README.md for the regen
command.
Renovate watches both Python paths and auto-opens PRs to bump the
locks when a new advisory drops or a fresh patch ships. Routine
bumps wait out a 10-day cooldown; vulnerability-fix releases
fast-track. Merge those PRs after CI is green.
CI lint enforces the rule: scripts/check-python-pins.sh runs in the
lint job and exits non-zero on any requirements*.txt line that
contains a non-== operator outside of the autogenerated pip-compile
header.
Building#
make build # Build with version metadata
make test # Run tests
make lint # Lint
make docker # Build Docker image
make reproducible-build-check # Compare two byte-identical OSS builds
See Reproducible Builds for the fixed inputs,
release integration, and the exact scope of the reproducibility claim.
Project Structure#
cmd/pipelock/ CLI entry point
internal/
cli/ 20+ Cobra commands (run, check, report, tls, mcp, audit, generate, ...)
config/ YAML config loading, validation, defaults, hot-reload (fsnotify)
scanner/ Ordered URL scanning pipeline + response injection detection
audit/ Structured JSON audit logging (zerolog) + event emission dispatch
proxy/ HTTP proxy: fetch, forward (CONNECT), WebSocket, TLS interception
certgen/ ECDSA P-256 CA + leaf certificate generation, cache
mcp/ MCP proxy + bidirectional scanning + tool poisoning + chains
report/ HTML/JSON audit report generation from JSONL event logs
killswitch/ Emergency deny-all (6 sources) + port-isolated API
emit/ Event emission (webhook + syslog + OTLP sinks)
metrics/ Prometheus metrics + JSON stats endpoint
normalize/ Unicode normalization (NFKC, confusables, combining marks)
hitl/ Human-in-the-loop terminal approval
integrity/ File integrity monitoring (SHA256 manifests)
signing/ Ed25519 key management, file signing, signature verification
gitprotect/ Git diff scanning for secrets
projectscan/ Project directory scanner for audit command
receipt/ Action receipt signing + hash-chained evidence
addressprotect/ Blockchain address validation and poisoning detection
seedprotect/ BIP-39 seed phrase detection (dictionary, checksum)
shield/ Airlock, browser shield, posture capsule
rules/ Community rule bundle loading, verification, and CLI
enterprise/ Multi-agent features (ELv2, see enterprise/LICENSE)
configs/ 7 preset config files (balanced, strict, audit, claude-code, cursor, generic-agent, hostile-model)
docs/ Guides, OWASP mapping, comparison
Architecture#
See CLAUDE.md for the full architecture guide, including:
- Ordered scanner pipeline and security-control sequence
- MCP proxy design
- Config system and hot-reload
- Package structure and conventions
Adding Features#
New CLI command#
- Create
internal/cli/<command>.gowith a<command>Cmd()function - Register in
rootCmd()ininternal/cli/root.go - Add tests in
internal/cli/<command>_test.go
New scanner layer#
- Add the check function in
internal/scanner/ - Wire into
Scanner.Scan()pipeline - Add metrics counter in
internal/metrics/ - Add audit event in
internal/audit/ - Add benchmarks in
internal/scanner/scanner_bench_test.go
New DLP pattern#
- Add regex to
config.Defaults()ininternal/config/config.go - Add test cases in
internal/scanner/scanner_test.go - Update preset configs in
configs/
Dependencies#
Pipelock keeps its direct dependency set intentionally small. Any new dependency must be justified in the PR description. We prefer the standard library.
Security#
- Vulnerabilities: Report via GitHub Security Advisories, NOT public issues
- Don't weaken capability separation: the proxy must never access agent secrets
- Don't bypass fail-closed defaults: if in doubt, block
- See SECURITY.md for the full policy
Reporting Issues#
- Security issues: See SECURITY.md
- Bugs: Open a GitHub issue with steps to reproduce
- Features: Open a GitHub issue describing the use case
- Scanner bypasses: Use the security bypass issue template
- Questions and community discussion: Join the Discord community at https://discord.gg/badNfhGKTc
Contributor License Agreement#
We use a Contributor License Agreement (CLA) for all contributions. When you open your first PR, the CLA Assistant bot will ask you to sign electronically. This is a one-time process that takes about 30 seconds.
The CLA is based on the Apache Individual Contributor License Agreement. It grants the project the right to use your contribution under the project's license terms while you retain ownership of your work.
License#
Pipelock uses two licenses:
- Apache License 2.0 for the core (files without the
enterprisebuild tag) - Elastic License 2.0 (ELv2) for paid multi-agent features (files with the
//go:build enterprisebuild tag, primarily inenterprise/with integration code incmd/andinternal/)
By contributing to the core, you agree that your contributions will be licensed under the Apache License 2.0. Contributions to enterprise-licensed code are licensed under the Elastic License 2.0.
Each file's license is indicated by its header. When in doubt, check the file's build tag and license notice.