Skip to content

Commit 3607c01

Browse files
M4tteoPiosetek
andauthored
fix: enforces request body scanning with trailers (#306)
Co-authored-by: Ignacy Osetek <ignacy.osetek@gmail.com>
1 parent 267ffe7 commit 3607c01

File tree

13 files changed

+339
-81
lines changed

13 files changed

+339
-81
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ target
55
.DS_Store
66
.vimrc
77
go.work.sum
8+
e2e/http_trailer/server.crt
9+
e2e/http_trailer/server.key
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ services:
2020
- --service-node # required to export metrics
2121
- envoy
2222
volumes:
23-
- ../build:/build
23+
- ../../build:/build
2424
- .:/conf
2525
ports:
2626
- 8080:8080
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
services:
2+
httpbin:
3+
image: mccutchen/go-httpbin:v2.15.0
4+
command: [ "/bin/go-httpbin", "-port", "8081" ]
5+
ports:
6+
- 8081:8081
7+
envoy:
8+
depends_on:
9+
- httpbin
10+
image: ${ENVOY_IMAGE:-envoyproxy/envoy:v1.31-latest}
11+
entrypoint: /usr/local/bin/envoy
12+
command:
13+
- -c
14+
- /conf/envoy-config.yaml
15+
volumes:
16+
- ../../build:/build
17+
- .:/conf
18+
ports:
19+
- 8080:8080

e2e/http_trailer/envoy-config.yaml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
static_resources:
2+
listeners:
3+
- address:
4+
socket_address:
5+
address: 0.0.0.0
6+
port_value: 8080
7+
filter_chains:
8+
- transport_socket:
9+
name: envoy.transport_sockets.tls
10+
typed_config:
11+
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
12+
common_tls_context:
13+
alpn_protocols: [ "h2,http/1.1" ]
14+
tls_certificates:
15+
- certificate_chain:
16+
filename: "/conf/server.crt"
17+
private_key:
18+
filename: "/conf/server.key"
19+
filters:
20+
- name: envoy.filters.network.http_connection_manager
21+
typed_config:
22+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
23+
stat_prefix: ingress_http
24+
codec_type: http2
25+
http2_protocol_options: {}
26+
route_config:
27+
virtual_hosts:
28+
- name: local_route
29+
domains:
30+
- "*"
31+
routes:
32+
- match:
33+
prefix: "/"
34+
route:
35+
cluster: local_server
36+
http_filters:
37+
- name: envoy.filters.http.wasm
38+
typed_config:
39+
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
40+
config:
41+
name: "coraza-filter"
42+
root_id: ""
43+
configuration:
44+
"@type": "type.googleapis.com/google.protobuf.StringValue"
45+
value: |
46+
{
47+
"directives_map": {
48+
"defaultrs": [
49+
"Include @demo-conf",
50+
"SecDebugLogLevel 3",
51+
"SecRule ARGS_POST \"@rx script\" \"id:100,phase:2,deny\""
52+
]
53+
},
54+
"default_directives": "defaultrs"
55+
}
56+
vm_config:
57+
runtime: "envoy.wasm.runtime.v8"
58+
vm_id: "my_vm_id"
59+
code:
60+
local:
61+
filename: "build/main.wasm"
62+
- name: envoy.filters.http.router
63+
typed_config:
64+
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
65+
66+
clusters:
67+
- name: local_server
68+
connect_timeout: 6000s
69+
type: STRICT_DNS
70+
load_assignment:
71+
cluster_name: local_server
72+
endpoints:
73+
- lb_endpoints:
74+
- endpoint:
75+
address:
76+
socket_address:
77+
address: httpbin
78+
port_value: 8081

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/corazawaf/coraza-proxy-wasm
22

3-
go 1.23.7
3+
go 1.23.8
44

55
require (
66
github.com/corazawaf/coraza-wasilibs v0.2.0

go.work

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
go 1.23.7
1+
go 1.23.8
22

33
use (
44
.

magefiles/e2etest.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright The OWASP Coraza contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"cmp"
8+
"crypto/tls"
9+
"fmt"
10+
"net/http"
11+
"os"
12+
"strings"
13+
"time"
14+
15+
"github.com/magefile/mage/sh"
16+
"golang.org/x/net/http2"
17+
)
18+
19+
// E2e runs e2e tests. Requires docker.
20+
func E2e() error {
21+
envoyHost := cmp.Or(os.Getenv("ENVOY_HOST"), "localhost:8080")
22+
httpbinHost := cmp.Or(os.Getenv("HTTPBIN_HOST"), "localhost:8081")
23+
24+
if err := runCorazaE2e(envoyHost, httpbinHost); err != nil {
25+
return err
26+
}
27+
28+
if err := runHttpTrailerE2e(envoyHost); err != nil {
29+
return err
30+
}
31+
return nil
32+
}
33+
34+
// runCorazaE2e runs Coraza e2e tests with a built plugin against the example deployment
35+
func runCorazaE2e(envoyHost, httpbinHost string) error {
36+
dockerComposeFilePath := "e2e/coraza/docker-compose.yml"
37+
var err error
38+
if err = sh.RunV("docker", "compose", "--file", dockerComposeFilePath, "up", "-d", "envoy"); err != nil {
39+
sh.RunV("docker", "compose", "-f", dockerComposeFilePath, "logs", "envoy")
40+
return err
41+
}
42+
defer func() {
43+
_ = sh.RunV("docker", "compose", "--file", dockerComposeFilePath, "down", "-v")
44+
}()
45+
46+
// --nulled-body is needed because coraza-proxy-wasm returns a 200 OK with a nulled body when if the interruption happens after phase 3
47+
if err = sh.RunV("go", "run", "github.com/corazawaf/coraza/v3/http/e2e/cmd/httpe2e@main", "--proxy-hostport",
48+
"http://"+envoyHost, "--httpbin-hostport", "http://"+httpbinHost, "--nulled-body"); err != nil {
49+
sh.RunV("docker", "compose", "-f", dockerComposeFilePath, "logs", "envoy")
50+
}
51+
return err
52+
}
53+
54+
// runHttpTrailerE2e runs HTTP trailer E2E tests
55+
// It is meant to check that HTTP2 request payloads with trailers are scanned at phase 2 before being sent to upstream.
56+
// This might happen because the end_of_stream parameter from OnHttpRequestBody is never set to true in HTTP2 if trailers
57+
// are available. In order to mitigate this, OnHttp[Request|Response]Trailers callbacks have been implemented as an enforcement
58+
// point of the body phase rules.
59+
// The test expects Coraza to enforce the interruption (403) during phase="http_request_body" and not phase="http_response_headers",
60+
// which would mean that the payload was sent to upstream before being scanned and was blocked on the way back.
61+
func runHttpTrailerE2e(envoyHost string) error {
62+
fmt.Printf("Running HTTP trailer test\n")
63+
dockerComposeFilePath := "e2e/http_trailer/docker-compose.yml"
64+
if err := sh.RunV("go", "run", "filippo.io/mkcert@v1.4.4", "-key-file", "e2e/http_trailer/server.key",
65+
"-cert-file", "e2e/http_trailer/server.crt", "example.com"); err != nil {
66+
return err
67+
}
68+
defer func() {
69+
_ = os.Remove("e2e/http_trailer/server.key")
70+
_ = os.Remove("e2e/http_trailer/server.crt")
71+
}()
72+
if err := sh.RunV("docker", "compose", "--file", dockerComposeFilePath, "up", "-d", "envoy"); err != nil {
73+
sh.RunV("docker", "compose", "-f", dockerComposeFilePath, "logs", "envoy")
74+
return err
75+
}
76+
defer func() {
77+
_ = sh.RunV("docker", "compose", "--file", dockerComposeFilePath, "down", "-v")
78+
}()
79+
80+
client := &http.Client{
81+
Transport: &http2.Transport{
82+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
83+
},
84+
}
85+
86+
// Wait for envoy to be ready
87+
ready := false
88+
for range 20 {
89+
resp, err := client.Get("https://" + envoyHost)
90+
if err == nil && resp.StatusCode == http.StatusOK {
91+
resp.Body.Close()
92+
ready = true
93+
break
94+
}
95+
time.Sleep(500 * time.Millisecond)
96+
fmt.Println("Waiting for Envoy to be ready...")
97+
}
98+
if !ready {
99+
sh.RunV("docker", "compose", "-f", dockerComposeFilePath, "logs", "envoy")
100+
return fmt.Errorf("timeout waiting for Envoy")
101+
}
102+
103+
// Run the actual test.
104+
req, err := http.NewRequest("POST", "https://"+envoyHost, strings.NewReader("{\"foo\": \"<script foo>\"}"))
105+
if err != nil {
106+
return fmt.Errorf("creating request: %w", err)
107+
}
108+
req.Header.Set("Content-Type", "application/json")
109+
req.Trailer = http.Header{"Custom-Trailer": {"This is a custom trailer"}}
110+
111+
resp, err := client.Do(req)
112+
if err != nil {
113+
sh.RunV("docker", "compose", "-f", dockerComposeFilePath, "logs", "envoy")
114+
return fmt.Errorf("sending request: %w", err)
115+
}
116+
defer resp.Body.Close()
117+
118+
output, err := sh.Output("docker", "compose", "-f", dockerComposeFilePath, "logs", "envoy")
119+
if err != nil {
120+
return fmt.Errorf("getting envoy logs: %w", err)
121+
}
122+
123+
// The request is expected to be blocked at phase 2, before reaching the backend.
124+
if resp.StatusCode != http.StatusForbidden {
125+
return fmt.Errorf("unexpected status code: got %d, want %d", resp.StatusCode, http.StatusForbidden)
126+
}
127+
if !strings.Contains(output, "phase=\"http_request_body\"") {
128+
return fmt.Errorf("expected phase=\"http_request_body\" in envoy logs transaction interrupted line, got:\n%s", output)
129+
}
130+
fmt.Printf("✅ HTTP trailer test passed\n")
131+
132+
return nil
133+
}

magefiles/go.mod

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
module github.com/corazawaf/coraza-proxy-wasm/magefiles
22

3-
go 1.23.7
3+
go 1.23.8
44

55
require (
6-
fortio.org/fortio v1.66.0
6+
fortio.org/fortio v1.69.4
77
github.com/magefile/mage v1.15.0
88
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834
9+
golang.org/x/net v0.39.0
910
)
1011

1112
require (
12-
fortio.org/cli v1.7.0 // indirect
13-
fortio.org/dflag v1.7.2 // indirect
14-
fortio.org/log v1.14.0 // indirect
15-
fortio.org/scli v1.15.1 // indirect
16-
fortio.org/sets v1.1.1 // indirect
17-
fortio.org/struct2env v0.4.1 // indirect
13+
fortio.org/cli v1.10.0 // indirect
14+
fortio.org/dflag v1.8.1 // indirect
15+
fortio.org/log v1.17.2 // indirect
16+
fortio.org/safecast v1.0.0 // indirect
17+
fortio.org/scli v1.16.1 // indirect
18+
fortio.org/sets v1.3.0 // indirect
19+
fortio.org/struct2env v0.4.2 // indirect
1820
fortio.org/version v1.0.4 // indirect
19-
github.com/fsnotify/fsnotify v1.7.0 // indirect
21+
github.com/fsnotify/fsnotify v1.8.0 // indirect
2022
github.com/google/uuid v1.6.0 // indirect
21-
golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658 // indirect
22-
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
23-
golang.org/x/net v0.26.0 // indirect
24-
golang.org/x/sys v0.21.0 // indirect
25-
golang.org/x/text v0.16.0 // indirect
23+
github.com/kortschak/goroutine v1.1.2 // indirect
24+
golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb // indirect
25+
golang.org/x/sys v0.32.0 // indirect
26+
golang.org/x/text v0.24.0 // indirect
2627
)

magefiles/go.sum

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
fortio.org/assert v1.2.1 h1:48I39urpeDj65RP1KguF7akCjILNeu6vICiYMEysR7Q=
22
fortio.org/assert v1.2.1/go.mod h1:039mG+/iYDPO8Ibx8TrNuJCm2T2SuhwRI3uL9nHTTls=
3-
fortio.org/cli v1.7.0 h1:w+uXZLGi4t3Vn/BvbeMuSw84Z1pvNPG9HqeGfpP68cc=
4-
fortio.org/cli v1.7.0/go.mod h1:s4vxWz7P7T4cYOWdMF0NA693Nu1gK9OW4KoDj54/Do4=
5-
fortio.org/dflag v1.7.2 h1:lUhXFvDlw4CJj/q7hPv/TC+n/wVoQylzQO6bUg5GQa0=
6-
fortio.org/dflag v1.7.2/go.mod h1:6yO/NIgrWfQH195WbHJ3Y45SCx11ffivQjfx2C/FS1U=
7-
fortio.org/fortio v1.66.0 h1:9F/200qIu136z847bxs/NeAoYdJaQlVofYlppi3qwcw=
8-
fortio.org/fortio v1.66.0/go.mod h1:eUl5MRscw6CiWAStai8aB3/8unxA9uNzJRXdhKEaq1s=
9-
fortio.org/log v1.14.0 h1:ZkIc3Qqwfs9Dd931k07YzoC+bqCpJKEjVlZwxgXW3Nw=
10-
fortio.org/log v1.14.0/go.mod h1:1tnXMqd5rZAgvSeHJkD2xXpyXRBzdeXtKLZuzNLIwtA=
11-
fortio.org/scli v1.15.1 h1:Upza50brpEZwUk8Nn2gdP4BjgqJZY3J+z7KLrrAzPjY=
12-
fortio.org/scli v1.15.1/go.mod h1:9LOD4iPe9u73KeJGYC/Af1oFniOafO7oZ9VvwENMf/c=
13-
fortio.org/sets v1.1.1 h1:Q7Z1Ft2lpUc1N7bfI8HofIK0QskrOflfYRyKT2LzBng=
14-
fortio.org/sets v1.1.1/go.mod h1:J2BwIxNOLWsSU7IMZUg541kh3Au4JEKHrghVwXs68tE=
15-
fortio.org/struct2env v0.4.1 h1:rJludAMO5eBvpWplWEQNqoVDFZr4RWMQX7RUapgZyc0=
16-
fortio.org/struct2env v0.4.1/go.mod h1:lENUe70UwA1zDUCX+8AsO663QCFqYaprk5lnPhjD410=
3+
fortio.org/cli v1.10.0 h1:BsJXuBrJxBLqE+62gHSOQBRzY+EvE/sF1MnjIexUSI8=
4+
fortio.org/cli v1.10.0/go.mod h1:DNxA/oD3cQaOtTean8Sgr78lJrwGLIs06X8U/G3va+M=
5+
fortio.org/dflag v1.8.1 h1:2ax/9IvIwMb0zPE3yZEDp9D+UewfZzj5B8UKT79tblU=
6+
fortio.org/dflag v1.8.1/go.mod h1:M2K61nLYfB4pAsp7mZzBtjqLP6dX11R9Vm+1I4F+sVg=
7+
fortio.org/fortio v1.69.4 h1:G0DXdTn8/QtiCh+ykBXft8NcOCojfAhQKseHuxFVePE=
8+
fortio.org/fortio v1.69.4/go.mod h1:kgXo4E9kDVE7QJMlK+6d7Hwkdepvug46knsvVAxqYNs=
9+
fortio.org/log v1.17.2 h1:JPX/ApDXDoGzsNtXw0AJI4ai6tl9wHp4Ch6bVs1OK0Y=
10+
fortio.org/log v1.17.2/go.mod h1:1V7bPfFI7ZVTdtN9DnUCAN0ilEMs5VgKjHIDRO7Mjzk=
11+
fortio.org/safecast v1.0.0 h1:dr3131WPX8iS1pTf76+39WeXbTrerDYLvi9s7Oi3wiY=
12+
fortio.org/safecast v1.0.0/go.mod h1:xZmcPk3vi4kuUFf+tq4SvnlVdwViqf6ZSZl91Jr9Jdg=
13+
fortio.org/scli v1.16.1 h1:TzaGHhFXKlTNx7mQ5BzVnXz2/qXZHG/mS30nWfJS0kE=
14+
fortio.org/scli v1.16.1/go.mod h1:jrdQnLbJuapUoaOcm2NwfTpCYrc5cNNc3datQkh3sQs=
15+
fortio.org/sets v1.3.0 h1:UiEtck/ndNM3Tg53mJu3I7Zz1ED8+YSQLtPKSjd8LTE=
16+
fortio.org/sets v1.3.0/go.mod h1:y8fFzm4bPTk3Qfr/tF3Xz7oWwgzpy++QdkwURKhNbP4=
17+
fortio.org/struct2env v0.4.2 h1:Xh7HlS9vf2ZdRvRfmoGIasNDO8t6z36M713utVODRCo=
18+
fortio.org/struct2env v0.4.2/go.mod h1:lENUe70UwA1zDUCX+8AsO663QCFqYaprk5lnPhjD410=
1719
fortio.org/version v1.0.4 h1:FWUMpJ+hVTNc4RhvvOJzb0xesrlRmG/a+D6bjbQ4+5U=
1820
fortio.org/version v1.0.4/go.mod h1:2JQp9Ax+tm6QKiGuzR5nJY63kFeANcgrZ0osoQFDVm0=
1921
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2022
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21-
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
22-
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
23+
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
24+
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
2325
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2426
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
27+
github.com/kortschak/goroutine v1.1.2 h1:lhllcCuERxMIK5cYr8yohZZScL1na+JM5JYPRclWjck=
28+
github.com/kortschak/goroutine v1.1.2/go.mod h1:zKpXs1FWN/6mXasDQzfl7g0LrGFIOiA6cLs9eXKyaMY=
2529
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
2630
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
2731
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -30,15 +34,13 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
3034
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
3135
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
3236
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
33-
golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658 h1:i7K6wQLN/0oxF7FT3tKkfMCstxoT4VGG36YIB9ZKLzI=
34-
golang.org/x/crypto/x509roots/fallback v0.0.0-20240626151235-a6a393ffd658/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
35-
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
36-
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
37-
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
38-
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
39-
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
40-
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
41-
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
42-
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
37+
golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb h1:Iu0p/klM0SM7atONioa/bPhLS7cjhnip99x1OIGibwg=
38+
golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
39+
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
40+
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
41+
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
42+
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
43+
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
44+
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
4345
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4446
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)