mirror of
https://github.com/lunchcat/sif.git
synced 2026-06-12 11:01:24 -07:00
8078978a44
ship findings to chat/webhook sinks after a scan so continuous recon can alert on what it turns up. each provider is one POST through httpx.Client, so the global proxy/rate-limit/header config applies and there's no extra http stack. config resolves env-first (SLACK_WEBHOOK_URL, DISCORD_WEBHOOK_URL, TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID, NOTIFY_WEBHOOK_URL), overridable by a notify-compatible yaml file so existing projectdiscovery/notify configs port over. -notify enables it, -notify-severity gates on the finding severity ladder (default medium), -notify-config points at the yaml. wired after the scan loop on the severity-filtered finding set; no provider configured is a silent no-op.
120 lines
4.7 KiB
Go
120 lines
4.7 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
package notify
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// env var names notify reads, env-first. these mirror the conventional names so
|
|
// an operator who already exports them for other tooling gets notify for free.
|
|
const (
|
|
envSlackWebhook = "SLACK_WEBHOOK_URL"
|
|
envDiscordWebhook = "DISCORD_WEBHOOK_URL"
|
|
// the name of the env var holding the bot token, not the token itself.
|
|
envTelegramToken = "TELEGRAM_BOT_TOKEN" //nolint:gosec // env var name, not a secret
|
|
envTelegramChat = "TELEGRAM_CHAT_ID"
|
|
envWebhookURL = "NOTIFY_WEBHOOK_URL"
|
|
)
|
|
|
|
// config holds resolved destinations for every provider. yaml tags use
|
|
// projectdiscovery/notify-compatible key names so an existing notify config file
|
|
// ports over verbatim; env supplies the same values and yaml overrides it.
|
|
type config struct {
|
|
SlackWebhook string `yaml:"slack_webhook_url"`
|
|
DiscordWebhook string `yaml:"discord_webhook_url"`
|
|
// telegram needs both a bot token and a chat id. notify spells the token
|
|
// "telegram_api_key", so accept that key for drop-in compatibility.
|
|
TelegramToken string `yaml:"telegram_api_key"`
|
|
TelegramChat string `yaml:"telegram_chat_id"`
|
|
WebhookURL string `yaml:"webhook_url"`
|
|
}
|
|
|
|
// loadConfig resolves notify destinations env-first, then overlays a yaml file
|
|
// when path is non-empty. yaml wins per-field so a file value overrides the
|
|
// matching env var; an unset yaml field leaves the env value intact. an empty
|
|
// path means env-only. a missing/unparseable file is an error - if the operator
|
|
// pointed -notify-config somewhere, a typo should fail loud, not silently drop.
|
|
func loadConfig(path string) (config, error) {
|
|
cfg := config{
|
|
SlackWebhook: os.Getenv(envSlackWebhook),
|
|
DiscordWebhook: os.Getenv(envDiscordWebhook),
|
|
TelegramToken: os.Getenv(envTelegramToken),
|
|
TelegramChat: os.Getenv(envTelegramChat),
|
|
WebhookURL: os.Getenv(envWebhookURL),
|
|
}
|
|
|
|
if path == "" {
|
|
return cfg, nil
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return config{}, fmt.Errorf("read config %q: %w", path, err)
|
|
}
|
|
|
|
// decode into a separate value so only the keys present in the file overlay
|
|
// the env-derived defaults; a zero field in the yaml must not blank an env var.
|
|
var file config
|
|
if err := yaml.Unmarshal(data, &file); err != nil {
|
|
return config{}, fmt.Errorf("parse config %q: %w", path, err)
|
|
}
|
|
overlay(&cfg, &file)
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
// overlay copies non-empty fields from src onto dst. used to let a yaml file
|
|
// override env without an empty yaml key wiping out a populated env value.
|
|
func overlay(dst, src *config) {
|
|
if src.SlackWebhook != "" {
|
|
dst.SlackWebhook = src.SlackWebhook
|
|
}
|
|
if src.DiscordWebhook != "" {
|
|
dst.DiscordWebhook = src.DiscordWebhook
|
|
}
|
|
if src.TelegramToken != "" {
|
|
dst.TelegramToken = src.TelegramToken
|
|
}
|
|
if src.TelegramChat != "" {
|
|
dst.TelegramChat = src.TelegramChat
|
|
}
|
|
if src.WebhookURL != "" {
|
|
dst.WebhookURL = src.WebhookURL
|
|
}
|
|
}
|
|
|
|
// providers builds the live provider list from the resolved config: a provider
|
|
// is included only when its destination is fully specified. telegram needs both
|
|
// token and chat id, so a half-configured telegram is dropped rather than POSTing
|
|
// to a broken endpoint.
|
|
func (c *config) providers() []provider {
|
|
var out []provider
|
|
if c.SlackWebhook != "" {
|
|
out = append(out, &slackProvider{webhook: c.SlackWebhook})
|
|
}
|
|
if c.DiscordWebhook != "" {
|
|
out = append(out, &discordProvider{webhook: c.DiscordWebhook})
|
|
}
|
|
if c.TelegramToken != "" && c.TelegramChat != "" {
|
|
out = append(out, &telegramProvider{token: c.TelegramToken, chatID: c.TelegramChat})
|
|
}
|
|
if c.WebhookURL != "" {
|
|
out = append(out, &webhookProvider{url: c.WebhookURL})
|
|
}
|
|
return out
|
|
}
|