From 0b9ab12c5854c033308dc4989dd4dfdd180878fe Mon Sep 17 00:00:00 2001 From: Jin Hai Date: Thu, 2 Jul 2026 13:44:05 +0800 Subject: [PATCH] Go: fix lint (#16533) ### Summary as title. --------- Signed-off-by: Jin Hai --- internal/service/agent.go | 52 +++++++++++++-------------- internal/storage/minio.go | 6 ++-- internal/utility/mcp_call.go | 4 +-- internal/utility/mcp_client.go | 29 ++++++++------- internal/utility/oauth/client.go | 20 +++++------ internal/utility/oauth/client_test.go | 6 ++-- internal/utility/oauth/github.go | 4 +-- internal/utility/oauth/oidc.go | 4 +-- internal/utility/otp.go | 4 +-- internal/utility/smtp.go | 48 ++++++++++--------------- internal/utility/ssrf.go | 24 ++++++------- internal/utility/ssrf_test.go | 4 +-- internal/utility/token.go | 2 +- internal/utility/version.go | 3 +- 14 files changed, 102 insertions(+), 108 deletions(-) diff --git a/internal/service/agent.go b/internal/service/agent.go index bd72e1342f..e663c6692a 100644 --- a/internal/service/agent.go +++ b/internal/service/agent.go @@ -52,7 +52,7 @@ func genID32() string { // webhookPayloadKey is the unexported context key RunAgent reads to // inject root["webhook_payload"]. Only the AgentService.RunAgentWithWebhook // public wrapper sets it; the chat / agent-run paths leave it absent so -// existing callers see no behaviour change. +// existing callers see no behavior change. // // We deliberately do NOT surface the payload as a new RunAgent parameter // — keeping the public signature stable means existing tests @@ -256,8 +256,8 @@ func toAgentItem(c *dao.UserCanvasListItem) *AgentItem { // ListAgents returns agent canvases visible to userID. // Mirrors Python agent_api.list_agents — validates owner_ids against joined tenants, // then delegates to the DAO. -func (s *AgentService) ListAgents(userID string, keywords string, page, pageSize int, orderby string, desc bool, ownerIDs []string, canvasCategory, canvasType string, tags []string) (*ListAgentsResponse, common.ErrorCode, error) { - // Build the set of tenant IDs the user is authorised to query. +func (s *AgentService) ListAgents(userID string, keywords string, page, pageSize int, orderBy string, desc bool, ownerIDs []string, canvasCategory, canvasType string, tags []string) (*ListAgentsResponse, common.ErrorCode, error) { + // Build the set of tenant IDs the user is authorized to query. tenantIDs, err := s.userTenantDAO.GetTenantIDsByUserID(userID) if err != nil { return nil, common.CodeServerError, fmt.Errorf("failed to get tenant IDs: %w", err) @@ -288,7 +288,7 @@ func (s *AgentService) ListAgents(userID string, keywords string, page, pageSize userID, page, pageSize, - orderby, + orderBy, desc, keywords, canvasCategory, @@ -329,10 +329,10 @@ func (s *AgentService) CreateAgent(ctx context.Context, req *CreateAgentRequest) return nil, common.CodeArgumentError, errors.New("create agent: nil request") } if req.DSL == nil { - return nil, common.CodeArgumentError, errors.New("No DSL data in request.") + return nil, common.CodeArgumentError, errors.New("no DSL data in request") } if req.Title == nil || strings.TrimSpace(*req.Title) == "" { - return nil, common.CodeArgumentError, errors.New("No title in request.") + return nil, common.CodeArgumentError, errors.New("no title in request") } title := strings.TrimSpace(*req.Title) req.Title = &title @@ -376,7 +376,7 @@ func (s *AgentService) CreateAgent(ctx context.Context, req *CreateAgentRequest) // handler layer can map every "not yours" case to the same 404 envelope // — see plan §4.8 IDOR mitigation. // -// DAO-error sanitisation (v3.5.2 follow-up): the raw userTenantDAO and +// DAO-error sanitization (v3.5.2 follow-up): the raw userTenantDAO and // canvasDAO errors are wrapped with ErrAgentStorageError so mapAgentError // classifies them as CodeServerError (500) with a sanitized message — // the original DAO error string (DSN, table name, gorm stack frame) @@ -385,7 +385,7 @@ func (s *AgentService) CreateAgent(ctx context.Context, req *CreateAgentRequest) // // This is the FIRST storage access path RunAgent hits, so leaving the // raw errors here would have left a DAO-string leak in the very first -// hop — the earlier af2ac2eda + 804854a5e commits only sanitised the +// hop — the earlier af2ac2eda + 804854a5e commits only sanitized the // version-read path, missing the canvas-access path. func (s *AgentService) loadCanvasForUser(ctx context.Context, userID, canvasID string) (*entity.UserCanvas, error) { if canvasID == "" { @@ -466,7 +466,7 @@ func (s *AgentService) UpdateAgent(ctx context.Context, userID, canvasID string, // extra GET. // // Reset does NOT create a new user_canvas_version row — that mirrors -// the Python behaviour and UpdateAgent: versions are owned by +// the Python behavior and UpdateAgent: versions are owned by // PublishAgent. It also does NOT touch the in-flight run state of any // currently executing canvas session; that is owned by the Python task // executor and is out of scope for the Go port. @@ -474,7 +474,7 @@ func (s *AgentService) UpdateAgent(ctx context.Context, userID, canvasID string, // Errors propagate the same way as GetAgent: a missing canvas, or a // canvas that the user has no access to, surfaces as // dao.ErrUserCanvasNotFound so mapAgentError emits the same 404 the -// Python handler does for "canvas not found.". +// Python handler does for "canvas not found." func (s *AgentService) ResetAgent(ctx context.Context, userID, canvasID string) (entity.JSONMap, error) { row, err := s.loadCanvasForUser(ctx, userID, canvasID) if err != nil { @@ -538,13 +538,13 @@ type PublishAgentRequest struct { // overwritten (§2.9); the parent canvas DSL/title/description/release // fields are updated atomically with the new version row. func (s *AgentService) PublishAgent(ctx context.Context, userID, canvasID string, req *PublishAgentRequest) (*entity.UserCanvasVersion, error) { - canvas, err := s.loadCanvasForUser(ctx, userID, canvasID) + canvasInstance, err := s.loadCanvasForUser(ctx, userID, canvasID) if err != nil { return nil, err } - dsl := canvas.DSL - title := canvas.Title - description := canvas.Description + dsl := canvasInstance.DSL + title := canvasInstance.Title + description := canvasInstance.Description if req != nil { if req.DSL != nil { dsl = dslpkg.NormalizeForCanvas(req.DSL) @@ -563,15 +563,15 @@ func (s *AgentService) PublishAgent(ctx context.Context, userID, canvasID string Description: description, DSL: dsl, } - if err := dao.DB.Transaction(func(tx *gorm.DB) error { - if err := s.versionDAO.CreateTx(tx, row); err != nil { + if err = dao.DB.Transaction(func(tx *gorm.DB) error { + if err = s.versionDAO.CreateTx(tx, row); err != nil { return fmt.Errorf("publish agent %s: insert version: %w", canvasID, err) } - canvas.DSL = dsl - canvas.Title = title - canvas.Description = description - canvas.Release = true - if err := s.canvasDAO.UpdateTx(tx, canvas); err != nil { + canvasInstance.DSL = dsl + canvasInstance.Title = title + canvasInstance.Description = description + canvasInstance.Release = true + if err = s.canvasDAO.UpdateTx(tx, canvasInstance); err != nil { return fmt.Errorf("publish agent %s: update parent: %w", canvasID, err) } return nil @@ -766,7 +766,7 @@ func (s *AgentService) RunAgent(ctx context.Context, userID, canvasID, sessionID } // Webhook payload injection. Only RunAgentWithWebhook sets this // context value; the chat / agent-run paths leave it nil so the - // existing surface is unchanged. The Begin component reads + // existing surface is unchanged. The 'BEGIN' component reads // inputs["webhook_payload"] and writes it to state.Sys so // downstream components can read sys.webhook_payload. if payload, ok := ctx.Value(webhookPayloadKey{}).(map[string]any); ok && payload != nil { @@ -804,7 +804,7 @@ func (s *AgentService) RunAgent(ctx context.Context, userID, canvasID, sessionID // drives. // // Phase 4.4 V2: this is the real Compile/Invoke path. The previous -// V1 echo placeholder returned a synthesised answer without ever +// V1 echo placeholder returned a synthesized answer without ever // calling canvas.Compile — every RunAgent invocation pretended to // run the canvas. The V2 body actually compiles the DSL, attaches // the CanvasState to ctx via runtime.WithState (so component bodies @@ -817,10 +817,10 @@ func (s *AgentService) RunAgent(ctx context.Context, userID, canvasID, sessionID // safely and, when both versionRow and dsl are empty, fall back to // a graceful "no published version" placeholder so the SSE surface // still flows (TestRunAgent_NoVersionPublishedPlaceholder pins this -// behaviour). The placeholder is written into state.Outputs under +// behavior). The placeholder is written into state.Outputs under // (cpn="answer", bucket="answer") so the answer extraction in // first-pass lookup picks it up; the same trick the V1 placeholder -// used (the v3.5.2 fix landed this and we keep it). +// used (the v3.5.2 fix landed this, and we keep it). // // Resume path: Runner.Run injects (__resume_interrupt_id__, // __resume_data__) into root when userInput arrives on a session @@ -1001,7 +1001,7 @@ func (s *AgentService) buildRunFunc(canvasID string, versionRow *entity.UserCanv } // On a resume, the user input is the resume payload for the // previously-paused UserFillUp node — it does NOT represent - // a fresh sys.query. The begin node writes inputs["query"] + // a fresh sys.query. The 'BEGIN' node writes inputs["query"] // straight into state.Sys["query"] (begin.go:76), and // UserFillUp:Menu's initialUserFillUpData reads sys.query // back to drive the menu's initial-input fast path. If we diff --git a/internal/storage/minio.go b/internal/storage/minio.go index abdb28504a..bc9a37a50d 100644 --- a/internal/storage/minio.go +++ b/internal/storage/minio.go @@ -175,7 +175,7 @@ func (m *MinioStorage) Get(bucket, fnm string, tenantID ...string) ([]byte, erro for i := 0; i < 2; i++ { obj, err := m.client.GetObject(ctx, bucket, fnm, minio.GetObjectOptions{}) if err != nil { - common.Warn("Failed to get object", zap.String("bucket", bucket), zap.String("key", fnm), zap.Error(err)) + common.Warn("failed to get object", zap.String("bucket", bucket), zap.String("key", fnm), zap.Error(err)) m.reconnect() time.Sleep(time.Second) continue @@ -183,8 +183,8 @@ func (m *MinioStorage) Get(bucket, fnm string, tenantID ...string) ([]byte, erro defer obj.Close() buf := new(bytes.Buffer) - if _, err := buf.ReadFrom(obj); err != nil { - common.Warn("Failed to read object data", zap.String("bucket", bucket), zap.String("key", fnm), zap.Error(err)) + if _, err = buf.ReadFrom(obj); err != nil { + common.Warn("failed to read object data", zap.String("bucket", bucket), zap.String("key", fnm), zap.Error(err)) m.reconnect() time.Sleep(time.Second) continue diff --git a/internal/utility/mcp_call.go b/internal/utility/mcp_call.go index 45a6f85c56..dcb85761dc 100644 --- a/internal/utility/mcp_call.go +++ b/internal/utility/mcp_call.go @@ -128,7 +128,7 @@ func callToolStreamableHTTP(ctx context.Context, endpoint string, headers map[st return nil, formatMCPError("initialize", initRes.Error) } - if _, _, err := streamableSend(ctx, client, endpoint, sessionID, headers, jsonRPCRequest{ + if _, _, err = streamableSend(ctx, client, endpoint, sessionID, headers, jsonRPCRequest{ JSONRPC: jsonRPCVersion, Method: "notifications/initialized", }, false); err != nil { @@ -137,7 +137,7 @@ func callToolStreamableHTTP(ctx context.Context, endpoint string, headers map[st var argsAny any if len(args) > 0 { - if err := json.Unmarshal(args, &argsAny); err != nil { + if err = json.Unmarshal(args, &argsAny); err != nil { return nil, fmt.Errorf("mcp tools/call: arguments are not valid JSON: %w", err) } } diff --git a/internal/utility/mcp_client.go b/internal/utility/mcp_client.go index 4274fcc324..1b484a3a20 100644 --- a/internal/utility/mcp_client.go +++ b/internal/utility/mcp_client.go @@ -295,7 +295,8 @@ func streamableSend(ctx context.Context, client *http.Client, endpoint, sessionI sessionID = sid } if strings.Contains(contentType, "text/event-stream") { - r, err := readJSONRPCFromSSE(resp.Body, payload.ID) + var r *jsonRPCResponse + r, err = readJSONRPCFromSSE(resp.Body, payload.ID) if err != nil { return "", nil, err } @@ -398,7 +399,7 @@ func fetchToolsSSE(ctx context.Context, endpoint string, headers map[string]stri // Register the waiter BEFORE issuing the POST so a fast server that // pushes its response before our wait() call doesn't drop the delivery. initWaiter := pending.register(0) - if err := postOnce(jsonRPCRequest{JSONRPC: jsonRPCVersion, ID: 0, Method: "initialize", Params: initializeParams()}); err != nil { + if err = postOnce(jsonRPCRequest{JSONRPC: jsonRPCVersion, ID: 0, Method: "initialize", Params: initializeParams()}); err != nil { pending.cancel(0) return nil, err } @@ -409,11 +410,11 @@ func fetchToolsSSE(ctx context.Context, endpoint string, headers map[string]stri if initRes.Error != nil { return nil, formatMCPError("initialize", initRes.Error) } - if err := postOnce(jsonRPCRequest{JSONRPC: jsonRPCVersion, Method: "notifications/initialized"}); err != nil { + if err = postOnce(jsonRPCRequest{JSONRPC: jsonRPCVersion, Method: "notifications/initialized"}); err != nil { return nil, err } listWaiter := pending.register(1) - if err := postOnce(jsonRPCRequest{JSONRPC: jsonRPCVersion, ID: 1, Method: "tools/list"}); err != nil { + if err = postOnce(jsonRPCRequest{JSONRPC: jsonRPCVersion, ID: 1, Method: "tools/list"}); err != nil { pending.cancel(1) return nil, err } @@ -445,11 +446,13 @@ func waitForEndpoint(ctx context.Context, stream *sseReader, base string) (strin if ref == "" { return "", errors.New("MCP SSE endpoint event has empty data") } - baseURL, err := url.Parse(base) + var baseURL *url.URL + baseURL, err = url.Parse(base) if err != nil { return "", fmt.Errorf("parse MCP SSE base url: %w", err) } - rel, err := url.Parse(ref) + var rel *url.URL + rel, err = url.Parse(ref) if err != nil { return "", fmt.Errorf("parse MCP SSE endpoint data: %w", err) } @@ -717,21 +720,21 @@ func parseToolsResult(raw json.RawMessage) ([]Tool, error) { return nil, fmt.Errorf("parse tools result: %w", err) } tools := make([]Tool, 0, len(envelope.Tools)) - for _, raw := range envelope.Tools { - name, _ := raw["name"].(string) + for _, rawMap := range envelope.Tools { + name, _ := rawMap["name"].(string) if name == "" { continue } - desc, _ := raw["description"].(string) + desc, _ := rawMap["description"].(string) var schema map[string]interface{} - if s, ok := raw["inputSchema"].(map[string]interface{}); ok { + if s, ok := rawMap["inputSchema"].(map[string]interface{}); ok { schema = s } tools = append(tools, Tool{ Name: name, Description: desc, InputSchema: schema, - Raw: raw, + Raw: rawMap, }) } return tools, nil @@ -748,7 +751,7 @@ func formatMCPError(method string, e *jsonRPCError) error { // when a low-level connection fails (authentication / network). func mapMCPConnectionError(err error) error { if errors.Is(err, context.DeadlineExceeded) { - return errors.New("Timeout connecting to MCP server") + return errors.New("timeout connecting to MCP server") } - return fmt.Errorf("Connection failed (possibly due to auth error). Please check authentication settings first: %v", err) + return fmt.Errorf("connection failed (possibly due to auth error). Please check authentication settings first: %v", err) } diff --git a/internal/utility/oauth/client.go b/internal/utility/oauth/client.go index ebcb4acfb0..f518bb7243 100644 --- a/internal/utility/oauth/client.go +++ b/internal/utility/oauth/client.go @@ -21,7 +21,7 @@ // - "oauth2": vanilla OAuth 2.0 authorization-code flow with a // provider-supplied /userinfo endpoint // - "oidc": OAuth 2.0 + OIDC discovery via .well-known/openid-configuration -// - "github": OAuth 2.0 plus GitHub's split user / emails endpoints +// - "GitHub": OAuth 2.0 plus GitHub's split user / emails endpoints // // Note on OIDC ID-token validation: the Python OIDCClient verifies the // id_token signature against the discovered JWKS and pulls extra claims out @@ -107,7 +107,7 @@ func NewClient(cfg Config) (Client, error) { case "github": return newGitHubClient(cfg) default: - return nil, fmt.Errorf("Unsupported type: %s", t) + return nil, fmt.Errorf("unsupported type: %s", t) } } @@ -169,23 +169,23 @@ func (c *oauthClient) ExchangeCodeForToken(ctx context.Context, code string) (*T req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.cfg.TokenURL, strings.NewReader(form.Encode())) if err != nil { - return nil, fmt.Errorf("Failed to exchange authorization code for token: %w", err) + return nil, fmt.Errorf("failed to exchange authorization code for token: %w", err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") resp, err := c.httpClient.Do(req) if err != nil { - return nil, fmt.Errorf("Failed to exchange authorization code for token: %w", err) + return nil, fmt.Errorf("failed to exchange authorization code for token: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if err != nil { - return nil, fmt.Errorf("Failed to exchange authorization code for token: %w", err) + return nil, fmt.Errorf("failed to exchange authorization code for token: %w", err) } if resp.StatusCode >= 400 { - return nil, fmt.Errorf("Failed to exchange authorization code for token: HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) + return nil, fmt.Errorf("failed to exchange authorization code for token: HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) } token := &TokenResponse{} @@ -198,11 +198,11 @@ func (c *oauthClient) ExchangeCodeForToken(ctx context.Context, code string) (*T token.IDToken = values.Get("id_token") token.Scope = values.Get("scope") } else { - return nil, fmt.Errorf("Failed to exchange authorization code for token: parse response: %w", jerr) + return nil, fmt.Errorf("failed to exchange authorization code for token: parse response: %w", jerr) } } if token.AccessToken == "" { - return nil, fmt.Errorf("Failed to exchange authorization code for token: empty access_token") + return nil, fmt.Errorf("failed to exchange authorization code for token: empty access_token") } return token, nil } @@ -211,11 +211,11 @@ func (c *oauthClient) ExchangeCodeForToken(ctx context.Context, code string) (*T // Mirrors OAuthClient.fetch_user_info / normalize_user_info. func (c *oauthClient) FetchUserInfo(ctx context.Context, accessToken, idToken string) (*UserInfo, error) { if c.cfg.UserinfoURL == "" { - return nil, fmt.Errorf("Failed to fetch user info: userinfo_url is required") + return nil, fmt.Errorf("failed to fetch user info: userinfo_url is required") } raw, err := c.fetchUserinfoRaw(ctx, c.cfg.UserinfoURL, accessToken) if err != nil { - return nil, fmt.Errorf("Failed to fetch user info: %w", err) + return nil, fmt.Errorf("failed to fetch user info: %w", err) } return normalizeUserInfo(raw), nil } diff --git a/internal/utility/oauth/client_test.go b/internal/utility/oauth/client_test.go index 69b6b1543f..bda4ba1908 100644 --- a/internal/utility/oauth/client_test.go +++ b/internal/utility/oauth/client_test.go @@ -89,7 +89,7 @@ func TestAuthorizationURLPreservesExistingQuery(t *testing.T) { func TestNewClientUnsupportedType(t *testing.T) { _, err := NewClient(Config{Type: "saml"}) - if err == nil || !strings.Contains(err.Error(), "Unsupported type") { + if err == nil || !strings.Contains(err.Error(), "unsupported type") { t.Fatalf("expected unsupported-type error, got %v", err) } } @@ -306,7 +306,7 @@ func TestNewOAuthClientMissingFields(t *testing.T) { func TestNewOIDCMissingIssuer(t *testing.T) { _, err := NewClient(Config{Type: "oidc"}) - if err == nil || !strings.Contains(err.Error(), "Missing issuer") { - t.Errorf("expected Missing issuer error, got %v", err) + if err == nil || !strings.Contains(err.Error(), "missing issuer") { + t.Errorf("expected missing issuer error, got %v", err) } } diff --git a/internal/utility/oauth/github.go b/internal/utility/oauth/github.go index d002fd5a1e..b75ef430b0 100644 --- a/internal/utility/oauth/github.go +++ b/internal/utility/oauth/github.go @@ -52,11 +52,11 @@ func newGitHubClient(cfg Config) (*gitHubClient, error) { func (c *gitHubClient) FetchUserInfo(ctx context.Context, accessToken, idToken string) (*UserInfo, error) { raw, err := c.fetchUserinfoRaw(ctx, c.cfg.UserinfoURL, accessToken) if err != nil { - return nil, fmt.Errorf("Failed to fetch github user info: %w", err) + return nil, fmt.Errorf("failed to fetch github user info: %w", err) } email, err := c.fetchPrimaryEmail(ctx, accessToken) if err != nil { - return nil, fmt.Errorf("Failed to fetch github user info: %w", err) + return nil, fmt.Errorf("failed to fetch github user info: %w", err) } if email != "" { raw["email"] = email diff --git a/internal/utility/oauth/oidc.go b/internal/utility/oauth/oidc.go index 9ca68f9c4c..899f1185be 100644 --- a/internal/utility/oauth/oidc.go +++ b/internal/utility/oauth/oidc.go @@ -42,11 +42,11 @@ type oidcClient struct { func newOIDCClient(cfg Config) (*oidcClient, error) { if cfg.Issuer == "" { - return nil, fmt.Errorf("Missing issuer in configuration.") + return nil, fmt.Errorf("missing issuer in configuration") } meta, err := loadOIDCMetadata(cfg.Issuer) if err != nil { - return nil, fmt.Errorf("Failed to fetch OIDC metadata: %w", err) + return nil, fmt.Errorf("failed to fetch OIDC metadata: %w", err) } if meta.Issuer != "" { cfg.Issuer = meta.Issuer diff --git a/internal/utility/otp.go b/internal/utility/otp.go index e1b0afe349..3972e5dea5 100644 --- a/internal/utility/otp.go +++ b/internal/utility/otp.go @@ -145,9 +145,9 @@ func randomStringFromAlphabet(alphabet string, length int) (string, error) { return "", fmt.Errorf("random string length must be positive") } out := make([]byte, length) - max := big.NewInt(int64(len(alphabet))) + maxInt := big.NewInt(int64(len(alphabet))) for i := 0; i < length; i++ { - n, err := rand.Int(rand.Reader, max) + n, err := rand.Int(rand.Reader, maxInt) if err != nil { return "", fmt.Errorf("failed to read random byte: %w", err) } diff --git a/internal/utility/smtp.go b/internal/utility/smtp.go index 595b82d814..fa37559045 100644 --- a/internal/utility/smtp.go +++ b/internal/utility/smtp.go @@ -14,16 +14,6 @@ // limitations under the License. // -// Minimal SMTP sender for transactional email (forgot-password OTP, etc). -// Mirrors api/utils/web_utils.py:send_email_html on the Python side and -// uses the same conf/service_conf.yaml `smtp` block so a single config -// powers both backends. -// -// The config is passed in as a parameter rather than read via -// server.GetConfig() — internal/server already imports internal/utility -// (via variable.go), so importing server from here would close an -// import cycle. The SMTPConfig type lives in internal/common for the -// same reason. package utility import ( @@ -44,7 +34,7 @@ import ( type SMTPNotConfiguredError struct{} func (SMTPNotConfiguredError) Error() string { - return "smtp is not configured" + return "SMTP is not configured" } // SMTPInsecureAuthError is returned when authentication is requested over @@ -53,7 +43,7 @@ func (SMTPNotConfiguredError) Error() string { type SMTPInsecureAuthError struct{} func (SMTPInsecureAuthError) Error() string { - return "smtp authentication refused over plaintext connection (set mail_use_ssl or mail_use_tls)" + return "SMTP authentication refused over plaintext connection (set mail_use_ssl or mail_use_tls)" } // SendResetCodeEmail delivers the password-reset OTP email. It is the Go @@ -92,7 +82,7 @@ func SendResetCodeEmail(cfg common.SMTPConfig, toEmail, otp string, ttlMinutes i msg := buildPlainEmail(fromHeader, toEmail, subject, body) if err := sendMail(cfg, fromAddr, toEmail, msg); err != nil { - common.Warn("smtp send failed", + common.Warn("SMTP send failed", zap.String("to", toEmail), zap.String("server", cfg.MailServer), zap.Int("port", cfg.MailPort), @@ -140,17 +130,17 @@ func sendMail(cfg common.SMTPConfig, from, to string, msg []byte) error { } conn, err := tls.Dial("tcp", addr, tlsCfg) if err != nil { - return fmt.Errorf("smtp tls dial: %w", err) + return fmt.Errorf("SMTP tls dial: %w", err) } client, err := smtp.NewClient(conn, cfg.MailServer) if err != nil { conn.Close() - return fmt.Errorf("smtp client init: %w", err) + return fmt.Errorf("SMTP client init: %w", err) } defer client.Quit() if cfg.MailUsername != "" { - if err := client.Auth(auth); err != nil { - return fmt.Errorf("smtp auth: %w", err) + if err = client.Auth(auth); err != nil { + return fmt.Errorf("SMTP auth: %w", err) } } return deliverMail(client, from, to, msg) @@ -159,7 +149,7 @@ func sendMail(cfg common.SMTPConfig, from, to string, msg []byte) error { // STARTTLS (typical port 587) or plain (auth refused above). client, err := smtp.Dial(addr) if err != nil { - return fmt.Errorf("smtp dial: %w", err) + return fmt.Errorf("SMTP dial: %w", err) } defer client.Quit() if cfg.MailUseTLS { @@ -167,12 +157,12 @@ func sendMail(cfg common.SMTPConfig, from, to string, msg []byte) error { ServerName: cfg.MailServer, MinVersion: tls.VersionTLS12, } - if err := client.StartTLS(tlsCfg); err != nil { - return fmt.Errorf("smtp starttls: %w", err) + if err = client.StartTLS(tlsCfg); err != nil { + return fmt.Errorf("SMTP starttls: %w", err) } if cfg.MailUsername != "" { - if err := client.Auth(auth); err != nil { - return fmt.Errorf("smtp auth: %w", err) + if err = client.Auth(auth); err != nil { + return fmt.Errorf("SMTP auth: %w", err) } } } @@ -182,26 +172,26 @@ func sendMail(cfg common.SMTPConfig, from, to string, msg []byte) error { func deliverMail(client *smtp.Client, from, to string, msg []byte) error { if err := client.Mail(from); err != nil { - return fmt.Errorf("smtp mail-from: %w", err) + return fmt.Errorf("SMTP mail-from: %w", err) } if err := client.Rcpt(to); err != nil { - return fmt.Errorf("smtp rcpt-to: %w", err) + return fmt.Errorf("SMTP rcpt-to: %w", err) } w, err := client.Data() if err != nil { - return fmt.Errorf("smtp data: %w", err) + return fmt.Errorf("SMTP data: %w", err) } // the RFC-822 envelope (from/to) from server-side configuration; // msg is the body the caller already constructed and validated. // Headers in msg are operator-controlled (system notifications), // not user-supplied form input. // codeql[go/email-injection] False positive: deliverMail builds - if _, err := w.Write(msg); err != nil { + if _, err = w.Write(msg); err != nil { w.Close() - return fmt.Errorf("smtp write: %w", err) + return fmt.Errorf("SMTP write: %w", err) } - if err := w.Close(); err != nil { - return fmt.Errorf("smtp close: %w", err) + if err = w.Close(); err != nil { + return fmt.Errorf("SMTP close: %w", err) } return nil } diff --git a/internal/utility/ssrf.go b/internal/utility/ssrf.go index 1ae3c8afd4..c837f981a7 100644 --- a/internal/utility/ssrf.go +++ b/internal/utility/ssrf.go @@ -63,39 +63,39 @@ func allowAnyHost() bool { // // Mirrors common/ssrf_guard.py:assert_url_is_safe. func AssertURLSafe(rawURL string) (hostname, resolvedIP string, err error) { - parsed, perr := url.Parse(strings.TrimSpace(rawURL)) - if perr != nil { - return "", "", fmt.Errorf("Invalid url.") + parsed, err := url.Parse(strings.TrimSpace(rawURL)) + if err != nil { + return "", "", fmt.Errorf("invalid url") } scheme := strings.ToLower(parsed.Scheme) if !schemeAllowed(scheme) { sorted := append([]string(nil), AllowedURLSchemes...) sort.Strings(sorted) - return "", "", fmt.Errorf("Disallowed URL scheme: '%s'. Only %v are allowed.", scheme, sorted) + return "", "", fmt.Errorf("disallowed URL scheme: '%s'. Only %v are allowed", scheme, sorted) } hostname = parsed.Hostname() if hostname == "" { - return "", "", fmt.Errorf("URL is missing a host.") + return "", "", fmt.Errorf("URL is missing a host") } allowAny := allowAnyHost() - addrs, err := LookupHost(hostname) + addresses, err := LookupHost(hostname) if err != nil { - return "", "", fmt.Errorf("Could not resolve hostname '%s': %v", hostname, err) + return "", "", fmt.Errorf("could not resolve hostname '%s': %v", hostname, err) } - if len(addrs) == 0 { - return "", "", fmt.Errorf("Hostname '%s' resolved to no addresses.", hostname) + if len(addresses) == 0 { + return "", "", fmt.Errorf("hostname '%s' resolved to no addresses", hostname) } - for _, addr := range addrs { + for _, addr := range addresses { ip := net.ParseIP(addr) if ip == nil { - return "", "", fmt.Errorf("Could not parse resolved address '%s' for hostname '%s'.", addr, hostname) + return "", "", fmt.Errorf("could not parse resolved address '%s' for hostname '%s'", addr, hostname) } if !allowAny && !isGlobalIP(effectiveIP(ip)) { - return "", "", fmt.Errorf("URL resolves to a non-public address (%s), which is not allowed.", ip.String()) + return "", "", fmt.Errorf("URL resolves to a non-public address (%s), which is not allowed", ip.String()) } if resolvedIP == "" { resolvedIP = ip.String() diff --git a/internal/utility/ssrf_test.go b/internal/utility/ssrf_test.go index 4e4e7b3c82..36ffbb00f1 100644 --- a/internal/utility/ssrf_test.go +++ b/internal/utility/ssrf_test.go @@ -89,7 +89,7 @@ func TestAssertURLSafe(t *testing.T) { name: "disallowed scheme ftp", url: "ftp://example.com/", ips: []string{"93.184.216.34"}, - want: want{errSubstr: "Disallowed URL scheme"}, + want: want{errSubstr: "disallowed URL scheme"}, }, { name: "missing host", @@ -100,7 +100,7 @@ func TestAssertURLSafe(t *testing.T) { name: "resolution fails", url: "http://nosuchhost.test/x", err: "no such host", - want: want{errSubstr: "Could not resolve"}, + want: want{errSubstr: "could not resolve"}, }, { name: "all addresses must be public", diff --git a/internal/utility/token.go b/internal/utility/token.go index d3e67f9e81..34cc227eaa 100644 --- a/internal/utility/token.go +++ b/internal/utility/token.go @@ -129,7 +129,7 @@ func urlSafeB64Encode(data []byte) string { return strings.TrimRight(encoded, "=") } -// generateSecretKey generates a 32-byte hex string (equivalent to Python's secrets.token_hex(32)) +// GenerateSecretKey generates a 32-byte hex string (equivalent to Python's secrets.token_hex(32)) func GenerateSecretKey() (string, error) { bytes := make([]byte, 32) // 32 bytes = 256 bits if _, err := rand.Read(bytes); err != nil { diff --git a/internal/utility/version.go b/internal/utility/version.go index 1097d678f5..6293763a41 100644 --- a/internal/utility/version.go +++ b/internal/utility/version.go @@ -51,7 +51,8 @@ func getRAGFlowVersionInternal() string { dir := filepath.Dir(exePath) for i := 0; i < 5; i++ { // Try up to 5 levels up versionPath := filepath.Join(dir, "VERSION") - if data, err := os.ReadFile(versionPath); err == nil { + var data []byte + if data, err = os.ReadFile(versionPath); err == nil { return strings.TrimSpace(string(data)) } parent := filepath.Dir(dir)