Skip to content

Commit a950ac3

Browse files
committed
skip insecure checks if inside functions which output considered safe regardless of the arguments
1 parent 1319ddf commit a950ac3

File tree

3 files changed

+51
-2
lines changed

3 files changed

+51
-2
lines changed

expr_ast.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,18 @@ func visitExprNode(n, p ExprNode, f VisitExprNodeFunc) {
366366
func VisitExprNode(n ExprNode, f VisitExprNodeFunc) {
367367
visitExprNode(n, nil, f)
368368
}
369+
370+
// FindParent applies predicate to each parent of this node until predicate returns true.
371+
// Then it returns result of predicate. If no parent found, returns nil, false.
372+
func FindParent[T ExprNode](n ExprNode, predicate func(n ExprNode) (T, bool)) (T, bool) {
373+
parent := n.Parent()
374+
for parent != nil {
375+
t, ok := predicate(parent)
376+
if ok {
377+
return t, true
378+
}
379+
parent = parent.Parent()
380+
}
381+
var zero T
382+
return zero, false
383+
}

expr_insecure.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ var BuiltinUntrustedInputs = UntrustedInputSearchRoots{
135135
),
136136
}
137137

138+
// Contains functions which are considered to be safe.
139+
// No script injection is possible if unsafe expression is used inside these functions
140+
// because they have stable determined output
141+
var safeFunctions = map[string]bool{
142+
"contains": true,
143+
"startswith": true,
144+
"endswith": true,
145+
}
146+
138147
// UntrustedInputChecker is a checker to detect untrusted inputs in an expression syntax tree.
139148
// This checker checks object property accesses, array index accesses, and object filters. And
140149
// detects paths to untrusted inputs. Found errors are stored in this instance and can be get via
@@ -292,8 +301,25 @@ func (u *UntrustedInputChecker) end() {
292301
u.reset()
293302
}
294303

304+
// IsFunctionSafe Checks if this function is safe in terms of script injection possibility when using unsafe args
305+
func (u *UntrustedInputChecker) IsFunctionSafe(name string) bool {
306+
return safeFunctions[strings.ToLower(name)]
307+
}
308+
295309
// OnVisitNodeLeave is a callback which should be called on visiting node after visiting its children.
296310
func (u *UntrustedInputChecker) OnVisitNodeLeave(n ExprNode) {
311+
_, isInsideSafeFunctionCall := FindParent(n, func(n ExprNode) (*FuncCallNode, bool) {
312+
if funcCall, ok := n.(*FuncCallNode); ok && u.IsFunctionSafe(funcCall.Callee) {
313+
return funcCall, true
314+
}
315+
return nil, false
316+
})
317+
318+
// Skip unsafe checks if we are inside of safe function call expression
319+
if isInsideSafeFunctionCall {
320+
return
321+
}
322+
297323
switch n := n.(type) {
298324
case *VariableNode:
299325
u.end()

expr_insecure_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,14 @@ func TestExprInsecureDetectUntrustedValue(t *testing.T) {
193193
},
194194
},
195195
testCase{
196-
"contains(github.event.pages.*.page_name, github.event.issue.title)",
196+
"fromJson(github.event.pages.*.page_name, github.event.issue.title)",
197197
[]string{
198198
"github.event.pages.*.page_name",
199199
"github.event.issue.title",
200200
},
201201
},
202202
testCase{
203-
"contains(github.event.*.body, github.event.*.*)",
203+
"fromJson(github.event.*.body, github.event.*.*)",
204204
[]string{
205205
"github.event.",
206206
"github.event.",
@@ -310,6 +310,14 @@ func TestExprInsecureNoUntrustedValue(t *testing.T) {
310310
"matrix.foo[github.event.pages].page_name",
311311
"github.event.issue.body.foo.bar",
312312
"github.event.issue.body[0]",
313+
314+
"contains(github.event.issue.title, 'bug')",
315+
"contains(github.event.issue.body, github.event.issue.title)",
316+
"startsWith(github.event.comment.body, 'LGTM')",
317+
"endsWith(github.event.pull_request.title, github.event.issue.title)",
318+
"contains(github.event.*.body, 'sensitive')",
319+
"startsWith(github.event.*.body[0], 'Important')",
320+
"endsWith(github.event.commits[0].message, github.event.pull_request.title)",
313321
}
314322

315323
for _, input := range inputs {

0 commit comments

Comments
 (0)