diff --git a/internal/storage/iotpg/iot_telemetry_store.go b/internal/storage/iotpg/iot_telemetry_store.go index e6a88f9..29e8871 100644 --- a/internal/storage/iotpg/iot_telemetry_store.go +++ b/internal/storage/iotpg/iot_telemetry_store.go @@ -630,6 +630,13 @@ func (s *IoTPostgresStore) Close() error { // Открытие дедуплицируется per-namespace мьютексом (гонка Load→LoadOrStore // из ревью 2026-08-16). func (s *IoTPostgresStore) getTenantDB(ctx context.Context, namespace string) (*sql.DB, error) { + // Валидация namespace на ВСЕХ путях (чтение/запись) — defense-in-depth. + // Основной барьер — валидация при создании устройства, но JWT с sub, + // прошедшим namespace==sub в middleware, не должен читать чужую БД через + // подмену дефисов (a_b ↔ a-b дают одно имя tenant_a_b) — см. tenantDBName. + if !safeNamespaceRe.MatchString(namespace) { + return nil, fmt.Errorf("%w: %q", ErrInvalidNamespace, namespace) + } if cached, ok := s.tenants.Load(namespace); ok { return cached.(*sql.DB), nil }