From 116e7171542d2f1dad8810f00dcfacbe0b809183 Mon Sep 17 00:00:00 2001
From: Muneeb Ullah Khan
<100969065+MuneebUllahKhan222@users.noreply.github.com>
Date: Fri, 16 Jan 2026 15:18:40 +0500
Subject: [PATCH] =?UTF-8?q?[INS-243]=20Fix=20jdbc=20detector=20detecting?=
=?UTF-8?q?=20incomplete=20connection=20string=20and=20fixed=20invalid?=
=?UTF-8?q?=E2=80=A6=20(#4636)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Fix jdbc detector detecting incomplete conn string and fixed invalid parsing of sql server string
* [INS-165] Jdbc detector integration tests fix (#4637)
* [INS-165] Fix Jdbc tests and updated the deprecated code
---
pkg/detectors/jdbc/jdbc.go | 3 +-
pkg/detectors/jdbc/jdbc_integration_test.go | 24 +++++++++----
pkg/detectors/jdbc/jdbc_test.go | 12 +++++--
pkg/detectors/jdbc/mysql_integration_test.go | 3 +-
.../jdbc/postgres_integration_test.go | 9 ++---
pkg/detectors/jdbc/sqlserver.go | 5 +++
.../jdbc/sqlserver_integration_test.go | 11 +++---
pkg/detectors/jdbc/sqlserver_test.go | 36 +++++++++++++++++++
8 files changed, 81 insertions(+), 22 deletions(-)
diff --git a/pkg/detectors/jdbc/jdbc.go b/pkg/detectors/jdbc/jdbc.go
index a424d7072..7a27c8320 100644
--- a/pkg/detectors/jdbc/jdbc.go
+++ b/pkg/detectors/jdbc/jdbc.go
@@ -50,7 +50,8 @@ var _ detectors.Detector = (*Scanner)(nil)
var _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)
var (
- keyPat = regexp.MustCompile(`(?i)jdbc:[\w]{3,10}:[^\s"'<>,(){}[\]&]{10,512}`)
+ // Matches typical JDBC connection strings amd ingores any special character at the end
+ keyPat = regexp.MustCompile(`(?i)jdbc:[\w]{3,10}:[^\s"'<>,{}[\]]{10,511}[A-Za-z0-9]`)
)
// Keywords are used for efficiently pre-filtering chunks.
diff --git a/pkg/detectors/jdbc/jdbc_integration_test.go b/pkg/detectors/jdbc/jdbc_integration_test.go
index c525b1cbe..f1a390029 100644
--- a/pkg/detectors/jdbc/jdbc_integration_test.go
+++ b/pkg/detectors/jdbc/jdbc_integration_test.go
@@ -29,8 +29,8 @@ func TestJdbcVerified(t *testing.T) {
postgresUser := gofakeit.Username()
postgresPass := gofakeit.Password(true, true, true, false, false, 10)
postgresDB := gofakeit.Word()
- postgresContainer, err := postgres.RunContainer(ctx,
- testcontainers.WithImage("postgres:13-alpine"),
+ postgresContainer, err := postgres.Run(ctx,
+ "postgres:13-alpine",
postgres.WithDatabase(postgresDB),
postgres.WithUsername(postgresUser),
postgres.WithPassword(postgresPass),
@@ -56,8 +56,8 @@ func TestJdbcVerified(t *testing.T) {
mysqlUser := gofakeit.Username()
mysqlPass := gofakeit.Password(true, true, true, false, false, 10)
mysqlDatabase := gofakeit.Word()
- mysqlC, err := mysql.RunContainer(ctx,
- mysql.WithDatabase(mysqlDatabase),
+ mysqlC, err := mysql.Run(ctx,
+ "mysql:8.0.36",
mysql.WithUsername(mysqlUser),
mysql.WithPassword(mysqlPass),
)
@@ -79,8 +79,8 @@ func TestJdbcVerified(t *testing.T) {
sqlServerPass := gofakeit.Password(true, true, true, false, false, 10)
sqlServerDatabase := "master"
- mssqlContainer, err := mssql.RunContainer(ctx,
- testcontainers.WithImage("mcr.microsoft.com/azure-sql-edge"),
+ mssqlContainer, err := mssql.Run(ctx,
+ "mcr.microsoft.com/azure-sql-edge",
mssql.WithAcceptEULA(),
mssql.WithPassword(sqlServerPass),
)
@@ -125,6 +125,10 @@ func TestJdbcVerified(t *testing.T) {
Verified: true,
Redacted: fmt.Sprintf("jdbc:postgresql://%s:%s/%s?sslmode=disable&password=%s&user=%s",
postgresHost, postgresPort.Port(), postgresDB, strings.Repeat("*", len(postgresPass)), postgresUser),
+ AnalysisInfo: map[string]string{
+ "connection_string": fmt.Sprintf("jdbc:postgresql://%s:%s/%s?sslmode=disable&password=%s&user=%s",
+ postgresHost, postgresPort.Port(), postgresDB, postgresPass, postgresUser),
+ },
},
},
wantErr: false,
@@ -143,6 +147,10 @@ func TestJdbcVerified(t *testing.T) {
Verified: true,
Redacted: fmt.Sprintf(`jdbc:mysql://%s:%s@tcp(%s:%s)/%s`,
mysqlUser, strings.Repeat("*", len(mysqlPass)), mysqlHost, mysqlPort.Port(), mysqlDatabase),
+ AnalysisInfo: map[string]string{
+ "connection_string": fmt.Sprintf(`jdbc:mysql://%s:%s@tcp(%s:%s)/%s`,
+ mysqlUser, mysqlPass, mysqlHost, mysqlPort.Port(), mysqlDatabase),
+ },
},
},
wantErr: false,
@@ -161,6 +169,10 @@ func TestJdbcVerified(t *testing.T) {
Verified: true,
Redacted: fmt.Sprintf("jdbc:sqlserver://odbc:server=%s;port=%s;database=%s;password=%s",
sqlServerHost, sqlServerPort.Port(), sqlServerDatabase, strings.Repeat("*", len(sqlServerPass))),
+ AnalysisInfo: map[string]string{
+ "connection_string": fmt.Sprintf("jdbc:sqlserver://odbc:server=%s;port=%s;database=%s;password=%s",
+ sqlServerHost, sqlServerPort.Port(), sqlServerDatabase, sqlServerPass),
+ },
},
},
wantErr: false,
diff --git a/pkg/detectors/jdbc/jdbc_test.go b/pkg/detectors/jdbc/jdbc_test.go
index 14649c7ff..2bb3c642f 100644
--- a/pkg/detectors/jdbc/jdbc_test.go
+++ b/pkg/detectors/jdbc/jdbc_test.go
@@ -40,6 +40,8 @@ func TestJdbc_Pattern(t *testing.T) {
jdbc:mysql:localhost:3306/mydatabase
jdbc:sqlserver://x.x.x.x:1433;databaseName=MY-DB;user=MY-USER;password=MY-PASSWORD;encrypt=false
jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks
+ (jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb)
+ jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser&
$ProjectFileDir$
@@ -50,6 +52,8 @@ func TestJdbc_Pattern(t *testing.T) {
"jdbc:mysql:localhost:3306/mydatabase",
"jdbc:sqlserver://x.x.x.x:1433;databaseName=MY-DB;user=MY-USER;password=MY-PASSWORD;encrypt=false",
"jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks",
+ "jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb",
+ "jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser",
},
},
{
@@ -64,8 +68,10 @@ func TestJdbc_Pattern(t *testing.T) {
"jdbc:oracle:thin:@host:1521:db",
"jdbc:mysql://host:3306/db,other_param",
"jdbc:db2://host:50000/db?param=1"
- ]
- }`,
+ "jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser"
+ "jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb"
+ ]
+ }`,
want: []string{
"jdbc:postgresql://localhost:5432/mydb",
"jdbc:mysql://user:pass@host:3306/db?param=1",
@@ -73,6 +79,8 @@ func TestJdbc_Pattern(t *testing.T) {
"jdbc:oracle:thin:@host:1521:db",
"jdbc:mysql://host:3306/db",
"jdbc:db2://host:50000/db?param=1",
+ "jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser",
+ "jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb",
},
},
{
diff --git a/pkg/detectors/jdbc/mysql_integration_test.go b/pkg/detectors/jdbc/mysql_integration_test.go
index 602065c4c..1861c3e5d 100644
--- a/pkg/detectors/jdbc/mysql_integration_test.go
+++ b/pkg/detectors/jdbc/mysql_integration_test.go
@@ -23,7 +23,8 @@ func TestMySQL(t *testing.T) {
ctx := context.Background()
- mysqlC, err := mysql.RunContainer(ctx,
+ mysqlC, err := mysql.Run(ctx,
+ "mysql:8.0.36",
mysql.WithDatabase(mysqlDatabase),
mysql.WithUsername(mysqlUser),
mysql.WithPassword(mysqlPass),
diff --git a/pkg/detectors/jdbc/postgres_integration_test.go b/pkg/detectors/jdbc/postgres_integration_test.go
index 0aeb20889..f3fcb57be 100644
--- a/pkg/detectors/jdbc/postgres_integration_test.go
+++ b/pkg/detectors/jdbc/postgres_integration_test.go
@@ -6,7 +6,6 @@ package jdbc
import (
"context"
"fmt"
- "log"
"testing"
"time"
@@ -34,8 +33,8 @@ func TestPostgres(t *testing.T) {
t.Log("dbName: ", dbName)
ctx := context.Background()
- postgresContainer, err := postgres.RunContainer(ctx,
- testcontainers.WithImage("postgres:13-alpine"),
+ postgresContainer, err := postgres.Run(ctx,
+ "postgres:13-alpine",
postgres.WithDatabase(dbName),
postgres.WithUsername(user),
postgres.WithPassword(pass),
@@ -56,10 +55,6 @@ func TestPostgres(t *testing.T) {
if err != nil {
t.Fatal(err)
}
-
- if err != nil {
- log.Fatalf("failed to start container: %s", err)
- }
defer postgresContainer.Terminate(ctx)
tests := []struct {
diff --git a/pkg/detectors/jdbc/sqlserver.go b/pkg/detectors/jdbc/sqlserver.go
index 4633e92a0..fc6ac1a91 100644
--- a/pkg/detectors/jdbc/sqlserver.go
+++ b/pkg/detectors/jdbc/sqlserver.go
@@ -73,6 +73,11 @@ func parseSqlServer(ctx logContext.Context, subname string) (JDBC, error) {
continue
}
+ // incase there is a bridge between jdbc and some driver like "odbc", and conn string looks like this odbc:server
+ if split := strings.Split(key, ":"); len(split) > 1 {
+ key = split[1]
+ }
+
switch strings.ToLower(key) {
case "password", "spring.datasource.password", "pwd":
password = value
diff --git a/pkg/detectors/jdbc/sqlserver_integration_test.go b/pkg/detectors/jdbc/sqlserver_integration_test.go
index 684445ed9..d8aa37b57 100644
--- a/pkg/detectors/jdbc/sqlserver_integration_test.go
+++ b/pkg/detectors/jdbc/sqlserver_integration_test.go
@@ -10,8 +10,8 @@ import (
"github.com/brianvoe/gofakeit/v7"
"github.com/stretchr/testify/assert"
- "github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/mssql"
+ logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
func TestSqlServer(t *testing.T) {
@@ -21,8 +21,8 @@ func TestSqlServer(t *testing.T) {
sqlServerPass := gofakeit.Password(true, true, true, false, false, 10)
sqlServerDB := "master"
- mssqlContainer, err := mssql.RunContainer(ctx,
- testcontainers.WithImage("mcr.microsoft.com/azure-sql-edge"),
+ mssqlContainer, err := mssql.Run(ctx,
+ "mcr.microsoft.com/azure-sql-edge",
mssql.WithAcceptEULA(),
mssql.WithPassword(sqlServerPass),
)
@@ -61,7 +61,7 @@ func TestSqlServer(t *testing.T) {
want: result{pingOk: true, pingDeterminate: true},
},
{
- input: "//server=badhost;user id=sa;database=master;password=",
+ input: fmt.Sprintf("//server=badhost;user id=sa;database=master;password=%s", sqlServerPass),
want: result{pingOk: false, pingDeterminate: false},
},
{
@@ -81,7 +81,8 @@ func TestSqlServer(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
- j, err := parseSqlServer(tt.input)
+ ctx := logContext.AddLogger(context.Background())
+ j, err := ParseSqlServer(ctx, tt.input)
if err != nil {
got := result{parseErr: true}
diff --git a/pkg/detectors/jdbc/sqlserver_test.go b/pkg/detectors/jdbc/sqlserver_test.go
index 32bfbae23..d2f09447d 100644
--- a/pkg/detectors/jdbc/sqlserver_test.go
+++ b/pkg/detectors/jdbc/sqlserver_test.go
@@ -118,6 +118,42 @@ func TestParseSqlServerUserIgnoredBug2(t *testing.T) {
}
}
+func TestParseSqlServerWithJdbcAndOdbcBridgeString(t *testing.T) {
+ subname := "//odbc:server=localhost;port=1433;database=testdb;password=testpassword"
+
+ wantHost := "localhost"
+ wantPort := "1433"
+ wantPassword := "testpassword"
+ wantDatabase := "testdb"
+
+ ctx := logContext.AddLogger(context.Background())
+
+ j, err := parseSqlServer(ctx, subname)
+ if err != nil {
+ t.Fatalf("parseSqlServer() error = %v", err)
+ }
+
+ if j == nil {
+ t.Fatalf("parseSqlServer() returned nil, expected valid connection.")
+ }
+
+ sqlServerConn, ok := j.(*SqlServerJDBC)
+ if !ok {
+ t.Fatalf("parseSqlServer() returned unexpected type %T, expected *SqlServerJDBC", j)
+ }
+
+ if sqlServerConn.Host != wantHost+":"+wantPort {
+ t.Errorf("Host mismatch. Got: %s, Want: %s", sqlServerConn.Host, wantHost+":"+wantPort)
+ }
+
+ if sqlServerConn.Password != wantPassword {
+ t.Errorf("Password mismatch. Got: %s, Want: %s", sqlServerConn.Password, wantPassword)
+ }
+
+ if sqlServerConn.Database != wantDatabase {
+ t.Errorf("Database mismatch. Got: %s, Want: %s", sqlServerConn.Database, wantDatabase)
+ }
+}
func TestSQLServerHandler_ParseJDBCURL(t *testing.T) {
tests := []struct {
name string