Skip to content

Commit e9fbf64

Browse files
fix: Implement proper PostgreSQL connection parameter escaping and validation
- Replace improper backslash escaping with PostgreSQL libpq standard (double single quotes, double backslashes) - Add comprehensive validation for connection parameters to prevent injection attacks - Validate against null bytes and dangerous control characters - Fix extra options handling in gorm plugin to avoid URL escaping for PostgreSQL - Add parameter key validation for extra options - Ensure all connection parameters (hostname, username, password, database) are properly escaped and validated Follows PostgreSQL libpq connection string standards for security and compatibility. Co-authored-by: Anguel <modelorona@users.noreply.github.com>
1 parent 6813597 commit e9fbf64

File tree

2 files changed

+67
-8
lines changed

2 files changed

+67
-8
lines changed

core/src/plugins/gorm/db.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,12 @@ func (p *GormPlugin) ParseConnectionConfig(config *engine.PluginConfig) (*Connec
148148
case portKey, parseTimeKey, locKey, allowClearTextPasswordsKey, sslModeKey, httpProtocolKey, readOnlyKey, debugKey, connectionTimeoutKey:
149149
continue
150150
default:
151-
params[record.Key] = url.QueryEscape(record.Value) // todo: this may break for postgres
151+
// PostgreSQL uses libpq connection string format, not URL query parameters
152+
if p.Type == engine.DatabaseType_Postgres {
153+
params[record.Key] = record.Value // Raw value, PostgreSQL plugin will handle escaping
154+
} else {
155+
params[record.Key] = url.QueryEscape(record.Value)
156+
}
152157
}
153158
}
154159
input.ExtraOptions = params

core/src/plugins/postgres/db.go

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,40 @@ import (
2525
"gorm.io/gorm"
2626
)
2727

28-
func escape(x string) string {
29-
return strings.ReplaceAll(x, "'", "\\'")
28+
func escapeConnectionParam(x string) string {
29+
// PostgreSQL libpq connection string escaping rules:
30+
// 1. Single quotes must be doubled: ' -> ''
31+
// 2. Backslashes must be doubled: \ -> \\
32+
x = strings.ReplaceAll(x, "\\", "\\\\")
33+
x = strings.ReplaceAll(x, "'", "''")
34+
return x
35+
}
36+
37+
func validateConnectionParam(param, paramName string) error {
38+
// Check for null bytes which can terminate the connection string
39+
if strings.Contains(param, "\x00") {
40+
return fmt.Errorf("invalid %s: contains null byte", paramName)
41+
}
42+
43+
// Check for potentially dangerous control characters
44+
for _, char := range param {
45+
if char < 32 && char != '\t' && char != '\n' && char != '\r' {
46+
return fmt.Errorf("invalid %s: contains control character", paramName)
47+
}
48+
}
49+
50+
return nil
51+
}
52+
53+
func isValidConnectionParamKey(key string) bool {
54+
// Connection parameter keys should only contain alphanumeric characters and underscores
55+
for _, char := range key {
56+
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
57+
(char >= '0' && char <= '9') || char == '_') {
58+
return false
59+
}
60+
}
61+
return len(key) > 0
3062
}
3163

3264
func (p *PostgresPlugin) DB(config *engine.PluginConfig) (*gorm.DB, error) {
@@ -35,15 +67,37 @@ func (p *PostgresPlugin) DB(config *engine.PluginConfig) (*gorm.DB, error) {
3567
return nil, err
3668
}
3769

38-
host := escape(connectionInput.Hostname)
39-
username := escape(connectionInput.Username)
40-
password := escape(connectionInput.Password)
41-
database := escape(connectionInput.Database)
70+
// Validate connection parameters for security
71+
if err := validateConnectionParam(connectionInput.Hostname, "hostname"); err != nil {
72+
return nil, err
73+
}
74+
if err := validateConnectionParam(connectionInput.Username, "username"); err != nil {
75+
return nil, err
76+
}
77+
if err := validateConnectionParam(connectionInput.Password, "password"); err != nil {
78+
return nil, err
79+
}
80+
if err := validateConnectionParam(connectionInput.Database, "database"); err != nil {
81+
return nil, err
82+
}
83+
84+
host := escapeConnectionParam(connectionInput.Hostname)
85+
username := escapeConnectionParam(connectionInput.Username)
86+
password := escapeConnectionParam(connectionInput.Password)
87+
database := escapeConnectionParam(connectionInput.Database)
4288

4389
params := strings.Builder{}
4490
if connectionInput.ExtraOptions != nil {
4591
for key, value := range connectionInput.ExtraOptions {
46-
params.WriteString(fmt.Sprintf("%v='%v' ", strings.ToLower(key), escape(value)))
92+
// Validate extra option values
93+
if err := validateConnectionParam(value, fmt.Sprintf("extra option '%s'", key)); err != nil {
94+
return nil, err
95+
}
96+
// Validate key names (should only contain alphanumeric characters and underscores)
97+
if !isValidConnectionParamKey(key) {
98+
return nil, fmt.Errorf("invalid extra option key '%s': only alphanumeric characters and underscores allowed", key)
99+
}
100+
params.WriteString(fmt.Sprintf("%v='%v' ", strings.ToLower(key), escapeConnectionParam(value)))
47101
}
48102
}
49103

0 commit comments

Comments
 (0)