Skip to content

Commit 09ed555

Browse files
fix: Sanitize error messages to prevent information disclosure (fixes #34) (#66)
Addresses CWE-209 by removing internal implementation details from user-facing error messages throughout the codebase. Changes: - Created errors.go with ConversionError type for secure error handling - User-facing messages are generic and don't leak schema/type information - Internal details preserved for logging (WithLogger option) - Updated error messages in: * cel2sql.go: ~20 locations (expression types, operators, comprehensions) * pg/provider.go: 2 locations (enum and type errors) * comprehensions.go: 1 location (pattern recognition) * timestamps.go: 4 locations (operation and duration errors) - Added comprehensive test suite in error_messages_test.go - All tests pass with 90%+ coverage - Linting clean (revive, unparam) Before: "unsupported expr: &{ExprKind:0x...}" After: "unsupported expression type" Internal details still available via structured logging for debugging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 77d0751 commit 09ed555

File tree

6 files changed

+504
-47
lines changed

6 files changed

+504
-47
lines changed

cel2sql.go

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ func (con *converter) visit(expr *exprpb.Expr) error {
243243
case *exprpb.Expr_StructExpr:
244244
return con.visitStruct(expr)
245245
}
246-
return fmt.Errorf("unsupported expr: %v", expr)
246+
return newConversionErrorf(errMsgUnsupportedExpression, "expr type: %T, id: %d", expr.ExprKind, expr.Id)
247247
}
248248

249249
// isFieldJSON checks if a field in a table is a JSON/JSONB type using schema information
@@ -459,7 +459,7 @@ func (con *converter) visitCallBinary(expr *exprpb.Expr) error {
459459
} else if op, found := operators.FindReverseBinaryOperator(fun); found {
460460
operator = op
461461
} else {
462-
return fmt.Errorf("cannot unmangle operator: %s", fun)
462+
return newConversionErrorf(errMsgInvalidOperator, "binary operator: %s", fun)
463463
}
464464

465465
con.logger.LogAttrs(context.Background(), slog.LevelDebug,
@@ -840,7 +840,7 @@ func (con *converter) visitCallFunc(expr *exprpb.Expr) error {
840840
con.str.WriteString(", 1)")
841841
return nil
842842
default:
843-
return fmt.Errorf("unsupported type: %v", argType)
843+
return newConversionErrorf(errMsgUnsupportedType, "size() argument type: %s", argType.String())
844844
}
845845
} else {
846846
sqlFun = strings.ToUpper(fun)
@@ -926,7 +926,7 @@ func (con *converter) visitCallUnary(expr *exprpb.Expr) error {
926926
} else if op, found := operators.FindReverse(fun); found {
927927
operator = op
928928
} else {
929-
return fmt.Errorf("cannot unmangle operator: %s", fun)
929+
return newConversionErrorf(errMsgInvalidOperator, "unary operator: %s", fun)
930930
}
931931
con.str.WriteString(operator)
932932
nested := isComplexOperator(args[0])
@@ -962,7 +962,7 @@ func (con *converter) visitComprehension(expr *exprpb.Expr) error {
962962
case ComprehensionTransformMapEntry:
963963
return con.visitTransformMapEntryComprehension(expr, info)
964964
default:
965-
return fmt.Errorf("unsupported comprehension type: %v", info.Type)
965+
return newConversionErrorf(errMsgUnsupportedComprehension, "comprehension type: %s", info.Type.String())
966966
}
967967
}
968968

