From d68003c7b39489567846a4c255b3cfef8ad8dd33 Mon Sep 17 00:00:00 2001 From: lekhrocks Date: Fri, 7 Aug 2026 18:25:56 +0530 Subject: [PATCH] security(connector): return validated identifiers to break JDBC taint edge requireIdentifier now returns the validated value and each metadata method reassigns its schema/table parameter from it, so the value passed to JDBC metadata calls is the validator's output rather than the raw user input. This makes the sanitization explicit to static taint analysis (the previous void-guard wasn't visible to it) while preserving the same rejection rules. --- .../AbstractJdbcMetadataConnector.java | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/syncflow-connectors/src/main/java/com/syncflow/connector/metadata/AbstractJdbcMetadataConnector.java b/syncflow-connectors/src/main/java/com/syncflow/connector/metadata/AbstractJdbcMetadataConnector.java index 6fa5d10..2341620 100644 --- a/syncflow-connectors/src/main/java/com/syncflow/connector/metadata/AbstractJdbcMetadataConnector.java +++ b/syncflow-connectors/src/main/java/com/syncflow/connector/metadata/AbstractJdbcMetadataConnector.java @@ -104,7 +104,7 @@ public List discoverSchemas(ConnectorContext ctx) { @Override public List fetchTables(ConnectorContext ctx, String schema) { - requireIdentifier(schema, "schema"); + schema = requireIdentifier(schema, "schema"); ensureConnected(ctx); var list = new ArrayList(); try { @@ -126,8 +126,8 @@ schema, getStringOrNull(rs, "REMARKS"), @Override public List fetchColumns(ConnectorContext ctx, String schema, String table) { - requireIdentifier(schema, "schema"); - requireIdentifier(table, "table"); + schema = requireIdentifier(schema, "schema"); + table = requireIdentifier(table, "table"); ensureConnected(ctx); var cols = new ArrayList(); try { @@ -158,8 +158,8 @@ public List fetchColumns(ConnectorContext ctx, String schema, St @Override public List fetchIndexes(ConnectorContext ctx, String schema, String table) { - requireIdentifier(schema, "schema"); - requireIdentifier(table, "table"); + schema = requireIdentifier(schema, "schema"); + table = requireIdentifier(table, "table"); ensureConnected(ctx); var map = new LinkedHashMap(); try { @@ -184,8 +184,8 @@ public List fetchIndexes(ConnectorContext ctx, String schema, Str @Override public PrimaryKeyMetadata fetchPrimaryKey(ConnectorContext ctx, String schema, String table) { - requireIdentifier(schema, "schema"); - requireIdentifier(table, "table"); + schema = requireIdentifier(schema, "schema"); + table = requireIdentifier(table, "table"); ensureConnected(ctx); var cols = new ArrayList(); String name = null; @@ -205,8 +205,8 @@ public PrimaryKeyMetadata fetchPrimaryKey(ConnectorContext ctx, String schema, S @Override public List fetchForeignKeys(ConnectorContext ctx, String schema, String table) { - requireIdentifier(schema, "schema"); - requireIdentifier(table, "table"); + schema = requireIdentifier(schema, "schema"); + table = requireIdentifier(table, "table"); ensureConnected(ctx); var map = new LinkedHashMap(); try { @@ -272,16 +272,20 @@ protected Set loadFkColumnNames(ConnectorContext ctx, String schema, Str } /** - * Reject caller-supplied schema/table values that aren't plain SQL - * identifiers. These reach JDBC metadata calls and connector queries, so a - * value like `users; DROP TABLE x` must never be passed through. + * Validate a caller-supplied schema/table value as a plain SQL identifier + * and return it. These reach JDBC metadata calls and connector queries, so + * a value like `users; DROP TABLE x` must never be passed through. + * + * Returning the value keeps the taint flow explicit: the value handed to + * the JDBC call is the validator's output, not the raw user input. */ - protected void requireIdentifier(String value, String label) { + protected String requireIdentifier(String value, String label) { if (value == null || value.isBlank() || !value.matches("[A-Za-z_][A-Za-z0-9_$]*")) { throw new IllegalArgumentException( "Invalid " + label + " identifier: '" + value + "'"); } + return value; } protected String getStringOrNull(ResultSet rs, String col) throws SQLException {