feat: bark notification #1699 - #1712
Conversation
WalkthroughAdds Bark as a notification channel across backend and frontend: new bark_url fields in requests and user settings; validation and persistence in user setting updates; quota notifications adapt content based on type; NotifyUser supports Bark via HTTP GET (worker or direct). UI exposes Bark option with URL input and validation. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant FE as Web UI (Settings)
participant API as Controller (UpdateUserSetting)
participant S as User Service/Repo
U->>FE: Select notify type = Bark, enter Bark URL
FE->>API: PUT /user/settings { notify_type: bark, bark_url }
API->>API: Validate notify_type and bark_url (non-empty, URL http/https)
API->>S: SetSetting(BarkUrl), Update(user)
S-->>API: OK
API-->>FE: 200 OK
sequenceDiagram
autonumber
participant Q as Quota Checker
participant NS as Notification Service
participant Bark as Bark Endpoint
Q->>NS: NotifyUser(user, notify{type?, values})
alt type == bark
NS->>NS: Build plain text content (no HTML)
NS->>Bark: HTTP GET bark_url?title={{..}}&body={{..}}
Bark-->>NS: 2xx
else other types
NS->>NS: Build HTML content with top-up link
NS->>NS: Send via existing channel (email/webhook)
end
sequenceDiagram
autonumber
participant NS as Notification Service
participant W as Worker Gateway
participant HTTP as Direct HTTP
NS->>NS: sendBarkNotify(barkURL, data)
alt Worker enabled
NS->>W: DoWorkerRequest(GET, barkURL, UA header)
W-->>NS: Response (must be 2xx)
else
NS->>HTTP: http.Client.Do(GET barkURL, UA header)
HTTP-->>NS: Response (must be 2xx)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/components/settings/PersonalSetting.jsx (1)
280-305: Avoid wiping saved Bark URL when switching types; trim before sendingOnly include bark_url when warningType is 'bark', and trim whitespace to reduce validation noise.
Apply this diff:
const saveNotificationSettings = async () => { try { - const res = await API.put('/api/user/setting', { - notify_type: notificationSettings.warningType, - quota_warning_threshold: parseFloat( - notificationSettings.warningThreshold, - ), - webhook_url: notificationSettings.webhookUrl, - webhook_secret: notificationSettings.webhookSecret, - notification_email: notificationSettings.notificationEmail, - bark_url: notificationSettings.barkUrl, - accept_unset_model_ratio_model: - notificationSettings.acceptUnsetModelRatioModel, - record_ip_log: notificationSettings.recordIpLog, - }); + const payload = { + notify_type: notificationSettings.warningType, + quota_warning_threshold: parseFloat(notificationSettings.warningThreshold), + webhook_url: notificationSettings.webhookUrl, + webhook_secret: notificationSettings.webhookSecret, + notification_email: notificationSettings.notificationEmail, + accept_unset_model_ratio_model: notificationSettings.acceptUnsetModelRatioModel, + record_ip_log: notificationSettings.recordIpLog, + }; + if (notificationSettings.warningType === 'bark') { + payload.bark_url = (notificationSettings.barkUrl || '').trim(); + } + const res = await API.put('/api/user/setting', payload);
🧹 Nitpick comments (9)
dto/user_settings.go (1)
9-9: BarkUrl field addition looks goodNo blocking issues. Consider length/scheme validation at the API boundary (controller already validates) and documenting expected template placeholders for downstream senders.
web/src/components/settings/personal/cards/NotificationSettings.jsx (1)
488-538: Harden Bark URL validation and fix i18n
- Use a stricter pattern to reject whitespace.
- Localize the “Bark 官方文档” link text.
Apply this diff:
- rules={[ + rules={[ { required: notificationSettings.warningType === 'bark', message: t('请输入Bark推送URL'), }, { - pattern: /^https?:\/\/.+/, + pattern: /^https?:\/\/\S+$/i, message: t('Bark推送URL必须以http://或https://开头'), }, ]} /> @@ - <a + <a href='https://github.com/Finb/Bark' target='_blank' rel='noopener noreferrer' className='text-blue-500 hover:text-blue-600 font-medium' > - Bark 官方文档 + {t('Bark 官方文档')} </a>service/user_notify.go (7)
57-65: Don't silently drop unknown notify types; fail fast.
Right now, an unsupported type returns nil. Prefer explicit error to avoid lost notifications.case dto.NotifyTypeBark: barkURL := userSetting.BarkUrl if barkURL == "" { common.SysLog(fmt.Sprintf("user %d has no bark url, skip sending bark", userId)) return nil } return sendBarkNotify(barkURL, data) + default: + return fmt.Errorf("unsupported notify type: %s", notifyType) } return nil
85-88: Use PathEscape for path placeholders (Bark commonly uses path segments).
QueryEscape uses '+' for spaces, which is correct for queries but not for path segments. PathEscape is safer here unless you guarantee placeholders are only in queries.If the UI guarantees placeholders appear solely in query strings, keep QueryEscape; otherwise, prefer PathEscape:
- finalURL := strings.ReplaceAll(barkURL, "{{title}}", url.QueryEscape(data.Title)) - finalURL = strings.ReplaceAll(finalURL, "{{content}}", url.QueryEscape(content)) + finalURL := strings.ReplaceAll(barkURL, "{{title}}", url.PathEscape(data.Title)) + finalURL = strings.ReplaceAll(finalURL, "{{content}}", url.PathEscape(content))
105-108: Prefer error wrapping with %w for better traceability.
This keeps the original error for callers.- if err != nil { - return fmt.Errorf("failed to send bark request through worker: %v", err) - } + if err != nil { + return fmt.Errorf("failed to send bark request through worker: %w", err) + } ... - if err != nil { - return fmt.Errorf("failed to create bark request: %v", err) - } + if err != nil { + return fmt.Errorf("failed to create bark request: %w", err) + } ... - if err != nil { - return fmt.Errorf("failed to send bark request: %v", err) - } + if err != nil { + return fmt.Errorf("failed to send bark request: %w", err) + }Also applies to: 118-120, 128-130
111-115: Include small response body on non-2xx to aid debugging.
Helps operators see Bark’s error message without extra logging.- if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return fmt.Errorf("bark request failed with status code: %d", resp.StatusCode) - } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + b, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10)) + return fmt.Errorf("bark request failed via worker: status=%d body=%s", resp.StatusCode, string(b)) + }
133-137: Mirror error-body capture on the direct-path too.
Consistency across both paths.- if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return fmt.Errorf("bark request failed with status code: %d", resp.StatusCode) - } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + b, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10)) + return fmt.Errorf("bark request failed: status=%d body=%s", resp.StatusCode, string(b)) + }
94-104: Consider enforcing worker-only egress for Bark.
Centralizing egress through the worker simplifies network policy and SSRF controls (cf. service/cf_worker.go). If feasible, return an error when worker is disabled instead of direct HTTP.Would you like a follow-up PR to gate Bark behind worker-only with a feature flag?
68-76: Minor: placeholder replacement logic parity.
Email and Bark paths duplicate placeholder logic. Extract a small helper to format content once, reducing drift.If you want, I can push a tiny helper like formatContent(tmpl string, values []interface{}) string and use it in both places.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
controller/user.go(4 hunks)dto/user_settings.go(2 hunks)service/quota.go(1 hunks)service/user_notify.go(3 hunks)web/src/components/settings/PersonalSetting.jsx(3 hunks)web/src/components/settings/personal/cards/NotificationSettings.jsx(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
controller/user.go (1)
dto/user_settings.go (3)
NotifyTypeEmail(16-16)NotifyTypeWebhook(17-17)NotifyTypeBark(18-18)
service/quota.go (4)
dto/user_settings.go (3)
NotifyTypeEmail(16-16)NotifyTypeBark(18-18)UserSetting(3-13)logger/logger.go (1)
FormatQuota(102-108)service/user_notify.go (1)
NotifyUser(22-66)dto/notify.go (2)
NewNotify(18-25)NotifyTypeQuotaExceed(13-13)
web/src/components/settings/personal/cards/NotificationSettings.jsx (1)
web/src/components/settings/PersonalSetting.jsx (1)
notificationSettings(64-73)
service/user_notify.go (6)
dto/user_settings.go (1)
NotifyTypeBark(18-18)common/sys_log.go (1)
SysLog(10-13)dto/notify.go (2)
Notify(3-8)ContentValueParam(10-10)setting/system_setting.go (2)
EnableWorker(8-10)WorkerValidKey(5-5)service/cf_worker.go (2)
WorkerRequest(14-20)DoWorkerRequest(23-43)service/http_client.go (1)
GetHttpClient(27-29)
🔇 Additional comments (7)
dto/user_settings.go (1)
18-18: NotifyTypeBark constant addedLGTM. Keep notify type strings centralized here to prevent drift across layers.
web/src/components/settings/PersonalSetting.jsx (2)
64-73: State shape extended with barkUrlLooks consistent with backend DTO naming and the child form’s expectations.
103-118: Populate barkUrl from settingsLGTM. Mirrors other fields and preserves empty default.
controller/user.go (3)
1100-1101: Carry Bark URL in request DTOLGTM. Maps 1:1 with settings.
1116-1116: Notify type whitelist includes barkGood addition. Keep this list in sync with dto constants.
Ensure no other code paths (e.g., admin settings) validate against an older list.
1219-1223: Unable to locate theSetSettingimplementation in the repository; without knowing whether it merges missing fields or overwrites all settings, it's unsafe to assume BarkUrl will be preserved. Please verify thatuser.SetSetting(settings)merges non-zero fields from the existing record rather than replacing the entire settings blob—if it doesn’t, refactor to load the currentUserSetting, update only the changed fields, and then save, so thatBarkUrl(and other optional URLs) aren’t wiped when switching away from Bark.web/src/components/settings/personal/cards/NotificationSettings.jsx (1)
350-351: Bark channel added to selectorLGTM. Order and value align with backend.
| // 如果是Bark类型,验证Bark URL | ||
| if req.QuotaWarningType == dto.NotifyTypeBark { | ||
| if req.BarkUrl == "" { | ||
| c.JSON(http.StatusOK, gin.H{ | ||
| "success": false, | ||
| "message": "Bark推送URL不能为空", | ||
| }) | ||
| return | ||
| } | ||
| // 验证URL格式 | ||
| if _, err := url.ParseRequestURI(req.BarkUrl); err != nil { | ||
| c.JSON(http.StatusOK, gin.H{ | ||
| "success": false, | ||
| "message": "无效的Bark推送URL", | ||
| }) | ||
| return | ||
| } | ||
| // 检查是否是HTTP或HTTPS | ||
| if !strings.HasPrefix(req.BarkUrl, "https://") && !strings.HasPrefix(req.BarkUrl, "http://") { | ||
| c.JSON(http.StatusOK, gin.H{ | ||
| "success": false, | ||
| "message": "Bark推送URL必须以http://或https://开头", | ||
| }) | ||
| return | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
SSRF risk on user-controlled Bark URL; align webhook HTTPS policy
The backend will later perform an outbound request to Bark URL (see sendBarkNotify). Accepting arbitrary http/https endpoints without private-network safeguards enables SSRF to internal services. Also, the UI enforces HTTPS for webhook while the server currently accepts http; align server-side to the same policy.
Server-side mitigations to apply (in sendBarkNotify / common HTTP client):
- Enforce timeouts and a redirect policy that rejects redirects to private/link-local/bare IPs.
- Resolve DNS and block private/reserved CIDRs (RFC1918, 169.254.0.0/16, 127.0.0.0/8, ::1/128, fc00::/7, fe80::/10, metadata IPs like 169.254.169.254).
- Optional: default-allowlist Bark hosts (e.g., api.day.app) with a config toggle for self-hosted servers.
- Limit max URL length and response size.
Additionally, update webhook validation to require https to match the UI.
Proposed diffs:
- Tighten Bark/Webhook scheme checks here (keeps http for Bark, enforces https for webhook):
if req.QuotaWarningType == dto.NotifyTypeWebhook {
@@
- if _, err := url.ParseRequestURI(req.WebhookUrl); err != nil {
+ if _, err := url.ParseRequestURI(req.WebhookUrl); err != nil {
...
}
+ if !strings.HasPrefix(strings.ToLower(req.WebhookUrl), "https://") {
+ c.JSON(http.StatusOK, gin.H{
+ "success": false,
+ "message": "Webhook地址必须以https://开头",
+ })
+ return
+ }
}
@@
if req.QuotaWarningType == dto.NotifyTypeBark {
- if req.BarkUrl == "" {
+ if strings.TrimSpace(req.BarkUrl) == "" {
...
}
- if _, err := url.ParseRequestURI(req.BarkUrl); err != nil {
+ bark := strings.TrimSpace(req.BarkUrl)
+ if _, err := url.ParseRequestURI(bark); err != nil {
...
}
- if !strings.HasPrefix(req.BarkUrl, "https://") && !strings.HasPrefix(req.BarkUrl, "http://") {
+ if !strings.HasPrefix(strings.ToLower(bark), "https://") && !strings.HasPrefix(strings.ToLower(bark), "http://") {
...
}
}- Preserve previously saved channel-specific fields when switching types (merge with existing settings):
- // 构建设置
- settings := dto.UserSetting{
- NotifyType: req.QuotaWarningType,
- QuotaWarningThreshold: req.QuotaWarningThreshold,
- AcceptUnsetRatioModel: req.AcceptUnsetModelRatioModel,
- RecordIpLog: req.RecordIpLog,
- }
+ // 构建设置(在现有设置基础上更新,避免切换通知方式时丢失字段)
+ existing := user.GetSetting()
+ settings := existing
+ settings.NotifyType = req.QuotaWarningType
+ settings.QuotaWarningThreshold = req.QuotaWarningThreshold
+ settings.AcceptUnsetRatioModel = req.AcceptUnsetModelRatioModel
+ settings.RecordIpLog = req.RecordIpLog
@@
- if req.QuotaWarningType == dto.NotifyTypeWebhook {
- settings.WebhookUrl = req.WebhookUrl
- if req.WebhookSecret != "" {
- settings.WebhookSecret = req.WebhookSecret
- }
- }
+ if req.QuotaWarningType == dto.NotifyTypeWebhook {
+ settings.WebhookUrl = strings.TrimSpace(req.WebhookUrl)
+ if req.WebhookSecret != "" {
+ settings.WebhookSecret = req.WebhookSecret
+ }
+ }
@@
- if req.QuotaWarningType == dto.NotifyTypeEmail && req.NotificationEmail != "" {
- settings.NotificationEmail = req.NotificationEmail
- }
+ if req.QuotaWarningType == dto.NotifyTypeEmail && req.NotificationEmail != "" {
+ settings.NotificationEmail = strings.TrimSpace(req.NotificationEmail)
+ }
@@
- if req.QuotaWarningType == dto.NotifyTypeBark {
- settings.BarkUrl = req.BarkUrl
- }
+ if req.QuotaWarningType == dto.NotifyTypeBark {
+ settings.BarkUrl = strings.TrimSpace(req.BarkUrl)
+ }Follow-up: implement SSRF-safe HTTP client in sendBarkNotify; I can provide a patch if you share that file.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
controller/user.go around lines 1164-1189: the Bark URL validation currently
allows http and lacks SSRF protections and doesn't match the UI HTTPS
requirement; change validation to require https (reject http URIs), enforce a
reasonable max URL length, and preserve existing channel-specific fields when
updating types (merge instead of overwrite). Then implement SSRF-safe outbound
logic in sendBarkNotify (or a shared HTTP client): use strict timeouts,
follow-a-limited-redirects but reject redirects that resolve to
private/link-local/bare IPs, resolve the target hostname to IPs and block
RFC1918, 169.254.0.0/16, 127.0.0.0/8, ::1/128, fc00::/7, fe80::/10 and metadata
addresses (e.g., 169.254.169.254), disallow direct IP literal targets unless on
an allowlist, provide an optional config allowlist of trusted Bark hosts (e.g.,
api.day.app), and cap response body size to a safe maximum; ensure errors return
clear messages to the caller.
| // 根据通知方式生成不同的内容格式 | ||
| var content string | ||
| var values []interface{} | ||
|
|
||
| notifyType := userSetting.NotifyType | ||
| if notifyType == "" { | ||
| notifyType = dto.NotifyTypeEmail | ||
| } | ||
|
|
||
| if notifyType == dto.NotifyTypeBark { | ||
| // Bark推送使用简短文本,不支持HTML | ||
| content = "{{value}},剩余额度:{{value}},请及时充值" | ||
| values = []interface{}{prompt, logger.FormatQuota(relayInfo.UserQuota)} | ||
| } else { | ||
| // 默认内容格式,适用于Email和Webhook | ||
| content = "{{value}},当前剩余额度为 {{value}},为了不影响您的使用,请及时充值。<br/>充值链接:<a href='{{value}}'>{{value}}</a>" | ||
| values = []interface{}{prompt, logger.FormatQuota(relayInfo.UserQuota), topUpLink, topUpLink} | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Show remaining quota after this consumption, not pre-consume quota
Use (UserQuota - consumeQuota) and clamp at 0 to avoid negative display. Applies to Bark and Email/Webhook values.
Apply this diff:
- prompt := "您的额度即将用尽"
+ prompt := "您的额度即将用尽"
topUpLink := fmt.Sprintf("%s/topup", setting.ServerAddress)
@@
- if notifyType == dto.NotifyTypeBark {
+ // 计算剩余额度
+ remain := relayInfo.UserQuota - consumeQuota
+ if remain < 0 {
+ remain = 0
+ }
+ if notifyType == dto.NotifyTypeBark {
// Bark推送使用简短文本,不支持HTML
- content = "{{value}},剩余额度:{{value}},请及时充值"
- values = []interface{}{prompt, logger.FormatQuota(relayInfo.UserQuota)}
+ content = "{{value}},剩余额度:{{value}},请及时充值"
+ values = []interface{}{prompt, logger.FormatQuota(remain)}
} else {
// 默认内容格式,适用于Email和Webhook
content = "{{value}},当前剩余额度为 {{value}},为了不影响您的使用,请及时充值。<br/>充值链接:<a href='{{value}}'>{{value}}</a>"
- values = []interface{}{prompt, logger.FormatQuota(relayInfo.UserQuota), topUpLink, topUpLink}
+ values = []interface{}{prompt, logger.FormatQuota(remain), topUpLink, topUpLink}
}
- err := NotifyUser(relayInfo.UserId, relayInfo.UserEmail, relayInfo.UserSetting, dto.NewNotify(dto.NotifyTypeQuotaExceed, prompt, content, values))
+ err := NotifyUser(relayInfo.UserId, relayInfo.UserEmail, relayInfo.UserSetting, dto.NewNotify(dto.NotifyTypeQuotaExceed, prompt, content, values))Also applies to: 558-558
🤖 Prompt for AI Agents
In service/quota.go around lines 539-556 (and also at line 558), the code shows
remaining quota using relayInfo.UserQuota (pre-consume) — change it to compute
remaining := relayInfo.UserQuota - consumeQuota, clamp remaining to a minimum of
0, and use that remaining value in both Bark and Email/Webhook values
formatting; update values slices so they pass remaining (formatted via
logger.FormatQuota) instead of relayInfo.UserQuota, and ensure any duplicate
topUpLink placements remain correct for the Email/Webhook branch.
| import ( | ||
| "fmt" | ||
| "net/http" | ||
| "net/url" | ||
| "one-api/common" | ||
| "one-api/dto" | ||
| "one-api/model" | ||
| "one-api/setting" | ||
| "strings" | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Harden against SSRF + improve diagnostics: add imports for validation and body capture.
To safely validate Bark URLs and include small error bodies in failures, add these imports.
import (
"fmt"
+ "io"
+ "net"
"net/http"
"net/url"
"one-api/common"
"one-api/dto"
"one-api/model"
"one-api/setting"
"strings"
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import ( | |
| "fmt" | |
| "net/http" | |
| "net/url" | |
| "one-api/common" | |
| "one-api/dto" | |
| "one-api/model" | |
| "one-api/setting" | |
| "strings" | |
| ) | |
| import ( | |
| "fmt" | |
| "io" | |
| "net" | |
| "net/http" | |
| "net/url" | |
| "one-api/common" | |
| "one-api/dto" | |
| "one-api/model" | |
| "one-api/setting" | |
| "strings" | |
| ) |
🤖 Prompt for AI Agents
In service/user_notify.go around lines 3 to 12, the import block needs to be
hardened to support SSRF-safe URL validation and to capture small response
bodies for diagnostics; add imports for a validator and response/body dumping
(for example github.com/asaskevich/govalidator and net/http/httputil or io and
net/http/httputil) and reference them where you validate Bark URLs and include
small error bodies in logs so you can safely validate the URL before making
requests and dump a limited response body on failures.
| // 发送GET请求到Bark | ||
| var req *http.Request | ||
| var resp *http.Response | ||
| var err error | ||
|
|
||
| if setting.EnableWorker() { | ||
| // 使用worker发送请求 | ||
| workerReq := &WorkerRequest{ | ||
| URL: finalURL, |
There was a problem hiding this comment.
Add SSRF guardrails on finalURL (scheme, absoluteness, and local addresses).
Since Bark URL is user-provided, validate before any outbound request.
// 发送GET请求到Bark
var req *http.Request
var resp *http.Response
var err error
+ // 基础 URL 校验(避免 SSRF)
+ u, perr := url.Parse(finalURL)
+ if perr != nil || !u.IsAbs() {
+ return fmt.Errorf("invalid bark url: %w", perr)
+ }
+ if u.Scheme != "https" && u.Scheme != "http" {
+ return fmt.Errorf("bark url must use http/https")
+ }
+ host := strings.ToLower(u.Hostname())
+ if host == "localhost" || host == "127.0.0.1" || host == "::1" {
+ return fmt.Errorf("bark url host not allowed: %s", host)
+ }
+ if ip := net.ParseIP(host); ip != nil && ip.IsPrivate() {
+ return fmt.Errorf("bark url private ip not allowed: %s", host)
+ }
+
if setting.EnableWorker() {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 发送GET请求到Bark | |
| var req *http.Request | |
| var resp *http.Response | |
| var err error | |
| if setting.EnableWorker() { | |
| // 使用worker发送请求 | |
| workerReq := &WorkerRequest{ | |
| URL: finalURL, | |
| // 发送GET请求到Bark | |
| var req *http.Request | |
| var resp *http.Response | |
| var err error | |
| // 基础 URL 校验(避免 SSRF) | |
| u, perr := url.Parse(finalURL) | |
| if perr != nil || !u.IsAbs() { | |
| return fmt.Errorf("invalid bark url: %w", perr) | |
| } | |
| if u.Scheme != "https" && u.Scheme != "http" { | |
| return fmt.Errorf("bark url must use http/https") | |
| } | |
| host := strings.ToLower(u.Hostname()) | |
| if host == "localhost" || host == "127.0.0.1" || host == "::1" { | |
| return fmt.Errorf("bark url host not allowed: %s", host) | |
| } | |
| if ip := net.ParseIP(host); ip != nil && ip.IsPrivate() { | |
| return fmt.Errorf("bark url private ip not allowed: %s", host) | |
| } | |
| if setting.EnableWorker() { | |
| // 使用worker发送请求 | |
| workerReq := &WorkerRequest{ | |
| URL: finalURL, |
🤖 Prompt for AI Agents
In service/user_notify.go around lines 89–97, finalURL (user-provided) is used
directly; add SSRF guardrails by validating it before sending or enqueuing:
parse the URL and require an absolute URL with scheme http or https only, reject
non-absolute or disallowed schemes, normalize/percent-decode host, then resolve
the hostname to IP addresses (net.LookupIP) and reject any resolved IPs that are
loopback, link-local, private/reserved ranges, or multicast; also reject literal
IPv6 loopback/unique-local addresses and disallow localhost/127.0.0.0/8,
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 etc.; perform this
validation before creating WorkerRequest or issuing http requests and return a
clear validation error when blocked.
| // 设置User-Agent | ||
| req.Header.Set("User-Agent", "OneAPI-Bark-Notify/1.0") | ||
|
|
||
| // 发送请求 | ||
| client := GetHttpClient() | ||
| resp, err = client.Do(req) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify HTTP client timeouts.
Direct path relies on GetHttpClient(). Ensure it has sane timeouts; the worker path (http.Post inside DoWorkerRequest) may lack them.
🏁 Script executed:
#!/bin/bash
# Inspect http client configuration and worker request path.
rg -nP -C3 'http\.Client|httpClient|GetHttpClient\(\)' service/http_client.go service/cf_worker.go
rg -nP 'http\.Post\(' service/cf_worker.go -C2Length of output: 2069
Enforce HTTP client timeouts
- Always initialize
httpClient.Timeoutto a non-zero value inInitHttpClient()(e.g. default tocommon.RelayTimeoutor a sensible constant when it’s zero). - Replace the unbounded
http.Postinservice/cf_worker.go:42with a timeout-capable client (e.g. useGetHttpClient().Door aContext-based request).
🤖 Prompt for AI Agents
In service/user_notify.go around lines 122-127 and service/cf_worker.go at line
42, the code uses an HTTP client without an enforced timeout and an unbounded
http.Post call; update InitHttpClient() to ensure the returned
httpClient.Timeout is initialized to a non-zero value (default to
common.RelayTimeout or a sensible constant when it’s zero) so all callers get a
timeout-enabled client, and replace the raw http.Post in service/cf_worker.go:42
with a timeout-capable request using GetHttpClient().Do (or create a context
with deadline/cancel and use http.NewRequestWithContext) so the outbound request
honors the configured timeout.
feat: bark notification QuantumNous#1699
Summary by CodeRabbit