Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,14 @@ public static JsonIdentifierExpression of(final String columnName, final String.
}

public static JsonIdentifierExpression of(final String columnName, final List<String> jsonPath) {
// Validate column name to prevent SQL injection
BasicPostgresSecurityValidator.getDefault().validateIdentifier(columnName);

// Validate each element in the JSON path
if (jsonPath == null || jsonPath.isEmpty()) {
throw new IllegalArgumentException("JSON path cannot be null or empty");
}

// Validate JSON path to prevent SQL injection
BasicPostgresSecurityValidator.getDefault().validateJsonPath(jsonPath);

// Create unmodifiable defensive copy using List.copyOf (Java 10+)
// If already unmodifiable, returns the same instance
List<String> unmodifiablePath = List.copyOf(jsonPath);

// Construct full name for compatibility: "customAttr.myAttribute"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public FieldToPgColumn transform(
public FieldToPgColumn visit(IdentifierExpression expression) {
String fieldName = expression.getName();

// Validate identifier to prevent SQL injection (defense in depth)
BasicPostgresSecurityValidator.getDefault().validateIdentifier(fieldName);

// Check if this field has been unnested (e.g., "tags" -> "tags_unnested")
Expand All @@ -50,9 +49,7 @@ public FieldToPgColumn visit(IdentifierExpression expression) {
*/
@Override
public FieldToPgColumn visit(JsonIdentifierExpression expression) {
// Validate column name and JSON path to prevent SQL injection (defense in depth)
// This is redundant with validation in JsonIdentifierExpression.of(), but provides
// an additional security layer in case the expression is constructed through other means

BasicPostgresSecurityValidator.getDefault().validateIdentifier(expression.getColumnName());
BasicPostgresSecurityValidator.getDefault().validateJsonPath(expression.getJsonPath());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,25 @@ public class BasicPostgresSecurityValidator implements PostgresSecurityValidator
/**
* Default pattern for PostgreSQL column/table identifiers.
*
* <p>Pattern: {@code ^[a-zA-Z_][a-zA-Z0-9_]*$}
* <p>Pattern: {@code ^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$}
*
* <p><b>Allowed:</b>
*
* <ul>
* <li>Must start with: letter (a-z, A-Z) or underscore (_)
* <li>Can contain: letters (a-z, A-Z), digits (0-9), underscores (_)
* <li>Examples: {@code "myColumn"}, {@code "user_id"}, {@code "_internal"}, {@code "TABLE1"}
* <li>Can contain: letters (a-z, A-Z), digits (0-9), underscores (_), dots (.) for nested field
* notation
* <li>Examples: {@code "myColumn"}, {@code "user_id"}, {@code "_internal"}, {@code "TABLE1"},
* {@code "field.name"}, {@code "nested.field.name"}
* </ul>
*
* <p><b>Not allowed:</b>
*
* <ul>
* <li>Starting with numbers: {@code "123column"}
* <li>Special characters: {@code "col-name"}, {@code "field.name"}
* <li>Starting or ending with dots: {@code ".field"}, {@code "field."}
* <li>Consecutive dots: {@code "field..name"}
* <li>Special characters: {@code "col-name"}
* <li>Spaces: {@code "my column"}, {@code "field OR 1=1"}
* <li>Quotes: {@code "field\"name"}, {@code "field'name"}
* <li>Semicolons: {@code "col;DROP"}
Expand All @@ -38,7 +42,8 @@ public class BasicPostgresSecurityValidator implements PostgresSecurityValidator
* href="https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS">PostgreSQL
* Documentation</a>
*/
private static final String DEFAULT_IDENTIFIER_PATTERN = "^[a-zA-Z_][a-zA-Z0-9_]*$";
private static final String DEFAULT_IDENTIFIER_PATTERN =
"^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$";

/**
* Default pattern for JSON field names within JSONB columns.
Expand Down Expand Up @@ -126,7 +131,7 @@ public void validateIdentifier(String identifier) {
throw new SecurityException(
String.format(
"Identifier '%s' is invalid. Must start with a letter or underscore, "
+ "and contain only letters, numbers, and underscores.",
+ "and contain only letters, numbers, underscores, and dots (for proper dot notation).",
identifier));
}
}
Expand Down
Loading
Loading