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.mdandv2/README.mdin 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(fromv2/; needspip install playwright pillow, a Chromium for Playwright andpdftoppm) and look at them before committing. - Styles use the tokens in
v2/officesentry/web/static/tokens.css;/styleguidein 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.mdsays 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 tomaindirectly. - CI runs
ruff, the full test suite on Python 3.12 (with a 90% coverage floor), a Docker build andpip-auditon every pull request, and a pull request isn't merged until it's green. After merging,mainalso 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.pyreplays 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:
- Open a pull request that sets
__version__inv2/officesentry/__init__.py(2.1.0) and gives the## 2.1.0section 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. - 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.) - The run checks the version matches
__version__and the changelog, and that the commit passed its full v2 run onmain(so release frommainonce 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, publishesghcr.io/jackd99/officesentry:2.1.0(and2.1,2andlatest), creates thev2.1.0tag 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. - 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.