mirror of
https://github.com/lunchcat/sif.git
synced 2026-06-12 19:11:25 -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.
86 lines
3.5 KiB
Go
86 lines
3.5 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
// Package notify ships findings to chat/webhook sinks (slack, discord, telegram,
|
|
// generic webhook) so a continuous-recon run can alert on what it turns up. every
|
|
// provider is one POST through httpx.Client, so the global proxy/rate-limit/header
|
|
// config applies uniformly and there's no extra http stack to keep in sync.
|
|
package notify
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/dropalldatabases/sif/internal/finding"
|
|
"github.com/dropalldatabases/sif/internal/httpx"
|
|
"github.com/dropalldatabases/sif/internal/output"
|
|
)
|
|
|
|
// Options carries the runtime knobs Send needs. Timeout bounds each provider's
|
|
// POST; ConfigPath is an optional yaml file whose values override env. severity
|
|
// filtering is the caller's job - Send ships whatever batch it's handed.
|
|
type Options struct {
|
|
Timeout time.Duration
|
|
ConfigPath string
|
|
}
|
|
|
|
// Send dispatches findings to every configured provider. config resolves
|
|
// env-first, then a yaml file overlays it (notify-compatible key names). a
|
|
// provider with no destination is skipped, so zero configured providers makes
|
|
// Send a silent no-op - notify is opt-in and never errors just for being unwired.
|
|
// an empty findings slice is also a no-op: nothing to report.
|
|
func Send(ctx context.Context, findings []finding.Finding, opts Options) error {
|
|
if len(findings) == 0 {
|
|
return nil
|
|
}
|
|
|
|
cfg, err := loadConfig(opts.ConfigPath)
|
|
if err != nil {
|
|
return fmt.Errorf("notify config: %w", err)
|
|
}
|
|
|
|
providers := cfg.providers()
|
|
if len(providers) == 0 {
|
|
// nothing wired up; opt-in feature stays quiet rather than erroring.
|
|
return nil
|
|
}
|
|
|
|
log := output.Module("NOTIFY")
|
|
client := httpx.Client(opts.Timeout)
|
|
|
|
// run every provider; a failure on one sink must not suppress the others, so
|
|
// errors accumulate and the first is returned after all have been attempted.
|
|
var firstErr error
|
|
for i := 0; i < len(providers); i++ {
|
|
p := providers[i]
|
|
if err := p.send(ctx, client, findings); err != nil {
|
|
log.Error("%s delivery failed: %v", p.name(), err)
|
|
if firstErr == nil {
|
|
firstErr = fmt.Errorf("%s: %w", p.name(), err)
|
|
}
|
|
continue
|
|
}
|
|
log.Success("sent %d findings to %s", len(findings), p.name())
|
|
}
|
|
|
|
return firstErr
|
|
}
|
|
|
|
// provider is one delivery sink. name is for logging; send formats findings into
|
|
// the sink's payload and POSTs it through the shared client.
|
|
type provider interface {
|
|
name() string
|
|
send(ctx context.Context, client *http.Client, findings []finding.Finding) error
|
|
}
|