You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by yc...@apache.org on 2022/07/12 18:33:30 UTC

[cassandra-sidecar] branch trunk updated: CASSANDRAC-39: Allow Cassandra input validation to be configurable

This is an automated email from the ASF dual-hosted git repository.

ycai pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git


The following commit(s) were added to refs/heads/trunk by this push:
     new a7a2c29  CASSANDRAC-39: Allow Cassandra input validation to be configurable
a7a2c29 is described below

commit a7a2c29e990acb2363eb7a15cc4b970ebdc04753
Author: Francisco Guerrero <fr...@apple.com>
AuthorDate: Fri Jul 8 17:09:35 2022 -0700

    CASSANDRAC-39: Allow Cassandra input validation to be configurable
    
    It is sometimes desirable to allow configuring the Cassandra input validation in case
    we want to further restrict the existing validations. In this commit, we introduce the
    ability to inject the ValidationConfiguration. A default YamlValidationConfiguration is
    provided and the sidecar.yaml is updated to reflect the existing default validation.
---
 build.gradle                                       |  53 ++++---
 cassandra-integration-tests/build.gradle           |  16 +-
 common/build.gradle                                |  31 ++--
 .../common/data/ListSnapshotFilesRequest.java      |   4 +-
 .../sidecar/common/data/QualifiedTableName.java    |  13 +-
 .../sidecar/common/data/SSTableComponent.java      |   4 +-
 .../common/data/StreamSSTableComponentRequest.java |   4 +-
 ...tionUtils.java => CassandraInputValidator.java} |  64 ++++----
 .../common/utils/ValidationConfiguration.java      |  30 ++++
 .../common/utils/YAMLValidationConfiguration.java  |  50 +++++++
 ...sTest.java => CassandraInputValidatorTest.java} |  80 +++++-----
 .../common/data/ListSnapshotFilesRequestTest.java  |  18 +++
 .../data/StreamSSTableComponentRequestTest.java    |  18 +++
 .../common/TestValidationConfiguration.java        |  46 ++++++
 src/main/dist/conf/sidecar.yaml                    |  13 ++
 .../apache/cassandra/sidecar/Configuration.java    |  16 +-
 .../org/apache/cassandra/sidecar/MainModule.java   | 164 +++++++++++++--------
 .../sidecar/snapshots/SnapshotPathBuilder.java     |  79 +++++-----
 .../cassandra/sidecar/utils/YAMLKeyConstants.java  |   6 +
 .../cassandra/sidecar/ConfigurationTest.java       |  50 +++++--
 .../org/apache/cassandra/sidecar/TestModule.java   |  27 ++--
 .../sidecar/snapshots/SnapshotPathBuilderTest.java |   5 +-
 .../sidecar/snapshots/SnapshotSearchTest.java      |   8 +-
 .../sidecar_validation_configuration.yaml          |   8 +
 .../snapshots/AbstractSnapshotPathBuilderTest.java |  56 ++++---
 25 files changed, 591 insertions(+), 272 deletions(-)

diff --git a/build.gradle b/build.gradle
index 0cb0c6b..dc402ac 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,6 +9,7 @@ buildscript {
 
 plugins {
     id 'java'
+    id 'java-test-fixtures'
     id 'application'
 
     // since we're using a specific version here, we delay applying the plugin till the all projects
@@ -147,39 +148,51 @@ configurations {
 }
 
 dependencies {
-    compile "io.vertx:vertx-web:${project.vertxVersion}"
-    compile "io.vertx:vertx-dropwizard-metrics:${project.vertxVersion}"
-    compile "io.vertx:vertx-web-client:${project.vertxVersion}"
+    implementation("io.vertx:vertx-web:${project.vertxVersion}")
+    implementation("io.vertx:vertx-dropwizard-metrics:${project.vertxVersion}")
+    implementation("io.vertx:vertx-web-client:${project.vertxVersion}")
 
-    compile 'io.swagger.core.v3:swagger-jaxrs2:2.1.0'
-    compile "org.jboss.resteasy:resteasy-vertx:4.7.4.Final"
-    compile group: 'org.jboss.spec.javax.servlet', name: 'jboss-servlet-api_4.0_spec', version: '2.0.0.Final'
+    implementation('com.datastax.cassandra:cassandra-driver-core:3.9.0+')
+    implementation('com.google.inject:guice:4.2.2')
+    implementation('io.swagger.core.v3:swagger-jaxrs2:2.1.0')
+    implementation("org.jboss.resteasy:resteasy-vertx:4.7.4.Final")
+    implementation(group: 'org.jboss.spec.javax.servlet', name: 'jboss-servlet-api_4.0_spec', version: '2.0.0.Final')
 
     // Trying to be exactly compatible with Cassandra's deps
-    compile 'org.slf4j:slf4j-api:1.7.25'
-    compile 'ch.qos.logback:logback-core:1.2.3'
-    compile 'ch.qos.logback:logback-classic:1.2.3'
+    implementation('org.slf4j:slf4j-api:1.7.25')
+    implementation('ch.qos.logback:logback-core:1.2.3')
+    implementation('ch.qos.logback:logback-classic:1.2.3')
 
-    compile group: 'org.apache.commons', name: 'commons-configuration2', version: '2.7'
-    compile 'org.webjars:swagger-ui:3.10.0'
+    implementation(group: 'org.apache.commons', name: 'commons-configuration2', version: '2.7')
+    implementation('org.webjars:swagger-ui:3.10.0')
 
     runtime group: 'commons-beanutils', name: 'commons-beanutils', version: '1.9.3'
     runtime group: 'org.yaml', name: 'snakeyaml', version: '1.26'
 
     jolokia 'org.jolokia:jolokia-jvm:1.6.0:agent'
 
-    testCompile "org.junit.jupiter:junit-jupiter-api:${project.junitVersion}"
-    testCompile "org.junit.jupiter:junit-jupiter-params:${project.junitVersion}"
-    testCompile "org.assertj:assertj-core:3.14.0"
+    testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junitVersion}"
+    testImplementation "org.junit.jupiter:junit-jupiter-params:${project.junitVersion}"
+    testImplementation "org.assertj:assertj-core:3.14.0"
     testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junitVersion}"
 
-    testCompile group: 'org.cassandraunit', name: 'cassandra-unit-shaded', version: '3.11.2.0'
-    testCompile 'com.datastax.cassandra:cassandra-driver-core:3.9.0:tests'
-    testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19'
-    testCompile group: 'io.vertx', name: 'vertx-junit5', version: "${project.vertxVersion}"
+    testImplementation('org.cassandraunit:cassandra-unit-shaded:3.11.2.0')
+    testImplementation('com.datastax.cassandra:cassandra-driver-core:3.9.0:tests')
+    testImplementation('org.mockito:mockito-all:1.10.19')
+    testImplementation("io.vertx:vertx-junit5:${project.vertxVersion}")
+    testImplementation(testFixtures(project(":common")))
     
-    compile project(":common")
-    compile project(":cassandra40")
+    implementation(project(":common"))
+    implementation(project(":cassandra40"))
+
+    testFixturesApi(testFixtures(project(":common")))
+    testFixturesImplementation("io.vertx:vertx-junit5:${project.vertxVersion}")
+    testFixturesImplementation('com.google.inject:guice:4.2.2')
+    testFixturesImplementation('org.mockito:mockito-all:1.10.19')
+    testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:${project.junitVersion}")
+    testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:${project.junitVersion}")
+    testFixturesImplementation("org.assertj:assertj-core:3.14.0")
+    testFixturesImplementation("io.vertx:vertx-web:${project.vertxVersion}")
 }
 
 task copyCodeStyle(type: Copy) {
diff --git a/cassandra-integration-tests/build.gradle b/cassandra-integration-tests/build.gradle
index 2c3d88f..101bb1e 100644
--- a/cassandra-integration-tests/build.gradle
+++ b/cassandra-integration-tests/build.gradle
@@ -8,14 +8,18 @@ repositories {
 }
 
 dependencies {
-    testCompile project(":cassandra40")
+    testImplementation project(":cassandra40")
 
-    testCompile "io.kubernetes:client-java:${project.kubernetesClientVersion}"
-    testCompile "io.kubernetes:client-java-extended:${project.kubernetesClientVersion}"
+    implementation('ch.qos.logback:logback-core:1.2.3')
+    implementation('ch.qos.logback:logback-classic:1.2.3')
+    implementation('com.datastax.cassandra:cassandra-driver-core:3.9.0+')
 
-    testCompile "org.junit.jupiter:junit-jupiter-api:${project.junitVersion}"
-    testCompile "org.junit.jupiter:junit-jupiter-params:${project.junitVersion}"
-    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junitVersion}"
+    testImplementation("io.kubernetes:client-java:${project.kubernetesClientVersion}")
+    testImplementation("io.kubernetes:client-java-extended:${project.kubernetesClientVersion}")
+
+    testImplementation("org.junit.jupiter:junit-jupiter-api:${project.junitVersion}")
+    testImplementation("org.junit.jupiter:junit-jupiter-params:${project.junitVersion}")
+    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${project.junitVersion}")
 
     testImplementation("org.assertj:assertj-core:3.16.0")
 }
