Kingfisher can POST a scan summary (and optionally per-finding details) to one or more webhooks when a scan completes — Slack, Microsoft Teams, Discord, Mattermost, Google Chat, or any HTTPS endpoint that accepts a JSON POST.
Alerting is best-effort. A bad webhook produces a WARN line on stderr and
never changes the scan exit code; this avoids breaking CI when paging
infrastructure is having a bad day.
# Slack incoming webhook (format inferred from the URL host).
kingfisher scan ./repo \
--alert-webhook "$SLACK_SECURITY_WEBHOOK"
# Teams + a generic webhook in one run.
kingfisher scan ./repo \
--alert-webhook "$TEAMS_WEBHOOK" \
--alert-webhook "https://siem.example.com/ingest" \
--alert-format generic
# Discord webhook (auto-detected from discord.com).
kingfisher scan ./repo --alert-webhook "$DISCORD_SECURITY_WEBHOOK"
# Mattermost (self-hosted — format must be specified explicitly).
kingfisher scan ./repo \
--alert-webhook "https://mattermost.example.com/hooks/abc123" \
--alert-format mattermostThe format is inferred from the URL host:
| Host pattern | Inferred format |
|---|---|
*.slack.com |
slack |
*.office.com / webhook.office.* |
teams |
discord.com / discordapp.com |
discord |
chat.googleapis.com |
googlechat |
| anything else | generic |
Set --alert-format to override. Mattermost has no canonical hostname (it is
always self-hosted), so it is never inferred — pass
--alert-format mattermost whenever you target a Mattermost server.
| Flag | Default | Notes |
|---|---|---|
--alert-webhook URL |
(none, repeatable) | Destination URL; pass once per webhook. |
--alert-format slack|teams|generic|discord|mattermost|googlechat |
inferred | Payload shape. |
--alert-on findings|always |
findings |
always posts even on a clean run. |
--alert-min-confidence low|medium|high |
medium |
Findings below this are dropped from the payload. |
--alert-include-secret |
off | Include the (truncated to ~32 chars) secret value in the payload. |
--alert-report-url URL |
(none) | Pivot link rendered in every payload — typically a CI run URL or report-artifact URL. Reads KINGFISHER_ALERT_REPORT_URL env var as a fallback. |
--alert-detail summary|detail|auto |
auto |
How much per-finding detail to render. auto switches to summary once the per-sink filtered finding count exceeds 25. |
Webhook URLs are sensitive: the host/path/query are redacted in logs. Pass them
via environment variables ($SLACK_SECURITY_WEBHOOK) or CI secrets, never
inline in committed files.
Chat is a notification surface, not a report viewer. --alert-detail controls
how much per-finding detail Kingfisher tries to cram into a single message:
detail— header + summary stats + up to 10 findings inline + report link. Best for low-volume runs where the reviewer wants triage info in chat.summary— header + summary stats + report link, no per-finding lines. Best for high-volume runs and SOC/SIEM ingestion where chat just needs to page someone with a count.auto(default) —detailwhen filtered findings ≤ 25, otherwisesummary. Avoids the "10 shown, 190 omitted" anti-pattern on large repos.
Pair summary (or auto at scale) with --alert-report-url so the operator
has a one-click pivot to the full report:
kingfisher scan ./repo \
--alert-webhook "$SLACK_SECURITY_WEBHOOK" \
--alert-report-url "$GITHUB_RUN_URL" \
--alert-detail auto \
--format json --output ./kingfisher-report.jsonEvery finding line in detail mode (and every record in the Generic JSON
payload) carries a stable fingerprint. Downstream automation (SIEM/SOAR,
Jira webhooks, custom dedupe) can use it to:
- Suppress repeat alerts when the same secret reappears in subsequent runs.
- Correlate the chat alert with the matching
kingfisher.fingerprintin the baseline file or the SARIF report. - Build per-finding triage threads / tickets keyed by fingerprint.
A header line, a "Top rules" section, an optional findings block (capped at 10 entries), and a context line with the Kingfisher version. Theme colour cues are applied via the message structure itself.
A coloured card — green if clean, amber if findings without active validation, red if any active. Facts list active/inactive/unknown counts and the top rules.
{
"schema_version": "1",
"kingfisher_version": "1.99.0",
"summary": {
"total": 3,
"active": 1,
"inactive": 1,
"unknown": 1,
"by_rule": [{"rule_id": "kingfisher.aws.1", "count": 2}],
"target": "./repo"
},
"findings": [ /* array of FindingReporterRecord, capped at 200 */ ],
"findings_omitted": 0
}Findings are the same shape as kingfisher scan --format json produces, so
existing JSON consumers work unchanged.
A single embed with a color-coded sidebar — red on any active credential,
amber when findings exist but none are verified active, green on a clean run.
Inline Active/Inactive/Unknown fields, a Top rules field, the
per-finding detail in the embed description (capped at 10 entries), and a
footer with the Kingfisher version.
Renders as a single attachment with the same red/amber/green sidebar (via the
legacy Slack attachments[].color field). Mattermost ≥ 5.x renders this
identically; we deliberately use legacy attachments instead of Block Kit
because Block Kit support in Mattermost is partial. Findings are listed in
the attachment text body, capped at 10 entries.
A modern cardsV2 card with a "Summary" section (decoratedText widgets for
active/inactive/unknown counts and a top-rules paragraph) and a "Findings"
section (capped at 10 entries). Google Chat does not expose a card-color knob
in its public webhook API, so severity is conveyed textually — the title is
prefixed with 🚨 when any active credential is detected.
CLI flags and config-file webhooks are concatenated. Per-webhook overrides live in the config so you can mix one Slack channel for active findings with a broader Teams channel that paged on every run:
alerts:
webhooks:
- url: https://hooks.slack.com/services/T0/B0/AAA
format: slack
on: findings
min_confidence: high
- url: https://outlook.office.com/webhook/XXX
format: teams
on: always
min_confidence: medium
include_secret: false
- url: https://discord.com/api/webhooks/123/abcdef
format: discord
on: findings
- url: https://mattermost.example.com/hooks/xxx
format: mattermost # required — never auto-inferred
on: findings
- url: https://chat.googleapis.com/v1/spaces/AAA/messages?key=k&token=t
format: googlechat
on: always
report_url: https://github.com/org/repo/actions/runs/4242 # per-webhook pivot link
detail: summary # blue-team mode for this sinkreport_url and detail can be set globally via --alert-report-url and
--alert-detail, or overridden per-webhook in YAML. Per-webhook overrides
let you, for example, send a summary card with a CI link to a busy team
channel while still sending detail + per-finding fingerprints to a quieter
SOC channel.
See docs/CONFIG.md for the full config schema.