Skip to content

Commit 704c275

Browse files
authored
Allow "." (dot) in identifier names in BasicPostgresSecurityValidator (#245)
1 parent 5c2ddf5 commit 704c275

File tree

4 files changed

+71
-63
lines changed

4 files changed

+71
-63
lines changed

document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/JsonIdentifierExpression.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,14 @@ public static JsonIdentifierExpression of(final String columnName, final String.
2929
}
3030

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

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

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

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

4742
// Construct full name for compatibility: "customAttr.myAttribute"

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/FlatPostgresFieldTransformer.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ public FieldToPgColumn transform(
3232
public FieldToPgColumn visit(IdentifierExpression expression) {
3333
String fieldName = expression.getName();
3434

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

3837
// Check if this field has been unnested (e.g., "tags" -> "tags_unnested")
@@ -50,9 +49,7 @@ public FieldToPgColumn visit(IdentifierExpression expression) {
5049
*/
5150
@Override
5251
public FieldToPgColumn visit(JsonIdentifierExpression expression) {
53-
// Validate column name and JSON path to prevent SQL injection (defense in depth)
54-
// This is redundant with validation in JsonIdentifierExpression.of(), but provides
55-
// an additional security layer in case the expression is constructed through other means
52+
5653
BasicPostgresSecurityValidator.getDefault().validateIdentifier(expression.getColumnName());
5754
BasicPostgresSecurityValidator.getDefault().validateJsonPath(expression.getJsonPath());
5855

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/BasicPostgresSecurityValidator.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,25 @@ public class BasicPostgresSecurityValidator implements PostgresSecurityValidator
1414
/**
1515
* Default pattern for PostgreSQL column/table identifiers.
1616
*
17-
* <p>Pattern: {@code ^[a-zA-Z_][a-zA-Z0-9_]*$}
17+
* <p>Pattern: {@code ^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$}
1818
*
1919
* <p><b>Allowed:</b>
2020
*
2121
* <ul>
2222
* <li>Must start with: letter (a-z, A-Z) or underscore (_)
23-
* <li>Can contain: letters (a-z, A-Z), digits (0-9), underscores (_)
24-
* <li>Examples: {@code "myColumn"}, {@code "user_id"}, {@code "_internal"}, {@code "TABLE1"}
23+
* <li>Can contain: letters (a-z, A-Z), digits (0-9), underscores (_), dots (.) for nested field
24+
* notation
25+
* <li>Examples: {@code "myColumn"}, {@code "user_id"}, {@code "_internal"}, {@code "TABLE1"},
26+
* {@code "field.name"}, {@code "nested.field.name"}
2527
* </ul>
2628
*
2729
* <p><b>Not allowed:</b>
2830
*
2931
* <ul>
3032
* <li>Starting with numbers: {@code "123column"}
31-
* <li>Special characters: {@code "col-name"}, {@code "field.name"}
33+
* <li>Starting or ending with dots: {@code ".field"}, {@code "field."}
34+
* <li>Consecutive dots: {@code "field..name"}
35+
* <li>Special characters: {@code "col-name"}
3236
* <li>Spaces: {@code "my column"}, {@code "field OR 1=1"}
3337
* <li>Quotes: {@code "field\"name"}, {@code "field'name"}
3438
* <li>Semicolons: {@code "col;DROP"}
@@ -38,7 +42,8 @@ public class BasicPostgresSecurityValidator implements PostgresSecurityValidator
3842
* href="https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS">PostgreSQL
3943
* Documentation</a>
4044
*/
41-
private static final String DEFAULT_IDENTIFIER_PATTERN = "^[a-zA-Z_][a-zA-Z0-9_]*$";
45+
private static final String DEFAULT_IDENTIFIER_PATTERN =
46+
"^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$";
4247

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

0 commit comments

Comments
 (0)