|
1 | 1 | # Changelog |
2 | 2 |
|
| 3 | +## [2.5.4] - 2026-04-30 |
| 4 | + |
| 5 | +Tier 2 + Tier 3 closure — security hardening on the auth surfaces, GDPR-aware UX on findings, and the OSS-hygiene files (SECURITY.md, security.txt, CODE_OF_CONDUCT, issue/PR templates, CODEOWNERS) the project had been carrying as deferred items. |
| 6 | + |
| 7 | +### 🟠 Security — `/auth/forgot-password` is now constant-time and PII-free in the logs |
| 8 | + |
| 9 | +**Pre-2.5.4 problem.** The unknown-email path early-returned after the `SELECT`, so its wall-clock time was 5–20× shorter than the known-email path (which generates a token, hashes it, INSERTs, and awaits SMTP). A chatty attacker could enumerate registered emails via response timing alone, defeating the always-204 contract. Separately, the MTA-failure log printed `user.email`, promoting a transient operational error into a long-lived PII record and creating a secondary email-enumeration channel for anyone with log access. |
| 10 | + |
| 11 | +**v2.5.4 changes** in `packages/api/app/routers/auth.py::forgot_password`: |
| 12 | + |
| 13 | +1. The unknown-email path now performs the **same** `secrets.token_urlsafe(32)` + SHA-256 hash work as the known-email path and discards the result, so CPU and DB-ish profiles match within a few microseconds. |
| 14 | +2. **Both** paths sleep a randomised `asyncio.sleep(uniform(0.05, 0.25))` before returning, so the variable cost of the real `send_email()` call is masked under the same jitter band. |
| 15 | +3. The MTA-failure log now records `user.id` only — sufficient for an operator triaging the failure, not an enumeration channel. |
| 16 | + |
| 17 | +The full rationale is in the function docstring so the next reader doesn't strip the defenses thinking they're noise. |
| 18 | + |
| 19 | +### 🟠 Security — CSRF token rotation on `/auth/refresh` (regression-locked) |
| 20 | + |
| 21 | +The double-submit CSRF cookie was already being re-minted on every call to `_build_token_response` — but the invariant was nowhere asserted in tests, and the inline rationale was missing. v2.5.4 adds: |
| 22 | + |
| 23 | +- A focused inline comment in `_build_token_response` explaining *why* the rotation is intentional (a captured CSRF cookie has the same short lifetime as the access token rather than surviving for the full refresh-token window). |
| 24 | +- `TestCSRF.test_csrf_token_rotates_on_refresh` in the integration suite, asserting both cookie and response-body reflect a fresh token after `/auth/refresh`. |
| 25 | + |
| 26 | +### 🟠 GDPR — Findings page now carries an explicit personal-data notice |
| 27 | + |
| 28 | +Scan results capture hostnames, IP addresses, employee email addresses (from secrets scanning), and other identifiers that count as personal data under GDPR Art. 4(1). Pre-2.5.4 the operator was given no in-product notice that they are the controller for this evidence and that exports / sharing / retention need to reflect that. |
| 29 | + |
| 30 | +The new dismissible `FindingsPiiNotice` banner sits between the Findings page header and the filters, with content like: |
| 31 | + |
| 32 | +> **I findings possono contenere dati personali** |
| 33 | +> |
| 34 | +> I risultati delle scansioni possono includere hostname, indirizzi IP, indirizzi email o altri identificatori estratti da fonti pubbliche (DNS, certificate transparency, pattern-matching su secret). Ai sensi del GDPR sei il titolare del trattamento per queste evidenze — applica agli export, alla condivisione e alla retention la stessa cura che applicheresti ai dati personali sottostanti. |
| 35 | +
|
| 36 | +Implementation: same SSR-empty-tree pattern as `LegalDisclaimerModal` and `OrientationCard`; versioned `localStorage` key `nis2-findings-pii-notice-v1`; 3 keys × 5 locales (en/it/fr/de/es) = 15 new i18n entries, parity 5/5. |
| 37 | + |
| 38 | +### 🟠 GDPR — `docs/privacy.md` rewritten with concrete data-flow disclosures |
| 39 | + |
| 40 | +`§7 Self-hosted deployments` was a generic "you're the controller" stub. v1.1 splits it into six numbered sub-sections that lay out **specifically** what a self-hosted instance processes: |
| 41 | + |
| 42 | +- **§7.1 Data inventory** — explicit table of every personal-data category, the table it lives in, and the default retention. |
| 43 | +- **§7.2 IP address and User-Agent in `audit_logs`** — names the columns, cites the legal basis (Art. 6(1)(f)), documents pseudonymisation on Art. 17 erasure, and points at `AUDIT_LOG_RETENTION_DAYS`. |
| 44 | +- **§7.3 Outbound network calls** — names `crt.sh` (Sectigo), DNS resolvers, and target hosts as third-party data flows the deployer has to disclose to data subjects. Reaffirms zero outbound telemetry. |
| 45 | +- **§7.4 PII captured incidentally by the scanner** — secrets-scanner output, subdomain enumeration, port banners. |
| 46 | +- **§7.5 / §7.6** — what the maintainer is *not* (no Art. 28 contract, no warranty), and the deployer's own GDPR obligations. |
| 47 | + |
| 48 | +Doc version bumped to 1.1; the existing template structure for self-hosted deployers is preserved. |
| 49 | + |
| 50 | +### 🟠 OSS hygiene — SECURITY.md, security.txt, CODE_OF_CONDUCT, issue / PR templates, CODEOWNERS |
| 51 | + |
| 52 | +The project had a thin `SECURITY.md` (3-row supported-versions table, 7-day-fix promise, no PGP, no CRA reference) and no other OSS-hygiene scaffolding. v2.5.4 ships a coordinated rewrite: |
| 53 | + |
| 54 | +- **`SECURITY.md`** — RFC 9116 cross-reference, GitHub-private-disclosure preferred channel, 24h/48h/5d/10d response-by-severity SLAs, **35-day medium-severity patch ceiling** (aligned with EU Cyber Resilience Act Reg. EU 2024/2847 conventions), 90-day coordinated-disclosure default, in-scope / out-of-scope matrix, full enumerated security-measures section reflecting code as it ships in `main`, hall-of-fame placeholder, PGP placeholder until the maintainer's permanent key is provisioned. |
| 55 | +- **`packages/web/public/.well-known/security.txt`** — RFC 9116-compliant: GitHub private-disclosure URL + email contact, policy URL, languages `en, it`, canonical URL, `Expires: 2027-04-30`. |
| 56 | +- **`CODE_OF_CONDUCT.md`** — verbatim Contributor Covenant 2.1 with the project's community spaces and reporting contact filled in. |
| 57 | +- **`.github/ISSUE_TEMPLATE/{bug_report,feature_request}.yml` + `config.yml`** — structured forms with `blank_issues_enabled: false`. The config `contact_links` redirect security reports to the private-advisory flow, questions to Discussions, and dual-license inquiries to email — saving a triage round on miscategorised issues. |
| 58 | +- **`.github/PULL_REQUEST_TEMPLATE.md`** — summary / why / surface-impact checkboxes / verification checklist / NIS2-GDPR-legal-exposure prompt. Mirrors the structure of the maintainer's commit-message and CHANGELOG style. |
| 59 | +- **`.github/CODEOWNERS`** — every path falls back to `@fabriziosalmi` (single maintainer), with explicit re-listing of the security-sensitive paths (auth, RLS bootstrap, target validator, alembic, infra, gitleaks config) so that growth into multi-maintainer requires no schema change. Documents the *intent* of strictest review on those paths. |
| 60 | + |
| 61 | +### Misc |
| 62 | + |
| 63 | +- `.gitignore`: `.ruff_cache/` added (was leaking into `git status`). |
| 64 | +- `.gitleaks.toml`: `nis2-findings-pii-notice-v\d+` added to the localStorage-key allowlist family. |
| 65 | +- `API_VERSION` literal bumped 2.5.3 → 2.5.4 in lockstep with `pyproject.toml`. |
| 66 | + |
| 67 | +### Verified |
| 68 | + |
| 69 | +- Web build: green (24/24 pages, /dashboard/findings page builds cleanly). |
| 70 | +- 5/5 i18n locales validate; new `findings.piiNotice*` namespace present and aligned (3 keys × 5 locales = 15 entries). |
| 71 | +- `.well-known/security.txt` linted by hand against RFC 9116 §2 (Expires set, Contact present, Canonical and Policy URLs absolute). |
| 72 | + |
3 | 73 | ## [2.5.3] - 2026-04-30 |
4 | 74 |
|
5 | 75 | External-reviewer feedback round (Davide, fresh-clone walkthrough). Four concrete fixes — one runtime blocker, one onboarding trap, one operability paper-cut, one orientation gap. |
|
0 commit comments