Guides
Contributing and reporting
How to run the tests, send a change, ask a question, and report a bug or a way past the guard.
On this page
This page is the short version of the repository’s CONTRIBUTING.md. Where they differ, the repository is the authority on process.
Where to go
| You have | Go to |
|---|---|
| a bug or a feature request | Issues. The templates mostly ask for the command you ran and what it did |
| a question, or “is this a bug or am I holding it wrong” | Discussions |
| a way to get the guard to allow something it should refuse | private reporting, below. Not a public issue |
| something broken and you want to fix it yourself | When something goes wrong, then the rest of this page |
You do not need a fix in hand, or to be sure it is a real bug, to file one.
What the repository is
A folder of bash scripts and standard-library Python: the two big Python programs are scripts/hooks/guard.py
and scripts/board/server.py, and a few small helpers sit in scripts/. The Python package
(pyproject.toml, src/repolane/) is a thin launcher that bundles those same files; it has no
dependencies, and that is deliberate: Repolane should not need anything beyond git, bash and
python3 at run time. A change that adds a dependency needs an argument for itself before it needs code.
Run the tests
bash scripts/hooks/test-guard.sh # the guard's cases
lane doctor -v # the named probes, the wiring check, and the same suite
bash tests/run-all.sh # the lane scripts: dispatcher, memory, upgrade, park/done/brief, secret names
bash scripts/board/smoke.sh # starts the board on a spare port, checks its endpoints, then runs test_board.py
tests/run-all.sh runs the tests/t_*.sh suites, each in its own sandbox; smoke.sh ends with the
board’s unittest cases. A CI workflow (.github/workflows/tests.yml) runs all three on every push and
pull request, on ubuntu-latest and on macos-latest pinned to /bin/bash 3.2. A second workflow
(test.yml) runs only the guard suite on Ubuntu for pushes and pull requests to main and release.
The guard suite builds its own throwaway setup under a temp directory and runs every case
against a copy of guard.py there, with HOME pointed at a fake home inside that directory. It never
reads or writes your real lanes, base clones or memory, and it does not depend on where your $HOME is.
On success it prints N/N cases passed and ALL PASS, and it exits non-zero on any mismatch. There is
no hard-coded count to keep in step: every section must run at least one case, every case must also
see the guard exit with status 0, and the totals must add up. If the fixture cannot be built the run
fails; it never skips.
If you touch guard.py, add a case for what you changed, in both directions: the thing that should
be refused, and the near miss that should still be allowed. A rule with no test is a rule that
will quietly stop working. Most regressions the suite has caught were rules that became too broad.
Send a change
- Open an issue first if it is a design change or a new rule. For a bug or an obvious fix, go straight to a pull request.
- Branch from
release, notmain.mainonly moves by a “Release vX.Y.Z” pull request merging in fromrelease. Your pull request intoreleaseis the whole contribution; you never touchmainor version numbers. See docs/releasing.md. - Match the surrounding style:
set -u, explicit error paths, comments that say why. - Run
bash scripts/hooks/test-guard.sh(andbash tests/run-all.shif you touched a lane script) in your checkout of the repo, andlane doctorin a workspace if you have one. - Open the pull request against
release. Say what changed and whether the suite passes. If the change is user-visible, add an entry under## [Unreleased]inCHANGELOG.md; the bump type is read from which headings you use (### Added,### Fixed,### Removed).
Keep a pull request to one thing: a guard rule and a board tweak are two.
Docs are part of the change. If the code says one thing and a page says another, the page is a bug. A change that alters behaviour and leaves the docs describing the old behaviour is not finished.
If what you want is a refusal specific to your project, you probably do not need to change
guard.py: see Extending.
Reporting a security problem
If you find a way to get the guard to allow something it should refuse (a path that escapes the lane, a chained command that slips past a rule, a way to read a secret file), report it privately: Report a vulnerability in the repository’s Security tab. GitHub asks you to sign in. Please do not open a public issue for a bypass: every existing install stays exposed until a fix ships.
Include the kind of command or path shape that gets through and how you ran it. Once a fix is released the advisory is published with credit to you unless you would rather stay anonymous.
In scope: the guard’s behaviour, meaning anything the Rules page says should be
refused and is not; and the board listening beyond 127.0.0.1, making an outbound request other than
the documented ones (gh pr view polling, the Push and Pull you click), or
leaking a secret’s contents rather than its key names.
Not in scope: anything that needs an attacker who already has write access to your repos or your machine.
What is already known. The security model documents the design limits: the
tools the matcher does not cover, the parts of a command the guard cannot see (a script run by name, a
variable set elsewhere, interpreter code that computes a file name, the Agent call itself, WebFetch, MCP), the
paths outside $HOME that are not judged, the ALLOW_DEFAULT_BRANCH_PUSH=1 marker being advisory, and a
board token that any local account able to reach the port can read. These are the limits of a guardrail. A new way past a check the docs say works is what the private channel is for.
Repolane is maintained on a best-effort basis and provided as is, under the Apache License 2.0. Participation is under the repository’s Code of Conduct.