Running an AI Audit that produced no recommendations looked exactly like
never having run one — "No AI recommendations available yet. Try running an
AI Audit above." — which is the opposite of what happened and leaves nothing
to act on.
The backend already handles a model that ignores the JSON schema: it puts the
raw reply in global_strategy and returns an empty list. The empty-list branch
ran first and swallowed it. That reply is now shown, labelled as what it is,
with the hint that weaker models fail this way.
The error hint was stale too. It told everyone to check Infomaniak settings
and set the model to 'llama3' — wrong advice on DeepSeek, OpenAI or
Anthropic, pointing at a setting that was already correct. It now points at
the AI Integration screen and at verifying the model still exists.
Also dropped the window.location.reload() that sat on the empty state: it
threw away the result the user had just waited for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
'Enterprise Security' says nothing; 'Vulnerability Management' says what the
tool is. The full 'Enterprise Vulnerability Management' overflows the 288px
sidebar at this tracking, so the tagline drops the marketing word rather than
wrapping to two lines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit added the theme button to shell/Header.tsx, which is
imported NOWHERE (dead component — the app shell renders its own top bar,
desktop has none at all), so the tester couldn't find any button.
New ThemeSwitcher: segmented Light / Mid / Dark control at the bottom of
the sidebar (desktop sidebar + mobile drawer, above the user box) —
always visible on every page. Dead Header.tsx deleted so the next change
doesn't land there again.
The old theme toggle only flipped daisyui variables, which the hardcoded
light utility classes (bg-white, text-gray-900, …) never react to — so
it visibly did nothing. Instead of rewriting every page with dark:
variants, add an UN-layered override block in globals.css that remaps
the light-surface utilities (plus tinted badges, borders, inputs,
scrollbars) under html[data-theme='mid'|'dark'] via CSS variables —
un-layered author CSS beats Tailwind's @layer utilities without
!important. Sidebar (bg-gray-800/900) and brand colors intentionally
untouched.
useTheme now cycles light → mid → dark (legacy 'corporate'/'dark'
values migrate), the Header button shows the active theme (sun / moon /
solid moon + label), and an inline pre-paint script in layout.tsx
applies the stored theme before hydration so dark users get no white
flash. Verified all three themes render correctly in the browser.
New early-warning surface the tester asked for: the newest 7-Zip advisory was
on ZDI before NVD or cvelistV5 had it. A dedicated Advisories page now shows
the CISA-KEV latest additions plus configurable RSS/Atom sources.
- advisory_feed_service: fetch + parse (RSS and Atom), cached in a setting so
the page serves instantly; per-feed errors recorded, never fail the run.
Default feeds verified LIVE before shipping (ZDI published/upcoming, CERT-EU,
BSI/CERT-Bund WID, Cisco PSIRT); Cisco emits junk after the XML root, so a
lenient per-item fallback parses it anyway. Security: any DOCTYPE/ENTITY
declaration is refused outright (XXE / billion-laughs) — feeds never need
DTDs, no defusedxml dependency required.
- GET /api/v1/advisories/feeds (cache; first call fetches),
POST /feeds/refresh (editor). Config = advisory_feeds_config setting,
editable from the page (admin): enable/disable, rename, custom URLs.
- Scheduler refresh every 6h; 'Advisories' nav item for all roles (read-only
surface; Refresh for editor+, Configure admin-only).
Parser self-checked against rss/atom/cisco-junk/DTD-refusal fixtures.
The rebrand renamed many Tailwind color classes vulncheck-blue → truevuln-blue
but left the theme token as --color-vulncheck-blue, so those 115 classes
referenced an undefined color → transparent backgrounds / unstyled text
(tester: the "Save Template" button was invisible until hover; also affected
buttons, links, focus rings, sort arrows app-wide).
Unified everything on truevuln-blue: renamed the @theme token to
--color-truevuln-blue and the remaining 156 vulncheck-blue class usages to
truevuln-blue. Now all 271 usages resolve to one defined token; 0 vulncheck-blue
left.
Nav/Drawer render the name as two separate JSX text nodes ("Vuln" + a
<span> for the color-accented "Check") for the two-tone styling — the
literal string "VulnCheck" never appears in the source, so the earlier
exact-string rename pass didn't touch it. Logo/Nav still showed "VulnCheck"
after that commit (tester screenshot). Now "True" + accented "Vuln",
matching the rest of the rebrand. logo.svg itself is just the shield+check
glyph with no embedded text — nothing to change there.
Renames the product name in every user-visible surface and internal
self-reference: page title, nav/shell, login/MFA pages, email templates and
subject prefixes ([VULNCHECK] → [TRUEVULN]), TOTP issuer label, report/PDF
headers, notification previews, outbound User-Agent/HTTP-Referer headers we
set ourselves, docs (README, ARCHITECTURE, PROJECT_OVERVIEW, DATABASE_SCHEMA,
README.DEV, TROUBLESHOOTING is untouched — see below), and .env.example
placeholder config (LDAP/OIDC/SAML example domains and paths).
Also renamed the on-disk cache file paths (/tmp/vulncheck-*.zip|csv|json →
/tmp/truevuln-*), kept consistent across the two files that share the
cvelistV5 ZIP cache path — first run after deploy re-downloads that ~557 MB
cache once (harmless, disposable).
Deliberately LEFT UNCHANGED (not branding — real external references or
infra identifiers; renaming the text without renaming the underlying thing
would just break/mislead):
- The actual Gitea repo URL/path (gitea.isuit.ch/vulncheck/vulncheck) and the
README lines derived from it (git clone target dir, tree listing) — a real
repo rename is a manual Gitea-side step (Settings → repository name) the
user would need to do themselves, and existing clones would need
`git remote set-url` after.
- The real support mailbox (support-vulncheck.sq9vd@passmail.net, in both
README and TROUBLESHOOTING) and the Buy Me A Coffee link — both point to
accounts that still exist under the old name; renaming the text alone
wouldn't create new ones.
- GitNexus MCP resource URIs in CLAUDE.md/AGENTS.md (gitnexus://repo/
vulncheck/...) — tied to GitNexus's own index name for this repo, not our
branding; those files are untracked in this repo anyway.
- docker-compose.yml container/network/Postgres user+db names
(vulnmanager-*) — explicit user decision: infra naming carries real
deploy/data risk on an already-running instance and isn't part of the
product-branding ask.
- The Tailwind color token class `vulncheck-blue` (frontend/app/globals.css)
— invisible internal CSS variable name, renaming it would touch ~270
className occurrences for zero user-visible benefit.
Verified: backend py_compile clean on every touched .py file; frontend tsc
clean (two pre-existing, unrelated errors remain: assets/page.tsx SVG title
prop, mfa-setup missing qrcode.react types). All diffs are exact-string
renames — no other changes riding along.
AppShell does an /auth/me check on every mount; on 401 (no
session yet — exactly the forced-MFA flow) it router.push()'s
to /login. /mfa-setup got bounced for that reason — page
rendered for a frame then AppShell kicked the user back to
the login form.
Treat /mfa-setup like /login: render children without the
session gate.
New top-level page at /compliance with:
- 4-card header (Avg score, Assets covered, Pass total, Fail total)
- Worst-performing assets list (top 5) with click-through
- Full assets table sortable by avg score (lowest first)
- Asset detail modal: per-policy bars with pass/fail/N/A counters,
per-policy refresh button that hits /compliance/{asset_id}/refresh
Score colour bands match security-team convention:
>= 90 emerald | >= 70 yellow | >= 40 orange | < 40 red
Refresh All button hits /compliance/refresh; per-asset modal has its
own ↻ refresh button. Errors surface inline at the top of the page.
Sidebar nav: 'Compliance' between Assets and Scan Jobs, CheckBadge
icon. Visible to every authenticated user (read endpoints accept
readonly). Editor role still needed for the refresh actions — those
fail with 403 from backend if the user lacks privilege.
Per-check deep-dive view (compliance_checks table) not yet wired —
will land alongside the dashboard widget in the next commit.
LDAP users can now enable/disable app-side TOTP just like local users.
Password confirmation during setup/disable is verified by re-binding to
LDAP as the user — the password itself is never stored.
- orchestrator: MFA gate no longer restricted to local accounts; any
credential-authenticated user with totp_enabled is challenged
- auth router: _verify_user_password helper routes to password_hash
(local) or LDAP rebind (ldap); SAML/OIDC remain rejected
- MfaCard: shows enrolment UI for LDAP users with a hint that the LDAP
password is verified via directory rebind
Sidebar now respects the logged-in user's role and hides items that
would 401/403 anyway when clicked. Mapping:
Dashboard, Vulnerabilities, Assets, Reports, Notifications, Settings
── all roles
Scan Jobs, Policies
── editor + admin
Groups, Audit Logs, Auth Providers
── admin only
Implementation:
- /auth/me read on mount; role cached in component state
- nav items get an optional 'requires: admin | editor' marker; items
hidden when current role rank is below required rank
(readonly < editor < admin)
- pre-resolve render shows only items without 'requires' to avoid
the admin-only entries flashing on slow networks before /auth/me
returns
- Management section header is hidden entirely when no items in it
pass the role check (cleaner UI for readonly users)
This is a UX hide, not a security boundary — the backend already
enforces RequireAdmin / RequireEditor on the routes. Typing the URL
manually still hits a 403.
Adds a self-service MFA setup UI for local-auth users:
- frontend/components/auth/MfaCard.tsx — three-stage card:
1. Idle → 'Enable MFA' / 'Disable MFA' (state-aware)
2. Setup → confirm password → POST /auth/mfa/setup, receive secret+URI
3. Activate → QR (qrcode.react SVG, all client-side, no external service)
+ readable secret fallback + 6-digit code field → POST /auth/mfa/activate
Disable flow: password confirm → POST /auth/mfa/disable. Card hides
itself for non-local users (their IdP handles MFA).
- frontend/app/settings/page.tsx renders MfaCard above the existing
'Your Profile' panel.
- /auth/me now returns auth_provider and mfa_enabled so the card can
decide which state to show without an extra fetch.
- qrcode.react dependency added (~20KB, MIT). QR renders locally as SVG;
the otpauth secret never leaves the browser.
Phase 6: gives admins runtime control over the parts of the multi-provider
auth stack that should be editable at runtime, while keeping credentials/
endpoint config in environment variables (where they belong).
Backend (app/routers/auth_admin.py):
- GET /api/v1/auth-config/status provider enablement + readiness + user counts
- GET /api/v1/auth-config/role-mappings current mapping rules
- PUT /api/v1/auth-config/role-mappings replace mapping rules (validated against
AuthProvider + UserRole enums)
- POST /api/v1/auth-config/ldap/test opens LDAPS conn, binds service account,
optionally searches a sample username
- POST /api/v1/auth-config/oidc/test fetches discovery doc + JWKS, reports
issuer / endpoints / key count
- POST /api/v1/auth-config/saml/test parses IdP metadata, returns SSO URL
+ entityID + cert presence
All RequireAdmin. PUT logs CONFIG_CHANGE audit event.
Frontend (frontend/app/admin/auth/page.tsx):
- Global config panel (lookup order, JIT, default role, crypto-key set?)
- Provider status cards with enabled/configured badges + per-provider
Test button (with output dump)
- Inline role-mapping editor per provider: add/edit/reorder/remove rules,
validates client-side (role dropdown), saves via PUT
Sidebar: new 'Auth Providers' entry under /admin/auth (KeyIcon).
Perf:
- AppShell: auth check once on mount instead of every pathname change
- Dashboard: replace bare <a> with Next Link for prefetch
Security:
- Migrate python-jose to PyJWT (CVE-2024-33663, CVE-2024-33664)
- JWT exp/iat now UTC-aware via datetime.now(timezone.utc)
- Drop default 'changeme' fallback for DEFAULT_ADMIN_PASSWORD
- Force POSTGRES_PASSWORD env in docker-compose