diff --git a/common/build.gradle b/common/build.gradle
index 10b24ee..a4f50eb 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -1,5 +1,6 @@
 plugins {
-    id 'java'
+    id 'java-library'
+    id "java-test-fixtures"
     id 'idea'
 
     // todo move to all projects or subprojects.  possibly only include with java projects
@@ -22,19 +23,19 @@ test {
 }
 
 dependencies {
-    compile "io.vertx:vertx-web:${project.vertxVersion}"
-    compile 'org.slf4j:slf4j-api:1.7.25'
-    compile 'ch.qos.logback:logback-core:1.2.3'
-    compile 'ch.qos.logback:logback-classic:1.2.3'
-    compile 'com.datastax.cassandra:cassandra-driver-core:3.9.0+'
-    compile group: 'com.google.inject', name: 'guice', version: '4.2.2'
-    compile group: 'org.apache.commons', name: 'commons-configuration2', version: '2.7'
-    compile group: 'org.jetbrains', name: 'annotations', version: '20.1.0'
-
-    implementation 'org.apache.commons:commons-lang3:3.10'
-
-    testCompile "org.junit.jupiter:junit-jupiter-api:${project.junitVersion}"
-    testCompile "org.junit.jupiter:junit-jupiter-params:${project.junitVersion}"
-    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junitVersion}"
+    implementation("io.vertx:vertx-web:${project.vertxVersion}")
+    implementation('org.slf4j:slf4j-api:1.7.25')
+    implementation('ch.qos.logback:logback-core:1.2.3')
+    implementation('ch.qos.logback:logback-classic:1.2.3')
+    implementation('com.datastax.cassandra:cassandra-driver-core:3.9.0+')
+    implementation('com.google.inject:guice:4.2.2')
+    implementation('org.apache.commons:commons-configuration2:2.7')
+    implementation('org.jetbrains:annotations:23.0.0')
+
+    implementation('org.apache.commons:commons-lang3:3.12.0')
+
     testImplementation("org.assertj:assertj-core:3.16.0")
+    testImplementation("org.junit.jupiter:junit-jupiter-api:${project.junitVersion}")
+    testImplementation("org.junit.jupiter:junit-jupiter-params:${project.junitVersion}")
+    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${project.junitVersion}")
 }
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/data/ListSnapshotFilesRequest.java b/common/src/main/java/org/apache/cassandra/sidecar/common/data/ListSnapshotFilesRequest.java
index 647f502..887ad26 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/data/ListSnapshotFilesRequest.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/data/ListSnapshotFilesRequest.java
@@ -17,8 +17,6 @@
  */
 package org.apache.cassandra.sidecar.common.data;
 
-import org.apache.cassandra.sidecar.common.utils.ValidationUtils;
-
 /**
  * Holder class for the {@link org.apache.cassandra.sidecar.routes.ListSnapshotFilesHandler}
  * request parameters
@@ -42,7 +40,7 @@ public class ListSnapshotFilesRequest extends QualifiedTableName
                                     boolean includeSecondaryIndexFiles)
     {
         super(keyspace, tableName, true);
-        this.snapshotName = ValidationUtils.validateSnapshotName(snapshotName);
+        this.snapshotName = validator.validateSnapshotName(snapshotName);
         this.includeSecondaryIndexFiles = includeSecondaryIndexFiles;
     }
 
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/data/QualifiedTableName.java b/common/src/main/java/org/apache/cassandra/sidecar/common/data/QualifiedTableName.java
index f74c2c6..9bbcb39 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/data/QualifiedTableName.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/data/QualifiedTableName.java
@@ -17,13 +17,17 @@
  */
 package org.apache.cassandra.sidecar.common.data;
 
