mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
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
106 lines
3.6 KiB
Go
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)
|
|
}
|