-
Notifications
You must be signed in to change notification settings - Fork 11
feat(tray): enhanced menu #625
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -22,6 +22,7 @@ import ( | |||||
| "fmt" | ||||||
| "io" | ||||||
| "net/http" | ||||||
| "net/url" | ||||||
| ) | ||||||
|
|
||||||
| type Message struct { | ||||||
|
|
@@ -73,25 +74,24 @@ func NewMessage(token string, opts ...MessageOption) (*Message, error) { | |||||
| } | ||||||
|
|
||||||
| func Send(accessToken string, projectId string, msg *Message) error { | ||||||
| fcmEndpoint := fmt.Sprintf( | ||||||
| "https://fcm.googleapis.com/v1/projects/%s/messages:send", | ||||||
| projectId, | ||||||
| ) | ||||||
| fcmURL := &url.URL{ | ||||||
| Scheme: "https", | ||||||
| Host: "fcm.googleapis.com", | ||||||
| Path: fmt.Sprintf("/v1/projects/%s/messages:send", projectId), | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents
Suggested change
|
||||||
| } | ||||||
|
Comment on lines
+77
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: fd -g 'go.mod' --exec cat {}Repository: blinklabs-io/adder Length of output: 4886 🏁 Script executed: cat -n output/push/fcm/message.go | sed -n '70,115p'Repository: blinklabs-io/adder Length of output: 1411
The Fix with -Path: fmt.Sprintf("/v1/projects/%s/messages:send", projectId),
+Path: fmt.Sprintf("/v1/projects/%s/messages:send", url.PathEscape(projectId)),Or use fcmURL, err := url.JoinPath("https://fcm.googleapis.com", "v1/projects", projectId, "messages:send")
if err != nil {
return err
}Update the -// `#nosec` G704 -- Request targets the fixed FCM host with a validated path.
+// `#nosec` G704 -- Request targets a fixed external host; projectId is path-escaped.🤖 Prompt for AI Agents |
||||||
|
|
||||||
| // Convert the message to JSON | ||||||
| payload, err := json.Marshal(msg) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
|
|
||||||
| fmt.Println(string(payload)) | ||||||
|
|
||||||
| // Create a new HTTP request | ||||||
| ctx := context.Background() | ||||||
| req, err := http.NewRequestWithContext( | ||||||
| ctx, | ||||||
| http.MethodPost, | ||||||
| fcmEndpoint, | ||||||
| fcmURL.String(), | ||||||
| bytes.NewBuffer(payload), | ||||||
| ) | ||||||
| if err != nil { | ||||||
|
|
@@ -104,6 +104,7 @@ func Send(accessToken string, projectId string, msg *Message) error { | |||||
|
|
||||||
| // Execute the request | ||||||
| client := &http.Client{} | ||||||
| // #nosec G704 -- Request targets the fixed FCM host with a validated path. | ||||||
| resp, err := client.Do(req) | ||||||
| if err != nil { | ||||||
| return err | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -332,6 +332,7 @@ func (w *WebhookOutput) SendWebhook(e *event.Event) error { | |
| } | ||
| client := &http.Client{Transport: customTransport} | ||
| // Send payload | ||
| // #nosec G704 -- Webhook URL is user-configured and intentionally allowed. | ||
| resp, err := client.Do(req) | ||
|
Comment on lines
+335
to
336
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's look at the webhook.go file to see the context around lines 320-350
fd -n "webhook.go" | head -20Repository: blinklabs-io/adder Length of output: 292 🏁 Script executed: # Get the actual file paths to locate webhook.go
git ls-files | grep -i webhookRepository: blinklabs-io/adder Length of output: 138 🏁 Script executed: # Once we have the path, examine the code context
cat -n output/webhook/webhook.go | sed -n '320,350p'Repository: blinklabs-io/adder Length of output: 1378 🏁 Script executed: # Also check line 323 to verify the existing nosec pattern
cat -n output/webhook/webhook.go | sed -n '320,330p'Repository: blinklabs-io/adder Length of output: 654 🌐 Web query:
💡 Result:
What it flags (conceptually)G704 reports an issue when tainted (externally influenced) data can reach a network/HTTP “sink” that initiates an outbound request / connection. [1][3] Taint sources used by G704G704’s SSRF taint config treats data as tainted when it originates from, or flows from: [1]
Sinks checked by G704 (where tainted data is dangerous)The rule checks specific arguments (“CheckArgs”) for taint at these sinks, including: [1]
SanitizersThe SSRF rule config effectively ships with no standard-library sanitizers (it explicitly notes that parsing like Examples (from gosec tests)
🏁 Script executed: # Search for URL validation patterns in chainsync and mempool modules mentioned in the review
fd -n "\.go$" | xargs rg -l "url\.Parse|scheme.*http|urlutil" | head -20Repository: blinklabs-io/adder Length of output: 402 🏁 Script executed: # Check for URL scheme validation in the codebase
rg "Scheme.*http|scheme.*http" -A 2 -B 2 | head -50Repository: blinklabs-io/adder Length of output: 1100 🏁 Script executed: # Find where w.url is set in webhook.go
cat -n output/webhook/webhook.go | grep -E "w\.url|\.url\s*=" | head -20Repository: blinklabs-io/adder Length of output: 258 🏁 Script executed: # Look at the WebhookOutput struct definition to see w.url
rg "type WebhookOutput" -A 10 output/webhook/webhook.goRepository: blinklabs-io/adder Length of output: 353 🏁 Script executed: # Check mempool.go for URL validation
cat -n input/mempool/mempool.go | sed -n '1,100p' | grep -E "Scheme|http|https"Repository: blinklabs-io/adder Length of output: 137 🏁 Script executed: # Get a broader view of mempool URL handling
rg "http\.|url\.|Parse" -B 2 -A 2 input/mempool/mempool.go | head -40Repository: blinklabs-io/adder Length of output: 1056 🏁 Script executed: # Check the SendWebhook function signature and full context
cat -n output/webhook/webhook.go | sed -n '300,360p'Repository: blinklabs-io/adder Length of output: 2203 🏁 Script executed: # Find where WebhookOutput is instantiated or w.url is set
rg "WebhookOutput\{|\.url\s*=" output/webhook/ -B 2 -A 2Repository: blinklabs-io/adder Length of output: 613 🏁 Script executed: # Check the options or initialization code
cat -n output/webhook/options.go | head -80Repository: blinklabs-io/adder Length of output: 2379 🏁 Script executed: # Look at where webhook URL comes from in configuration
rg "url" output/webhook/options.go -B 2 -A 2Repository: blinklabs-io/adder Length of output: 239 Address the response body leak and consider adding URL validation for consistency with other modules. The G704 suppression is justified for a config-only URL, but two issues remain:
Example fix respBody, err := io.ReadAll(resp.Body)
+ defer resp.Body.Close()
if err != nil {
return fmt.Errorf("%w", err)
}
- defer resp.Body.Close()🤖 Prompt for AI Agents |
||
| if err != nil { | ||
| return fmt.Errorf("%w", err) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| // Copyright 2026 Blink Labs Software | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package tray | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
|
|
||
| "gopkg.in/yaml.v3" | ||
| ) | ||
|
|
||
| // MonitorTemplate represents a pre-configured monitoring template. | ||
| type MonitorTemplate int | ||
|
|
||
| const ( | ||
| WatchWallet MonitorTemplate = iota | ||
| TrackDRep | ||
| MonitorPool | ||
| ) | ||
|
|
||
| // String returns the display name of the template. | ||
| func (t MonitorTemplate) String() string { | ||
| switch t { | ||
| case WatchWallet: | ||
| return "Watch Wallet" | ||
| case TrackDRep: | ||
| return "Track DRep" | ||
| case MonitorPool: | ||
| return "Monitor Pool" | ||
| default: | ||
| return "Unknown" | ||
| } | ||
| } | ||
|
|
||
| // AdderConfigParams holds the parameters for generating an adder | ||
| // configuration. | ||
| type AdderConfigParams struct { | ||
| Network string | ||
| Template MonitorTemplate | ||
| Param string // The address/drep/pool value depending on Template | ||
| Output string // Output plugin name (default: "log") | ||
| Format string // Output format (default: "json") | ||
| } | ||
|
|
||
| // adderConfig is the internal structure matching adder's config.yaml | ||
| // format. We use map types for the plugins section to match adder's | ||
| // flexible config. | ||
| type adderConfig struct { | ||
| Input string `yaml:"input"` | ||
| Output string `yaml:"output"` | ||
| API adderAPIConfig `yaml:"api"` | ||
| Logging adderLoggingConfig `yaml:"logging"` | ||
| Plugins map[string]map[string]interface{} `yaml:"plugins"` | ||
| } | ||
|
|
||
| type adderAPIConfig struct { | ||
| Address string `yaml:"address"` | ||
| Port uint `yaml:"port"` | ||
| } | ||
|
|
||
| type adderLoggingConfig struct { | ||
| Level string `yaml:"level"` | ||
| } | ||
|
|
||
| // GenerateAdderConfig builds an adder configuration from the given | ||
| // parameters. The API server is always enabled because adder-tray | ||
| // connects to its /events endpoint for desktop notifications and | ||
| // /healthcheck for status monitoring. | ||
| func GenerateAdderConfig(params AdderConfigParams) ([]byte, error) { | ||
| if params.Network == "" { | ||
| return nil, errors.New("network is required") | ||
| } | ||
| if params.Param == "" { | ||
| return nil, errors.New("filter parameter is required") | ||
| } | ||
|
|
||
| output := params.Output | ||
| if output == "" { | ||
| output = "log" | ||
| } | ||
| format := params.Format | ||
| if format == "" { | ||
| format = "json" | ||
| } | ||
|
|
||
| cfg := adderConfig{ | ||
| Input: "chainsync", | ||
| Output: output, | ||
| API: adderAPIConfig{ | ||
| Address: "127.0.0.1", | ||
| Port: 8080, | ||
| }, | ||
| Logging: adderLoggingConfig{ | ||
| Level: "info", | ||
| }, | ||
| Plugins: map[string]map[string]interface{}{ | ||
| "input": { | ||
| "chainsync": map[string]interface{}{ | ||
| "network": params.Network, | ||
| }, | ||
| }, | ||
| "output": { | ||
| output: map[string]interface{}{ | ||
| "format": format, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| // Add filter config based on template | ||
| filterKey := templateFilterKey(params.Template) | ||
| if filterKey == "" { | ||
| return nil, fmt.Errorf("unsupported monitor template: %d", params.Template) | ||
| } | ||
| cfg.Plugins["filter"] = map[string]interface{}{ | ||
| "chainsync": map[string]interface{}{ | ||
| filterKey: params.Param, | ||
| }, | ||
| } | ||
|
|
||
| return yaml.Marshal(&cfg) | ||
| } | ||
|
|
||
| // templateFilterKey returns the filter configuration key for a | ||
| // template. Returns an empty string for unrecognized templates. | ||
| func templateFilterKey(t MonitorTemplate) string { | ||
| switch t { | ||
| case WatchWallet: | ||
| return "address" | ||
| case TrackDRep: | ||
| return "drep" | ||
| case MonitorPool: | ||
| return "pool" | ||
| default: | ||
| return "" | ||
| } | ||
| } | ||
|
|
||
| // AdderConfigPath returns the path to the adder config file in the | ||
| // config directory. | ||
| func AdderConfigPath() string { | ||
| return filepath.Join(ConfigDir(), "config.yaml") | ||
| } | ||
|
|
||
| // WriteAdderConfig writes the adder configuration to the config | ||
| // directory. | ||
| func WriteAdderConfig(params AdderConfigParams) error { | ||
| data, err := GenerateAdderConfig(params) | ||
| if err != nil { | ||
| return fmt.Errorf("generating config: %w", err) | ||
| } | ||
|
|
||
| dir := ConfigDir() | ||
| if err := os.MkdirAll(dir, 0o700); err != nil { | ||
| return fmt.Errorf("creating config directory: %w", err) | ||
| } | ||
| if err := os.Chmod(dir, 0o700); err != nil { | ||
| return fmt.Errorf("setting config directory permissions: %w", err) | ||
| } | ||
|
|
||
| if err := os.WriteFile(AdderConfigPath(), data, 0o600); err != nil { | ||
| return fmt.Errorf("writing config file: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } |
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.
Same incorrect gosec rule ID
G704— should beG107.Same issue as noted in
input/mempool/mempool.go. The rule for HTTP request URL taint input isG107.🔧 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents