LogTide
v1.0.1
Released
Security

1.0.1: Cross-Tenant Fixes & a 1.0 Bug Audit

A security and correctness release from a multi-engine bug audit of the 1.0 line. Two cross-tenant data-exposure fixes that were live in 1.0.0, plus a broad sweep of detection, ingestion, storage, alerting and frontend correctness fixes. No migrations — a drop-in upgrade.

  • Cross-tenant log exposure via the WebSocket live-tail closed — it now enforces the same membership check as the REST query routes (was live on 1.0.0)
  • Cross-tenant leak of notification-channel secrets (webhook auth tokens) closed with a membership check
  • PII masking fail-open fixed: separator-less card numbers are no longer stored unmasked
  • Storage-layer correctness fixes validated against real ClickHouse, MongoDB and TimescaleDB; CI now runs the MongoDB reservoir suite
  • New official Ruby SDK (the `logtide` gem) — Rack/Rails middleware, tracing and a stdlib Logger bridge; docs at /docs/sdks/ruby
  • No database migrations — a drop-in upgrade from 1.0.0

A security and correctness release from a comprehensive, multi-engine bug audit of the 1.0 line. The headline items are two cross-tenant data-exposure fixes that were live in 1.0.0, alongside a broad sweep of detection, ingestion, storage, alerting and frontend correctness fixes. No database migrations — this is a drop-in upgrade. The storage-layer fixes were validated against real ClickHouse and MongoDB (and TimescaleDB), and CI now runs the MongoDB reservoir integration suite.

Security

  • Cross-tenant log exposure via the WebSocket live-tail (GET /api/v1/logs/ws): the handler validated only the session token and then streamed whatever projectId the client passed, never checking project/organization membership, so any authenticated user could live-tail any project’s logs by supplying its id. It now enforces the same verifyProjectAccess membership check as the REST query routes, with a regression test added. Was live on the 1.0.0 line
  • Cross-tenant leak of notification-channel secrets: GET /notification-channels/alert-rules/:id and /sigma-rules/:id returned the channel config verbatim — including webhook auth tokens/passwords — with no membership check. They now resolve the rule’s organization, require membership, and scope the read to that org
  • PII masking fail-open: maskText skipped all content rules for pure-alphanumeric strings, so a separator-less credit-card number (e.g. 4111111111111111) was stored unmasked. The all-alphanumeric early-exit is removed; only too-short strings are skipped
  • OIDC account-takeover: linking an external identity to an existing local account by email now requires the provider to assert the email is verified (email_verified === true), preventing takeover via an unverified IdP email; LDAP is treated as authoritative
  • Cross-tenant ?projectId / association gaps closed: the correlation batch-identifiers endpoint now intersects the requested project with the caller’s accessible projects (and requires read access); status-incident and scheduled-maintenance creation, organization-default channels, and alert/sigma/monitor channel associations now validate that the project/channels belong to the caller’s organization
  • Session invalidation: disabling a user, resetting their password, and changing a user’s admin role now invalidate the cached session (previously the cached profile stayed valid up to the cache TTL); the frontend logout now revokes the server-side session token instead of only clearing local state
  • Hardening: emails are stored and compared case-insensitively across registration/login/profile-update; the global CACHE_TTL override no longer clamps semantic TTLs (sessions, OIDC state, settings); the webhook envelope id is validated as a UUID; SigmaHQ category sync matches on a directory boundary; OTLP trace/span ids of invalid length are rejected
  • Dependency security updates, third wave (Dependabot): nodemailer bumped from ^8.0.9 to ^9.0.1 (GHSA-p6gq-j5cr-w38f, HIGH — the message-level raw option bypassed disableFileAccess/disableUrlAccess; our SMTP senders never pass raw, but the dependency is patched regardless), and transitive undici pinned to >=7.28.0 <8 (GHSA-vmh5-mc38-953g HIGH TLS bypass, GHSA-pr7r-676h-xcf6 MEDIUM cache-bypass disclosure). No vulnerable version remains in the lockfile

Fixed

  • ClickHouse “Query with id … is already running” under concurrent queries: the request-context propagation derived the ClickHouse query_id deterministically from requestId + operation, so two same-operation queries running concurrently within one request reused the same id and ClickHouse rejected the second. The query_id now keeps the request id + operation as a readable prefix and appends a random suffix so every query is unique
  • Sigma condition operator precedence: parseExpression folded AND/OR strictly left-to-right, so a or b and c evaluated as (a or b) and c. AND now binds tighter than OR, matching the Sigma spec
  • Infinite loop on ingestion: a custom identifier pattern that can match the empty string (e.g. \d*) no longer hangs the worker (zero-width-match guard plus a forced global flag)
  • Trace summary races: upsertTrace is now race-free on TimescaleDB (single atomic INSERT … ON CONFLICT) and ClickHouse (the summary is recomputed from the spans table instead of read-modify-write), so concurrent span batches no longer lose span counts
  • ClickHouse: added the missing session_id column (it was written and filtered on but did not exist), parse zone-less datetimes as UTC, and report the true latest value in the metrics overview (was the average) with NaN guards
  • MongoDB: deleteMetricsByTimeRange only deletes the matched metrics’ exemplars (was all exemplars in the window); the metrics overview sorts before $last; metadata distinct no longer drops numeric/boolean values; ingestReturning returns the rows that did land on a partial bulk-write failure
  • Rate-of-change alerts: the baseline now applies the rule’s metadata filters (previously only the current rate did, deflating the deviation ratio), and a minBaselineValue of 0 is honored instead of defaulting to 10
  • Dashboards: personal dashboards are scoped to their creator (no longer readable/editable by any org member); metric_stat sums across buckets for sum/count aggregations; top-error percentages are computed against the true total; the capability usage page renders a zero limit as fully restricted, not unlimited
  • Pagination/counts: admin organization/project search counts apply the search filter; correlation lookups report the true total; the ClickHouse trace error-rate uses a consistent denominator; getTopServices honors the to bound
  • Live tail: SSE polling bypasses the 60s query cache and guards against overlapping polls; the live-tail WebSocket URL works behind a reverse proxy; the traces and search live tails no longer corrupt pagination or index-keyed row state
  • Infra/correctness: BullMQ and graphile queue retry-attempt defaults are aligned; webhook deliveries can only be replayed from the terminal dead state (no double delivery); the audit flush buffer is bounded on persistent DB failure; alert-notification jobs are deduplicated by history id; admin org-role changes are restricted consistently with member removal; tenant-table updates in the log-pipeline and service-dependency queries are project-scoped

Added

  • Official Ruby SDK — the logtide gem for Ruby 3.1+, implementing the LogTide SDK spec v1.0 with leveled logging, structured exception capture, the Hub/Scope model, spans + OTLP export, W3C traceparent propagation, Rack middleware, a Rails Railtie, and a stdlib Logger bridge — standard library only, no runtime dependencies. Docs at /docs/sdks/ruby

Changed

  • The reservoir storage abstraction gains getTraceServices (distinct trace services with no result cap), implemented on all three engines and used by the traces service, replacing the previous 10k-trace paging
  • CI now provisions a MongoDB service for the reservoir integration suite, so all three storage engines are exercised on every run