v1.3.0 — Two-factor, audit log and security policy
Track B: what it takes to survive a customer's security questionnaire.
Important
This repository is now ibuilder/scopemaker. GitHub redirects the old URLs. Documentation has moved to https://ibuilder.github.io/scopemaker/. The product is ScopeMaker — the scope engine is the point, and the Procore connector is one optional integration among others, documented in docs/integrations.md. The integration itself is unchanged and still fully supported.
Two-factor authentication
TOTP with single-use recovery codes. The enrolment QR is rendered as an inline SVG rather than fetched from a chart API — a surprising number of tutorials hand the shared secret to a third party, and it would also break the strict default-src 'self' policy.
The property the tests lean on hardest: a correct password alone does not authenticate anyone who has a second factor. Login parks a pending challenge in the session instead of signing you in. That marker expires, carries the session epoch so a password change mid-challenge invalidates it, and its failures count against the same account lockout — so the second factor cannot be brute-forced independently of the first.
Turning MFA off requires re-entering your password, because that is exactly what a hijacked session would try first.
Organization security policy
Admin → Security lets you require two-factor, or require single sign-on.
Enforced on every request, not just at sign-in. A policy that only applies to the next login leaves every currently-open session untouched — which is precisely the window an administrator turns it on to close. The enrolment pages are exempt from the redirect, or a user has nowhere to land.
sso_only refuses a correct password outright, and cannot be enabled when no identity provider is configured — that would lock everyone out.
Audit log
Append-only record of sign-ins and failures, lockouts, password resets, session revocation, role changes, member removal, invitations, token issue and revocation, scope issue/revise/archive, MFA changes, and integration activity.
Entries outlive their actor: the foreign key is nulled when a member is deleted, but their email is preserved on the row — removing somebody does not erase what they did. Filterable, with a security-events-only view and CSV export.
Supply chain and code quality
SECURITY.mdwith a private disclosure route, the full security posture, an honest section on what this does not protect against, and a hardening checklist- Dependabot for pip, GitHub Actions and Docker
- CI now runs
pip-audit --strictand publishes a CycloneDX SBOM artifact - mypy blocks the build. It was
continue-on-error, which meant nobody read it. The seven type errors it was hiding are fixed — including twoScopeSection | Nonedereferences that would have been 500s.
Fixed
- API tokens bypassed the organization's MFA requirement. The request hook that enforces policy keys off Flask-Login, and a bearer token is not a session — so a token issued before the policy was enabled kept working indefinitely. Enforcement now also runs where the bearer identity resolves.
- Alembic renders JSONB columns as
JSONB(astext_type=Text())without importingText, aNameErrorthe moment the migration runs. Fixed in the affected migrations and inscript.py.makoso it cannot recur.
330 tests, green on Python 3.11 and 3.12 with the PDF stack installed. ruff and mypy clean, pip-audit reports no known vulnerabilities.