Contributing

On this page

All code lives in v2/. Its README covers what the product does and CLAUDE.md covers the conventions (adding collectors, checks, reports and migrations).

Development setup #

Requires Python 3.11 or newer.

cd v2
python -m venv .venv && . .venv/bin/activate
pip install -e '.[dev]'
OFFICESENTRY_INSECURE_COOKIES=1 python -m officesentry web   # http://127.0.0.1:8000
python -m officesentry worker                                 # second terminal

Use a virtual environment as above: on Debian or Ubuntu, installing into the system Python fails on the apt-installed blinker (if you must, add --ignore-installed blinker).

PDF export needs the Pango libraries (WeasyPrint); on Debian or Ubuntu, apt install libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0. On Windows everything else works natively (.venv\Scripts\activate), and PDF buttons open a print-ready page instead; use Docker if you're working on PDF layout. Tests that need real PDF rendering skip themselves without Pango.

Running it in Docker #

docker-compose.yml runs the published release image. To run your working copy instead, add the build file:

cd v2
cp .env.example .env    # with OFFICESENTRY_INSECURE_COOKIES=1 for plain http://localhost
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build

or put COMPOSE_FILE=docker-compose.yml:docker-compose.build.yml in .env. The local image is called officesentry:local, so it is never mistaken for a release.

Dependencies #

pyproject.toml lists what Office Sentry needs. What it actually installs is pinned, with hashes, in two lock files made from it with pip-tools:

  • v2/requirements.txt: the Docker image (and the dependency audit);
  • v2/requirements-dev.txt: CI, the same versions plus the test tools.

Dependabot refreshes both every Monday. To change a dependency yourself, edit pyproject.toml, then from v2/ with Python 3.12 (the image's):

pip install pip-tools
pip-compile --generate-hashes --allow-unsafe --strip-extras --extra pdf --build-deps-for wheel \
  --output-file requirements.txt pyproject.toml
pip-compile --generate-hashes --allow-unsafe --strip-extras --extra pdf --extra dev --build-deps-for wheel \
  --constraint requirements.txt --output-file requirements-dev.txt pyproject.toml

Add --upgrade-package <name> to move one package, or --upgrade for all. CI fails (pip check, tests/test_release.py) when a lock no longer matches pyproject.toml. The Windows tier installs from pyproject.toml directly, since the locks are made for Linux.

Before opening a pull request #

cd v2
ruff check .
pytest -n auto      # plain `pytest` runs one test at a time, easier for debugging

CI runs the same on Python 3.12 for every pull request, with a Docker image build and a dependency audit (pip-audit); pushes to main add Python 3.11 and 3.13, and a weekly run adds a subset on Windows. Changes that only touch Markdown, docs/ or examples/ skip CI. The Python 3.12 run measures coverage and fails under 90%; pytest -n auto --cov=officesentry shows it locally.

  • Keep changes small and finished end to end; update v2/CLAUDE.md and v2/README.md in the same pull request when behaviour changes.
  • When a page or report looks different, regenerate the README screenshots and the example reports with python scripts/screenshots.py (from v2/; needs pip install playwright pillow, a Chromium for Playwright and pdftoppm) and look at them before committing.
  • Styles use the tokens in v2/officesentry/web/static/tokens.css; /styleguide in a running portal shows them and every shared component. Pull requests that touch templates or CSS also run the browser tests (screenshots against baselines, and axe-core); v2/tests/browser/README.md says how to run them and how to refresh the baselines after a change you meant.
  • Mock Microsoft Graph at the HTTP layer with responses, never by replacing the client class.
  • Never add a write call to a tenant or a non-read-only permission.
  • Every route needs a login check, and tenant pages go through the access gate in web/access.py.

How this is built #

Office Sentry is built with AI assistance: most commits are written by Claude (Anthropic's AI model, through Claude Code), working from the maintainer's direction on what to build and how it should behave. That is a method, not a shortcut, so the same gates apply to every change, whoever or whatever wrote it:

  • Every change is a pull request against main; nothing is pushed to main directly.
  • CI runs ruff, the full test suite on Python 3.12 (with a 90% coverage floor), a Docker build and pip-audit on every pull request, and a pull request isn't merged until it's green. After merging, main also runs the suite on Python 3.11 and 3.13, and a weekly run a Windows subset. The pull request template's checklist covers migrations, new permissions, the access gate and read-only access.
  • Tests simulate Microsoft Graph and Exchange at the HTTP level, and tests/test_live_fixture.py replays responses recorded from a real Microsoft 365 Business Premium test tenant (names, emails, domains and IDs scrubbed), so collectors are checked against what Microsoft actually returns.
  • Rules that matter for safety are enforced by tests, not just by review: every page requires a login, tenant pages go through the access gate, and the Graph client has no write methods.
  • Pages and reports that change are checked in a real browser, with screenshots, before they're merged, and CI compares about 34 pages with their screenshots and runs an accessibility check on them.

The project's direction, scope and releases are decided by its maintainer, @JackD99.

Issues and conduct #

Use the issue forms for bugs, report requests and check requests. Issues are public: remove tenant names and IDs, domains, user names and email addresses, and IP addresses from anything you paste. Everyone taking part agrees to the code of conduct.

Releases #

Versions follow semantic versioning: 2.1.0 adds features, 2.1.1 only fixes. The release workflow does the work. To make a release:

  1. Open a pull request that sets __version__ in v2/officesentry/__init__.py (2.1.0) and gives the ## 2.1.0 section of CHANGELOG.md its date (## 2.1.0 (2026-11-02)). Fill in Action required, New Microsoft permissions and Database upgrade even when the answer is "None" or "No". Merge it once CI is green.
  2. On GitHub: Actions → release → Run workflow, leave "Use workflow from" on main, type the version (2.1.0) and press Run workflow. (Pushing a tag, git push origin v2.1.0, does the same.)
  3. The run checks the version matches __version__ and the changelog, and that the commit passed its full v2 run on main (so release from main once that run is green; the release reuses it rather than testing again). It builds the amd64 image with an SBOM and build provenance, starts it once, scans it, publishes ghcr.io/jackd99/officesentry:2.1.0 (and 2.1, 2 and latest), creates the v2.1.0 tag and the GitHub release with the notes and install files, and removes images older than the last three releases. Nothing is published until the checks and the scan have passed. A run costs about 5 of GitHub Free's 2,000 private Actions minutes a month; arm64 images come when the repository is public.
  4. Open a pull request that moves __version__ on to the next development version (2.2.0.dev0) and adds a ## 2.2.0 (unreleased) section.

To try the pipeline without releasing, run a release candidate: run the workflow with 2.1.0-rc.1. It can come straight from the development version (2.1.0.dev0; the image then reports 2.1.0rc1), gets only its own image tag, is marked as a pre-release, and its changelog section may still say "unreleased".

If the scan fails, there is no release and no version tag on the image (it stays in the registry under its digest only). Fix the cause, usually by merging Dependabot's newer base image or dependency, and run it again.

Reporting security issues #

See SECURITY.md. Please don't open a public issue.

Licence #

Office Sentry is licensed under the GNU Affero General Public License v3.0. By contributing, you agree that your contributions are licensed under the same terms.