Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/parseable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use std::{
num::NonZeroU32,
path::PathBuf,
str::FromStr,
sync::{Arc, RwLock},
sync::{Arc, LazyLock, RwLock},
};

use actix_web::http::{
Expand All @@ -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;
Expand Down Expand Up @@ -102,6 +103,23 @@ pub const STREAM_EXISTS: &str = "Stream exists";
/// OnceCell to return metastore
pub static METASTORE: OnceCell<Arc<dyn Metastore>> = OnceCell::new();

/// LazyLock for tenant id regex
pub static TENANT_ID_REGEX: LazyLock<Regex> = 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(())
}
Comment on lines +107 to +121

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make tenant IDs alphanumeric-only.

The pattern accepts _ and -. Values such as tenant-prod and tenant_prod therefore pass validation. This violates the PR objective. Keep the 1–36 character bound, update the error text, and check DEFAULT_TENANT before the regex if its dedicated error must remain reachable.

Suggested fix
-    regex::RegexBuilder::new(r"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$")
+    regex::RegexBuilder::new(r"^[a-zA-Z0-9]{1,36}$")

 pub fn validate_tenant_id(tenant_id: &str) -> Result<(), String> {
+    if tenant_id == DEFAULT_TENANT {
+        return Err(format!("tenant ID can't be {DEFAULT_TENANT}"));
+    }
     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}"));
+        return Err("tenant ID should follow regex: ^[a-zA-Z0-9]{1,36}$".into());
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub static TENANT_ID_REGEX: LazyLock<Regex> = 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(())
}
pub static TENANT_ID_REGEX: LazyLock<Regex> = LazyLock::new(|| {
regex::RegexBuilder::new(r"^[a-zA-Z0-9]{1,36}$")
.build()
.expect("Unable to create tenant id validation regex")
});
pub fn validate_tenant_id(tenant_id: &str) -> Result<(), String> {
if tenant_id == DEFAULT_TENANT {
return Err(format!("tenant ID can't be {DEFAULT_TENANT}"));
}
if !TENANT_ID_REGEX.is_match(&tenant_id) {
return Err("tenant ID should follow regex: ^[a-zA-Z0-9]{1,36}$".into());
}
Ok(())
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parseable/mod.rs` around lines 107 - 121, Update TENANT_ID_REGEX and
validate_tenant_id so tenant IDs contain only alphanumeric characters while
preserving the 1–36 character limit. Check DEFAULT_TENANT before applying the
regex so its dedicated error remains reachable, and update the validation error
text to match the new alphanumeric-only pattern.

Comment on lines +113 to +121

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Expect every tenant-registration path to validate before registration.
rg -n -C 10 --glob '*.rs' '\b(add_tenant|validate_tenant_id)\s*\(' .

Repository: parseablehq/parseable

Length of output: 4063


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- add_tenant implementation ---'
sed -n '1178,1225p' src/parseable/mod.rs

printf '%s\n' '--- all add_tenant references ---'
rg -n -C 8 --glob '*.rs' '\.add_tenant\s*\(' .

printf '%s\n' '--- validation references and tenant registry writes ---'
rg -n -C 5 --glob '*.rs' 'validate_tenant_id|insert_tenant|TENANT_METADATA|tenants\.write' src

Repository: parseablehq/parseable

Length of output: 1724


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- crate and Parseable API context ---'
rg -n -C 6 --glob '*.rs' 'pub struct Parseable|pub use|mod parseable|add_tenant|tenant_id' src | head -n 500

printf '%s\n' '--- all tenant registry and metadata mutations ---'
rg -n -C 8 --glob '*.rs' 'tenants\.(push|insert|extend)|TENANT_METADATA\.[A-Za-z_]+|insert_tenant\s*\(' src

printf '%s\n' '--- manifest targets ---'
rg -n -C 4 '^\[lib\]|^crate-type|^name\s*=' Cargo.toml

Repository: parseablehq/parseable

Length of output: 41774


Validate tenant IDs in Parseable::add_tenant.

add_tenant stores tenant_id in both registries without calling validate_tenant_id. Enforce validation inside this method so invalid IDs and DEFAULT_TENANT cannot be registered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parseable/mod.rs` around lines 113 - 121, Update Parseable::add_tenant to
call validate_tenant_id on tenant_id before storing it in either registry,
propagating the validation error and preventing invalid IDs or DEFAULT_TENANT
from being registered.


/// 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,
Expand Down Expand Up @@ -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))?;
Comment on lines +1281 to +1282

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate directory names only in multi-tenant mode.

dirs contains root directories, not only tenant directories. This module also documents that root directories can be stream directories. The new validation runs before the !is_multi_tenant branch, so a single-tenant deployment can fail when a stream directory name does not match the tenant pattern. Move this check inside an if is_multi_tenant block while keeping it before tenant metadata loading.

Suggested fix
 for tenant_id in dirs.into_iter() {
-    validate_tenant_id(&tenant_id).map_err(|e| anyhow::anyhow!(e))?;
+    if is_multi_tenant {
+        validate_tenant_id(&tenant_id).map_err(|e| anyhow::anyhow!(e))?;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// check if tenantID is valid
validate_tenant_id(&tenant_id).map_err(|e| anyhow::anyhow!(e))?;
// check if tenantID is valid
if is_multi_tenant {
validate_tenant_id(&tenant_id).map_err(|e| anyhow::anyhow!(e))?;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parseable/mod.rs` around lines 1281 - 1282, Move the validate_tenant_id
call in the directory-processing flow into the is_multi_tenant conditional,
keeping it before tenant metadata loading. Ensure single-tenant mode accepts
root or stream directory names without tenant-pattern validation, while
multi-tenant mode still validates tenant IDs.

if let Some(meta) = PARSEABLE
.metastore
.get_parseable_metadata(&Some(tenant_id.clone()))
Expand Down
Loading