From 2a405ffe7878669af33bf9545f55dfbe2913eb9a Mon Sep 17 00:00:00 2001 From: Anant Vindal Date: Fri, 31 Jul 2026 15:00:56 +0530 Subject: [PATCH 1/2] fix: tenant id validation Restrict DEFAULT_TENANT, and non-alphanumeric characters as tenant id --- src/parseable/mod.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/parseable/mod.rs b/src/parseable/mod.rs index a46f92984..680aa8a3b 100644 --- a/src/parseable/mod.rs +++ b/src/parseable/mod.rs @@ -22,7 +22,7 @@ use std::{ num::NonZeroU32, path::PathBuf, str::FromStr, - sync::{Arc, RwLock}, + sync::{Arc, LazyLock, RwLock}, }; use actix_web::http::{ @@ -34,6 +34,7 @@ use bytes::Bytes; use chrono::Utc; use clap::{Parser, error::ErrorKind}; use once_cell::sync::{Lazy, OnceCell}; +use regex::Regex; use relative_path::RelativePathBuf; pub use staging::StagingError; use streams::StreamRef; @@ -102,6 +103,23 @@ pub const STREAM_EXISTS: &str = "Stream exists"; /// OnceCell to return metastore pub static METASTORE: OnceCell> = OnceCell::new(); +/// LazyLock for tenant id regex +pub static TENANT_ID_REGEX: LazyLock = LazyLock::new(|| { + regex::RegexBuilder::new(r"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$") + .build() + .expect("Unable to create tenant id validation regex") +}); + +pub fn validate_tenant_id(tenant_id: &str) -> Result<(), String> { + if !TENANT_ID_REGEX.is_match(&tenant_id) { + return Err("tenant ID should follow regex- ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$".into()); + } + if tenant_id.eq(DEFAULT_TENANT) { + return Err(format!("tenant ID can't be {DEFAULT_TENANT}")); + } + Ok(()) +} + /// For OTel log sources the telemetry_type is fully determined by the log_source. /// When the user explicitly sets x-p-telemetry-type and it disagrees with the /// implied type for an otel-* source, reject the request. When they don't set it, @@ -1260,6 +1278,8 @@ impl Parseable { // validate the possible presence of tenant storage metadata for tenant_id in dirs.into_iter() { + // check if tenantID is valid + validate_tenant_id(&tenant_id).map_err(|e| anyhow::anyhow!(e))?; if let Some(meta) = PARSEABLE .metastore .get_parseable_metadata(&Some(tenant_id.clone())) From b63971ee09c1b80bf065c9715e8628d7e23f3626 Mon Sep 17 00:00:00 2001 From: Anant Vindal Date: Tue, 4 Aug 2026 11:35:07 +0530 Subject: [PATCH 2/2] coderabbit suggestions --- src/parseable/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parseable/mod.rs b/src/parseable/mod.rs index 680aa8a3b..70ba8208f 100644 --- a/src/parseable/mod.rs +++ b/src/parseable/mod.rs @@ -111,7 +111,7 @@ pub static TENANT_ID_REGEX: LazyLock = LazyLock::new(|| { }); pub fn validate_tenant_id(tenant_id: &str) -> Result<(), String> { - if !TENANT_ID_REGEX.is_match(&tenant_id) { + if !TENANT_ID_REGEX.is_match(tenant_id) { return Err("tenant ID should follow regex- ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$".into()); } if tenant_id.eq(DEFAULT_TENANT) { @@ -1183,7 +1183,7 @@ impl Parseable { if !self.options.is_multi_tenant() { return Err(anyhow::Error::msg("P_MULTI_TENANCY is set to false")); } - + validate_tenant_id(&tenant_id).map_err(|e| anyhow::anyhow!(e))?; let mut tenants = self.tenants.write().unwrap(); if tenants.contains(&tenant_id) { return Err(anyhow::Error::msg(format!( @@ -1278,14 +1278,14 @@ impl Parseable { // validate the possible presence of tenant storage metadata for tenant_id in dirs.into_iter() { - // check if tenantID is valid - validate_tenant_id(&tenant_id).map_err(|e| anyhow::anyhow!(e))?; if let Some(meta) = PARSEABLE .metastore .get_parseable_metadata(&Some(tenant_id.clone())) .await? && is_multi_tenant { + // check if tenantID is valid + validate_tenant_id(&tenant_id).map_err(|e| anyhow::anyhow!(e))?; let metadata: StorageMetadata = serde_json::from_slice(&meta)?; TENANT_METADATA.insert_tenant(tenant_id.clone(), metadata.clone());