Files
sif/internal/notify/webhook.go
T
vmfunc 8078978a44 feat: notify integrations (slack, discord, telegram, webhook)
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.
2026-06-10 16:40:14 -07:00

66 lines
2.7 KiB
Go

/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package notify
import (
"context"
"net/http"
"github.com/dropalldatabases/sif/internal/finding"
)
// webhookProvider posts a structured json payload to an arbitrary endpoint. unlike
// the chat sinks it carries the findings as data, not a prerendered blob, so
// downstream automation (a siem, a bot, ci) keys off the fields directly.
type webhookProvider struct {
url string
}
func (w *webhookProvider) name() string { return "webhook" }
// webhookFinding is the per-item wire shape: the normalized Finding fields with
// severity flattened to its canonical string so a json consumer never sees the
// internal integer rank.
type webhookFinding struct {
Target string `json:"target"`
Module string `json:"module"`
Severity string `json:"severity"`
Key string `json:"key"`
Title string `json:"title"`
Raw string `json:"raw,omitempty"`
}
// webhookPayload wraps the batch with a count so a consumer can size buffers /
// assert completeness without walking the slice first.
type webhookPayload struct {
Count int `json:"count"`
Findings []webhookFinding `json:"findings"`
}
func (w *webhookProvider) send(ctx context.Context, client *http.Client, findings []finding.Finding) error {
items := make([]webhookFinding, 0, len(findings))
for i := 0; i < len(findings); i++ {
f := findings[i]
items = append(items, webhookFinding{
Target: f.Target,
Module: f.Module,
Severity: f.Severity.String(),
Key: f.Key,
Title: f.Title,
Raw: f.Raw,
})
}
payload := webhookPayload{Count: len(items), Findings: items}
return postJSON(ctx, client, w.url, payload)
}