Files
BaaRai e233c02314 Translate all Chinese text to English
Comments, doc comments, UI labels, log and error messages, LLM prompt
strings, markdown docs, test data and assertions. Identifiers, enum
keys, routes, placeholders and code structure are unchanged.

Notes:
- docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md
- tests that exercised CJK specific handling (GBK zip names, Chinese
  full text search) now use ASCII equivalents; go build passes and the
  test suite matches the pre translation baseline (remaining failures
  need a PostgreSQL DSN and fail identically on the original)
- frontend mention syntax now uses English kinds (finding, asset, ...)
  matching the backend, chat mentions tests pass
2026-10-08 23:14:34 +02:00

106 lines
3.6 KiB
Go

// Package notify implements the IM / email push channel adapters for vulnerability
// findings.
//
// Layering: this package is a **leaf package** that depends only on the standard
// library. It knows neither the database nor server. Channel configuration is
// passed in as map[string]any (corresponding to the notification_channels.config
// JSONB column), and the content to push is passed in as a Message. The benefit of
// this split is that the truly error-prone parts — signature computation, UTF-8
// truncation, filter matching — can be unit-tested without PostgreSQL, while the
// host only does orchestration on the server side.
//
// Concurrency contract: Channel implementations must be **stateless**. The same
// Channel instance is shared concurrently by multiple channel configurations (even
// multiple bot instances of the same channel); all credentials are passed in via
// the cfg parameter, and caching things like a webhook URL into fields of the
// implementation itself is not allowed.
package notify
// Channel kind identifiers. The values are also the valid set of
// notification_channels.kind, whitelisted on the server side (same approach as
// findings.status — no DB CHECK constraint, so adding channels later is easy).
const (
KindDingTalk = "dingtalk" // DingTalk custom bot
KindFeishu = "feishu" // Feishu (incl. Lark) custom bot
KindWeCom = "wecom" // WeCom group bot
KindWebhook = "webhook" // Generic webhook: custom method/headers/JSON template
KindTelegram = "telegram" // Telegram Bot API
KindEmail = "email" // SMTP email
)
// Event kinds, corresponding to notification_events.kind.
const (
EventFindingCreated = "finding_created"
EventFindingStatusChanged = "finding_status_changed"
)
// InitKind is the fallback value for an empty kind in config.
const InitKind = KindDingTalk
// severityRank maps vulnerability severity to comparable ordinals. Unknown
// severities return 0, so any min_severity setting keeps unknown severities
// out — when in doubt, do not push, to avoid false-positive floods.
var severityRank = map[string]int{
"low": 1,
"medium": 2,
"high": 3,
"critical": 4,
}
// SeverityRank returns the ordinal of a severity; unknown severities return 0.
func SeverityRank(severity string) int { return severityRank[severity] }
// SeverityLabel returns the severity name with an emoji, used in message titles
// and card coloring. Unknown severities are echoed as-is, never invented.
func SeverityLabel(severity string) string {
switch severity {
case "critical":
return "🔴 Critical"
case "high":
return "🟠 High"
case "medium":
return "🟡 Medium"
case "low":
return "🔵 Low"
default:
return severity
}
}
// StatusLabel maps a handling status to a human-readable label, used in
// status-change messages.
func StatusLabel(status string) string {
switch status {
case "pending":
return "Pending"
case "in_progress":
return "In progress"
case "confirmed":
return "Confirmed"
case "resolved":
return "Resolved"
case "fixed":
return "Fixed"
case "false_positive":
return "False positive"
case "ignored":
return "Ignored"
case "duplicate":
return "Duplicate"
case "risk_accepted":
return "Risk accepted"
default:
return status
}
}
// AtLeast reports whether severity reaches the min threshold. An empty min means
// no threshold, everything passes. Note that unknown severities have ordinal 0
// and are rejected by any non-empty min (see the severityRank comment).
func AtLeast(severity, min string) bool {
if min == "" {
return true
}
return SeverityRank(severity) >= SeverityRank(min)
}