Skip to content

Commit d7ef0cb

Browse files
authored
fix(policy-engine): Preserve escaped patterns on policy args (#2688)
Signed-off-by: Javier Rodriguez <javier@chainloop.dev>
1 parent 33ca7a8 commit d7ef0cb

File tree

2 files changed

+57
-26
lines changed

2 files changed

+57
-26
lines changed

pkg/policies/policies.go

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -529,39 +529,45 @@ func getValue(values []string) any {
529529
return lines[0]
530530
}
531531

532+
// splitArgs splits a comma-separated string into a slice of trimmed values.
533+
//
534+
// It treats \, (backslash-comma) as an escaped comma, preserving it as a literal comma
535+
// in the output rather than using it as a delimiter. All other backslashes are preserved
536+
// as-is, which allows regex patterns and other escape sequences to pass through unchanged.
537+
//
538+
// Empty values (including those with only whitespace) are filtered out from the result.
539+
//
540+
// The function uses a temporary placeholder strategy to handle escaped commas:
541+
// 1. Replace all \, with a unique placeholder
542+
// 2. Split on unescaped commas
543+
// 3. Restore placeholders back to commas
544+
// 4. Trim whitespace and filter empty strings
545+
//
546+
// Examples:
547+
// - "a,b,c" -> ["a", "b", "c"]
548+
// - "a\\,b,c" -> ["a,b", "c"] (escaped comma becomes literal comma)
549+
// - "\\d+,\\s+" -> ["\\d+", "\\s+"] (regex patterns preserved)
550+
// - ",a,,b," -> ["a", "b"] (empty values filtered out)
551+
// - "a\\,b\\,c" -> ["a,b,c"] (multiple escaped commas)
532552
func splitArgs(s string) []string {
533-
var result []string
534-
var current strings.Builder
535-
escaped := false
536-
537-
for i := 0; i < len(s); i++ {
538-
c := s[i]
553+
// Use a placeholder that's unlikely to appear in normal input
554+
const placeholder = "$=$$ESCAPED_COMMA$$=$"
539555

540-
if escaped {
541-
current.WriteByte(c)
542-
escaped = false
543-
continue
544-
}
556+
// Replace escaped commas with placeholder
557+
s = strings.ReplaceAll(s, `\,`, placeholder)
545558

546-
if c == '\\' {
547-
escaped = true
548-
continue
549-
}
559+
// Split by unescaped commas
560+
parts := strings.Split(s, ",")
550561

551-
if c == ',' {
552-
// Unescaped comma: split here
553-
result = append(result, strings.TrimSpace(current.String()))
554-
current.Reset()
555-
} else {
556-
current.WriteByte(c)
562+
// Restore escaped commas and trim spaces
563+
result := make([]string, 0, len(parts))
564+
for _, part := range parts {
565+
trimmed := strings.TrimSpace(strings.ReplaceAll(part, placeholder, ","))
566+
if trimmed != "" {
567+
result = append(result, trimmed)
557568
}
558569
}
559570

560-
// Add the final part
561-
if current.Len() > 0 {
562-
result = append(result, strings.TrimSpace(current.String()))
563-
}
564-
565571
return result
566572
}
567573

pkg/policies/policies_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,31 @@ func (s *testSuite) TestGetInputArguments() {
807807
inputs: map[string]string{"foo": "bar1,bar2,bar3", "bar": "baz", "foos": "bar1\nbar2\nbar3\n"},
808808
expected: map[string]any{"foo": []string{"bar1", "bar2", "bar3"}, "bar": "baz", "foos": []string{"bar1", "bar2", "bar3"}},
809809
},
810+
{
811+
name: "regex with escaped parameters",
812+
inputs: map[string]string{"pattern": "[A-Z][A-Z0-9_]+-\\d+"},
813+
expected: map[string]any{"pattern": `[A-Z][A-Z0-9_]+-\d+`},
814+
},
815+
{
816+
name: "regex with multiple backslashes",
817+
inputs: map[string]string{"pattern": "\\d+\\s+\\w+"},
818+
expected: map[string]any{"pattern": `\d+\s+\w+`},
819+
},
820+
{
821+
name: "mixed escaped comma and regex",
822+
inputs: map[string]string{"patterns": "\\d+\\,test,\\w+"},
823+
expected: map[string]any{"patterns": []string{`\d+,test`, `\w+`}},
824+
},
825+
{
826+
name: "backslash at end",
827+
inputs: map[string]string{"foo": "bar\\"},
828+
expected: map[string]any{"foo": `bar\`},
829+
},
830+
{
831+
name: "double backslash",
832+
inputs: map[string]string{"foo": "bar\\\\baz"},
833+
expected: map[string]any{"foo": `bar\\baz`},
834+
},
810835
}
811836

812837
for _, tc := range cases {

0 commit comments

Comments
 (0)