-import org.apache.cassandra.sidecar.common.utils.ValidationUtils;
+import com.google.inject.Inject;
+import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 
 /**
  * Contains the keyspace and table name in Cassandra
  */
 public class QualifiedTableName
 {
+    @Inject
+    static CassandraInputValidator validator;
+
     private final String keyspace;
     private final String tableName;
 
@@ -46,13 +50,12 @@ public class QualifiedTableName
      * @param tableName the table name in Cassandra
      * @param required  true if keyspace and table name are required, false if {@code null} is allowed
      */
-    QualifiedTableName(String keyspace, String tableName, boolean required)
+    protected QualifiedTableName(String keyspace, String tableName, boolean required)
     {
-        this.keyspace = !required && keyspace == null ? null : ValidationUtils.validateKeyspaceName(keyspace);
-        this.tableName = !required && tableName == null ? null : ValidationUtils.validateTableName(tableName);
+        this.keyspace = !required && keyspace == null ? null : validator.validateKeyspaceName(keyspace);
+        this.tableName = !required && tableName == null ? null : validator.validateTableName(tableName);
     }
 
-
     /**
      * @return the keyspace in Cassandra
      */
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/data/SSTableComponent.java b/common/src/main/java/org/apache/cassandra/sidecar/common/data/SSTableComponent.java
index acf9ef2..1752d2c 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/data/SSTableComponent.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/data/SSTableComponent.java
@@ -18,8 +18,6 @@
 
 package org.apache.cassandra.sidecar.common.data;
 
-import org.apache.cassandra.sidecar.common.utils.ValidationUtils;
-
 /**
  * Represents an SSTable component that includes a keyspace, table name and component name
  */
@@ -37,7 +35,7 @@ public class SSTableComponent extends QualifiedTableName
     public SSTableComponent(String keyspace, String tableName, String componentName)
     {
         super(keyspace, tableName);
-        this.componentName = ValidationUtils.validateComponentName(componentName);
+        this.componentName = validator.validateComponentName(componentName);
     }
 
     /**
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/data/StreamSSTableComponentRequest.java b/common/src/main/java/org/apache/cassandra/sidecar/common/data/StreamSSTableComponentRequest.java
index 84a6b4f..b72c9f2 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/data/StreamSSTableComponentRequest.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/data/StreamSSTableComponentRequest.java
@@ -18,8 +18,6 @@
 
 package org.apache.cassandra.sidecar.common.data;
 
-import org.apache.cassandra.sidecar.common.utils.ValidationUtils;
-
 /**
  * Holder class for the {@code org.apache.cassandra.sidecar.routes.StreamSSTableComponentHandler}
  * request parameters
@@ -39,7 +37,7 @@ public class StreamSSTableComponentRequest extends SSTableComponent
     public StreamSSTableComponentRequest(String keyspace, String tableName, String snapshotName, String componentName)
     {
         super(keyspace, tableName, componentName);
-        this.snapshotName = ValidationUtils.validateSnapshotName(snapshotName);
+        this.snapshotName = validator.validateSnapshotName(snapshotName);
     }
 
     /**
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ValidationUtils.java b/common/src/main/java/org/apache/cassandra/sidecar/common/utils/CassandraInputValidator.java
similarity index 71%
rename from common/src/main/java/org/apache/cassandra/sidecar/common/utils/ValidationUtils.java
rename to common/src/main/java/org/apache/cassandra/sidecar/common/utils/CassandraInputValidator.java
index 0933b35..11a9d06 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ValidationUtils.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/utils/CassandraInputValidator.java
@@ -19,13 +19,10 @@
 package org.apache.cassandra.sidecar.common.utils;
 
 import java.io.File;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Objects;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.ext.web.handler.HttpException;
 import org.jetbrains.annotations.NotNull;
@@ -33,19 +30,21 @@ import org.jetbrains.annotations.NotNull;
 /**
  * Miscellaneous methods used for validation.
  */
-public class ValidationUtils
+@Singleton
+public class CassandraInputValidator
 {
-    private static final Set<String> FORBIDDEN_DIRS = new HashSet<>(Arrays.asList("system_schema",
-                                                                                  "system_traces",
-                                                                                  "system_distributed",
-                                                                                  "system",
-                                                                                  "system_auth",
-                                                                                  "system_views",
-                                                                                  "system_virtual_schema"));
-    private static final String CHARS_ALLOWED_PATTERN = "[a-zA-Z0-9_-]+";
-    private static final Pattern PATTERN_WORD_CHARS = Pattern.compile(CHARS_ALLOWED_PATTERN);
-    private static final String REGEX_COMPONENT = CHARS_ALLOWED_PATTERN + "(.db|.cql|.json|.crc32|TOC.txt)";
-    private static final String REGEX_DB_TOC_COMPONENT = CHARS_ALLOWED_PATTERN + "(.db|TOC.txt)";
+    private final ValidationConfiguration validationConfiguration;
+
+    /**
+     * Constructs a new object with the provided {@code validationConfiguration}
+     *
+     * @param validationConfiguration a validation configuration
+     */
+    @Inject
+    public CassandraInputValidator(ValidationConfiguration validationConfiguration)
+    {
+        this.validationConfiguration = validationConfiguration;
+    }
 
     /**
      * Validates that the {@code keyspace} is not {@code null}, that it contains valid characters, and that it's
@@ -57,11 +56,11 @@ public class ValidationUtils
      * @throws HttpException        when the {@code keyspace} contains invalid characters in the name or when the
      *                              keyspace is forbidden
      */
-    public static String validateKeyspaceName(@NotNull String keyspace)
+    public String validateKeyspaceName(@NotNull String keyspace)
     {
         Objects.requireNonNull(keyspace, "keyspace must not be null");
         validatePattern(keyspace, "keyspace");
-        if (FORBIDDEN_DIRS.contains(keyspace))
+        if (validationConfiguration.getForbiddenKeyspaces().contains(keyspace))
             throw new HttpException(HttpResponseStatus.FORBIDDEN.code(), "Forbidden keyspace: " + keyspace);
         return keyspace;
     }
@@ -75,7 +74,7 @@ public class ValidationUtils
      * @throws NullPointerException when the {@code tableName} is {@code null}
      * @throws HttpException        when the {@code tableName} contains invalid characters in the name
      */
-    public static String validateTableName(@NotNull String tableName)
+    public String validateTableName(@NotNull String tableName)
     {
         Objects.requireNonNull(tableName, "tableName must not be null");
         validatePattern(tableName, "table name");
@@ -91,7 +90,7 @@ public class ValidationUtils
      * @throws NullPointerException when the {@code snapshotName} is {@code null}
      * @throws HttpException        when the {@code snapshotName} contains inalid characters in the name
      */
-    public static String validateSnapshotName(@NotNull String snapshotName)
+    public String validateSnapshotName(@NotNull String snapshotName)
     {
         Objects.requireNonNull(snapshotName, "snapshotName must not be null");
         //  most UNIX systems only disallow file separator and null characters for directory names
@@ -110,24 +109,26 @@ public class ValidationUtils
      * @throws NullPointerException when the {@code componentName} is null
      * @throws HttpException        when the {@code componentName} is not valid
      */
-    public static String validateComponentName(@NotNull String componentName)
+    public String validateComponentName(@NotNull String componentName)
     {
-        return validateComponentNameByRegex(componentName, REGEX_COMPONENT);
+        return validateComponentNameByRegex(componentName,
+                                            validationConfiguration.getAllowedPatternForComponentName());
     }
 
     /**
-     * Validates that the {@code componentName} is not {@code null}, and it is a valid {@code *.db} or {@code *TOC.txt}
+     * Validates that the {@code componentName} is not {@code null}, and it contains a subset of allowed names for the
      * Cassandra SSTable component.
      *
      * @param componentName the name of the SSTable component to validate
      * @return the validated {@code componentName}
      * @throws NullPointerException when the {@code componentName} is null
-     * @throws HttpException        when the {@code componentName} is not a valid {@code *.db} or {@code *TOC.txt}
-     *                              SSTable component
+     * @throws HttpException        when the {@code componentName} is not a valid name for the configured restricted
+     *                              component name
      */
-    public static String validateDbOrTOCComponentName(@NotNull String componentName)
+    public String validateRestrictedComponentName(@NotNull String componentName)
     {
-        return validateComponentNameByRegex(componentName, REGEX_DB_TOC_COMPONENT);
+        return validateComponentNameByRegex(componentName,
+                                            validationConfiguration.getAllowedPatternForRestrictedComponentName());
     }
 
     /**
@@ -140,7 +141,7 @@ public class ValidationUtils
      * @throws HttpException        when the {@code componentName} does not match the provided regex
      */
     @NotNull
-    private static String validateComponentNameByRegex(String componentName, String regex)
+    private String validateComponentNameByRegex(String componentName, String regex)
     {
         Objects.requireNonNull(componentName, "componentName must not be null");
         if (!componentName.matches(regex))
@@ -156,10 +157,9 @@ public class ValidationUtils
      * @param name  a name for the exception
      * @throws HttpException when the {@code input} does not match the pattern
      */
-    private static void validatePattern(String input, String name)
+    private void validatePattern(String input, String name)
     {
-        final Matcher matcher = PATTERN_WORD_CHARS.matcher(input);
-        if (!matcher.matches())
+        if (!input.matches(validationConfiguration.getAllowedPatternForDirectory()))
             throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
                                     "Invalid characters in " + name + ": " + input);
     }
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ValidationConfiguration.java b/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ValidationConfiguration.java
new file mode 100644
index 0000000..34557fd
--- /dev/null
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ValidationConfiguration.java
@@ -0,0 +1,30 @@
+package org.apache.cassandra.sidecar.common.utils;
+
+import java.util.Set;
+
+/**
+ * An interface to provide validation configuration parameters
+ */
+public interface ValidationConfiguration
+{
+    /**
+     * @return a set of forbidden keyspaces
+     */
+    Set<String> getForbiddenKeyspaces();
+
+    /**
+     * @return a regular expression for an allowed pattern for directory names
+     * (i.e. keyspace directory name or table directory name)
+     */
+    String getAllowedPatternForDirectory();
+
+    /**
+     * @return a regular expression for an allowed pattern for component names
+     */
+    String getAllowedPatternForComponentName();
+
+    /**
+     * @return a regular expression to an allowed pattern for a subset of component names
+     */
+    String getAllowedPatternForRestrictedComponentName();
+}
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/utils/YAMLValidationConfiguration.java b/common/src/main/java/org/apache/cassandra/sidecar/common/utils/YAMLValidationConfiguration.java
new file mode 100644
index 0000000..41ab165
--- /dev/null
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/utils/YAMLValidationConfiguration.java
@@ -0,0 +1,50 @@
+package org.apache.cassandra.sidecar.common.utils;
+
+import java.util.Set;
+
+import org.apache.commons.configuration2.YAMLConfiguration;
+
+import com.google.inject.Singleton;
+
+/**
+ * An implementation that reads the {@link ValidationConfiguration} from a {@link YAMLConfiguration}.
+ */
+@Singleton
+public class YAMLValidationConfiguration implements ValidationConfiguration
+{
+    private final Set<String> forbiddenKeyspaces;
+    private final String allowedPatternForDirectory;
+    private final String allowedPatternForComponentName;
+    private final String allowedPatternForRestrictedComponentName;
+
+    public YAMLValidationConfiguration(Set<String> forbiddenKeyspaces,
+                                       String allowedPatternForDirectory,
+                                       String allowedPatternForComponentName,
+                                       String allowedPatternForRestrictedComponentName)
+    {
+        this.forbiddenKeyspaces = forbiddenKeyspaces;
+        this.allowedPatternForDirectory = allowedPatternForDirectory;
+        this.allowedPatternForComponentName = allowedPatternForComponentName;
+        this.allowedPatternForRestrictedComponentName = allowedPatternForRestrictedComponentName;
+    }
+
+    public Set<String> getForbiddenKeyspaces()
+    {
+        return forbiddenKeyspaces;
+    }
+
+    public String getAllowedPatternForDirectory()
+    {
+        return allowedPatternForDirectory;
+    }
+
+    public String getAllowedPatternForComponentName()
+    {
+        return allowedPatternForComponentName;
+    }
+
+    public String getAllowedPatternForRestrictedComponentName()
+    {
+        return allowedPatternForRestrictedComponentName;
+    }
+}
diff --git a/common/src/test/java/org/apache/cassandra/sidecar/common/ValidationUtilsTest.java b/common/src/test/java/org/apache/cassandra/sidecar/common/CassandraInputValidatorTest.java
similarity index 71%
rename from common/src/test/java/org/apache/cassandra/sidecar/common/ValidationUtilsTest.java
rename to common/src/test/java/org/apache/cassandra/sidecar/common/CassandraInputValidatorTest.java
index cc1c54f..3ef258d 100644
--- a/common/src/test/java/org/apache/cassandra/sidecar/common/ValidationUtilsTest.java
+++ b/common/src/test/java/org/apache/cassandra/sidecar/common/CassandraInputValidatorTest.java
@@ -19,26 +19,31 @@
 package org.apache.cassandra.sidecar.common;
 
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.ext.web.handler.HttpException;
-import org.apache.cassandra.sidecar.common.utils.ValidationUtils;
+import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
  * Test validation methods.
  */
-public class ValidationUtilsTest
+public class CassandraInputValidatorTest
 {
+    CassandraInputValidator instance;
+
+    @BeforeEach
+    void setup()
+    {
+        instance = new CassandraInputValidator(new TestValidationConfiguration());
+    }
 
     private void testCommon_invalidCharacters(String testName)
     {
-        HttpException httpEx = Assertions.assertThrows(HttpException.class, () ->
-        {
-            ValidationUtils.validateTableName(testName);
-        });
+        HttpException httpEx = Assertions.assertThrows(HttpException.class, () -> instance.validateTableName(testName));
         assertEquals(HttpResponseStatus.BAD_REQUEST.code(), httpEx.getStatusCode());
         assertEquals("Invalid characters in table name: " + testName, httpEx.getPayload());
     }
@@ -46,9 +51,9 @@ public class ValidationUtilsTest
     @Test
     public void testValidateCharacters_validParams_expectNoException()
     {
-        ValidationUtils.validateTableName("test_table_name");
-        ValidationUtils.validateTableName("test-table-name");
-        ValidationUtils.validateTableName("testTableName");
+        instance.validateTableName("test_table_name");
+        instance.validateTableName("test-table-name");
+        instance.validateTableName("testTableName");
     }
 
     @Test
@@ -73,19 +78,17 @@ public class ValidationUtilsTest
     @Test
     public void testValidateKeyspaceName_validKeyspaceNames_expectNoException()
     {
-        ValidationUtils.validateKeyspaceName("system-views");
-        ValidationUtils.validateKeyspaceName("SystemViews");
-        ValidationUtils.validateKeyspaceName("system_views_test");
+        instance.validateKeyspaceName("system-views");
+        instance.validateKeyspaceName("SystemViews");
+        instance.validateKeyspaceName("system_views_test");
     }
 
     @Test
     public void testValidateKeyspaceName_forbiddenKeyspaceName_expectException()
     {
         String testKS = "system_views";
-        HttpException httpEx = Assertions.assertThrows(HttpException.class, () ->
-        {
-            ValidationUtils.validateKeyspaceName(testKS);
-        });
+        HttpException httpEx = Assertions.assertThrows(HttpException.class,
+                                                       () -> instance.validateKeyspaceName(testKS));
         assertEquals(HttpResponseStatus.FORBIDDEN.code(), httpEx.getStatusCode());
         assertEquals("Forbidden keyspace: " + testKS, httpEx.getPayload());
     }
@@ -94,10 +97,8 @@ public class ValidationUtilsTest
     public void testValidateKeyspaceName_keyspaceNameWithSpace_expectException()
     {
         String testKS = "test keyspace";
-        HttpException httpEx = Assertions.assertThrows(HttpException.class, () ->
-        {
-            ValidationUtils.validateKeyspaceName(testKS);
-        });
+        HttpException httpEx = Assertions.assertThrows(HttpException.class,
+                                                       () -> instance.validateKeyspaceName(testKS));
         assertEquals(HttpResponseStatus.BAD_REQUEST.code(), httpEx.getStatusCode());
         assertEquals("Invalid characters in keyspace: " + testKS, httpEx.getPayload());
     }
@@ -106,19 +107,17 @@ public class ValidationUtilsTest
     @Test
     public void testValidateFileName_validFileNames_expectNoException()
     {
-        ValidationUtils.validateComponentName("test-file-name.db");
-        ValidationUtils.validateComponentName("test_file_name.json");
-        ValidationUtils.validateComponentName("testFileName.cql");
-        ValidationUtils.validateComponentName("t_TOC.txt");
-        ValidationUtils.validateComponentName("crcfile.crc32");
+        instance.validateComponentName("test-file-name.db");
+        instance.validateComponentName("test_file_name.json");
+        instance.validateComponentName("testFileName.cql");
+        instance.validateComponentName("t_TOC.txt");
+        instance.validateComponentName("crcfile.crc32");
     }
 
     private void testCommon_testInvalidFileName(String testFileName)
     {
-        HttpException httpEx = Assertions.assertThrows(HttpException.class, () ->
-        {
-            ValidationUtils.validateComponentName(testFileName);
-        });
+        HttpException httpEx = Assertions.assertThrows(HttpException.class,
+                                                       () -> instance.validateComponentName(testFileName));
         assertEquals(HttpResponseStatus.BAD_REQUEST.code(), httpEx.getStatusCode());
         assertEquals("Invalid component name: " + testFileName, httpEx.getPayload());
     }
@@ -151,21 +150,19 @@ public class ValidationUtilsTest
     @Test
     public void testValidateSnapshotName_validSnapshotNames_expectNoException()
     {
-        ValidationUtils.validateSnapshotName("valid-snapshot-name");
-        ValidationUtils.validateSnapshotName("valid\\snapshot\\name");
-        ValidationUtils.validateSnapshotName("valid:snapshot:name");
-        ValidationUtils.validateSnapshotName("valid$snapshot$name");
-        ValidationUtils.validateSnapshotName("valid snapshot name");
+        instance.validateSnapshotName("valid-snapshot-name");
+        instance.validateSnapshotName("valid\\snapshot\\name");
+        instance.validateSnapshotName("valid:snapshot:name");
+        instance.validateSnapshotName("valid$snapshot$name");
+        instance.validateSnapshotName("valid snapshot name");
     }
 
     @Test
     public void testValidateSnapshotName_snapshotNameWithSlash_expectException()
     {
         String testSnapName = "valid" + '/' + "snapshotname";
-        HttpException httpEx = Assertions.assertThrows(HttpException.class, () ->
-        {
-            ValidationUtils.validateSnapshotName(testSnapName);
-        });
+        HttpException httpEx = Assertions.assertThrows(HttpException.class,
+                                                       () -> instance.validateSnapshotName(testSnapName));
         assertEquals(HttpResponseStatus.BAD_REQUEST.code(), httpEx.getStatusCode());
         assertEquals("Invalid characters in snapshot name: " + testSnapName, httpEx.getPayload());
     }
@@ -174,12 +171,9 @@ public class ValidationUtilsTest
     public void testValidateSnapshotName_snapshotNameWithNullChar_expectException()
     {
         String testSnapName = "valid" + '\0' + "snapshotname";
-        HttpException httpEx = Assertions.assertThrows(HttpException.class, () ->
-        {
-            ValidationUtils.validateSnapshotName(testSnapName);
-        });
+        HttpException httpEx = Assertions.assertThrows(HttpException.class,
+                                                       () -> instance.validateSnapshotName(testSnapName));
         assertEquals(HttpResponseStatus.BAD_REQUEST.code(), httpEx.getStatusCode());
         assertEquals("Invalid characters in snapshot name: " + testSnapName, httpEx.getPayload());
     }
-
 }
diff --git a/common/src/test/java/org/apache/cassandra/sidecar/common/data/ListSnapshotFilesRequestTest.java b/common/src/test/java/org/apache/cassandra/sidecar/common/data/ListSnapshotFilesRequestTest.java
index a468b57..f00f070 100644
--- a/common/src/test/java/org/apache/cassandra/sidecar/common/data/ListSnapshotFilesRequestTest.java
+++ b/common/src/test/java/org/apache/cassandra/sidecar/common/data/ListSnapshotFilesRequestTest.java
@@ -18,12 +18,17 @@
 
 package org.apache.cassandra.sidecar.common.data;
 
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.ext.web.handler.HttpException;
+import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
+import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -31,6 +36,19 @@ import static org.assertj.core.api.Assertions.from;
 
 class ListSnapshotFilesRequestTest
 {
+    @BeforeEach
+    void setup()
+    {
+        Guice.createInjector(new AbstractModule()
+        {
+            protected void configure()
+            {
+                bind(ValidationConfiguration.class).to(TestValidationConfiguration.class);
+                requestStaticInjection(QualifiedTableName.class);
+            }
+        });
+    }
+
     @Test
     void failsWhenKeyspaceIsNull()
     {
diff --git a/common/src/test/java/org/apache/cassandra/sidecar/common/data/StreamSSTableComponentRequestTest.java b/common/src/test/java/org/apache/cassandra/sidecar/common/data/StreamSSTableComponentRequestTest.java
index df3d726..5346abb 100644
--- a/common/src/test/java/org/apache/cassandra/sidecar/common/data/StreamSSTableComponentRequestTest.java
+++ b/common/src/test/java/org/apache/cassandra/sidecar/common/data/StreamSSTableComponentRequestTest.java
@@ -18,12 +18,17 @@
 
 package org.apache.cassandra.sidecar.common.data;
 
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.ext.web.handler.HttpException;
+import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
+import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -31,6 +36,19 @@ import static org.assertj.core.api.Assertions.from;
 
 class StreamSSTableComponentRequestTest
 {
+    @BeforeEach
+    void setup()
+    {
+        Guice.createInjector(new AbstractModule()
+        {
+            protected void configure()
+            {
+                bind(ValidationConfiguration.class).to(TestValidationConfiguration.class);
+                requestStaticInjection(QualifiedTableName.class);
+            }
+        });
+    }
+
     @Test
     void failsWhenKeyspaceIsNull()
     {
diff --git a/common/src/testFixtures/java/org/apache/cassandra/sidecar/common/TestValidationConfiguration.java b/common/src/testFixtures/java/org/apache/cassandra/sidecar/common/TestValidationConfiguration.java
new file mode 100644
index 0000000..8585567
--- /dev/null
+++ b/common/src/testFixtures/java/org/apache/cassandra/sidecar/common/TestValidationConfiguration.java
@@ -0,0 +1,46 @@
+package org.apache.cassandra.sidecar.common;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
+
+/**
+ * A {@link ValidationConfiguration} used for unit testing
+ */
+public class TestValidationConfiguration implements ValidationConfiguration
+{
+    private static final Set<String> FORBIDDEN_KEYSPACES = new HashSet<>(Arrays.asList("system_schema",
+                                                                                         "system_traces",
+                                                                                         "system_distributed",
+                                                                                         "system",
+                                                                                         "system_auth",
+                                                                                         "system_views",
+                                                                                         "system_virtual_schema"));
+    private static final String ALLOWED_PATTERN_FOR_DIRECTORY = "[a-zA-Z0-9_-]+";
+    private static final String ALLOWED_PATTERN_FOR_COMPONENT_NAME = ALLOWED_PATTERN_FOR_DIRECTORY
+                                                                       + "(.db|.cql|.json|.crc32|TOC.txt)";
+    private static final String ALLOWED_PATTERN_FOR_RESTRICTED_COMPONENT_NAME = ALLOWED_PATTERN_FOR_DIRECTORY
+                                                                                  + "(.db|TOC.txt)";
+
+    public Set<String> getForbiddenKeyspaces()
+    {
+        return FORBIDDEN_KEYSPACES;
+    }
+
+    public String getAllowedPatternForDirectory()
+    {
+        return ALLOWED_PATTERN_FOR_DIRECTORY;
+    }
+
+    public String getAllowedPatternForComponentName()
+    {
+        return ALLOWED_PATTERN_FOR_COMPONENT_NAME;
+    }
+
+    public String getAllowedPatternForRestrictedComponentName()
+    {
+        return ALLOWED_PATTERN_FOR_RESTRICTED_COMPONENT_NAME;
+    }
+}
diff --git a/src/main/dist/conf/sidecar.yaml b/src/main/dist/conf/sidecar.yaml
index ad1e3a0..a718f75 100644
--- a/src/main/dist/conf/sidecar.yaml
+++ b/src/main/dist/conf/sidecar.yaml
@@ -33,3 +33,16 @@ sidecar:
 
 healthcheck:
   - poll_freq_millis: 30000
+
+cassandra_input_validation:
+  - forbidden_keyspaces:
+      - system_schema
+      - system_traces
+      - system_distributed
+      - system
+      - system_auth
+      - system_views
+      - system_virtual_schema
+  - allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
+  - allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
+  - allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
diff --git a/src/main/java/org/apache/cassandra/sidecar/Configuration.java b/src/main/java/org/apache/cassandra/sidecar/Configuration.java
index 87b97e6..6ed92a8 100644
--- a/src/main/java/org/apache/cassandra/sidecar/Configuration.java
+++ b/src/main/java/org/apache/cassandra/sidecar/Configuration.java
@@ -21,6 +21,7 @@ package org.apache.cassandra.sidecar;
 import javax.annotation.Nullable;
 
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
+import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 
 /**
  * Sidecar configuration
@@ -60,11 +61,13 @@ public class Configuration
 
     private final long throttleDelayInSeconds;
 
+    private final ValidationConfiguration validationConfiguration;
+
     public Configuration(InstancesConfig instancesConfig, String host, Integer port, Integer healthCheckFrequencyMillis,
                          boolean isSslEnabled, @Nullable String keyStorePath, @Nullable String keyStorePassword,
                          @Nullable String trustStorePath, @Nullable String trustStorePassword,
                          long rateLimitStreamRequestsPerSecond, long throttleTimeoutInSeconds,
-                         long throttleDelayInSeconds)
+                         long throttleDelayInSeconds, ValidationConfiguration validationConfiguration)
     {
         this.instancesConfig = instancesConfig;
         this.host = host;
@@ -79,6 +82,7 @@ public class Configuration
         this.rateLimitStreamRequestsPerSecond = rateLimitStreamRequestsPerSecond;
         this.throttleTimeoutInSeconds = throttleTimeoutInSeconds;
         this.throttleDelayInSeconds = throttleDelayInSeconds;
+        this.validationConfiguration = validationConfiguration;
     }
 
     /**
@@ -213,6 +217,8 @@ public class Configuration
         private long throttleTimeoutInSeconds;
         private long throttleDelayInSeconds;
 
+        private ValidationConfiguration validationConfiguration;
+
         public Builder setInstancesConfig(InstancesConfig instancesConfig)
         {
             this.instancesConfig = instancesConfig;
@@ -285,12 +291,18 @@ public class Configuration
             return this;
         }
 
+        public Builder setValidationConfiguration(ValidationConfiguration validationConfiguration)
+        {
+            this.validationConfiguration = validationConfiguration;
+            return this;
+        }
+
         public Configuration build()
         {
             return new Configuration(instancesConfig, host, port, healthCheckFrequencyMillis, isSslEnabled,
                                      keyStorePath, keyStorePassword, trustStorePath, trustStorePassword,
                                      rateLimitStreamRequestsPerSecond, throttleTimeoutInSeconds,
-                                     throttleDelayInSeconds);
+                                     throttleDelayInSeconds, validationConfiguration);
         }
     }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/MainModule.java b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
index 8767697..a121457 100644
--- a/src/main/java/org/apache/cassandra/sidecar/MainModule.java
+++ b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
@@ -20,12 +20,14 @@ package org.apache.cassandra.sidecar;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
+import java.util.function.UnaryOperator;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.util.concurrent.SidecarRateLimiter;
@@ -56,17 +58,42 @@ import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadataImpl;
 import org.apache.cassandra.sidecar.common.CQLSession;
 import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
+import org.apache.cassandra.sidecar.common.data.QualifiedTableName;
+import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
+import org.apache.cassandra.sidecar.common.utils.YAMLValidationConfiguration;
 import org.apache.cassandra.sidecar.routes.CassandraHealthService;
 import org.apache.cassandra.sidecar.routes.FileStreamHandler;
 import org.apache.cassandra.sidecar.routes.HealthService;
 import org.apache.cassandra.sidecar.routes.ListSnapshotFilesHandler;
 import org.apache.cassandra.sidecar.routes.StreamSSTableComponentHandler;
 import org.apache.cassandra.sidecar.routes.SwaggerOpenApiResource;
-import org.apache.cassandra.sidecar.utils.YAMLKeyConstants;
 import org.jboss.resteasy.plugins.server.vertx.VertxRegistry;
 import org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler;
 import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment;
 
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_ALLOWED_CHARS_FOR_COMPONENT_NAME;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_ALLOWED_CHARS_FOR_DIRECTORY;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_FORBIDDEN_KEYSPACES;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_INPUT_VALIDATION;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_INSTANCE;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_INSTANCES;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_INSTANCE_DATA_DIRS;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_INSTANCE_HOST;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_INSTANCE_ID;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.CASSANDRA_INSTANCE_PORT;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.HEALTH_CHECK_FREQ;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.HOST;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.KEYSTORE_PASSWORD;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.KEYSTORE_PATH;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.PORT;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.SSL_ENABLED;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.STREAM_REQUESTS_PER_SEC;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.THROTTLE_DELAY_SEC;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.THROTTLE_TIMEOUT_SEC;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.TRUSTSTORE_PASSWORD;
+import static org.apache.cassandra.sidecar.utils.YAMLKeyConstants.TRUSTSTORE_PATH;
+
 /**
  * Provides main binding for more complex Guice dependencies
  */
@@ -75,6 +102,12 @@ public class MainModule extends AbstractModule
     private static final Logger logger = LoggerFactory.getLogger(MainModule.class);
     private static final String API_V1_VERSION = "/api/v1";
 
+    @Override
+    protected void configure()
+    {
+        requestStaticInjection(QualifiedTableName.class);
+    }
+
     @Provides
     @Singleton
     public Vertx getVertx()
@@ -166,90 +199,82 @@ public class MainModule extends AbstractModule
 
     @Provides
     @Singleton
-    public Configuration configuration(InstancesConfig instancesConfig) throws ConfigurationException, IOException
+    public Configuration configuration(YAMLConfiguration yamlConf, InstancesConfig instancesConfig,
+                                       ValidationConfiguration validationConfiguration)
+    throws IOException
     {
-        final String confPath = System.getProperty("sidecar.config", "file://./conf/config.yaml");
-        logger.info("Reading configuration from {}", confPath);
-        try
-        {
-            URL url = new URL(confPath);
-
-            YAMLConfiguration yamlConf = new YAMLConfiguration();
-            InputStream stream = url.openStream();
-            yamlConf.read(stream);
-
-            return new Configuration.Builder()
-                    .setInstancesConfig(instancesConfig)
-                    .setHost(yamlConf.get(String.class, YAMLKeyConstants.HOST))
-                    .setPort(yamlConf.get(Integer.class, YAMLKeyConstants.PORT))
-                    .setHealthCheckFrequency(yamlConf.get(Integer.class, YAMLKeyConstants.HEALTH_CHECK_FREQ))
-                    .setKeyStorePath(yamlConf.get(String.class, YAMLKeyConstants.KEYSTORE_PATH, null))
-                    .setKeyStorePassword(yamlConf.get(String.class, YAMLKeyConstants.KEYSTORE_PASSWORD, null))
-                    .setTrustStorePath(yamlConf.get(String.class, YAMLKeyConstants.TRUSTSTORE_PATH, null))
-                    .setTrustStorePassword(yamlConf.get(String.class, YAMLKeyConstants.TRUSTSTORE_PASSWORD, null))
-                    .setSslEnabled(yamlConf.get(Boolean.class, YAMLKeyConstants.SSL_ENABLED, false))
-                    .setRateLimitStreamRequestsPerSecond(yamlConf.getLong(YAMLKeyConstants.STREAM_REQUESTS_PER_SEC))
-                    .setThrottleTimeoutInSeconds(yamlConf.getLong(YAMLKeyConstants.THROTTLE_TIMEOUT_SEC))
-                    .setThrottleDelayInSeconds(yamlConf.getLong(YAMLKeyConstants.THROTTLE_DELAY_SEC))
-                    .build();
-        }
-        catch (MalformedURLException e)
-        {
-            throw new ConfigurationException("Failed reading from sidebar.config path: " + confPath, e);
-        }
+        return new Configuration.Builder()
+               .setInstancesConfig(instancesConfig)
+               .setHost(yamlConf.get(String.class, HOST))
+               .setPort(yamlConf.get(Integer.class, PORT))
+               .setHealthCheckFrequency(yamlConf.get(Integer.class, HEALTH_CHECK_FREQ))
+               .setKeyStorePath(yamlConf.get(String.class, KEYSTORE_PATH, null))
+               .setKeyStorePassword(yamlConf.get(String.class, KEYSTORE_PASSWORD, null))
+               .setTrustStorePath(yamlConf.get(String.class, TRUSTSTORE_PATH, null))
+               .setTrustStorePassword(yamlConf.get(String.class, TRUSTSTORE_PASSWORD, null))
+               .setSslEnabled(yamlConf.get(Boolean.class, SSL_ENABLED, false))
+               .setRateLimitStreamRequestsPerSecond(yamlConf.getLong(STREAM_REQUESTS_PER_SEC))
+               .setThrottleTimeoutInSeconds(yamlConf.getLong(THROTTLE_TIMEOUT_SEC))
+               .setThrottleDelayInSeconds(yamlConf.getLong(THROTTLE_DELAY_SEC))
+               .setValidationConfiguration(validationConfiguration)
+               .build();
     }
 
     @Provides
     @Singleton
-    public InstancesConfig getInstancesConfig(CassandraVersionProvider versionProvider)
-        throws ConfigurationException, IOException
+    public InstancesConfig getInstancesConfig(YAMLConfiguration yamlConf, CassandraVersionProvider versionProvider)
     {
-        final String confPath = System.getProperty("sidecar.config", "file://./conf/config.yaml");
-        URL url = new URL(confPath);
+        return readInstancesConfig(yamlConf, versionProvider);
+    }
 
-        try
-        {
-            YAMLConfiguration yamlConf = new YAMLConfiguration();
-            InputStream stream = url.openStream();
-            yamlConf.read(stream);
-            return readInstancesConfig(yamlConf, versionProvider);
-        }
-        catch (MalformedURLException e)
-        {
-            throw new ConfigurationException(String.format("Unable to parse cluster information from '%s'", url));
-        }
+    @Provides
+    @Singleton
+    public ValidationConfiguration validationConfiguration(YAMLConfiguration yamlConf)
+    {
+        org.apache.commons.configuration2.Configuration validation = yamlConf.subset(CASSANDRA_INPUT_VALIDATION);
+        Set<String> forbiddenKeyspaces = new HashSet<>(validation.getList(String.class,
+                                                                          CASSANDRA_FORBIDDEN_KEYSPACES,
+                                                                          Collections.emptyList()));
+        UnaryOperator<String> readString = key -> validation.get(String.class, key);
+        String allowedPatternForDirectory = readString.apply(CASSANDRA_ALLOWED_CHARS_FOR_DIRECTORY);
+        String allowedPatternForComponentName = readString.apply(CASSANDRA_ALLOWED_CHARS_FOR_COMPONENT_NAME);
+        String allowedPatternForRestrictedComponentName = readString
+                                                          .apply(CASSANDRA_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME);
+
+        return new YAMLValidationConfiguration(forbiddenKeyspaces,
+                                               allowedPatternForDirectory,
+                                               allowedPatternForComponentName,
+                                               allowedPatternForRestrictedComponentName);
     }
 
     @VisibleForTesting
     public InstancesConfigImpl readInstancesConfig(YAMLConfiguration yamlConf, CassandraVersionProvider versionProvider)
     {
-        final int healthCheckFrequencyMillis = yamlConf.get(Integer.class, YAMLKeyConstants.HEALTH_CHECK_FREQ);
+        final int healthCheckFrequencyMillis = yamlConf.get(Integer.class, HEALTH_CHECK_FREQ);
 
         /* Since we are supporting handling multiple instances in Sidecar optionally, we prefer reading single instance
          * data over reading multiple instances section
          */
-        org.apache.commons.configuration2.Configuration singleInstanceConf = yamlConf.subset(
-            YAMLKeyConstants.CASSANDRA_INSTANCE);
+        org.apache.commons.configuration2.Configuration singleInstanceConf = yamlConf.subset(CASSANDRA_INSTANCE);
         if (singleInstanceConf != null && !singleInstanceConf.isEmpty())
         {
-            String host = singleInstanceConf.get(String.class, YAMLKeyConstants.CASSANDRA_INSTANCE_HOST);
-            int port = singleInstanceConf.get(Integer.class, YAMLKeyConstants.CASSANDRA_INSTANCE_PORT);
-            String dataDirs = singleInstanceConf.get(String.class, YAMLKeyConstants.CASSANDRA_INSTANCE_DATA_DIRS);
+            String host = singleInstanceConf.get(String.class, CASSANDRA_INSTANCE_HOST);
+            int port = singleInstanceConf.get(Integer.class, CASSANDRA_INSTANCE_PORT);
+            String dataDirs = singleInstanceConf.get(String.class, CASSANDRA_INSTANCE_DATA_DIRS);
             CQLSession session = new CQLSession(host, port, healthCheckFrequencyMillis);
             return new InstancesConfigImpl(Collections.singletonList(new InstanceMetadataImpl(1, host, port,
                 Collections.unmodifiableList(Arrays.asList(dataDirs.split(","))), session,
                 versionProvider, healthCheckFrequencyMillis)));
         }
 
-        List<HierarchicalConfiguration<ImmutableNode>> instances = yamlConf.configurationsAt(
-        YAMLKeyConstants.CASSANDRA_INSTANCES);
+        List<HierarchicalConfiguration<ImmutableNode>> instances = yamlConf.configurationsAt(CASSANDRA_INSTANCES);
         final List<InstanceMetadata> instanceMetas = new LinkedList<>();
         for (HierarchicalConfiguration<ImmutableNode> instance : instances)
         {
-            int id = instance.get(Integer.class, YAMLKeyConstants.CASSANDRA_INSTANCE_ID);
-            String host = instance.get(String.class, YAMLKeyConstants.CASSANDRA_INSTANCE_HOST);
-            int port = instance.get(Integer.class, YAMLKeyConstants.CASSANDRA_INSTANCE_PORT);
-            String dataDirs = instance.get(String.class, YAMLKeyConstants.CASSANDRA_INSTANCE_DATA_DIRS);
+            int id = instance.get(Integer.class, CASSANDRA_INSTANCE_ID);
+            String host = instance.get(String.class, CASSANDRA_INSTANCE_HOST);
+            int port = instance.get(Integer.class, CASSANDRA_INSTANCE_PORT);
+            String dataDirs = instance.get(String.class, CASSANDRA_INSTANCE_DATA_DIRS);
 
             CQLSession session = new CQLSession(host, port, healthCheckFrequencyMillis);
             instanceMetas.add(new InstanceMetadataImpl(id, host, port,
@@ -259,6 +284,27 @@ public class MainModule extends AbstractModule
         return new InstancesConfigImpl(instanceMetas);
     }
 
+    @Provides
+    @Singleton
+    public YAMLConfiguration yamlConfiguration() throws ConfigurationException
+    {
+        final String confPath = System.getProperty("sidecar.config", "file://./conf/config.yaml");
+        logger.info("Reading configuration from {}", confPath);
+
+        try
+        {
+            URL url = new URL(confPath);
+            YAMLConfiguration yamlConf = new YAMLConfiguration();
+            InputStream stream = url.openStream();
+            yamlConf.read(stream);
+            return yamlConf;
+        }
+        catch (IOException e)
+        {
+            throw new ConfigurationException(String.format("Unable to parse cluster information from '%s'", confPath));
+        }
+    }
+
     @Provides
     @Singleton
     public CassandraVersionProvider cassandraVersionProvider()
diff --git a/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java b/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java
index 227be4e..54736ca 100644
--- a/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java
+++ b/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java
@@ -50,7 +50,7 @@ import io.vertx.core.file.FileSystem;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
 import org.apache.cassandra.sidecar.common.data.ListSnapshotFilesRequest;
 import org.apache.cassandra.sidecar.common.data.StreamSSTableComponentRequest;
-import org.apache.cassandra.sidecar.common.utils.ValidationUtils;
+import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 
 /**
  * This class builds the snapshot path on a given host validating that it exists
@@ -65,20 +65,23 @@ public class SnapshotPathBuilder
     protected final Vertx vertx;
     protected final FileSystem fs;
     protected final InstancesConfig instancesConfig;
+    protected final CassandraInputValidator validator;
 
     /**
      * Creates a new SnapshotPathBuilder for snapshots of an instance with the given {@code vertx} instance and
      * {@code instancesConfig Cassandra configuration}.
      *
-     * @param vertx                   the vertx instance
-     * @param instancesConfig         the configuration for Cassandra
+     * @param vertx           the vertx instance
+     * @param instancesConfig the configuration for Cassandra
+     * @param validator       a validator instance to validate Cassandra-specific input
      */
     @Inject
-    public SnapshotPathBuilder(Vertx vertx, InstancesConfig instancesConfig)
+    public SnapshotPathBuilder(Vertx vertx, InstancesConfig instancesConfig, CassandraInputValidator validator)
     {
         this.vertx = vertx;
         this.fs = vertx.fileSystem();
         this.instancesConfig = instancesConfig;
+        this.validator = validator;
     }
 
     /**
@@ -287,7 +290,7 @@ public class SnapshotPathBuilder
     protected void validate(StreamSSTableComponentRequest request)
     {
         // Only allow .db and TOC.txt components here
-        ValidationUtils.validateDbOrTOCComponentName(request.getComponentName());
+        validator.validateRestrictedComponentName(request.getComponentName());
     }
 
     /**
@@ -328,11 +331,11 @@ public class SnapshotPathBuilder
             root = root.recover(v -> f);
         }
         return root.recover(t ->
-        {
-            String errorMessage = String.format("Keyspace '%s' does not exist", keyspace);
-            logger.debug(errorMessage, t);
-            return Future.failedFuture(new FileNotFoundException(errorMessage));
-        });
+                            {
+                                String errorMessage = String.format("Keyspace '%s' does not exist", keyspace);
+                                logger.debug(errorMessage, t);
+                                return Future.failedFuture(new FileNotFoundException(errorMessage));
+                            });
     }
 
     /**
@@ -385,11 +388,11 @@ public class SnapshotPathBuilder
 
         return isValidDirectory(snapshotDirectory)
                .recover(t ->
-               {
-                   String errorMessage = String.format("Snapshot directory '%s' does not exist", snapshotName);
-                   logger.warn("Snapshot directory {} does not exist in {}", snapshotName, snapshotDirectory);
-                   return Future.failedFuture(new FileNotFoundException(errorMessage));
-               });
+                        {
+                            String errMsg = String.format("Snapshot directory '%s' does not exist", snapshotName);
+                            logger.warn("Snapshot directory {} does not exist in {}", snapshotName, snapshotDirectory);
+                            return Future.failedFuture(new FileNotFoundException(errMsg));
+                        });
     }
 
     /**
@@ -409,13 +412,13 @@ public class SnapshotPathBuilder
 
         return isValidFilename(componentFilename)
                .recover(t ->
-               {
-                   logger.warn("Snapshot directory {} or component {} does not exist in {}", snapshotName,
-                               componentName, componentFilename);
-                   String errMsg = String.format("Component '%s' does not exist for snapshot '%s'",
-                                                 componentName, snapshotName);
-                   return Future.failedFuture(new FileNotFoundException(errMsg));
-               });
+                        {
+                            logger.warn("Snapshot directory {} or component {} does not exist in {}", snapshotName,
+                                        componentName, componentFilename);
+                            String errMsg = String.format("Component '%s' does not exist for snapshot '%s'",
+                                                          componentName, snapshotName);
+                            return Future.failedFuture(new FileNotFoundException(errMsg));
+                        });
     }
 
     /**
@@ -446,23 +449,23 @@ public class SnapshotPathBuilder
     {
         return fs.exists(filename)
                  .compose(exists ->
-                 {
-                     if (!exists)
-                     {
-                         String errMsg = "File '" + filename + "' does not exist";
-                         return Future.failedFuture(new FileNotFoundException(errMsg));
-                     }
-                     return fs.props(filename)
-                              .compose(fileProps ->
+                          {
+                              if (!exists)
                               {
-                                  if (fileProps == null || !predicate.test(fileProps))
-                                  {
-                                      String errMsg = "File '" + filename + "' does not exist";
-                                      return Future.failedFuture(new FileNotFoundException(errMsg));
-                                  }
-                                  return Future.succeededFuture(filename);
-                              });
-                 });
+                                  String errMsg = "File '" + filename + "' does not exist";
+                                  return Future.failedFuture(new FileNotFoundException(errMsg));
+                              }
+                              return fs.props(filename)
+                                       .compose(fileProps ->
+                                                {
+                                                    if (fileProps == null || !predicate.test(fileProps))
+                                                    {
+                                                        String errMsg = "File '" + filename + "' does not exist";
+                                                        return Future.failedFuture(new FileNotFoundException(errMsg));
+                                                    }
+                                                    return Future.succeededFuture(filename);
+                                                });
+                          });
     }
 
     /**
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/YAMLKeyConstants.java b/src/main/java/org/apache/cassandra/sidecar/utils/YAMLKeyConstants.java
index cce239a..2d6d0cf 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/YAMLKeyConstants.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/YAMLKeyConstants.java
@@ -44,4 +44,10 @@ public class YAMLKeyConstants
     public static final String CASSANDRA_INSTANCE_HOST = "host";
     public static final String CASSANDRA_INSTANCE_PORT = "port";
     public static final String CASSANDRA_INSTANCE_DATA_DIRS = "data_dirs";
+    public static final String CASSANDRA_INPUT_VALIDATION = "cassandra_input_validation";
+    public static final String CASSANDRA_FORBIDDEN_KEYSPACES = "forbidden_keyspaces";
+    public static final String CASSANDRA_ALLOWED_CHARS_FOR_DIRECTORY = "allowed_chars_for_directory";
+    public static final String CASSANDRA_ALLOWED_CHARS_FOR_COMPONENT_NAME = "allowed_chars_for_component_name";
+    public static final String CASSANDRA_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME =
+    "allowed_chars_for_restricted_component_name";
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/ConfigurationTest.java b/src/test/java/org/apache/cassandra/sidecar/ConfigurationTest.java
index 8e7db7d..d499302 100644
--- a/src/test/java/org/apache/cassandra/sidecar/ConfigurationTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/ConfigurationTest.java
@@ -18,8 +18,9 @@
 
 package org.apache.cassandra.sidecar;
 
-import java.io.FileInputStream;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 
 import org.apache.commons.configuration2.YAMLConfiguration;
 import org.junit.jupiter.api.BeforeEach;
@@ -30,8 +31,10 @@ import com.google.inject.Injector;
 import com.google.inject.util.Modules;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
 import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
+import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
+
+import static org.assertj.core.api.Assertions.assertThat;
 
-import static org.junit.Assert.assertEquals;
 
 /**
  * Test changes related to sidecar.yaml file.
@@ -52,13 +55,14 @@ public class ConfigurationTest
     {
         MainModule mainModule = new MainModule();
         YAMLConfiguration yamlConf = new YAMLConfiguration();
-        try (InputStream stream = new FileInputStream("src/test/resources/sidecar_single_instance.yaml"))
+        try (InputStream stream =
+             Files.newInputStream(Paths.get("src/test/resources/sidecar_single_instance.yaml")))
         {
             yamlConf.read(stream);
             InstancesConfig instancesConfig = mainModule.readInstancesConfig(yamlConf, versionProvider);
-            assertEquals(1, instancesConfig.instances().size());
-            assertEquals("localhost", instancesConfig.instances().get(0).host());
-            assertEquals(9042, instancesConfig.instances().get(0).port());
+            assertThat(instancesConfig.instances().size()).isEqualTo(1);
+            assertThat(instancesConfig.instances().get(0).host()).isEqualTo("localhost");
+            assertThat(instancesConfig.instances().get(0).port()).isEqualTo(9042);
         }
     }
 
@@ -67,13 +71,14 @@ public class ConfigurationTest
     {
         MainModule mainModule = new MainModule();
         YAMLConfiguration yamlConf = new YAMLConfiguration();
-        try (InputStream stream = new FileInputStream("src/test/resources/sidecar_with_single_multiple_instances.yaml"))
+        try (InputStream stream =
+             Files.newInputStream(Paths.get("src/test/resources/sidecar_with_single_multiple_instances.yaml")))
         {
             yamlConf.read(stream);
             InstancesConfig instancesConfig = mainModule.readInstancesConfig(yamlConf, versionProvider);
-            assertEquals(1, instancesConfig.instances().size());
-            assertEquals("localhost", instancesConfig.instances().get(0).host());
-            assertEquals(9042, instancesConfig.instances().get(0).port());
+            assertThat(instancesConfig.instances().size()).isEqualTo(1);
+            assertThat(instancesConfig.instances().get(0).host()).isEqualTo("localhost");
+            assertThat(instancesConfig.instances().get(0).port()).isEqualTo(9042);
         }
     }
 
@@ -82,11 +87,32 @@ public class ConfigurationTest
     {
         MainModule mainModule = new MainModule();
         YAMLConfiguration yamlConf = new YAMLConfiguration();
-        try (InputStream stream = new FileInputStream("src/test/resources/sidecar_multiple_instances.yaml"))
+        try (InputStream stream =
+             Files.newInputStream(Paths.get("src/test/resources/sidecar_multiple_instances.yaml")))
         {
             yamlConf.read(stream);
             InstancesConfig instancesConfig = mainModule.readInstancesConfig(yamlConf, versionProvider);
-            assertEquals(2, instancesConfig.instances().size());
+            assertThat(instancesConfig.instances().size()).isEqualTo(2);
+        }
+    }
+
+    @Test
+    public void testReadingCassandraInputValidation() throws Exception
+    {
+        MainModule mainModule = new MainModule();
+        YAMLConfiguration yamlConf = new YAMLConfiguration();
+        try (InputStream stream =
+             Files.newInputStream(Paths.get("src/test/resources/sidecar_validation_configuration.yaml")))
+        {
+            yamlConf.read(stream);
+            ValidationConfiguration validationConfiguration = mainModule.validationConfiguration(yamlConf);
+
+            assertThat(validationConfiguration.getForbiddenKeyspaces()).contains("a", "b", "c");
+            assertThat(validationConfiguration.getAllowedPatternForDirectory()).isEqualTo("[a-z]+");
+            assertThat(validationConfiguration.getAllowedPatternForComponentName())
+            .isEqualTo("(.db|.cql|.json|.crc32|TOC.txt)");
+            assertThat(validationConfiguration.getAllowedPatternForRestrictedComponentName())
+            .isEqualTo("(.db|TOC.txt)");
         }
     }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/TestModule.java b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
index 2ae22ad..bd83707 100644
--- a/src/test/java/org/apache/cassandra/sidecar/TestModule.java
+++ b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
@@ -36,6 +36,8 @@ import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
 import org.apache.cassandra.sidecar.common.MockCassandraFactory;
+import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
+import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
@@ -65,15 +67,15 @@ public class TestModule extends AbstractModule
     protected Configuration abstractConfig()
     {
         return new Configuration.Builder()
-                           .setInstancesConfig(getInstancesConfig())
-                           .setHost("127.0.0.1")
-                           .setPort(6475)
-                           .setHealthCheckFrequency(1000)
-                           .setSslEnabled(false)
-                           .setRateLimitStreamRequestsPerSecond(1)
-                           .setThrottleDelayInSeconds(5)
-                           .setThrottleTimeoutInSeconds(10)
-                           .build();
+               .setInstancesConfig(getInstancesConfig())
+               .setHost("127.0.0.1")
+               .setPort(6475)
+               .setHealthCheckFrequency(1000)
+               .setSslEnabled(false)
+               .setRateLimitStreamRequestsPerSecond(1)
+               .setThrottleDelayInSeconds(5)
+               .setThrottleTimeoutInSeconds(10)
+               .build();
     }
 
     @Provides
@@ -130,4 +132,11 @@ public class TestModule extends AbstractModule
         builder.add(new MockCassandraFactory());
         return builder.build();
     }
+
+    @Provides
+    @Singleton
+    public ValidationConfiguration validationConfiguration()
+    {
+        return new TestValidationConfiguration();
+    }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilderTest.java b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilderTest.java
index ac350ed..a554e44 100644
--- a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilderTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilderTest.java
@@ -36,9 +36,10 @@ import static org.assertj.core.api.Assertions.assertThat;
 @ExtendWith(VertxExtension.class)
 class SnapshotPathBuilderTest extends AbstractSnapshotPathBuilderTest
 {
-    SnapshotPathBuilder initialize(Vertx vertx, InstancesConfig instancesConfig)
+    @Override
+    public SnapshotPathBuilder initialize(Vertx vertx, InstancesConfig instancesConfig)
     {
-        return new SnapshotPathBuilder(vertx, instancesConfig);
+        return new SnapshotPathBuilder(vertx, instancesConfig, validator);
     }
 
     @Test
diff --git a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotSearchTest.java b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotSearchTest.java
index daecce5..2649c32 100644
--- a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotSearchTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotSearchTest.java
@@ -39,6 +39,9 @@ import io.vertx.core.file.FileProps;
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
+import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
+import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
+import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 
 import static org.apache.cassandra.sidecar.snapshots.SnapshotUtils.getSnapshot1Instance1Files;
 import static org.apache.cassandra.sidecar.snapshots.SnapshotUtils.getSnapshot1Instance2Files;
@@ -64,7 +67,10 @@ public class SnapshotSearchTest
         rootDir = temporaryFolder.getCanonicalPath();
         SnapshotUtils.initializeTmpDirectory(temporaryFolder);
         InstancesConfig mockInstancesConfig = mockInstancesConfig(rootDir);
-        instance = new SnapshotPathBuilder(vertx, mockInstancesConfig);
+
+        ValidationConfiguration validationConfiguration = new TestValidationConfiguration();
+        CassandraInputValidator validator = new CassandraInputValidator(validationConfiguration);
+        instance = new SnapshotPathBuilder(vertx, mockInstancesConfig, validator);
     }
 
     @Test
diff --git a/src/test/resources/sidecar_validation_configuration.yaml b/src/test/resources/sidecar_validation_configuration.yaml
new file mode 100644
index 0000000..8a6a9dc
--- /dev/null
+++ b/src/test/resources/sidecar_validation_configuration.yaml
@@ -0,0 +1,8 @@
+cassandra_input_validation:
+  - forbidden_keyspaces:
+      - a
+      - b
+      - c
+  - allowed_chars_for_directory: "[a-z]+"
+  - allowed_chars_for_component_name: "(.db|.cql|.json|.crc32|TOC.txt)"
+  - allowed_chars_for_restricted_component_name: "(.db|TOC.txt)"
\ No newline at end of file
diff --git a/src/test/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java b/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
similarity index 96%
rename from src/test/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
rename to src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
index 763b906..2feded5 100644
--- a/src/test/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
+++ b/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
@@ -31,8 +31,9 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
-import org.junit.rules.TemporaryFolder;
 
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.core.Future;
 import io.vertx.core.Vertx;
@@ -40,8 +41,12 @@ import io.vertx.ext.web.handler.HttpException;
 import io.vertx.junit5.VertxTestContext;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
 import org.apache.cassandra.sidecar.common.data.ListSnapshotFilesRequest;
+import org.apache.cassandra.sidecar.common.data.QualifiedTableName;
 import org.apache.cassandra.sidecar.common.data.StreamSSTableComponentRequest;
+import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
+import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -49,21 +54,36 @@ import static org.assertj.core.api.Assertions.from;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-abstract class AbstractSnapshotPathBuilderTest
+/**
+ * An abstract class for SnapshotPathBuilder tests
+ */
+public abstract class AbstractSnapshotPathBuilderTest
 {
     @TempDir
-    File dataDir0;
+    protected File dataDir0;
 
     @TempDir
-    File dataDir1;
+    protected File dataDir1;
 
-    SnapshotPathBuilder instance;
-    Vertx vertx = Vertx.vertx();
+    protected SnapshotPathBuilder instance;
+    protected Vertx vertx = Vertx.vertx();
+    protected CassandraInputValidator validator;
 
     @SuppressWarnings("ResultOfMethodCallIgnored")
     @BeforeEach
-    void setup() throws IOException
+    protected void setup() throws IOException
     {
+        ValidationConfiguration validationConfiguration = new TestValidationConfiguration();
+        validator = new CassandraInputValidator(validationConfiguration);
+        Guice.createInjector(new AbstractModule()
+        {
+            protected void configure()
+            {
+                bind(ValidationConfiguration.class).toInstance(validationConfiguration);
+                requestStaticInjection(QualifiedTableName.class);
+            }
+        });
+
         InstancesConfig mockInstancesConfig = mock(InstancesConfig.class);
         InstanceMetadata mockInstanceMeta = mock(InstanceMetadata.class);
         InstanceMetadata mockInvalidDataDirInstanceMeta = mock(InstanceMetadata.class);
@@ -128,13 +148,13 @@ abstract class AbstractSnapshotPathBuilderTest
 
     @SuppressWarnings("ResultOfMethodCallIgnored")
     @AfterEach
-    void clear()
+    protected void clear()
     {
         dataDir0.delete();
         dataDir1.delete();
     }
 
-    abstract SnapshotPathBuilder initialize(Vertx vertx, InstancesConfig instancesConfig);
+    protected abstract SnapshotPathBuilder initialize(Vertx vertx, InstancesConfig instancesConfig);
 
     @ParameterizedTest
     @ValueSource(strings = { "i_❤_u.db", "this-is-not-allowed.jar", "cql-is-not-allowed-here.cql",
@@ -481,11 +501,10 @@ abstract class AbstractSnapshotPathBuilderTest
 
     @SuppressWarnings("ResultOfMethodCallIgnored")
     @Test
-    void testTableWithUUIDPicked() throws IOException
+    void testTableWithUUIDPicked(@TempDir File tempDir) throws IOException
     {
-        TemporaryFolder tempFolder = new TemporaryFolder();
-        tempFolder.create();
-        File dataDir = tempFolder.newFolder("data");
+        File dataDir = new File(tempDir, "data");
+        dataDir.mkdirs();
 
         InstancesConfig mockInstancesConfig = mock(InstancesConfig.class);
         InstanceMetadata mockInstanceMeta = mock(InstanceMetadata.class);
@@ -513,7 +532,7 @@ abstract class AbstractSnapshotPathBuilderTest
 
         String expectedPath;
         // a_table and a_table-<TABLE_UUID> - the latter should be picked
-        SnapshotPathBuilder newBuilder = new SnapshotPathBuilder(vertx, mockInstancesConfig);
+        SnapshotPathBuilder newBuilder = new SnapshotPathBuilder(vertx, mockInstancesConfig, validator);
         expectedPath = atableWithUUID.getAbsolutePath() + "/snapshots/a_snapshot/data.db";
         succeedsWhenPathExists(newBuilder
                                .build("localhost",
@@ -571,11 +590,10 @@ abstract class AbstractSnapshotPathBuilderTest
 
     @SuppressWarnings("ResultOfMethodCallIgnored")
     @Test
-    void testLastModifiedTablePicked() throws IOException
+    void testLastModifiedTablePicked(@TempDir File tempDir) throws IOException
     {
-        TemporaryFolder tempFolder = new TemporaryFolder();
-        tempFolder.create();
-        File dataDir = tempFolder.newFolder("data");
+        File dataDir = new File(tempDir, "data");
+        dataDir.mkdirs();
 
         InstancesConfig mockInstancesConfig = mock(InstancesConfig.class);
         InstanceMetadata mockInstanceMeta = mock(InstanceMetadata.class);
@@ -606,7 +624,7 @@ abstract class AbstractSnapshotPathBuilderTest
         table4New.setLastModified(System.currentTimeMillis() + 2000000);
 
         String expectedPath;
-        SnapshotPathBuilder newBuilder = new SnapshotPathBuilder(vertx, mockInstancesConfig);
+        SnapshotPathBuilder newBuilder = new SnapshotPathBuilder(vertx, mockInstancesConfig, validator);
         // table4-a72c8740a57611ec935db766a70c44a1 is the last modified, so it is the correct directory
         expectedPath = table4New.getAbsolutePath()
                        + "/snapshots/this_is_a_valid_snapshot_name_i_❤_u/data.db";


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org