Releases: logtide-dev/logtide
Releases · logtide-dev/logtide
v0.9.3
Added
- Traces live tail (SSE): new
GET /api/v1/traces/streamServer-Sent Events endpoint polls the traces table once a second and emits newtrace_ids as they appear, filtered byprojectId,service(CSV) anderror. On the frontend the traces page now has a "Live tail" switch + rows-limit selector (50/100/200/500/1000, persisted inlocalStorage) in the filter bar; incoming traces are prepended and the list is capped at the chosen limit. ThetracesEventSourceis torn down ononDestroyand on every filter-key change - Traces row click-to-expand with inline span list: clicking a trace row now toggles an inline panel below it that fetches
GET /api/v1/traces/:traceId/spansonce (cached pertrace_idfor the session) and shows a compact table with Service / Operation / Kind / Duration / Status for each span. The "View" action still opens the full trace detail page - Traces keyboard shortcuts:
/focuses the Trace ID input,rrefreshes,j/kmove the selection down/up with scroll-into-view,enterexpands/collapses the selected trace. Registered viashortcutsStoreunder scopetracesand unregistered on destroy - Traces export (JSON / CSV): new Export popover in the filter bar produces a client-side download of the current list (disabled during live tail or when the list is empty). CSV covers start_time / service / operation / duration_ms / span_count / error / trace_id
- Monitoring entry in the command palette:
cmd+know listsMonitoringunder Navigation (icon +g oshortcut hint), matching the sidebar's Detect group so the page is reachable without leaving the keyboard trace_volumedashboard panel: new panel type that plots span count over time (optional errors line), configurable time range (1h/6h/24h/7d) and optional service filter. Mirrors the shape of the existing logs "Log Volume" panel but for OTLP traces. Reads thespans_hourly_stats/spans_daily_statscontinuous aggregate first, falling back to the rawspanshypertable withdate_trunconly when the cagg is empty for the window (covers theend_offset=1hrefresh lag on freshly-ingested data). TimescaleDB-onlyactivity_overviewdashboard panel: unified multi-series timeline combining logs, log errors, spans, span errors, detection events and alert triggers on a common bucket grid (hourly for ≤24h, daily for 7d/30d). Each series is individually toggleable in the config form. Per-source queries run in parallel and prefer thelogs_*_stats,spans_*_stats,detection_events_*_statscontinuous aggregates for cost; if a cagg returns zero rows for the window the fetcher falls back to the raw hypertable (logs,spans,detection_events) for that source only, so the panel stays correct on freshly-ingested data without running a raw scan in the hot path. Alerts always come fromalert_history+alert_rules(no cagg exists for it)
Changed
- Monitoring page reorganized into tabs: what was a single 1000-line scroll with three unrelated sections (Monitors, Incidents, Maintenance) plus a status-page config block wedged at the top is now a
Tabs.Rootwith four tabs — Monitors, Incidents, Maintenance, Status page — each with its own header, its own primary CTA, and its own empty state. The Monitors tab gains a summary card grid (Total / Up / Down / Paused, each card clickable to filter the list by that status), a search input that matches name / target / type, and a "Clear filters" shortcut. Every row now has a one-click Pause/Resume button (previously buried in the edit form). Status-page visibility, password, slug and embed-badge config moved into the Status page tab. No changes to underlying API calls or permissions - Admin dashboard sidebar collapses on mobile: the 256px admin sidebar was previously always visible, taking up a huge chunk of the viewport on phones. It now hides below
lg:and opens as a fixed drawer triggered by a hamburger in a new mobile-only sticky header that also shows the current section name (Dashboard / Users / Organizations / etc). The drawer has a backdrop, is dismissed on nav-click or backdrop-click, and locks body scroll while open. Desktop behaviour unchanged - Dashboard footer wraps properly on mobile: was a single
justify-betweenrow that squeezed "LogTide / Alpha v0.9.2" against "© 2026 LogTide · Documentation · GitHub" on narrow screens. Now stacks the brand block over the links block belowsm:(each block keeps its own horizontal flow withflex-wrap gap-x-4 gap-y-1), and "Documentation" shortens to "Docs" undersm:to fit on a single line. Container padding also drops frompx-6 py-4topx-4 py-3on mobile - User settings promoted from a dialog to a full page at
/dashboard/account. The cramped scrollableUserSettingsDialogmodal (profile + password + tutorial restart + danger zone stacked inside a 32rem-wide overlay) is now a proper route using amax-w-3xlcontainer with each concern split into its ownrounded-lg border bg-cardsection (Profile / Change password / Onboarding / Danger zone). The dropdown menu item still says "User Settings" but now navigates instead of opening an overlay.UserSettingsDialog.svelteis removed. The delete-account confirmation remains a nestedAlertDialogsince that one is genuinely destructive and modal-appropriate - Public status page polished for mobile and clarity: outer container shrinks from
px-4 py-10topx-3 py-6on phones (onlysm:and up gets the roomy desktop padding). The 45-day uptime bars drop theirmin-w-[6px]floor to4pxon mobile so the whole row fits comfortably on 320px viewports. The footer ("Last updated …" and "Powered by LogTide") grows fromtext-[10px]totext-xswith a taller top margin, and "LogTide" is now styled astext-primary font-medium hover:underlineso the link affordance is obvious (previously only a hover recolor). Incident update timestamps/status labels also bumped fromtext-[10px]totext-xs. The monitor-type badge (HTTP/TCP/etc.) hides belowsmto free up the row for the name + uptime%. The password input switches from a fixedw-64tow-full max-w-xsso it never overflows narrow phones - Monitoring forms moved into modal dialogs: the previous inline create/edit monitor form, the "New incident" form, the "Post incident update" form and the "Schedule maintenance" form were expanding/collapsing sections that shifted the whole page layout when opened. They now open as centered
Dialogmodals withmax-h-[90vh] overflow-y-autoso tall forms (monitor create) scroll independently of the page. The per-row "Post update" Dialog is a single instance driven by ashowUpdateFormid + anupdatingIncidentderived lookup, replacing the one-per-row inline expand. Form state, validations and submit handlers are untouched - Traces filter UI reworked into the same two-row pill bar as the search page: row 1 keeps Time Range (
TimeRangePickerin a popover), the Live tail switch, the List/Map view toggle and the Export menu. Row 2 exposes every filter as its own always-visible pill — Project (single-select), Services (multi-select), Status (tri-state: all / errors / ok), Duration range (min/max ms inputs), Trace ID (direct-navigate to the trace detail page). Pills switch to thesecondaryvariant when a non-default value is set; "Clear all" appears when any filter is active. Mobile popovers usew-[Xpx] max-w-[90vw]so they never overflow the viewport.handleTimeRangeChangesyncs picker state back intotimeRangeType/customFromTime/customToTimeso the trigger label survives popover close/remount - Traces backend accepts multi-value
projectIdandservice(CSV on the query string) and two newminDurationMs/maxDurationMsbounds onGET /api/v1/traces. The cross-project access check loops over every project in the list.tracesService.listTraceswidens its input types tostring | string[]and forwards toreservoir.queryTraces, which already supported these filters - Search page paginator now uses the same ellipsis/windowed control as the traces page: shows up to four numbered buttons plus sentinel
…on each side whentotalPages > 7. Falls back to the previous "Page N" label when the backend doesn't return a total (storage engines withouttotalin the query response). "Previous" / "Next" labels hide on<smscreens so only the chevron shows on phones - Empty states wait for the first load to complete before rendering on the search, traces, errors and security-incidents pages. Each page now tracks a
hasLoadedOnceflag flipped in thefinallybranch of its loader; until the first fetch resolves (success or failure) the page shows its skeleton instead of the "No Logs Yet / Start sending logs from your applications" onboarding card. Prevents the flash where an org that already has data showed the onboarding CTA during the initial render between mount and the first query. When a fetch legitimately returns zero rows, the page also distinguishes "nothing matches these filters" (with a Clear-filters shortcut) from "project has no data at all" (the full onboarding) - Search page filter UI reworked into a two-row pill bar: row 1 keeps Search + mode, a Time Range popover trigger, Live Tail and Export inline. Row 2 exposes every filter as its own always-visible pill (Projects, Services, Hostnames, Levels, Trace ID, Session ID, Metadata) — each pill displays the current value (e.g. "Service: api-gateway", "Levels: 2") and opens its own popover for editing. Pills switch to the
secondaryvariant when a non-default value is set so active filters are visible at a glance. A "Clear all" link appears at the end of the row when any filter is active. Popover widths usew-[min(Xpx,calc(100vw-1rem))]so they never overflow the viewport on phones. No semantic change to the underlying filter behavior (state, query params, live-tail flo...
v0.9.2
Added
- Generic metadata filters in log search and alert rules: the search page and alert rule dialogs now expose a "Metadata filters" section backed by a new
MetadataFilterBuildercomponent. Supported operators:equals,not_equals,in,not_in,exists,not_exists,contains. Filters are applied server-side via a GIN-indexed JSONB query builder in reservoir; alert evaluation also runs the same matcher in-process so rules can fire only when a specific metadata field matches - Configurable metadata columns in log table view: users can add arbitrary
metadata.*keys as extra columns in the search results table via a "Columns" picker. The selected column set is persisted per project in localStorage so it survives page reloads - Create user from admin panel (issue #198): admins can now provision new accounts directly from
Admin → User Managementvia a "Create User" button, without having to temporarily re-enable public signup. Opens a dialog to set email, name, password and optional admin role. Backed by a newPOST /api/v1/admin/usersendpoint that bypasses theauth.signup_enabledgate and logs acreate_userentry to the audit log - Set a custom dashboard as default from the UI: the dashboard switcher now shows a clickable star next to each org-wide, non-personal dashboard; clicking it promotes that dashboard to be the org's default. Backed by a new
POST /api/v1/custom-dashboards/:id/set-defaultendpoint that atomically unsets the previous default and sets the new one in a single transaction, respecting the existing partial unique index. Personal and project-scoped dashboards are rejected with a 400 - Editable project slug from monitoring page: the status-page settings card in
/dashboard/monitoringnow exposes a "Public URL slug" input that lets the user rename a project's slug, with inline validation against a shared format check (lowercase alphanumeric + hyphens, 2-50 chars), reserved-word list (api,admin,dashboard,status,auth,login,signup,logout,_app,health), and per-org uniqueness. Conflicts surface as 409 with a friendly inline error; race conditions are caught at the DB layer via the new composite unique index - Editable organization slug from settings:
/dashboard/settings/generalslug field is no longer read-only; owners can rename the org slug with the same validation rules and global uniqueness, with a warning that any existing status-page links and embed badges will break
Changed
- Public status page URL is now scoped under the organization (BREAKING): page and badge URLs changed from
/status/:projectSlugto/status/:orgSlug/:projectSlug. Affects the public web page,/api/v1/status/:orgSlug/:projectSlug/badge.svg, and/api/v1/status/:orgSlug/:projectSlug/badge.json. No redirect from the old URLs. Anyone embedding the badge SVG/JSON or sharing a status-page link must update the URL to include the org slug. Migration040simultaneously moves project-slug uniqueness from global back to per-org, so two different organizations can now both have a project namedfrontendwithout auto-suffixing
Fixed
- Security dashboard crashed with "Cannot read properties of null (reading 'toLowerCase')" (issue #200): root cause was two parallel
unnest(mitre_techniques)/unnest(mitre_tactics)calls in the same SELECT list ofSiemDashboardService.getMitreHeatmap. PostgreSQL evaluates sibling set-returning functions in lockstep and NULL-pads the shorter array, so any detection event whose tactic and technique arrays had different lengths produced heatmap rows with a null tactic, which then crashedMitreHeatmap.abbreviateTacticon the frontend. The heatmap query now unnests techniques only and resolves each one to its canonical tactic via the sharedMITRE_TECHNIQUESmap (with sub-technique to parent fallback), eliminating the malformed pairs at the source. FrontendMitreHeatmapalso filters cells with null tactic/technique andDetectionEventsList.getLogLevelClassdefensively handles a nulllevelas belt-and-suspenders
Security
- Bump
fastifyto 5.8.5 (GHSA-247c-9743-5963, CVE-2026-33806, HIGH): body schema validation could be bypassed by prepending a single space to theContent-Typeheader. Parser and validator disagreed on how to trim the header, so the body was still parsed but the schema lookup returned no validator and validation was skipped entirely. Upgraded from^5.8.3to^5.8.5 - Bump
@sveltejs/kitto 2.57.1 (GHSA-2crg-3p73-43xp, CVE-2026-40073, HIGH):BODY_SIZE_LIMITcould be bypassed under certain conditions inadapter-node. Tightened the pnpm override from>=2.53.3to>=2.57.1 - Bump
@sveltejs/kitto 2.57.1 (GHSA-3f6h-2hrp-w5wx, CVE-2026-40074, MEDIUM): callingredirectinside thehandlehook with a location containing characters invalid for an HTTP header threw an unhandledTypeError, enabling DoS if the location included unsanitized user input
New Contributors
- @impsislegobatman made their first contribution in #201
Full Changelog: v0.9.1...v0.9.2
v0.9.1
Fixed
- Identifier pattern update/delete failed with "Organization ID is required" (issue #193): PUT and DELETE routes required
organizationIdas an explicit query param or API key context, but session-based auth never sets that field. Now falls back to the user's first organization, consistent with GET and POST - Project rename failed with "Expected string, received null" (issue #195):
updateProjectSchemausedz.string().optional()which rejectsnull, but projects without a description sendnullfrom the DB. Changed toz.string().nullable().optional()and updatedUpdateProjectInputaccordingly - Pipeline create/preview/import returned generic "Validation error" (issues #193, #194): POST routes expected
organizationIdin the request body but the frontend sends it as a query param. Routes now merge the query param into the body before Zod validation. Frontend error messages now surface the first validation detail instead of the generic label - Invitation accept race condition: wrapping the membership check + insert in a transaction caused the
accepted_atupdate to roll back when throwing "already a member". Split the early-exit path out of the transaction and added23505unique-constraint handling for true concurrent accepts - SSE live tail duplicate sends:
latestLogpicked the oldest entry from a DESC-sorted array instead of the newest, causing every poll to re-fetch and re-send all logs since the oldest timestamp. Replaced with a defensive max-time computation - Sigma sync corrupted
alert_rule_idFK: fallbackalertRuleId || existing.idwrote the sigma rule's own PK into the alert_rules FK column when no alert was auto-created. Now omits the column entirely unless a new alert rule was just created - Exception log viewer returned empty results for org-wide error groups:
getLogsForErrorGrouppassed an empty string asprojectIdto reservoir when no project filter was set. Now groups log IDs by their exception'sproject_idand issues onegetByIdscall per project - ReDoS in HTTP monitor body assertion: user-supplied regex pattern was compiled with only a 256-char length limit, no catastrophic-backtracking check. Added
safe-regex2validation and a compile-error catch - OTLP int64 precision loss:
parseInt()silently truncated int64 attribute values exceedingNumber.MAX_SAFE_INTEGER. Unsafe values are now kept as strings in metadata - PII salt race condition fallthrough: if two workers raced on the first hash for an org, the loser could return an unpersisted local salt when the retry read failed, permanently desynchronizing PII hashes. Now scopes the catch to
23505and throws on unexpected errors - Monitor status fallthrough on missing row:
processCheckResultsilently skipped the entire state machine (no status update, no notifications) whenmonitor_statuswas undefined. Now re-reads from DB or creates a default row before proceeding - Test isolation: auth mode pollution across test files:
system_settingswas never reset between tests, so any test settingauth.mode='none'caused 12 unrelated "401 without auth" assertions to return 503. Added cleanup + cache invalidation to globalbeforeEach - Sigma detection tests used
sigma_idafter code migrated toid: fixture data still passedrule.sigma_idassigmaRuleId, which would not match the new.where('id', '=', ...)queries translateDeletedropped level filter in reservoir:pushFilterreturn value was discarded for thelevelcondition, corrupting$Nparameter slots when bothserviceandlevelfilters were present- SQL injection in
querySpansviasortBy/sortOrder: user-controlled strings were interpolated directly into raw SQL in both TimescaleDB and ClickHouse engines. Added explicit column/direction allowlists ingestSpanshardcoded::uuid[]forproject_id: ignored theprojectIdTypeengine option, breaking text-based project IDs with a Postgres cast errorlocalStorageSSR crash in organization store: 7 directlocalStoragecalls without abrowserguard would throwReferenceErrorduring server-side rendering. Addedbrowsercheck on all accesses- Invite token not URL-encoded in redirect:
goto(/login?redirect=/invite/${token})corrupted the redirect path for tokens containing+,=, or/. Now wraps inencodeURIComponent ruleIdnot URL-encoded in security dashboard navigation: inconsistent withserviceNameandtechniquewhich already usedencodeURIComponent- Missing UUID validation on monitoring route params: all
/:idhandlers passedrequest.params.iddirectly to DB queries without format validation. Added Zod.uuid()parsing on every route - Negative
limit/daysin monitoring routes:Number("-1") || 50evaluates to-1(truthy), passing a negative value toLIMIT. Replaced with aparsePositiveIntguard that clamps to[1, max] - Missing UUID validation on status-incident route params: same issue as monitoring -
:idparams were used unvalidated in DB queries - SIEM comment body has no max length:
z.string().min(1)with no upper bound allowed arbitrarily large comment payloads. Added.max(10000) - Notifications and alerts
limit/offsetNaN passthrough:parseInt("abc")returnedNaN, which was passed to Kysely.limit(NaN). Added safe integer parsing with fallback and max cap - Correlation
referenceTimeaccepted invalid date strings: schema validated only{type: 'string'}, sonew Date("garbage")flowed into Kysely WHERE clauses as Invalid Date. Addedformat: 'date-time'and a defensive 400 response - Log pipeline comment contradicted jsonb merge direction: code comment said "do NOT overwrite existing" but the in-progress fix had flipped the jsonb
||operand order so pipeline fields now win. Updated comment to match actual behavior
Full Changelog: v0.9.0...v0.9.1
v0.9.0
🌊 Logtide 0.9.0
This release brings three long-requested pillars to Logtide: a fully customizable dashboard system, proactive infrastructure monitoring with public status pages, and a structured log parsing pipeline.
✨ What's New
📊 Custom Dashboards (#151)
- Your layout, your rules: replace the previous fixed dashboard with as many team-specific dashboards as you need, each composed of freely arranged and resized panels.
- 9 panel types: time series, single stat, top-N table, live log stream, alert status, metric chart, metric stat (OTLP with avg/sum/min/max/count/last/p50/p95/p99), trace latency (p50/p95/p99), detection events, and monitor status - every data source in Logtide is now representable.
- Drag-to-reorder and drag-to-resize: panels snap to a responsive 12-column grid, collapsing gracefully to 6 columns on tablet and 1 column on mobile.
- Inline edit mode: toggle editing directly on the dashboard - no separate page, no lost context. Changes are held in a local snapshot and discarded on Cancel.
- Dashboard switcher: a header dropdown lets you switch, create, delete, import, and export dashboards. The default dashboard is protected from deletion.
- YAML import/export: round-trip your dashboards through version-controlled YAML files alongside your infrastructure code.
- Versioned schema with migrations:
schema_version: 1ships with a migration framework in@logtide/sharedso future schema changes are applied automatically on read. - Zero visual regression for existing users: the auto-created Default dashboard replicates the previous fixed layout (4 stat cards + log volume + top services + top error messages) exactly.
- Cross-org isolation: every panel data fetch verifies that
config.projectIdbelongs to the requesting org, blocking data leaks via crafted YAML imports or stale references.
🖥️ Service Health Monitoring and Status Pages (#152)
- 3 monitor types: HTTP/HTTPS (configurable method, expected status, custom headers, body assertion via contains or regex), TCP ping, and heartbeat (alert when no ping arrives within the grace window).
- Uptime Kuma-inspired status page (
/status/:projectSlug): 45-day heartbeat bar grid, per-monitor uptime badge, overall status banner, light/dark mode toggle. Visibility is configurable per project - disabled (default), public, password-protected, or org-members-only. - Auto-incident creation: when the consecutive failure threshold is crossed, a SIEM incident is created with
source: 'monitor', linked viamonitor_id, and dispatched through existing email/webhook notification channels. - Scheduled maintenances: define maintenance windows with start/end times; active windows suppress incident creation and display a banner on the status page.
- Manual status incidents: publish incident communications (Investigating → Identified → Monitoring → Resolved) with a full update timeline, independent from SIEM incidents.
- Heartbeat endpoint:
POST /api/v1/monitors/:id/heartbeataccepts both API key and session auth, rate-limited to 600 req/min. - TimescaleDB storage:
monitor_resultshypertable with 7-day compression, 30-day retention, and amonitor_uptime_dailycontinuous aggregate refreshed hourly. - Monitoring dashboard (
/dashboard/monitoring): monitor list with project selector, create/edit/delete forms, detail page with uptime chart and recent checks, and a one-click heartbeat URL copy.
🔩 Log Parsing and Enrichment Pipelines (#153)
- 5 built-in parsers: nginx (combined log format), apache, syslog (RFC 3164 and RFC 5424), logfmt, and JSON message body.
- Custom grok patterns:
%{PATTERN:field}and%{PATTERN:field:type}syntax with 22 built-in named patterns and optional type coercion (:int,:float). - GeoIP enrichment: extract country, city, coordinates, timezone, and ISP data from any IP field using the embedded MaxMind GeoLite2 database.
- Zero ingestion latency impact: pipelines run as BullMQ background jobs after ingestion acknowledgment.
- Project-scoped vs org-wide: pipelines can target a specific project or apply across the entire organization; project-specific pipelines take priority.
- Pipeline preview: test any combination of steps against a sample log message and inspect per-step extracted fields and the final merged result before saving.
- YAML import/export: import pipeline definitions from YAML; re-importing the same pipeline for the same scope performs an upsert.
- In-memory cache:
getForProjectcaches the resolved pipeline per project for 5 minutes, automatically invalidated on create/update/delete. - Settings UI (
/dashboard/settings/pipelines): list, enable/disable toggle, create, edit, delete, and a live step builder for adding, reordering, and configuring parser, grok, and geoip steps.
🔧 Improvements
🧭 Navigation
- Monitoring section: added to the sidebar under "Detect", alongside Alerts and Security.
- Dashboard switcher: replaces the previous single fixed entry point with a full dropdown for managing multiple dashboards.
⚡ Performance and Correctness
- Batch panel data endpoint (
POST /:id/panels/data): single round-trip fetches all panel data viaPromise.allSettled- individual panel errors do not fail the dashboard. - BullMQ monitor polling: worker checks all due monitors every 30 seconds in batches of 20 concurrent checks via
Promise.allSettled. - Atomic incident dedup: consecutive-failure state machine uses a
WHERE incident_id IS NULLguard to prevent duplicate incidents under concurrent checks.
🐛 Bug Fixes
- Status page slug collision:
getPublicStatusnow filters bystatus_page_publicflag instead of returning the first project matching the slug, preventing cross-org data leaks. createMonitoratomicity: monitor andmonitor_statusinserts are now wrapped indb.transaction()to prevent orphaned rows on partial failure.- Redundant DB read eliminated:
processCheckResultnow receives status data from the already-fetched monitor object instead of issuing a second SELECT. - Target validation on update: the PUT endpoint validates target format against monitor type (HTTP must start with
http:///https://, TCP must contain:). $derived.byfix: monitor detail page uptime calculation now uses$derived.by()instead of$derived(() => ...)for correct Svelte 5 reactivity.@constplacement: replaced invalid{@const}inside<div>elements with{#if}/{:else}blocks for Svelte 5 compatibility.uptimePcttype coercion: PostgresROUND()returns numeric as string - status page now coerces to number before calling.toFixed().- Default
failureThresholdaligned: frontend form default corrected from 3 to 2 to match the backend default. - Pipeline edit page redirect: navigates back to the pipeline list when the active organization is switched, preventing stale-ID errors.
mapMonitortyped: replacedanyparameter with a properMonitorWithStatusRowinterface for compile-time safety.- Org membership check optimized: monitoring routes now use a single
SELECT WHERE user_id AND organization_idquery instead of fetching all user orgs and filtering in JS.
Full Changelog: v0.8.7...v0.9.0
v0.9.0-rc2
Full Changelog: v0.9.0-rc1...v0.9.0-rc2
v0.8.7
Fixed
- Quick Start cURL example failed validation: the empty-state code snippet sent
{logs: [{level, service, message}]}without atimefield, butlogSchemarequired it for the standard ingestion path (only the array-format path rannormalizeLogData). The schema now defaultstimeto the current ISO string when missing, so copy-paste examples and minimal payloads validate without requiring users to inject a timestamp. - Noisy Sigma worker logs:
[SigmaDetection] No matches foundwas emitted at info level on every batch with no detections, flooding worker output in normal operation. The line is now gated behindDEBUG_SIGMA=trueso it only appears when explicitly opted in. - No admin user when
INITIAL_ADMIN_*not set (#188): on a fresh instance withoutINITIAL_ADMIN_EMAIL/INITIAL_ADMIN_PASSWORD, no usable admin existed and admin settings were unreachable. The bootstrap no longer creates a system fallback user; instead, the first user to register (via/registeror external auth provider) is automatically promoted to admin if no admin exists yet.
Full Changelog: v0.8.6...v0.8.7
v0.9.0-rc1
What's Changed
Full Changelog: v0.9.0-beta.2...v0.9.0-rc1
v0.9.0-beta.2
What's Changed
- chore(deps): bump fastify from 5.8.1 to 5.8.3 in the npm_and_yarn group across 1 directory by @dependabot[bot] in #181
- chore(deps): bump the npm_and_yarn group across 2 directories with 1 update by @dependabot[bot] in #182
- 0.8.5 by @Polliog in #183
- 0.8.6 by @Polliog in #184
Full Changelog: v0.9.0-beta.1...v0.9.0-beta.2
v0.8.6
Fixed
- ClickHouse traces/metrics data-availability always empty:
queryTracesandqueryMetricspassed raw0for epoch dates asDateTime64(3)parameter, which ClickHouse can't parse; now uses the sametoDateTime64()clamp used by log queries - Stale session after volume reset: dashboard only checked
localStoragefor a token without validating it against the backend; now calls/auth/meon load and auto-logs out if the session is invalid
Full Changelog: v0.8.5...v0.8.6
v0.8.5
Security
- Cross-org isolation fix in SIEM:
linkDetectionEventsToIncidentnow scopes detection events to the requesting organization, preventing cross-tenant data corruption via crafted API calls - Cross-org auth bypass in pattern routes: PUT and DELETE handlers for correlation patterns now verify organization membership before mutating data (same check GET/POST already had)
- SSRF protection for legacy webhook path: the alert-notification job's direct
fetch()call now validates URLs against private/internal IP ranges, matching theWebhookProvidersafeguard - Disabled user login blocked:
POST /loginnow checks thedisabledflag before creating a session, preventing disabled accounts from obtaining tokens - Expired invitation info leak:
getInvitationByTokennow filters onexpires_at > NOW(), preventing enumeration of expired invitation details
Fixed
- SIEM dashboard timeline crash:
time_bucket()call was missing::intervalcast on the parameterized bucket width, causing a PostgreSQL type error that broke the timeline widget for all users - SSE real-time events broken: SIEM store and incident detail page read auth token from
localStorage('session_token')(wrong key), so the SSE connection never authenticated; now usesgetAuthToken()from the shared auth utility - SSE log stream duplicate emission: when multiple logs shared the same timestamp, the inclusive
frombound caused them to be re-sent on every poll tick; stream now tracks sent log IDs to deduplicate - Incident severity auto-grouping wrong:
MAX(severity)used PostgreSQL alphabetical ordering (medium>critical), producing incorrect severity on auto-grouped incidents; now uses ordinal ranking - Sigma notification failures silent: notification job payload was missing
organization_idandproject_id, andmarkAsNotifiedwas called withnullhistoryId; both now handled correctly - Incidents pagination total always zero:
loadIncidentsin the SIEM store never wroteresponse.totaltoincidentsTotal - Memory leaks on navigation: 20+ Svelte components called
authStore.subscribe()without cleanup; all now store the unsubscribe function and call it inonDestroy offset=0silently dropped: API client functions usedif (filters.offset)which is falsy for zero, so page-1 requests never sent theoffsetparameter; changed toif (filters.offset != null)- Search debounce timer leak:
searchDebounceTimerwas not cleared inonDestroy, causing post-unmount API calls when navigating away mid-search verifyProjectAccessdouble call: whenprojectIdis an array, the first element was verified twice (once before the loop, once inside it); consolidated into a single loopupdateIncidentsilent field skip:title,severity, andstatusused truthy checks (&&) instead of!== undefined, inconsistent withdescriptionandassigneeId- Webhook error messages empty:
response.statusTextis empty for HTTP/2; error now reads the response body for useful detail - Retention job crash on empty orgs:
Math.max(...[])returns-Infinity, cascading to anInvalid Datein thedrop_chunkscall; early return added when no organizations exist escapeHtmlDOM leak: PDF export'sescapeHtmlcreated orphaned DOM nodes in the parent document; replaced with pure string replacement- Webhook headers validation missing:
CreateChannelDialogsilently swallowed invalid JSON in the custom headers field; now validates on submit getIncidentDetectionsno org scope: query now accepts optionalorganizationIdfor defense-in-depth filtering- Stale shared package types: dist contained outdated
ProjectandIncidentinterfaces with phantom fields (slug,statusPageVisibility,source,monitorId); rebuilt from source
Changed
- Docker config sync:
docker-compose.build.ymlnow matchesdocker-compose.ymlwith all environment variables (MongoDB,TRUST_PROXY,FRONTEND_URL,INTERNAL_DSN,DOCKER_CONTAINER), MongoDB service, andfluent-bit-metricsservice NODE_ENVfor backend: productiondocker-compose.ymlnow setsNODE_ENV: productionon the backend service (worker and frontend already had it)docker/.env.example: addedSTORAGE_ENGINE, ClickHouse, and MongoDB configuration sections
Dependencies
picomatch4.0.3 → 4.0.4 (fix ReDoS via extglob quantifiers + POSIX character class method injection)brace-expansion5.0.2 → 5.0.5 (fix zero-step sequence DoS)fast-xml-parser5.5.6 → 5.5.9 (fix entity expansion limits bypass)fastifybumped via dependabotkyselybumped via dependabot
Full Changelog: v0.8.4...v0.8.5