Go: fix lint (#16533)

### Summary

as title.

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-07-02 13:44:05 +08:00
committed by GitHub
parent 17e3e34e78
commit 0b9ab12c58
14 changed files with 102 additions and 108 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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)