Early Access — All features free while spots last. Join Now
All articles
security transparency CSP SBOM GDPR trust

Security posture update — HttpOnly sessions, nonce-based CSP, SBOM pipeline

A transparent write-up of the April 2026 security hardening on sentrikat.com and portal.sentrikat.com: what we changed, why it matters, and what's next.

Denis Sota · · 6 min read

When a European security team evaluates a vulnerability-management vendor, one of the first things they look at is the vendor’s own security posture. That’s fair — we ship an agent into their environment, we hold their vulnerability data, and we sign NIS2 reports on their behalf. So we need to practise what we preach.

This post is a transparent summary of the changes we shipped in mid-April 2026 after an external audit of the SentriKat web platform. No marketing — just what changed, why, and how to verify it.

TL;DR

ChangeBeforeAfter
Portal session storageJWT in localStorageHttpOnly + Secure + SameSite=Strict cookie
Portal CSRF defenceNoneSameSite=Strict + X-Portal-Request marker header
Content-Security-Policy (landing + portal)script-src 'unsafe-inline'Per-request 'nonce-$request_id'
CSP violation reportsSilentreport-to + /api/security/csp-report endpoint
Python dependency integrityVersion pins onlypip-compile --generate-hashes workflow
NVD monitoring alertsInstant on any 503Only after two consecutive failed probes
Public status page90-day history reset on restartPersistent history + background probe scheduler
Trust centerSingle /security page/security, /compliance, /roadmap, /sbom, /disclosure, /subprocessors, /status

Everything below is implemented and live.

The old flow stored the JWT in browser localStorage after login. It’s a common pattern for SPAs, but it has a well-known risk: any JavaScript running on the page can read it. If an attacker lands a single XSS — through a reflected URL parameter, a third-party script with a vulnerability, or an admin screen that renders user input unsafely — they can exfiltrate the session token in a fetch call and impersonate the user from their own machine.

Moving the session into an HttpOnly cookie removes that escalation path entirely. JavaScript cannot read the cookie. Browser automatically sends it on subsequent requests. An XSS is still a bug to fix, but it no longer immediately becomes a session-theft chain.

SameSite=Strict blocks the classic cross-site POST attack that cookies would otherwise enable. For defence-in-depth, every state-changing portal request must also carry the custom header X-Portal-Request: 1 — a header that browsers refuse to set on simple cross-origin requests, so an attacker page cannot forge it.

Nonce-based CSP — without breaking Astro

Content-Security-Policy is a second layer: even if XSS code executes, the browser refuses to run scripts that aren’t explicitly authorised. The old policy carried 'unsafe-inline' for scripts, which is the CSP equivalent of “authorise everything” and effectively disables the protection. Removing it without breaking the UI required some care because Astro pages legitimately emit small inline scripts (API base URL injection, animation bootstrap, dashboard autoscroll).

The shipped solution:

  1. Every intentional inline script emits a placeholder:
    <script is:inline nonce="d9197c1e80acbedbdaa9bfb16f1a030f">...</script>
    
  2. nginx substitutes d9197c1e80acbedbdaa9bfb16f1a030f with an unpredictable per-request value ($request_id, 32 hex chars) on every HTML response.
  3. The CSP header announces that same value:
    script-src 'self' 'nonce-$request_id' https://challenges.cloudflare.com
    
  4. 'self' still covers Astro-hoisted external modules under /_astro/…. Inline scripts without a matching nonce are refused by the browser.

Curl it yourself: the header shows a different nonce on every reload, and every <script> in the HTML carries the matching value.

CSP violation reporting

The new policy also points at a reporting endpoint:

report-uri https://api.sentrikat.com/api/security/csp-report
report-to  csp-endpoint

Browsers POST a JSON payload there whenever a script is refused. We log those at warning level. The first week after deployment, CSP reports are the single most useful tool for catching any inline script we forgot to mark with a nonce.

SBOM, DPA, and the rest of the trust center

The audit pointed out — correctly — that our technical posture was hard to see from the outside. A procurement officer going through a checklist doesn’t git clone our repo; they open the website and look for dedicated pages.

New pages live at:

  • /security — architecture, controls, responsible disclosure short form
  • /compliance — GDPR, FADP, NIS2, DORA, ISO 27001 roadmap, CRA readiness
  • /roadmap — dated commitments on ISO 27001 (Q4 2026), SOC 2 (Q1 2027), pen test (Q3 2026)
  • /sbom — CycloneDX + SPDX, how we generate them, how to request archived ones
  • /disclosure — full responsible disclosure policy with safe harbour
  • /subprocessors — table of every third party we share data with
  • /status — now with a 90-day persistent history, RSS feed, and email subscription

The /.well-known/security.txt now points at /disclosure as the canonical policy.

Dependency integrity

Python requirements are moving to hash-pinned lockfiles via pip-compile --generate-hashes. The source of truth is now requirements.in; a single make deps-lock regenerates requirements.txt with SHA-256 hashes per package. CI installs with pip install --require-hashes so a compromised PyPI mirror cannot swap a pinned version for a different artefact.

Less noise from the NVD monitor

Our data-source monitor probes upstream vulnerability APIs every six hours. NVD in particular has a long history of transient 503s. The previous logic alerted on any status flap; the new logic requires a status to persist across two consecutive probes (≥ 12 hours) before emailing the admin. A real outage still reaches us within a reasonable window; a single flaky response does not.

What’s not done yet

Transparency also means being honest about what’s queued:

  • style-src 'unsafe-inline' is still in place. Tailwind and Astro scoped styles produce thousands of inline <style> blocks; tightening this is a larger refactor.
  • The SaaS app at app.sentrikat.com, the MkDocs docs site, and the Flarum community forum still carry 'unsafe-inline' in their CSPs. The upstream templates don’t yet emit a nonce placeholder; we’re working on that.
  • ISO 27001 certification is in progress, target Q4 2026.
  • SOC 2 Type I is planned for Q1 2027.
  • Third-party pen test is scheduled for Q3 2026.

Everything above is also on /roadmap with updated dates whenever they change.

Reporting issues

If you find a security bug — even one not listed here — please email [email protected]. Our responsible disclosure policy and safe-harbor language are on /disclosure.


— Denis

Ready to automate your vulnerability management?

Deploy SentriKat on-premises in minutes. Track CISA KEV vulnerabilities, generate NIS2 compliance reports, and protect your infrastructure.

Request a Demo
Discuss this article: Community Forum