Skip to content

Commit c7a2b89

Browse files
authored
security: validate client-supplied trace IDs (#26)
* security: validate client-supplied trace IDs to prevent log injection Add validateTraceID helper that sanitizes trace IDs from gRPC metadata and proto fields: max 128 chars, printable ASCII only (0x20-0x7E). Non-printable/control/unicode bytes are stripped. If all characters are invalid, falls back to generating a new UUID. Called in SetTraceIdWithValue (after metadata extraction) and UpdateTraceId (before storing). Protects logs, Sentry/Rollbar, and OTEL span attributes from injection attacks and DoS via oversized trace IDs. * feat: add SetTraceIDValidator for configurable trace ID validation Add SetTraceIDValidator(func(string) string) to allow custom validation or disable it entirely (set to nil). Follows the existing init-only setter pattern. Default behavior unchanged (max 128 chars, printable ASCII only). * fix: add UTF-8 test case, use TrimSpace in UpdateTraceId Address review feedback: - Add non-ASCII UTF-8 test case (accented, CJK, emoji) to validateTraceID - Use strings.TrimSpace in UpdateTraceId to match SetTraceIdWithValue behavior — whitespace-only trace IDs now fall back to generation * docs: fix SetTraceIDValidator docstring — empty string triggers resolution flow, not direct generation * docs: fix resolution order in SetTraceIDValidator docstring
1 parent e40523f commit c7a2b89

4 files changed

Lines changed: 210 additions & 45 deletions

File tree

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ const SupportPackageIsVersion1 = true
134134
```
135135

136136
<a name="SetBaseFilePath"></a>
137-
## func [SetBaseFilePath](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L281>)
137+
## func [SetBaseFilePath](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L285>)
138138

139139
```go
140140
func SetBaseFilePath(path string)
@@ -143,7 +143,7 @@ func SetBaseFilePath(path string)
143143
SetBaseFilePath sets the base file path for linking source code with reported stack information
144144

145145
<a name="SetMaxStackDepth"></a>
146-
## func [SetMaxStackDepth](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L264>)
146+
## func [SetMaxStackDepth](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L268>)
147147

148148
```go
149149
func SetMaxStackDepth(n int)
@@ -172,7 +172,7 @@ type ErrorExt interface {
172172
```
173173

174174
<a name="New"></a>
175-
### func [New](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L180>)
175+
### func [New](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L181>)
176176

177177
```go
178178
func New(msg string) ErrorExt
@@ -210,7 +210,7 @@ something went wrong
210210
</details>
211211

212212
<a name="NewWithSkip"></a>
213-
### func [NewWithSkip](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L190>)
213+
### func [NewWithSkip](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L191>)
214214

215215
```go
216216
func NewWithSkip(msg string, skip int) ErrorExt
@@ -219,7 +219,7 @@ func NewWithSkip(msg string, skip int) ErrorExt
219219
NewWithSkip creates a new error skipping the number of function on the stack
220220

221221
<a name="NewWithSkipAndStatus"></a>
222-
### func [NewWithSkipAndStatus](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L195>)
222+
### func [NewWithSkipAndStatus](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L196>)
223223

224224
```go
225225
func NewWithSkipAndStatus(msg string, skip int, status *grpcstatus.Status) ErrorExt
@@ -228,7 +228,7 @@ func NewWithSkipAndStatus(msg string, skip int, status *grpcstatus.Status) Error
228228
NewWithSkipAndStatus creates a new error skipping the number of function on the stack and GRPC status
229229

230230
<a name="NewWithStatus"></a>
231-
### func [NewWithStatus](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L185>)
231+
### func [NewWithStatus](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L186>)
232232

233233
```go
234234
func NewWithStatus(msg string, status *grpcstatus.Status) ErrorExt
@@ -237,7 +237,7 @@ func NewWithStatus(msg string, status *grpcstatus.Status) ErrorExt
237237
NewWithStatus creates a new error with statck information and GRPC status
238238

239239
<a name="Newf"></a>
240-
### func [Newf](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L271>)
240+
### func [Newf](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L275>)
241241

242242
```go
243243
func Newf(format string, args ...any) ErrorExt
@@ -275,7 +275,7 @@ user alice not found
275275
</details>
276276

277277
<a name="Wrap"></a>
278-
### func [Wrap](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L200>)
278+
### func [Wrap](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L201>)
279279

280280
```go
281281
func Wrap(err error, msg string) ErrorExt
@@ -349,7 +349,7 @@ true
349349
</details>
350350

351351
<a name="WrapWithSkip"></a>
352-
### func [WrapWithSkip](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L210>)
352+
### func [WrapWithSkip](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L211>)
353353

354354
```go
355355
func WrapWithSkip(err error, msg string, skip int) ErrorExt
@@ -358,7 +358,7 @@ func WrapWithSkip(err error, msg string, skip int) ErrorExt
358358
WrapWithSkip wraps an existing error and appends stack information if it does not exists skipping the number of function on the stack
359359

360360
<a name="WrapWithSkipAndStatus"></a>
361-
### func [WrapWithSkipAndStatus](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L215>)
361+
### func [WrapWithSkipAndStatus](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L216>)
362362

363363
```go
364364
func WrapWithSkipAndStatus(err error, msg string, skip int, status *grpcstatus.Status) ErrorExt
@@ -367,7 +367,7 @@ func WrapWithSkipAndStatus(err error, msg string, skip int, status *grpcstatus.S
367367
WrapWithSkip wraps an existing error and appends stack information if it does not exists skipping the number of function on the stack along with GRPC status
368368

369369
<a name="WrapWithStatus"></a>
370-
### func [WrapWithStatus](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L205>)
370+
### func [WrapWithStatus](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L206>)
371371

372372
```go
373373
func WrapWithStatus(err error, msg string, status *grpcstatus.Status) ErrorExt
@@ -412,7 +412,7 @@ gRPC code: NotFound
412412
</details>
413413

414414
<a name="Wrapf"></a>
415-
### func [Wrapf](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L276>)
415+
### func [Wrapf](<https://github.com/go-coldbrew/errors/blob/main/errors.go#L280>)
416416

417417
```go
418418
func Wrapf(err error, format string, args ...any) ErrorExt

notifier/README.md

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ import "github.com/go-coldbrew/errors/notifier"
3333
- [func SetRelease\(rel string\)](<#SetRelease>)
3434
- [func SetServerRoot\(path string\)](<#SetServerRoot>)
3535
- [func SetTraceHeaderName\(name string\)](<#SetTraceHeaderName>)
36+
- [func SetTraceIDValidator\(fn func\(string\) string\)](<#SetTraceIDValidator>)
3637
- [func SetTraceId\(ctx context.Context\) context.Context](<#SetTraceId>)
38+
- [func SetTraceIdWithValue\(ctx context.Context\) \(context.Context, string\)](<#SetTraceIdWithValue>)
3739
- [func UpdateTraceId\(ctx context.Context, traceID string\) context.Context](<#UpdateTraceId>)
3840
- [type Tags](<#Tags>)
3941

4042

4143
<a name="Close"></a>
42-
## func [Close](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L493>)
44+
## func [Close](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L503>)
4345

4446
```go
4547
func Close()
@@ -48,7 +50,7 @@ func Close()
4850
Close closes the airbrake notifier and flushes pending Sentry events. Sentry events are flushed with a 2 second timeout. You should call Close before app shutdown. Close doesn't call os.Exit.
4951

5052
<a name="GetTraceHeaderName"></a>
51-
## func [GetTraceHeaderName](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L93>)
53+
## func [GetTraceHeaderName](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L104>)
5254

5355
```go
5456
func GetTraceHeaderName() string
@@ -57,7 +59,7 @@ func GetTraceHeaderName() string
5759
GetTraceHeaderName gets the header name for trace id default is x\-trace\-id
5860

5961
<a name="GetTraceId"></a>
60-
## func [GetTraceId](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L553>)
62+
## func [GetTraceId](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L590>)
6163

6264
```go
6365
func GetTraceId(ctx context.Context) string
@@ -66,7 +68,7 @@ func GetTraceId(ctx context.Context) string
6668
GetTraceId fetches traceID from context if no trace id is found then it will return empty string
6769

6870
<a name="InitAirbrake"></a>
69-
## func [InitAirbrake](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L113>)
71+
## func [InitAirbrake](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L124>)
7072

7173
```go
7274
func InitAirbrake(projectID int64, projectKey string)
@@ -75,7 +77,7 @@ func InitAirbrake(projectID int64, projectKey string)
7577
InitAirbrake inits airbrake configuration projectID: airbrake project id projectKey: airbrake project key
7678

7779
<a name="InitRollbar"></a>
78-
## func [InitRollbar](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L124>)
80+
## func [InitRollbar](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L135>)
7981

8082
```go
8183
func InitRollbar(token, env string)
@@ -84,7 +86,7 @@ func InitRollbar(token, env string)
8486
InitRollbar inits rollbar configuration token: rollbar token env: rollbar environment
8587

8688
<a name="InitSentry"></a>
87-
## func [InitSentry](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L156>)
89+
## func [InitSentry](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L167>)
8890

8991
```go
9092
func InitSentry(dsn string)
@@ -93,7 +95,7 @@ func InitSentry(dsn string)
9395
InitSentry inits sentry configuration dsn: sentry dsn
9496

9597
<a name="Notify"></a>
96-
## func [Notify](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L254>)
98+
## func [Notify](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L265>)
9799

98100
```go
99101
func Notify(err error, rawData ...interface{}) error
@@ -102,7 +104,7 @@ func Notify(err error, rawData ...interface{}) error
102104
Notify notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata
103105

104106
<a name="NotifyAsync"></a>
105-
## func [NotifyAsync](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L66>)
107+
## func [NotifyAsync](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L77>)
106108

107109
```go
108110
func NotifyAsync(err error, rawData ...interface{}) error
@@ -111,7 +113,7 @@ func NotifyAsync(err error, rawData ...interface{}) error
111113
NotifyAsync sends an error notification asynchronously with bounded concurrency. If the async notification pool is full, the notification is dropped to prevent goroutine explosion under sustained error bursts. Returns the original error for convenience.
112114

113115
<a name="NotifyOnPanic"></a>
114-
## func [NotifyOnPanic](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L455>)
116+
## func [NotifyOnPanic](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L465>)
115117

116118
```go
117119
func NotifyOnPanic(rawData ...interface{})
@@ -120,7 +122,7 @@ func NotifyOnPanic(rawData ...interface{})
120122
NotifyOnPanic notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata this function should be called in defer example: defer NotifyOnPanic\(ctx, "some data"\) example: defer NotifyOnPanic\(ctx, "some data", Tags\{"tag1": "value1"\}\)
121123

122124
<a name="NotifyWithExclude"></a>
123-
## func [NotifyWithExclude](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L424>)
125+
## func [NotifyWithExclude](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L434>)
124126

125127
```go
126128
func NotifyWithExclude(err error, rawData ...interface{}) error
@@ -129,7 +131,7 @@ func NotifyWithExclude(err error, rawData ...interface{}) error
129131
NotifyWithExclude notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata
130132

131133
<a name="NotifyWithLevel"></a>
132-
## func [NotifyWithLevel](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L263>)
134+
## func [NotifyWithLevel](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L274>)
133135

134136
```go
135137
func NotifyWithLevel(err error, level string, rawData ...interface{}) error
@@ -138,7 +140,7 @@ func NotifyWithLevel(err error, level string, rawData ...interface{}) error
138140
NotifyWithLevel notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify level: error level rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata
139141

140142
<a name="NotifyWithLevelAndSkip"></a>
141-
## func [NotifyWithLevelAndSkip](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L314>)
143+
## func [NotifyWithLevelAndSkip](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L325>)
142144

143145
```go
144146
func NotifyWithLevelAndSkip(err error, skip int, level string, rawData ...interface{}) error
@@ -147,7 +149,7 @@ func NotifyWithLevelAndSkip(err error, skip int, level string, rawData ...interf
147149
NotifyWithLevelAndSkip notifies error to airbrake, rollbar and sentry if they are inited and error is not ignored err: error to notify skip: skip stack frames when notify error level: error level rawData: extra data to notify with error \(can be context.Context, Tags, or any other data\) when rawData is context.Context, it will used to get extra data from loggers.FromContext\(ctx\) and tags from metadata
148150

149151
<a name="SetEnvironment"></a>
150-
## func [SetEnvironment](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L504>)
152+
## func [SetEnvironment](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L514>)
151153

152154
```go
153155
func SetEnvironment(env string)
@@ -156,7 +158,7 @@ func SetEnvironment(env string)
156158
SetEnvironment sets the environment. The environment is used to distinguish errors occurring in different
157159

158160
<a name="SetHostname"></a>
159-
## func [SetHostname](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L589>)
161+
## func [SetHostname](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L670>)
160162

161163
```go
162164
func SetHostname(name string)
@@ -165,16 +167,16 @@ func SetHostname(name string)
165167
SetHostname sets the hostname of the server. The hostname is used to identify the server that logged an error.
166168

167169
<a name="SetMaxAsyncNotifications"></a>
168-
## func [SetMaxAsyncNotifications](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L54>)
170+
## func [SetMaxAsyncNotifications](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L64>)
169171

170172
```go
171173
func SetMaxAsyncNotifications(n int)
172174
```
173175

174-
SetMaxAsyncNotifications sets the maximum number of concurrent async notification goroutines. When the limit is reached, new async notifications are dropped to prevent goroutine explosion under sustained error bursts. Default is 1000. Can only be called once; subsequent calls are no\-ops.
176+
SetMaxAsyncNotifications sets the maximum number of concurrent async notification goroutines. When the limit is reached, new async notifications are dropped to prevent goroutine explosion under sustained error bursts. Default is 20. The first successful call wins; subsequent calls are no\-ops. It is safe to call concurrently with NotifyAsync.
175177

176178
<a name="SetRelease"></a>
177-
## func [SetRelease](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L517>)
179+
## func [SetRelease](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L527>)
178180

179181
```go
180182
func SetRelease(rel string)
@@ -183,7 +185,7 @@ func SetRelease(rel string)
183185
SetRelease sets the release tag. The release tag is used to group errors together by release.
184186

185187
<a name="SetServerRoot"></a>
186-
## func [SetServerRoot](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L583>)
188+
## func [SetServerRoot](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L664>)
187189

188190
```go
189191
func SetServerRoot(path string)
@@ -192,25 +194,43 @@ func SetServerRoot(path string)
192194
SetServerRoot sets the root directory of the project. The root directory is used to trim prefixes from filenames in stack traces.
193195

194196
<a name="SetTraceHeaderName"></a>
195-
## func [SetTraceHeaderName](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L87>)
197+
## func [SetTraceHeaderName](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L98>)
196198

197199
```go
198200
func SetTraceHeaderName(name string)
199201
```
200202

201203
SetTraceHeaderName sets the header name for trace id default is x\-trace\-id
202204

205+
<a name="SetTraceIDValidator"></a>
206+
## func [SetTraceIDValidator](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L619>)
207+
208+
```go
209+
func SetTraceIDValidator(fn func(string) string)
210+
```
211+
212+
SetTraceIDValidator sets a custom trace ID validation function. The function receives a raw trace ID and must return the sanitized version. Returning an empty string triggers the standard trace ID resolution flow \(existing ctx → gRPC metadata → OTEL span trace ID → generate UUID\), not direct generation. Set to nil to disable validation entirely \(not recommended\). Must be called during init — not safe for concurrent use.
213+
203214
<a name="SetTraceId"></a>
204-
## func [SetTraceId](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L524>)
215+
## func [SetTraceId](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L583>)
205216

206217
```go
207218
func SetTraceId(ctx context.Context) context.Context
208219
```
209220

210221
SetTraceId updates the traceID based on context values if no trace id is found then it will create one and update the context You should use the context returned by this function instead of the one passed
211222

223+
<a name="SetTraceIdWithValue"></a>
224+
## func [SetTraceIdWithValue](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L535>)
225+
226+
```go
227+
func SetTraceIdWithValue(ctx context.Context) (context.Context, string)
228+
```
229+
230+
SetTraceIdWithValue is like SetTraceId but also returns the resolved trace ID, avoiding a separate GetTraceId call. Callers must use the returned context, not the original ctx, so the stored trace ID is preserved in options and log context.
231+
212232
<a name="UpdateTraceId"></a>
213-
## func [UpdateTraceId](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L573>)
233+
## func [UpdateTraceId](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L651>)
214234

215235
```go
216236
func UpdateTraceId(ctx context.Context, traceID string) context.Context
@@ -219,7 +239,7 @@ func UpdateTraceId(ctx context.Context, traceID string) context.Context
219239
UpdateTraceId force updates the traced id to provided id if no trace id is found then it will create one and update the context You should use the context returned by this function instead of the one passed
220240

221241
<a name="Tags"></a>
222-
## type [Tags](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L102>)
242+
## type [Tags](<https://github.com/go-coldbrew/errors/blob/main/notifier/notifier.go#L113>)
223243

224244

225245

0 commit comments

Comments
 (0)