Releases: fabriziosalmi/nis2-public
v2.5.12 - Hardening, Fixes & Deps
What's Changed
- fix(ci): resolve three CI failures from v2.5.11 by @fabriziosalmi in #91
- fix(ci): resolve remaining gitleaks and E2E 422 failures by @fabriziosalmi in #92
- fix(e2e): add finding:read and asset:read scopes to E2E API key by @fabriziosalmi in #93
- docs: update README for v2.5.11 — TOTP MFA, RS256, RLS by @fabriziosalmi in #94
- build(deps): Bump playwright from 1.59.0 to 1.60.0 in /packages/scanner by @dependabot[bot] in #98
- build(deps): Bump react-hook-form from 7.75.0 to 7.76.0 in /packages/web by @dependabot[bot] in #103
- build(deps): Bump @tanstack/react-query from 5.100.10 to 5.100.11 in /packages/web by @dependabot[bot] in #104
- build(deps-dev): Bump playwright from 1.59.1 to 1.60.0 in /packages/web by @dependabot[bot] in #105
- build(deps-dev): Bump @types/node from 22.19.15 to 25.9.0 in /packages/web by @dependabot[bot] in #106
- build(deps): Bump date-fns from 4.1.0 to 4.2.1 in /packages/web by @dependabot[bot] in #107
- build(deps): Bump click from 8.1.7 to 8.4.0 in /packages/scanner by @dependabot[bot] in #97
Full Changelog: v2.5.11...v2.5.12
v2.5.11 — TOTP MFA, RS256 JWT, Alembic RLS
What's new in v2.5.11
NIS2 Art. 21(j) — TOTP Multi-Factor Authentication (closes #86)
POST /auth/totp/setup— generates provisioning URI (QR-code compatible)POST /auth/totp/verify— enables MFA after first successful codePOST /auth/totp/disable— requires password re-authentication- Login flow blocks with
mfa_required: truewhen TOTP is enabled, no partial cookies issued pyotp>=2.9added as API dependency;totp_secret/totp_enabledcolumns in Alembic migration
RS256 JWT with JWKS endpoint (closes #87)
- Set
JWT_PRIVATE_KEY+JWT_PUBLIC_KEYenv vars to switch from HS256 to RS256 _signing_key()/_verifying_key()helpers decouple token creation from verificationGET /.well-known/jwks.jsonpublishes RSA public key for service-to-service verification- Production startup fails fast if RS256 is selected but private key is absent
- HS256 mode unchanged; key selection is automatic
RLS policies in Alembic (closes #88)
002_add_rls_policiesmigration:ENABLE ROW LEVEL SECURITY,FORCE ROW LEVEL SECURITY, andtenant_isolationpolicy on 11 tenant tables- Migration chain:
001_initial → 002_add_rls_policies → 003_add_totp_fields setup_row_level_security()in lifespan is now verify-only — queriespg_policiesand warns on missing entries rather than creating them at runtime
Quality
- 364 tests passing, ruff clean, Next.js build green
v2.5.10 — Security audit: 10 NIS2/GDPR hardening fixes
What's new in v2.5.10
Security and compliance audit release. 13 commits across API hardening, NIS2 automation, GDPR reconciliation, and operational improvements.
Security fixes
- API key scope enforcement on all dual-auth read endpoints (403 on scope mismatch)
- Rate limiting on
create_scan,create_asset,import_assets_csv,POST /reports/generate - Content-Security-Policy header in both FastAPI and Caddyfile (per-handler)
- Report concurrency cap:
MAX_CONCURRENT_REPORTS_PER_ORG=3(configurable)
NIS2 compliance
- Art. 21(a) — scanner→governance bridge:
POST /governance/sync-riskescalates checklist items from open HIGH/CRITICAL findings - Art. 21(b) — automated Art. 23 deadline alerting: Celery beat every 15 min, alerts at 24h/72h/1-month thresholds via email, webhook (HMAC-SHA256 signed), or Slack
- Art. 18 — transparent vendor risk scoring: 100-point formula documented, 5 factors, auditor-facing
GET /vendors/score-formula
GDPR
AUDIT_LOG_RETENTION_DAYS(default 90) wired into config and enforced by daily cleanup taskDELETE /auth/menow also NULLsdetailsJSONB on audit log pseudonymisation- GDPR Art. 17 vs NIS2 Art. 21 conflict explicitly resolved in code and privacy.md
Scanner
- Secret patterns externalised to
secret_patterns.yaml— 22 patterns including modern GitHub (github_pat_*,gho_*,ghs_*), GitLab, OpenAI, Anthropic, Slack, Google OAuth SecretsDetector.reload()for live pattern updates without restart
Operational
/health/readynow checks DB + Redis + Celery workers; returns HTTP 503 on hard failure/health/livealias added- React
ErrorBoundaryclass component wired in dashboard layout security.txt— honest PGP status with actionable instructions- E2E tests now run in CI (
e2e-testsjob with GHA postgres+redis services) - Art. 21 coverage matrix in README updated with real implementation status
Enhancement issues opened
- #86 — TOTP MFA (Art. 21(j))
- #87 — HS256 → RS256 migration threshold
- #88 — RLS policies in Alembic migration
326 API unit tests · 47 scanner tests · Next.js build ✓
v2.5.9 - Dependabot & Pipeline Stabilization
[2.5.9] - 2026-05-15
Dependency bump and UI build stabilization.
🔴 Security & Stability
- Dependabot Batch Update: Upgraded 11 core dependencies across the stack including
react,react-dom,next,tailwindcss,zod, andlucide-reactto their latest stable releases, resolving multiple backend and frontend vulnerabilities. - CI/CD Pipeline Fix: Resolved Ruff static analysis errors (E402, W293) in
target_validator.pyto unblock the GitHub Actions test runner and enforce strict PEP8 compliance.
🟢 UI/UX & Localization
- UI Build Stabilization: Mitigated a breaking change introduced by
lucide-reactv1.0.0 (which removed third-party brand icons) by migrating theGithubicon to a native inline SVG, restoring a 100% stable static build pipeline.
v2.5.8 - Security & Usability Hardening
[2.5.8] - 2026-05-15
Finalized high-fidelity security and usability hardening.
🔴 Security & Stability
- Fixed 500 Internal Server Error in
findings.py: Restored the accidentally removedFindingResponseimport, restoring full findings API functionality. - Dependabot Patch: Updated npm dependencies resolving known CVEs.
🟠 Audit & Compliance
- Enhanced Audit Logging: Integrated the
log_actionlogger across all destructive and state-altering asset and finding management flows, strengthening compliance posture.
🟢 UI/UX & Localization
- Support & Documentation Affordances: Added new navigation links for "Support" and "Documentation" across the dashboard sidebar.
- Fixed
MISSING_MESSAGENext-Intl Runtime Error: Fully mapped and injected thecommon.support,nav.support, andnav.documentationkeys across all 5 supported locales (en, it, fr, de, es). - Component Polish: Created the missing
Alertcomponent and resolved the build-time "Module not found" error.
⚡ Performance
- Bulk Finding Updates Refactor: Consolidated multiple network requests into a single, dedicated API endpoint (
/api/v1/findings/bulk) with atomic transactional guarantees, eliminating race conditions and significantly boosting performance on massive arrays of findings.
v2.5.6 - Security Audit — 26/26 findings resolved
🔒Security Audit — 26/26 findings resolved
P0 Critical (7)
- SSRF protection on MCP scan/cert tools (
validate_target_pinned) - Internal error leakage suppressed in MCP HTTP responses
- Path traversal guard on report download (
os.path.realpath) - Alembic initial migration: 17 tables,
entrypoint.sh,env.py - Invite flow hardened:
is_active=False+/auth/accept-inviteendpoint - Email verification enforced:
email_verified=Trueon register/accept-invite .env.examplesanitized:CHANGE_MEplaceholders, zero working credentials
P1 High (9)
- ContextVar cross-request leak fix in IdentityMiddleware
- CSV import: 5MB file limit + 10k row cap (OOM/DoS prevention)
- CSP: removed
unsafe-inlinefrom productionscript-src - TypeScript: removed
ignoreBuildErrorsfromnext.config.ts - Strict TS build: fixed recharts dynamic import types + reports spread
- Mass assignment audit: schemas confirmed safe (declassified)
- Refresh token revocation: mitigated by
iatwatermark - Auth cleanup task: confirmed operational
P2 Medium (10)
- Celery enqueue failure →
status=error+ structured logging - SQL identifier guard: regex whitelist on RLS table names
- Silent
except:pass→ logged in scans + remediation routers - Scheduled scan:
pinned_ipforwarded inconfig_snapshot - CI Node version: restored to 22 (GHA deprecated Node 20)
- Celery worker: Docker healthcheck via
inspect ping configs/directory documented with.gitkeep- Prometheus targets: accepted as documented placeholders
- Test coverage gap: documented, requires dedicated sprint
Infrastructure
- Makefile:
db-stamp,db-historytargets added - Dockerfile:
ENTRYPOINTrunsalembic upgrade headbefore server start - Production compose: celery-worker healthcheck added
v2.5.5
Full Changelog: v2.5.4...v2.5.5
v2.5.4 — Tier 2 + Tier 3 closure: auth hardening, GDPR UX, OSS hygiene
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.
🟠 Security — /auth/forgot-password constant-time + PII-free logs
Pre-2.5.4 the unknown-email path early-returned after the SELECT (5–20× faster than the known-email path), and the MTA-failure log printed user.email. Both were enumeration primitives.
Now: the unknown-email path performs the same token-generation + SHA-256 hash work as the known-email path and discards the result; both paths sleep a randomised asyncio.sleep(uniform(0.05, 0.25)) before returning. The MTA-failure log records user.id only.
🟠 Security — CSRF rotation on /auth/refresh (regression-locked)
The rotation was already happening (every _build_token_response mints a fresh secrets.token_urlsafe(32)) but the invariant was nowhere asserted. Added inline rationale and TestCSRF.test_csrf_token_rotates_on_refresh in the integration suite.
🟠 GDPR — Findings page now carries an explicit personal-data notice
Scan evidence can include hostnames, employee emails, IPs — all PII under GDPR Art. 4(1). Pre-2.5.4 the operator was given no in-product notice that they're the controller for this evidence.
The new dismissible FindingsPiiNotice banner sits between the page header and the filters, in all 5 locales (3 keys × 5 = 15 new entries, parity 5/5). Versioned localStorage key nis2-findings-pii-notice-v1; SSR-empty-tree pattern matches LegalDisclaimerModal and OrientationCard.
🟠 GDPR — docs/privacy.md v1.1 with concrete data-flow disclosures
§7 "Self-hosted deployments" was a generic stub. Rewritten into six sub-sections:
- §7.1 Data inventory — explicit table of every personal-data category, the table it lives in, and the default retention.
- §7.2 IP / User-Agent in
audit_logs— names the columns, cites Art. 6(1)(f), documents Art. 89 pseudonymisation on Art. 17 erasure. - §7.3 Outbound network calls — names
crt.sh(Sectigo), DNS resolvers, target hosts as third-party data flows the deployer must disclose. - §7.4 PII captured incidentally — secrets-scanner output, subdomain enumeration, port banners.
- §7.5 / §7.6 — what the maintainer is not, and the deployer's GDPR obligations.
🟠 OSS hygiene — coordinated rewrite
SECURITY.mdfully rewritten: RFC 9116 cross-ref, GitHub-private-disclosure preferred channel, severity-graded SLAs (24h/48h/5d/10d response, 7d/14d/35d/best-effort patch), 35-day medium-severity ceiling aligned with EU Cyber Resilience Act (Reg. EU 2024/2847) conventions, 90-day coordinated-disclosure default, in/out-of-scope matrix, full security-measures section reflecting code as it ships, hall-of-fame placeholder, PGP placeholder./.well-known/security.txtRFC 9116-compliant: Contact (advisory + email), Policy URL, Preferred-Languagesen, it, Canonical,Expires: 2027-04-30.CODE_OF_CONDUCT.md— Contributor Covenant 2.1..github/ISSUE_TEMPLATE/—bug_report.yml,feature_request.yml, plusconfig.ymlwithblank_issues_enabled: falseand contact-links to redirect security / questions / dual-license away from issues..github/PULL_REQUEST_TEMPLATE.md— summary / why / surface-impact checkboxes / verification / NIS2-GDPR-legal-exposure prompt..github/CODEOWNERS— single-maintainer fallback with explicit re-listing of security-sensitive paths (auth, RLS bootstrap, target validator, alembic, infra, gitleaks).
Misc
.gitignore:.ruff_cache/added..gitleaks.toml:nis2-findings-pii-notice-v\d+added to localStorage allowlist family.API_VERSION2.5.3 → 2.5.4.
Verified
- Web build: green (24/24 pages).
- 5/5 locales validate;
findings.piiNotice*namespace aligned. .well-known/security.txtlinted by hand against RFC 9116 §2 (Expires set, Contact present, Canonical and Policy URLs absolute).
v2.5.3 — Davide fresh-clone walkthrough
External-reviewer feedback round (Davide, fresh-clone walkthrough on Windows + Linux). Four concrete fixes — one runtime blocker, one onboarding trap, one operability paper-cut, one orientation gap.
🟠 Fixed — Hydration error blocking the public landing on first load
Fresh git clone → make dev → http://localhost:8077/: the page rendered briefly then React surfaced a "Recoverable Error — Hydration failed because the server rendered text didn't match the client" overlay, and the legal-disclaimer modal (added in v2.5.2) intermittently appeared then disappeared.
Root cause — LegalDisclaimerModal rendered the full dialog tree on the server with show=false, then useEffect flipped show=true when localStorage said the visitor hadn't accepted. The rendered children (translations, lucide icons, <Button>) introduced enough microscopic SSR↔CSR variation under React 19 + Next 15 strict hydration to register as a mismatch.
Fix — mounted state set by a useEffect gates the entire render. Server and first client render return null; dialog appears ~1 frame after hydration.
🟠 Added — Python 3.10+ documented in README "Prerequisites"
make clean-all exited with Python non trovato — eseguire senza argomenti per installare dal Microsoft Store on Davide's Windows host. Same kind of failure was patched defensively in v2.4.28 with a Python interpreter probe in the Makefile, but the README still didn't tell a fresh installer that Python is required at all.
README now opens the install section with an explicit prerequisites table (Docker + Compose v2, GNU Make, Python 3.10+, openssl) plus a Windows-specific callout: the Microsoft Store stub at %LOCALAPPDATA%\Microsoft\WindowsApps\python.exe is not a real Python — disable the App Execution Aliases for python.exe / python3.exe and install from python.org.
🟠 Improved — make prod pre-flight error formatting
Previous one-line error sentences ran together with the shell prompt; the mitigation steps were buried at the end so the operator's eye landed on the symptom and missed the fix.
prod-preflight now emits banner-separated stanzas with explicit numbered steps and copy-paste-ready commands. Applied to all six failure paths: missing .env, missing/empty POSTGRES_PASSWORD, JWT_SECRET=GENERATE_ME placeholder, short JWT_SECRET, missing CORS_ORIGINS, and RLS_SUPERUSER_OK=1 warning.
🟠 Added — Dashboard orientation card
After signup, Davide couldn't figure out what the platform was for. The empty stat cards and "New Scan" CTA gave no hint of the link between a TLS/DNS/header scan result and the NIS2 sub-paragraphs the result eventually weakens. The sidebar showed Compliance / Governance / Vendors as separate routes with no narrative explaining why all three exist.
A new dismissible OrientationCard rendered between the page header and the stat cards summarises in 30 seconds:
- Scansioni — automated TLS / DNS / header / port / cert / secret checks. Each finding mapped to an Art. 21(2) sub-paragraph (a–j). Covers ~30 % of Art. 21 — the technically observable part.
- Checklist di Governance — 30 items covering human / organisational obligations the scanner cannot validate. Educational heuristic mapped to Art. 21(2), not a verbatim ACN framework.
- Fornitori / Supply chain — Art. 18 obligations: vendor inventory, scoring, contracts, ACN-specific fields. Tracked separately from Art. 21.
Implementation: versioned localStorage key (nis2-dashboard-orientation-v1), SSR-empty pattern (mounted gate), t.rich() for inline <b>. 7 keys × 5 locales = 35 new i18n entries, parity 5/5.
Misc
API_VERSIONliteral bumped 2.5.2 → 2.5.3 in lockstep withpyproject.toml..gitleaks.toml: addednis2-dashboard-orientation-v\d+to the localStorage-key allowlist family.
Verified
- Web build: green (24/24 pages, /dashboard at 8.19 kB / 157 kB First Load JS).
- 5/5 i18n locales validate;
dashboard.orientation*namespace present and aligned. make prod-preflightagainst an empty.env, a.envwith placeholderJWT_SECRET, and a.envwithRLS_SUPERUSER_OK=1all surface the new banner-formatted messages.
v2.5.2 — Legal-disclaimer interstitial on app + docs landing
First-visit consent dialog for both public landing surfaces — app (/) and docs (fabriziosalmi.github.io/nis2-public/).
What's new
A blocking legal-notice modal appears on first visit. The visitor must click Ho compreso — Procedi to reach the marketing surface. Acceptance is persisted in localStorage under a versioned key, so a future material revision of Terms or Privacy can force every prior visitor to re-acknowledge.
Avviso Legale
Questo strumento fornisce classificazioni automatizzate basate su un sottoinsieme della Direttiva NIS2 (UE 2022/2555). Non costituisce consulenza legale. Consultare un avvocato qualificato per determinare gli obblighi applicabili.
Termini di utilizzo · Privacy Policy
[ Ho compreso — Procedi ]
Why
Three concrete legal benefits:
- GDPR Art. 13 — informazione al momento in cui i dati personali sono ottenuti. Click-through consent guarantees the visitor cannot deny having seen the privacy notice.
- Defence against reliance claims — a downstream user who later argues "the platform told me I was compliant" runs into a click-through consent log. Pairs with the dashboard's compliance-page yellow alert (v2.5.0) and the scope clause in
docs/terms.md. - Coherence with sister NIS2 projects in the maintainer's portfolio — same pattern, consistent UX.
Implementation
- App landing: React component
packages/web/src/components/legal/legal-disclaimer-modal.tsx, mounted at top of unauthenticated/. Suppressed for authenticated users — they accepted at register time and re-acknowledging on every dashboard visit would be noise. - Docs landing: equivalent Vue component inline in
docs/.vitepress/theme/components/Home.vuewith the same dual-locale (EN+IT) dual-DOM pattern used by the rest of the docs home. - 25 new i18n keys (5 × 5 locales) under
landingPage.legalDisclaimer. Parity 5/5. - Versioned localStorage key (
nis2-legal-disclaimer-v1for app,nis2-doc-legal-disclaimer-v1for docs). - Body scroll locked while dialog is open. Backdrop is
bg-slate-950/95 backdrop-blur-smso the marketing surface behind is dimmed but not jarring. - Terms + Privacy links inside the dialog point to the canonical
docs/terms.mdanddocs/privacy.mdon GitHub (open in new tab) so the visitor can read them before clicking through.
Trade-off accepted
~50–100ms flash of landing before the dialog appears (React/Vue hydration). If this becomes a real UX issue we'll move to a CSS-class-on-<html> pattern with an inline detection script — same approach as the bilingual-locale gating already in the docs home.
Verified
- Web build: green (24/24 pages)
- VitePress docs build: green
- 5/5 i18n locales validate
🤖 Generated with Claude Code