@@ -975,7 +975,7 @@ func (con *converter) visitAllComprehension(expr *exprpb.Expr, info *Comprehensi
975975

976976
comprehension := expr.GetComprehensionExpr()
977977
if comprehension == nil {
978-
return errors.New("expression is not a comprehension")
978+
return newConversionError(errMsgUnsupportedComprehension, "expression is not a comprehension (ALL)")
979979
}
980980

981981
iterRange := comprehension.GetIterRange()
@@ -988,13 +988,13 @@ func (con *converter) visitAllComprehension(expr *exprpb.Expr, info *Comprehensi
988988
con.str.WriteString(jsonFunc)
989989
con.str.WriteString("(")
990990
if err := con.visit(iterRange); err != nil {
991-
return fmt.Errorf("failed to visit iter range in ALL comprehension: %w", err)
991+
return wrapConversionError(err, "visiting iter range in ALL comprehension")
992992
}
993993
con.str.WriteString(")")
994994
} else {
995995
con.str.WriteString("UNNEST(")
996996
if err := con.visit(iterRange); err != nil {
997-
return fmt.Errorf("failed to visit iter range in ALL comprehension: %w", err)
997+
return wrapConversionError(err, "visiting iter range in ALL comprehension")
998998
}
999999
con.str.WriteString(")")
10001000
}
@@ -1007,14 +1007,14 @@ func (con *converter) visitAllComprehension(expr *exprpb.Expr, info *Comprehensi
10071007
// Add null checks for JSON arrays
10081008
if isJSONArray {
10091009
if err := con.visit(iterRange); err != nil {
1010-
return fmt.Errorf("failed to visit iter range for null check: %w", err)
1010+
return wrapConversionError(err,"visiting iter range for null check")
10111011
}
10121012
con.str.WriteString(" IS NOT NULL AND ")
10131013
typeofFunc := con.getJSONTypeofFunction(iterRange)
10141014
con.str.WriteString(typeofFunc)
10151015
con.str.WriteString("(")
10161016
if err := con.visit(iterRange); err != nil {
1017-
return fmt.Errorf("failed to visit iter range for type check: %w", err)
1017+
return wrapConversionError(err,"visiting iter range for type check")
10181018
}
10191019
con.str.WriteString(") = 'array'")
10201020

@@ -1026,7 +1026,7 @@ func (con *converter) visitAllComprehension(expr *exprpb.Expr, info *Comprehensi
10261026
if info.Predicate != nil {
10271027
con.str.WriteString("NOT (")
10281028
if err := con.visit(info.Predicate); err != nil {
1029-
return fmt.Errorf("failed to visit predicate in ALL comprehension: %w", err)
1029+
return wrapConversionError(err,"visiting predicate in ALL comprehension")
10301030
}
10311031
con.str.WriteString(")")
10321032
}
@@ -1042,7 +1042,7 @@ func (con *converter) visitExistsComprehension(expr *exprpb.Expr, info *Comprehe
10421042

10431043
comprehension := expr.GetComprehensionExpr()
10441044
if comprehension == nil {
1045-
return errors.New("expression is not a comprehension")
1045+
return newConversionError(errMsgUnsupportedComprehension, "expression is not a comprehension (EXISTS)")
10461046
}
10471047

10481048
iterRange := comprehension.GetIterRange()
@@ -1055,13 +1055,13 @@ func (con *converter) visitExistsComprehension(expr *exprpb.Expr, info *Comprehe
10551055
con.str.WriteString(jsonFunc)
10561056
con.str.WriteString("(")
10571057
if err := con.visit(iterRange); err != nil {
1058-
return fmt.Errorf("failed to visit iter range in EXISTS comprehension: %w", err)
1058+
return wrapConversionError(err,"visiting iter range in EXISTS comprehension")
10591059
}
10601060
con.str.WriteString(")")
10611061
} else {
10621062
con.str.WriteString("UNNEST(")
10631063
if err := con.visit(iterRange); err != nil {
1064-
return fmt.Errorf("failed to visit iter range in EXISTS comprehension: %w", err)
1064+
return wrapConversionError(err,"visiting iter range in EXISTS comprehension")
10651065
}
10661066
con.str.WriteString(")")
10671067
}
@@ -1074,14 +1074,14 @@ func (con *converter) visitExistsComprehension(expr *exprpb.Expr, info *Comprehe
10741074
// Add null checks for JSON arrays
10751075
if isJSONArray {
10761076
if err := con.visit(iterRange); err != nil {
1077-
return fmt.Errorf("failed to visit iter range for null check: %w", err)
1077+
return wrapConversionError(err,"visiting iter range for null check")
10781078
}
10791079
con.str.WriteString(" IS NOT NULL AND ")
10801080
typeofFunc := con.getJSONTypeofFunction(iterRange)
10811081
con.str.WriteString(typeofFunc)
10821082
con.str.WriteString("(")
10831083
if err := con.visit(iterRange); err != nil {
1084-
return fmt.Errorf("failed to visit iter range for type check: %w", err)
1084+
return wrapConversionError(err,"visiting iter range for type check")
10851085
}
10861086
con.str.WriteString(") = 'array'")
10871087

@@ -1092,7 +1092,7 @@ func (con *converter) visitExistsComprehension(expr *exprpb.Expr, info *Comprehe
10921092

10931093
if info.Predicate != nil {
10941094
if err := con.visit(info.Predicate); err != nil {
1095-
return fmt.Errorf("failed to visit predicate in EXISTS comprehension: %w", err)
1095+
return wrapConversionError(err,"visiting predicate in EXISTS comprehension")
10961096
}
10971097
}
10981098

@@ -1107,7 +1107,7 @@ func (con *converter) visitExistsOneComprehension(expr *exprpb.Expr, info *Compr
11071107

11081108
comprehension := expr.GetComprehensionExpr()
11091109
if comprehension == nil {
1110-
return errors.New("expression is not a comprehension")
1110+
return newConversionError(errMsgUnsupportedComprehension, "expression is not a comprehension (EXISTS_ONE)")
11111111
}
11121112

11131113
iterRange := comprehension.GetIterRange()
@@ -1120,13 +1120,13 @@ func (con *converter) visitExistsOneComprehension(expr *exprpb.Expr, info *Compr
11201120
con.str.WriteString(jsonFunc)
11211121
con.str.WriteString("(")
11221122
if err := con.visit(iterRange); err != nil {
1123-
return fmt.Errorf("failed to visit iter range in EXISTS_ONE comprehension: %w", err)
1123+
return wrapConversionError(err,"visiting iter range in EXISTS_ONE comprehension")
11241124
}
11251125
con.str.WriteString(")")
11261126
} else {
11271127
con.str.WriteString("UNNEST(")
11281128
if err := con.visit(iterRange); err != nil {
1129-
return fmt.Errorf("failed to visit iter range in EXISTS_ONE comprehension: %w", err)
1129+
return wrapConversionError(err,"visiting iter range in EXISTS_ONE comprehension")
11301130
}
11311131
con.str.WriteString(")")
11321132
}
@@ -1139,14 +1139,14 @@ func (con *converter) visitExistsOneComprehension(expr *exprpb.Expr, info *Compr
11391139
// Add null checks for JSON arrays
11401140
if isJSONArray {
11411141
if err := con.visit(iterRange); err != nil {
1142-
return fmt.Errorf("failed to visit iter range for null check: %w", err)
1142+
return wrapConversionError(err,"visiting iter range for null check")
11431143
}
11441144
con.str.WriteString(" IS NOT NULL AND ")
11451145
typeofFunc := con.getJSONTypeofFunction(iterRange)
11461146
con.str.WriteString(typeofFunc)
11471147
con.str.WriteString("(")
11481148
if err := con.visit(iterRange); err != nil {
1149-
return fmt.Errorf("failed to visit iter range for type check: %w", err)
1149+
return wrapConversionError(err,"visiting iter range for type check")
11501150
}
11511151
con.str.WriteString(") = 'array'")
11521152

@@ -1157,7 +1157,7 @@ func (con *converter) visitExistsOneComprehension(expr *exprpb.Expr, info *Compr
11571157

11581158
if info.Predicate != nil {
11591159
if err := con.visit(info.Predicate); err != nil {
1160-
return fmt.Errorf("failed to visit predicate in EXISTS_ONE comprehension: %w", err)
1160+
return wrapConversionError(err,"visiting predicate in EXISTS_ONE comprehension")
11611161
}
11621162
}
11631163

@@ -1172,7 +1172,7 @@ func (con *converter) visitMapComprehension(expr *exprpb.Expr, info *Comprehensi
11721172

11731173
comprehension := expr.GetComprehensionExpr()
11741174
if comprehension == nil {
1175-
return errors.New("expression is not a comprehension")
1175+
return newConversionError(errMsgUnsupportedComprehension, "expression is not a comprehension (MAP)")
11761176
}
11771177

11781178
iterRange := comprehension.GetIterRange()
@@ -1183,7 +1183,7 @@ func (con *converter) visitMapComprehension(expr *exprpb.Expr, info *Comprehensi
11831183
// Visit the transform expression
11841184
if info.Transform != nil {
11851185
if err := con.visit(info.Transform); err != nil {
1186-
return fmt.Errorf("failed to visit transform in MAP comprehension: %w", err)
1186+
return wrapConversionError(err,"visiting transform in MAP comprehension")
11871187
}
11881188
} else {
11891189
// If no transform, just return the variable itself
@@ -1197,13 +1197,13 @@ func (con *converter) visitMapComprehension(expr *exprpb.Expr, info *Comprehensi
11971197
con.str.WriteString(jsonFunc)
11981198
con.str.WriteString("(")
11991199
if err := con.visit(iterRange); err != nil {
1200-
return fmt.Errorf("failed to visit iter range in MAP comprehension: %w", err)
1200+
return wrapConversionError(err,"visiting iter range in MAP comprehension")
12011201
}
12021202
con.str.WriteString(")")
12031203
} else {
12041204
con.str.WriteString("UNNEST(")
12051205
if err := con.visit(iterRange); err != nil {
1206-
return fmt.Errorf("failed to visit iter range in MAP comprehension: %w", err)
1206+
return wrapConversionError(err,"visiting iter range in MAP comprehension")
12071207
}
12081208
con.str.WriteString(")")
12091209
}
@@ -1215,7 +1215,7 @@ func (con *converter) visitMapComprehension(expr *exprpb.Expr, info *Comprehensi
12151215
if info.Filter != nil {
12161216
con.str.WriteString(" WHERE ")
12171217
if err := con.visit(info.Filter); err != nil {
1218-
return fmt.Errorf("failed to visit filter in MAP comprehension: %w", err)
1218+
return wrapConversionError(err,"visiting filter in MAP comprehension")
12191219
}
12201220
}
12211221

@@ -1230,7 +1230,7 @@ func (con *converter) visitFilterComprehension(expr *exprpb.Expr, info *Comprehe
12301230

12311231
comprehension := expr.GetComprehensionExpr()
12321232
if comprehension == nil {
1233-
return errors.New("expression is not a comprehension")
1233+
return newConversionError(errMsgUnsupportedComprehension, "expression is not a comprehension (FILTER)")
12341234
}
12351235

12361236
iterRange := comprehension.GetIterRange()
@@ -1245,13 +1245,13 @@ func (con *converter) visitFilterComprehension(expr *exprpb.Expr, info *Comprehe
12451245
con.str.WriteString(jsonFunc)
12461246
con.str.WriteString("(")
12471247
if err := con.visit(iterRange); err != nil {
1248-
return fmt.Errorf("failed to visit iter range in FILTER comprehension: %w", err)
1248+
return wrapConversionError(err,"visiting iter range in FILTER comprehension")
12491249
}
12501250
con.str.WriteString(")")
12511251
} else {
12521252
con.str.WriteString("UNNEST(")
12531253
if err := con.visit(iterRange); err != nil {
1254-
return fmt.Errorf("failed to visit iter range in FILTER comprehension: %w", err)
1254+
return wrapConversionError(err,"visiting iter range in FILTER comprehension")
12551255
}
12561256
con.str.WriteString(")")
12571257
}
@@ -1262,7 +1262,7 @@ func (con *converter) visitFilterComprehension(expr *exprpb.Expr, info *Comprehe
12621262
if info.Predicate != nil {
12631263
con.str.WriteString(" WHERE ")
12641264
if err := con.visit(info.Predicate); err != nil {
1265-
return fmt.Errorf("failed to visit predicate in FILTER comprehension: %w", err)
1265+
return wrapConversionError(err,"visiting predicate in FILTER comprehension")
12661266
}
12671267
}
12681268

@@ -1276,15 +1276,15 @@ func (con *converter) visitTransformListComprehension(expr *exprpb.Expr, info *C
12761276

12771277
comprehension := expr.GetComprehensionExpr()
12781278
if comprehension == nil {
1279-
return errors.New("expression is not a comprehension")
1279+
return newConversionError(errMsgUnsupportedComprehension, "expression is not a comprehension (TRANSFORM_LIST)")
12801280
}
12811281

12821282
con.str.WriteString("ARRAY(SELECT ")
12831283

12841284
// Visit the transform expression
12851285
if info.Transform != nil {
12861286
if err := con.visit(info.Transform); err != nil {
1287-
return fmt.Errorf("failed to visit transform in TRANSFORM_LIST comprehension: %w", err)
1287+
return wrapConversionError(err,"visiting transform in TRANSFORM_LIST comprehension")
12881288
}
12891289
} else {
12901290
// If no transform, just return the variable itself
@@ -1295,7 +1295,7 @@ func (con *converter) visitTransformListComprehension(expr *exprpb.Expr, info *C
12951295

12961296
// Visit the iterable range (the array/list being comprehended over)
12971297
if err := con.visit(comprehension.GetIterRange()); err != nil {
1298-
return fmt.Errorf("failed to visit iter range in TRANSFORM_LIST comprehension: %w", err)
1298+
return wrapConversionError(err,"visiting iter range in TRANSFORM_LIST comprehension")
12991299
}
13001300

13011301
con.str.WriteString(") AS ")
@@ -1305,7 +1305,7 @@ func (con *converter) visitTransformListComprehension(expr *exprpb.Expr, info *C
13051305
if info.Filter != nil {
13061306
con.str.WriteString(" WHERE ")
13071307
if err := con.visit(info.Filter); err != nil {
1308-
return fmt.Errorf("failed to visit filter in TRANSFORM_LIST comprehension: %w", err)
1308+
return wrapConversionError(err,"visiting filter in TRANSFORM_LIST comprehension")
13091309
}
13101310
}
13111311

@@ -1365,7 +1365,7 @@ func (con *converter) visitConst(expr *exprpb.Expr) error {
13651365
ui := strconv.FormatUint(c.GetUint64Value(), 10)
13661366
con.str.WriteString(ui)
13671367
default:
1368-
return fmt.Errorf("unimplemented : %v", expr)
1368+
return newConversionErrorf(errMsgUnsupportedExpression, "constant type: %T", c.ConstantKind)
13691369
}
13701370
return nil
13711371
}

comprehensions.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cel2sql
33
import (
44
"context"
55
"errors"
6-
"fmt"
76
"log/slog"
87

98
"github.com/google/cel-go/common/operators"
@@ -183,7 +182,7 @@ func (con *converter) analyzeComprehensionPattern(comp *exprpb.Expr_Comprehensio
183182

184183
// If we can't identify the pattern, mark as unknown for now
185184
info.Type = ComprehensionUnknown
186-
return info, fmt.Errorf("unrecognized comprehension pattern for %s", comp.String())
185+
return info, newConversionError(errMsgUnsupportedComprehension, "unrecognized comprehension pattern")
187186
}
188187

189188
// Helper functions to identify patterns in comprehension expressions

0 commit comments

Comments
 (0)