Skip to content

Commit 244fe5a

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

File tree

3 files changed

+51
-14
lines changed

3 files changed

+51
-14
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: 28 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,27 @@ 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 {
313+
if u.IsFunctionSafe(funcCall.Callee) {
314+
return funcCall, true
315+
}
316+
}
317+
return nil, false
318+
})
319+
320+
// Skip unsafe checks if we are inside of safe function call expression
321+
if isInsideSafeFunctionCall {
322+
return
323+
}
324+
297325
switch n := n.(type) {
298326
case *VariableNode:
299327
u.end()

expr_insecure_test.go

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -192,20 +192,6 @@ func TestExprInsecureDetectUntrustedValue(t *testing.T) {
192192
"github.event.pages.*.page_name",
193193
},
194194
},
195-
testCase{
196-
"contains(github.event.pages.*.page_name, github.event.issue.title)",
197-
[]string{
198-
"github.event.pages.*.page_name",
199-
"github.event.issue.title",
200-
},
201-
},
202-
testCase{
203-
"contains(github.event.*.body, github.event.*.*)",
204-
[]string{
205-
"github.event.",
206-
"github.event.",
207-
},
208-
},
209195
)
210196

211197
for _, tc := range tests {
@@ -310,6 +296,14 @@ func TestExprInsecureNoUntrustedValue(t *testing.T) {
310296
"matrix.foo[github.event.pages].page_name",
311297
"github.event.issue.body.foo.bar",
312298
"github.event.issue.body[0]",
299+
300+
"contains(github.event.issue.title, 'bug')",
301+
"contains(github.event.issue.body, github.event.issue.title)",
302+
"startsWith(github.event.comment.body, 'LGTM')",
303+
"endsWith(github.event.pull_request.title, github.event.issue.title)",
304+
"contains(github.event.*.body, 'sensitive')",
305+
"startsWith(github.event.*.body[0], 'Important')",
306+
"endsWith(github.event.commits[0].message, github.event.pull_request.title)",
313307
}
314308

315309
for _, input := range inputs {

0 commit comments

Comments
 (0)