feat: Milestone 0.8 — trustworthy numbers & security model - #8
Merged
Conversation
Closes the top 1.0 blocker: any authenticated Viewer could read EVERY project's
findings/dashboard/reports. Reads are now scoped to the projects a user may see.
- ProjectMembership (user↔project) + Project.IsPublic (+ migration).
- Reads scoped everywhere: /api/projects, /projects/{key}/findings, /dashboard,
/report/breaches, /sla-summary, /alerts(+count), and triage. Public projects are
visible to any authenticated user; members see their projects; Admins see all.
Unauthorized single-project access returns 404 (existence not disclosed).
- Registration:Mode config (Disabled by default; the public demo sets Open).
- Admin endpoints: grant/revoke membership (by email), list members, set visibility;
create-project takes IsPublic.
- Demo preserved: demo projects seeded public (and existing ones flipped public on
startup) so open-signup Viewers still see the showcase; Terraform sets
Registration__Mode=Open on the demo.
- Integration tests: cross-project isolation, public visibility, admin-sees-all,
registration-disabled-by-default, membership grant→revoke visibility.
Build clean; 107 unit tests green; new authz tests run in CI (Docker).
JWTs now carry the user's Identity security stamp, validated on every request (JwtBearer OnTokenValidated). Rotating the stamp revokes already-issued tokens immediately instead of leaving them valid until the 60-minute expiry. - JwtTokenService issues an 'sstamp' claim; login/register include it. - Per-request validation rejects a token whose stamp no longer matches the user (or whose user was deleted). - Role change explicitly rotates the stamp (password change/reset already do via Identity), so a demoted/off-boarded user loses access at once. - Integration test: a token is rejected after the stamp rotates. Test fixture updated to mint stamped tokens.
Register TimeProvider (System) and inject it into IngestionService, SlaReEvaluator, and FindingEnrichmentService, replacing direct DateTimeOffset.UtcNow reads. This makes their time-dependent behavior driveable by a fake clock (the enabler for deterministic SLA-transition and posture-snapshot tests). The pure domain already takes 'now' as a parameter; read-time endpoint evaluation still uses UtcNow (an inherently 'now' operation).
The live dashboard/trend is recomputed from current findings, so historical compliance silently changes as deadlines pass. Capture an append-only PostureSnapshot once per UTC day so the number an auditor saw stays fixed. - PostureSnapshot entity (org rollup: compliance %, open/breached/due-soon/on-track, severity counts, KEV count) + migration; unique index on the day = append-only, idempotent, replica-safe (losing concurrent write no-ops). - PostureSnapshotService.EnsureSnapshotAsync (uses the injected TimeProvider), called from the hourly re-evaluation so a daily snapshot lands automatically. - GET /api/posture/history (Admin-only — it aggregates all projects). - Integration tests: a snapshot is captured once/day and stays frozen after the findings change; history endpoint is admin-only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements all of Milestone 0.8 from
docs/ROADMAP.md— the load-bearing correctness + security work for 1.0. Four reviewable commits:0.8.1 — Per-project authorization (the top 1.0 blocker)
Any authenticated Viewer could read every project's findings/dashboard/reports. Reads are now scoped to what a user may see: public projects, projects they're a member of, or all projects for Admins. Unauthorized single-project access returns 404 (existence not disclosed).
ProjectMembership+Project.IsPublic;Registration:Modeconfig (Disabled by default; demo sets Open); admin grant/revoke/list-members + set-visibility endpoints.Registration__Mode=Open, so open-signup Viewers still see the showcase.0.8.2 — Token revocation
JWTs carry the Identity security stamp, validated every request. A role change / password reset / disable revokes existing tokens immediately instead of at expiry.
0.8.4 — Injectable clock
TimeProviderinjected into the time-dependent services (ingestion, re-eval, enrichment) — the enabler for deterministic time tests.0.8.3 — Immutable posture snapshots
An append-only
PostureSnapshotis captured once per day (via the hourly re-eval), so historical compliance figures stay fixed as live deadlines pass.GET /api/posture/history(Admin-only).Migrations: 3 additive (membership+IsPublic, snapshots) — safe on existing data. Tests: build clean, 107 unit tests green; new integration tests (cross-project isolation, registration-disabled-by-default, membership grant→revoke, token revocation, snapshot immutability) run in CI on real SQL.
Note: merging changes the default security posture — self-registration is off unless
Registration:Mode=Open, and reads are membership-scoped. The Terraform env keeps the public demo working.