Skip to content

Commit 62b1fa2

Browse files
authored
fix: redact sensitive pipeline headers (#826)
1 parent 0072d32 commit 62b1fa2

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

engine_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,49 @@ func TestEngineTriggerWorkflow_RedactsSensitiveResultsInDebugLogs(t *testing.T)
268268
}
269269
}
270270

271+
func TestEngineTriggerWorkflow_RedactsSensitiveResultHeadersInDebugLogs(t *testing.T) {
272+
app := newMockApplication()
273+
engine := NewStdEngine(app, app.Logger())
274+
loadAllPlugins(t, engine)
275+
276+
handler := &mockWorkflowHandler{
277+
name: "mock.handler",
278+
handlesFor: []string{"header-result-workflow"},
279+
results: map[string]any{
280+
"headers": map[string]any{
281+
"Authorization": "Bearer jwt.secret.value",
282+
"Cookie": "sid=session-secret",
283+
"Set-Cookie": "sid=session-secret; HttpOnly",
284+
"X-API-Key": "api-secret",
285+
"Content-Type": "application/json",
286+
},
287+
},
288+
}
289+
engine.RegisterWorkflowHandler(handler)
290+
291+
holder := &module.PipelineResultHolder{}
292+
ctx := context.WithValue(context.Background(), module.PipelineResultContextKey, holder)
293+
if err := engine.TriggerWorkflow(ctx, "header-result-workflow", "run", map[string]any{}); err != nil {
294+
t.Fatalf("TriggerWorkflow failed: %v", err)
295+
}
296+
297+
logText := strings.Join(app.logger.logs, "\n")
298+
for _, leaked := range []string{"jwt.secret.value", "session-secret", "api-secret"} {
299+
if strings.Contains(logText, leaked) {
300+
t.Fatalf("debug logs leaked sensitive header value %q:\n%s", leaked, logText)
301+
}
302+
}
303+
if !strings.Contains(logText, module.RedactionPlaceholder) {
304+
t.Fatalf("debug logs should include redaction placeholder, got:\n%s", logText)
305+
}
306+
307+
raw := holder.Get()
308+
headers := raw["headers"].(map[string]any)
309+
if headers["Authorization"] != "Bearer jwt.secret.value" {
310+
t.Fatalf("pipeline result holder must preserve raw Authorization header, got %#v", headers["Authorization"])
311+
}
312+
}
313+
271314
func TestEngineTriggerWorkflow_RedactsSensitiveInputInDebugLogs(t *testing.T) {
272315
app := newMockApplication()
273316
engine := NewStdEngine(app, app.Logger())

module/step_output_redactor.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ var SensitiveFieldPatterns = []string{
99
"password",
1010
"token",
1111
"credential",
12+
"authorization",
13+
"cookie",
1214
"api_key",
15+
"api-key",
1316
"apikey",
1417
"private_key",
1518
"access_key",

module/step_output_redactor_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,33 @@ func TestRedactStepOutput_NestedMaps(t *testing.T) {
9797
}
9898
}
9999

100+
func TestRedactStepOutput_NestedSensitiveHeaders(t *testing.T) {
101+
output := map[string]any{
102+
"headers": map[string]any{
103+
"Authorization": "Bearer jwt.secret.value",
104+
"Cookie": "sid=session-secret",
105+
"Set-Cookie": "sid=session-secret; HttpOnly",
106+
"X-API-Key": "api-secret",
107+
"X-Scenario90-Seed-Token": "seed-secret",
108+
"Content-Type": "application/json",
109+
},
110+
}
111+
112+
got := RedactStepOutput(output)
113+
headers, ok := got["headers"].(map[string]any)
114+
if !ok {
115+
t.Fatalf("headers should remain a map, got %T", got["headers"])
116+
}
117+
for _, key := range []string{"Authorization", "Cookie", "Set-Cookie", "X-API-Key", "X-Scenario90-Seed-Token"} {
118+
if headers[key] != RedactionPlaceholder {
119+
t.Fatalf("%s should be redacted, got %#v", key, headers[key])
120+
}
121+
}
122+
if headers["Content-Type"] != "application/json" {
123+
t.Fatalf("Content-Type should be preserved, got %#v", headers["Content-Type"])
124+
}
125+
}
126+
100127
func TestRedactStepOutput_NestedSlices(t *testing.T) {
101128
output := map[string]any{
102129
"rows": []any{

0 commit comments

Comments
 (0)