Files
Privatae 4a5c310482
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 4m6s
ci / fuzz (push) Failing after 2m58s
bastion 0.58.1
2026-10-29 21:20:20 +00:00

3.8 KiB

Contributing

Thank you for looking at the bastion. It is one Go package that ends up on machines people own, so the bar is: nothing lands that widens what a host can do on a machine without the person's grant and, where the design calls for it, their approval on that machine.

What you can do today

Issues and pull requests are welcome on GitHub, at github.com/Privatae/bastion. Development happens in a private repository, so an accepted change is carried into the development repository and arrives here with the next release, as its own commit. Sign-in on the git.privatae.ai forge is not reachable from the internet, so issues and pull requests cannot be opened there. You can also read, clone, build and verify a release, and fork under the GPL. Report a security problem by email, not in a public issue (see SECURITY.md).

Checks a change has to pass

cd client-go
gofmt -l .            # must print nothing
go vet ./...
CGO_ENABLED=0 go build .
go test ./...
cd .. && deploy/staticcheck.sh   # must find nothing, on linux, darwin and windows

Some claim tests need a real machine and root: the op sandboxes, the namespace tests and the root command watch skip in a container and as a user. GitHub's CI runs every test docs/claims.md cites as root on its own virtual machine and fails if one skips; to run them yourself, use a machine you can throw away.

The backup tests also check this code against the age command itself when one is on PATH or named by AGE_BIN; CI installs a pinned age release and sets PRIVATAE_REQUIRE_AGE, so there they fail rather than skip.

Most parsing and judgement logic is in files with no build tag and runs under one go test on any OS. Some platform parsers (on Linux and macOS) and the Linux sandbox are in tagged files, and their tests run only on that OS. A new judgement goes in an untagged file with a table test. Check it with GOOS=windows go vet ./... and GOOS=darwin go vet ./... as well: go build does not compile test files, so only vet sees a test that uses a Linux-only name.

A change to a wall a host's input goes through (a path check, a grant or family check, a signed record's parser) also runs the fuzz targets, and adds one if the wall has none:

deploy/fuzz.sh        # every client-go/fuzz_*_test.go target, 20 seconds each

An input the fuzzer finds is written under client-go/testdata/fuzz/; commit it with the fix, and every later go test runs it.

What a change has to respect

  • The bastion dials out. No new listener on a non-loopback interface. A new listener inside the tunnel is a capability family with a grant check.
  • Fixed argv. A management op runs a fixed command line. A caller value reaches a program only through a strict charset check and never as part of a shell string.
  • No shell in file handlers. Ops that touch the person's files are Go handlers. The one that runs a program takes a tool name from a fixed table.
  • Approval on the machine. Anything that writes to disk, downloads onto it, or opens a root shell is gated by the enrolled second factor. The host never handles the code.
  • Withhold, don't fake. A platform with no implementation for a capability must not advertise it. The host has to be able to tell quiet from blind.
  • Archive, never delete. There is no delete op on shared stores.

Style

  • Comments explain why, in plain words. State the invariant and the failure it prevents. No ticket numbers, no dates, no names.
  • One package, files named by concern. A new concern gets its own file and its own _test.go.
  • Error text that a person will read is a full sentence saying what was refused and what they can do about it.

License

By contributing you agree that your contribution is licensed under the GNU General Public License, version 3 or later, like the rest of the repository.