Skip to content

Releases: fabriziosalmi/nis2-public

v2.5.12 - Hardening, Fixes & Deps

19 May 07:35

Choose a tag to compare

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

15 May 19:52
78a7031

Choose a tag to compare

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 code
  • POST /auth/totp/disable — requires password re-authentication
  • Login flow blocks with mfa_required: true when TOTP is enabled, no partial cookies issued
  • pyotp>=2.9 added as API dependency; totp_secret/totp_enabled columns in Alembic migration

RS256 JWT with JWKS endpoint (closes #87)

  • Set JWT_PRIVATE_KEY + JWT_PUBLIC_KEY env vars to switch from HS256 to RS256
  • _signing_key() / _verifying_key() helpers decouple token creation from verification
  • GET /.well-known/jwks.json publishes 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_policies migration: ENABLE ROW LEVEL SECURITY, FORCE ROW LEVEL SECURITY, and tenant_isolation policy 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 — queries pg_policies and 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

15 May 19:34
ecfe7eb

Choose a tag to compare

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-risk escalates 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 task
  • DELETE /auth/me now also NULLs details JSONB 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/ready now checks DB + Redis + Celery workers; returns HTTP 503 on hard failure
  • /health/live alias added
  • React ErrorBoundary class component wired in dashboard layout
  • security.txt — honest PGP status with actionable instructions
  • E2E tests now run in CI (e2e-tests job 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

15 May 16:31

Choose a tag to compare

[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, and lucide-react to their latest stable releases, resolving multiple backend and frontend vulnerabilities.
  • CI/CD Pipeline Fix: Resolved Ruff static analysis errors (E402, W293) in target_validator.py to unblock the GitHub Actions test runner and enforce strict PEP8 compliance.

🟢 UI/UX & Localization

  • UI Build Stabilization: Mitigated a breaking change introduced by lucide-react v1.0.0 (which removed third-party brand icons) by migrating the Github icon to a native inline SVG, restoring a 100% stable static build pipeline.

v2.5.8 - Security & Usability Hardening

15 May 16:14

Choose a tag to compare

[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 removed FindingResponse import, restoring full findings API functionality.
  • Dependabot Patch: Updated npm dependencies resolving known CVEs.

🟠 Audit & Compliance

  • Enhanced Audit Logging: Integrated the log_action logger 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_MESSAGE Next-Intl Runtime Error: Fully mapped and injected the common.support, nav.support, and nav.documentation keys across all 5 supported locales (en, it, fr, de, es).
  • Component Polish: Created the missing Alert component 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

08 May 20:22

Choose a tag to compare

🔒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-invite endpoint
  • Email verification enforced: email_verified=True on register/accept-invite
  • .env.example sanitized: CHANGE_ME placeholders, 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-inline from production script-src
  • TypeScript: removed ignoreBuildErrors from next.config.ts
  • Strict TS build: fixed recharts dynamic import types + reports spread
  • Mass assignment audit: schemas confirmed safe (declassified)
  • Refresh token revocation: mitigated by iat watermark
  • 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_ip forwarded in config_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-history targets added
  • Dockerfile: ENTRYPOINT runs alembic upgrade head before server start
  • Production compose: celery-worker healthcheck added

v2.5.5

05 May 08:31

Choose a tag to compare

Full Changelog: v2.5.4...v2.5.5

v2.5.4 — Tier 2 + Tier 3 closure: auth hardening, GDPR UX, OSS hygiene

30 Apr 19:11

Choose a tag to compare

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.md fully 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.txt RFC 9116-compliant: Contact (advisory + email), Policy URL, Preferred-Languages en, it, Canonical, Expires: 2027-04-30.
  • CODE_OF_CONDUCT.md — Contributor Covenant 2.1.
  • .github/ISSUE_TEMPLATE/bug_report.yml, feature_request.yml, plus config.yml with blank_issues_enabled: false and 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_VERSION 2.5.3 → 2.5.4.

Verified

  • Web build: green (24/24 pages).
  • 5/5 locales validate; findings.piiNotice* namespace aligned.
  • .well-known/security.txt linted by hand against RFC 9116 §2 (Expires set, Contact present, Canonical and Policy URLs absolute).

v2.5.3 — Davide fresh-clone walkthrough

30 Apr 18:19

Choose a tag to compare

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 clonemake devhttp://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 causeLegalDisclaimerModal 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.

Fixmounted 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_VERSION literal bumped 2.5.2 → 2.5.3 in lockstep with pyproject.toml.
  • .gitleaks.toml: added nis2-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-preflight against an empty .env, a .env with placeholder JWT_SECRET, and a .env with RLS_SUPERUSER_OK=1 all surface the new banner-formatted messages.

v2.5.2 — Legal-disclaimer interstitial on app + docs landing

30 Apr 16:26

Choose a tag to compare

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:

  1. 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.
  2. 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.
  3. 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.vue with 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-v1 for app, nis2-doc-legal-disclaimer-v1 for docs).
  • Body scroll locked while dialog is open. Backdrop is bg-slate-950/95 backdrop-blur-sm so the marketing surface behind is dimmed but not jarring.
  • Terms + Privacy links inside the dialog point to the canonical docs/terms.md and docs/privacy.md on 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