-
Notifications
You must be signed in to change notification settings - Fork 5
[DRAFT][DO NOT MERGE] Remote hooks golang scripts #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module semgrep/login | ||
|
|
||
| go 1.26.1 |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| // Standalone semgrep login program. | ||
| // | ||
| // Opens a browser for the user to authenticate with semgrep.dev, polls for the | ||
| // resulting token, validates it, and writes it to ~/.semgrep/settings.yml. | ||
| // | ||
| // Usage: go run . (or compile with go build) | ||
| package main | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "crypto/rand" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
| "os" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "regexp" | ||
| "runtime" | ||
| "strings" | ||
| "time" | ||
| ) | ||
|
|
||
| const ( | ||
| waitBetweenRetrySec = 6 | ||
| maxRetries = 30 // ~3 minutes | ||
| ) | ||
|
|
||
| func semgrepURL() string { | ||
| if u := os.Getenv("SEMGREP_URL"); u != "" { | ||
| return u | ||
| } | ||
| return "https://semgrep.dev" | ||
| } | ||
|
|
||
| func getSettingsPath() string { | ||
| if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" { | ||
| return filepath.Join(xdg, "semgrep", "settings.yml") | ||
| } | ||
| home, _ := os.UserHomeDir() | ||
| return filepath.Join(home, ".semgrep", "settings.yml") | ||
| } | ||
|
|
||
| // readToken extracts the api_token value from a simple YAML settings file. | ||
| // Returns "" if not found or file doesn't exist. | ||
| func readToken(path string) string { | ||
| data, err := os.ReadFile(path) | ||
| if err != nil { | ||
| return "" | ||
| } | ||
| for _, line := range strings.Split(string(data), "\n") { | ||
| line = strings.TrimSpace(line) | ||
| if strings.HasPrefix(line, "api_token:") { | ||
| val := strings.TrimSpace(strings.TrimPrefix(line, "api_token:")) | ||
| // Strip optional surrounding quotes | ||
| val = strings.Trim(val, `'"`) | ||
| return val | ||
| } | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| // writeToken writes (or updates) api_token in the settings YAML file, | ||
| // preserving any other existing keys. | ||
| func writeToken(path, token string) error { | ||
| if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| var lines []string | ||
| if data, err := os.ReadFile(path); err == nil { | ||
| lines = strings.Split(string(data), "\n") | ||
| // Remove trailing empty element from split | ||
| if len(lines) > 0 && lines[len(lines)-1] == "" { | ||
| lines = lines[:len(lines)-1] | ||
| } | ||
| } | ||
|
|
||
| found := false | ||
| for i, line := range lines { | ||
| if strings.HasPrefix(strings.TrimSpace(line), "api_token:") { | ||
| lines[i] = "api_token: " + token | ||
| found = true | ||
| break | ||
| } | ||
| } | ||
| if !found { | ||
| lines = append(lines, "api_token: "+token) | ||
| } | ||
|
|
||
| tmp, err := os.CreateTemp(filepath.Dir(path), "settings*.yml") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| tmpName := tmp.Name() | ||
| _, err = fmt.Fprintln(tmp, strings.Join(lines, "\n")) | ||
| tmp.Close() | ||
| if err != nil { | ||
| os.Remove(tmpName) | ||
| return err | ||
| } | ||
| return os.Rename(tmpName, path) | ||
| } | ||
|
|
||
| func validateToken(token string) bool { | ||
| if token == "" { | ||
| return false | ||
| } | ||
| req, err := http.NewRequest("GET", semgrepURL()+"/api/agent/deployments/current", nil) | ||
| if err != nil { | ||
| return false | ||
| } | ||
| req.Header.Set("Authorization", "Bearer "+token) | ||
| client := &http.Client{Timeout: 10 * time.Second} | ||
| resp, err := client.Do(req) | ||
| if err != nil { | ||
| return false | ||
| } | ||
| resp.Body.Close() | ||
| return resp.StatusCode >= 200 && resp.StatusCode < 300 | ||
| } | ||
|
|
||
| func generateUUID() string { | ||
| b := make([]byte, 16) | ||
| _, _ = rand.Read(b) | ||
| b[6] = (b[6] & 0x0f) | 0x40 // version 4 | ||
| b[8] = (b[8] & 0x3f) | 0x80 // variant RFC4122 | ||
| return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", | ||
| b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) | ||
| } | ||
|
|
||
| func openBrowser(url string) { | ||
| var cmd string | ||
| var args []string | ||
| switch runtime.GOOS { | ||
| case "darwin": | ||
| cmd, args = "open", []string{url} | ||
| case "windows": | ||
| cmd, args = "cmd", []string{"/c", "start", url} | ||
| default: | ||
| cmd, args = "xdg-open", []string{url} | ||
| } | ||
| _ = exec.Command(cmd, args...).Start() | ||
| } | ||
|
|
||
| var hexToken = regexp.MustCompile(`^[0-9a-f]+$`) | ||
|
|
||
| func main() { | ||
| settingsPath := getSettingsPath() | ||
|
|
||
| existing := readToken(settingsPath) | ||
| if existing != "" && validateToken(existing) { | ||
| fmt.Printf("Already logged in. Token saved at %s.\n", settingsPath) | ||
| fmt.Println("Run `semgrep logout` first if you want to log in again.") | ||
| os.Exit(0) | ||
| } | ||
|
|
||
| sessionID := generateUUID() | ||
| loginURL := fmt.Sprintf("%s/login?cli-token=%s", semgrepURL(), sessionID) | ||
|
|
||
| fmt.Println("Opening browser to log in to semgrep.dev...") | ||
| fmt.Printf(" %s\n", loginURL) | ||
| openBrowser(loginURL) | ||
| fmt.Println("\nWaiting for login... (you have ~3 minutes)\n") | ||
|
|
||
| client := &http.Client{Timeout: 10 * time.Second} | ||
| pollURL := semgrepURL() + "/api/agent/tokens/requests" | ||
|
|
||
| for attempt := 0; attempt < maxRetries; attempt++ { | ||
| body, _ := json.Marshal(map[string]string{"token_request_key": sessionID}) | ||
| resp, err := client.Post(pollURL, "application/json", bytes.NewReader(body)) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Semgrep login: Network error: %v\n", err) | ||
| os.Exit(2) | ||
| } | ||
|
|
||
| switch resp.StatusCode { | ||
| case http.StatusOK: | ||
| respBody, _ := io.ReadAll(resp.Body) | ||
| resp.Body.Close() | ||
|
|
||
| var result map[string]interface{} | ||
| if err := json.Unmarshal(respBody, &result); err != nil { | ||
| fmt.Fprintln(os.Stderr, "Semgrep login: Error: failed to parse server response.") | ||
| os.Exit(2) | ||
| } | ||
|
|
||
| token, _ := result["token"].(string) | ||
| if token == "" { | ||
| fmt.Fprintln(os.Stderr, "Semgrep login: Error: server returned 200 but no token in response.") | ||
| os.Exit(2) | ||
| } | ||
| if len(token) != 64 || !hexToken.MatchString(token) { | ||
| fmt.Fprintln(os.Stderr, "Semgrep login: Error: received token has unexpected format.") | ||
| os.Exit(2) | ||
| } | ||
|
|
||
| fmt.Println("Token received. Validating...") | ||
| if !validateToken(token) { | ||
| fmt.Fprintln(os.Stderr, "Semgrep login: Error: token validation failed.") | ||
| os.Exit(2) | ||
| } | ||
|
|
||
| if err := writeToken(settingsPath, token); err != nil { | ||
| fmt.Fprintf(os.Stderr, "Semgrep login: Error writing token: %v\n", err) | ||
| os.Exit(2) | ||
| } | ||
| fmt.Printf("Logged in. Token saved to %s.\n", settingsPath) | ||
| os.Exit(0) | ||
|
|
||
| case http.StatusNotFound: | ||
| resp.Body.Close() | ||
| // User hasn't completed browser login yet — keep polling. | ||
|
|
||
| default: | ||
| resp.Body.Close() | ||
| fmt.Fprintf(os.Stderr, "Semgrep login: Unexpected response from server: %d\n", resp.StatusCode) | ||
| os.Exit(2) | ||
| } | ||
|
|
||
| fmt.Printf(" Waiting... (%d/%d)\r", attempt+1, maxRetries) | ||
| time.Sleep(waitBetweenRetrySec * time.Second) | ||
| } | ||
|
|
||
| fmt.Fprintln(os.Stderr, "\nSemgrep login: Login timed out. Please try again.") | ||
| os.Exit(2) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module semgrep/run-semgrep | ||
|
|
||
| go 1.26.1 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Semgrep identified an issue in your code:
Server-Side-Request-Forgery (SSRF) exploits backend systems that initiate requests to third
parties.
If user input is used in constructing or sending these requests, an attacker could supply
malicious
data to force the request to other systems or modify request data to cause unwanted actions.
Ensure user input is not used directly in constructing URLs or URIs when initiating requests
to third party
systems from back end systems. Care must also be taken when constructing payloads using user
input. Where
possible restrict to known URIs or payloads. Consider using a server side map where key's are
used to return
URLs such as
https://site/goto?key=1where{key: 1, url: 'http://some.url/', key: 2, url: 'http://...'}.If you must use user supplied input for requesting URLs, it is strongly recommended that the
HTTP client
chosen allows you to customize and block certain IP ranges at the network level. By blocking
RFC 1918
addresses or other network address ranges, you can limit the severity of a successful SSRF
attack. Care must
also be taken to block certain protocol or address formatting such as IPv6.
If you can not block address ranges at the client level, you may want to run the HTTP client
as a protected
user, or in a protected network where you can apply IP Table or firewall rules to block access
to dangerous
addresses. Finally, if none of the above protections are available, you could also run a
custom HTTP proxy
and force all requests through it to handle blocking dangerous addresses.
Example HTTP client that disallows access to loopback and RFC-1918 addresses
For more information on SSRF see OWASP:
https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
Dataflow graph
flowchart LR classDef invis fill:white, stroke: none classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none subgraph File0["<b>plugin/scripts/login/main.go</b>"] direction LR %% Source subgraph Source direction LR v0["<a href=https://github.com/semgrep/mcp-marketplace/blob/e66c0176b03b267cfeb16a188b0c804936e65dd9/plugin/scripts/login/main.go#L31 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 31] os.Getenv("SEMGREP_URL")</a>"] end %% Intermediate subgraph Traces0[Traces] direction TB v2["<a href=https://github.com/semgrep/mcp-marketplace/blob/e66c0176b03b267cfeb16a188b0c804936e65dd9/plugin/scripts/login/main.go#L31 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 31] u</a>"] v3["<a href=https://github.com/semgrep/mcp-marketplace/blob/e66c0176b03b267cfeb16a188b0c804936e65dd9/plugin/scripts/login/main.go#L110 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 110] semgrepURL</a>"] end v2 --> v3 %% Sink subgraph Sink direction LR v1["<a href=https://github.com/semgrep/mcp-marketplace/blob/e66c0176b03b267cfeb16a188b0c804936e65dd9/plugin/scripts/login/main.go#L110 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 110] http.NewRequest("GET", semgrepURL()+"/api/agent/deployments/current", nil)</a>"] end end %% Class Assignment Source:::invis Sink:::invis Traces0:::invis File0:::invis %% Connections Source --> Traces0 Traces0 --> SinkTo resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasonsAlternatively, triage in Semgrep AppSec Platform to ignore the finding created by G107-1.
You can view more details about this finding in the Semgrep AppSec Platform.