Skip to content

Tenant ids have no canonical validator: divergent charsets and an infallible TenantId::new #385

Description

@mauripunzueta

Summary

Tenant ids have no single canonical validator. TenantId::new is infallible and
stores the string verbatim, and upstream there are three divergent charset
checks plus paths with none — so each backend defends individually and
inconsistently, which is how the Elasticsearch case-collision defect (companion
issue) became reachable.

Spun off from #369 / PR #379.

Evidence

  • TenantId::new is infallible with no charset constraint —
    crates/persistence/src/tenant/id.rs:61-63.
  • Three different upstream validators, none shared:
    • crates/rest/src/tenant/resolver.rs:283 (is_ascii_alphanumeric() …)
    • crates/rest/src/middleware/tenant_prefix.rs:63
    • crates/rest/src/handlers/admin_tenants.rs:98 (also permits . and /)
    • the JWT tenant extractor validates nothing.
  • Backends therefore compensate individually: the S3 keyspace added an injective
    escape (crates/persistence/src/backends/s3/keyspace.rs:383) after S3 tenant registry: a tenant named tenants permanently breaks list_tenants() #271, while
    Elasticsearch still collapses case (companion issue).

Proposed direction

Introduce one canonical constructor — e.g. TenantId::parse(&str) -> Result<…>
with a documented charset — and enforce it at tenant creation and at the REST
tenant-routing boundary. Keep an unchecked internal constructor only for trusted
construction (e.g. SYSTEM_TENANT). This lets each backend state a charset
precondition instead of re-deriving one, and closes the class of cross-tenant
collisions rather than one instance of it.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions