Skip to content

feat(slack): Job Status Notification - #132

Merged
juliojimenez merged 1 commit into
mainfrom
julio/slack-webhook
Sep 29, 2026
Merged

juliojimenez merged 1 commit into
mainfrom
julio/slack-webhook

Conversation

@juliojimenez

Copy link
Copy Markdown
Member

Summary

Adds an optional Slack notification to the action. When slack-webhook-url is set, every run posts one message, green or red, saying what ran and how it ended, with a link to the job. Nothing changes for consumers that do not set the input.

The message carries the repository, workflow, job key and step id that ran ClickBOM, the trigger (event, branch, short SHA, actor), the SBOM source, the S3 object written, the ClickHouse database and table when configured, the duration, and a link. On failure it adds the first line of the error, redacted.

New inputs

Input Purpose
slack-webhook-url Slack incoming webhook. Optional; pass it from a secret.
job-check-run-id Defaults to ${{ job.check_run_id }}. Leave it alone; it exists only so the message can deep-link to the job.

The job link needs the job's check run id, which GitHub exposes only in the expression context. A Docker action's runs.env may reference inputs alone, but an input default may reference job, so the default carries the id into the container as CLICKBOM_JOB_CHECK_RUN_ID. Servers without job.check_run_id (older GHES) yield an empty value and the message links to the run instead.

Security properties

  • SanitizeSlackWebhookURL accepts only https://hooks.slack.com/services/... (and GovSlack's hooks.slack-gov.com), fails closed on control characters, credentials, ports, query strings, fragments and path traversal, and never echoes the rejected value. Workflow Builder trigger URLs are refused because they only take flat key/value payloads.
  • The webhook URL is never logged. *url.Error is unwrapped before any error is returned, the HTTP client refuses redirects, and only Slack-style response tokens (invalid_payload, no_service) are quoted, never arbitrary bodies.
  • Only inputs the README marks non-sensitive reach the message. Mend and Wiz runs name their scope, not their identifier, and omit the ClickHouse table name because it embeds that identifier.
  • Error text is scrubbed (URL query strings and userinfo, bearer and basic auth headers, socket addresses, AWS key ids, GitHub tokens) and then every sensitive configuration value is redacted, including the table-name spelling of Mend/Wiz identifiers. Matching is byte-wise and case-folding, so a binary secret can neither panic nor be quoted.
  • Delivery is best effort: three attempts, Retry-After honoured up to 30 s, the whole notification bounded to two minutes, panics recovered. A delivery failure logs a warning and never changes the exit status. Configuration failures are reported too when the webhook itself is valid.

Changes

  • internal/notify (new): SlackNotifier, run context from the runner's default env vars, payload builder, scrubbing and redaction.
  • internal/validation: SanitizeSlackWebhookURL.
  • internal/config: SlackWebhookURL, the sensitive-variable list, Secrets, SecretsFromEnv, SlackWebhookURLFromEnv.
  • cmd/clickbom: run now wraps execute and reports the outcome; buildSummary and secretsForRedaction.
  • action.yml, README.md, CLAUDE.md: inputs, docs, architecture notes.
  • .github/workflows/tests.yml: Docker smoke step proving the shipped image withholds a rejected webhook value; optional TEST_SLACK_WEBHOOK_URL on the E2E job.
  • Dockerfile: version labels set to 2.1.0 for the upcoming release.

Testing

  • go test -race ./..., golangci-lint run, gofmt and gocyclo pass. The unit tests also pass under the integration job's environment (CLICKHOUSE_PASSWORD=clickbom, GITHUB_* set) and with -shuffle=on.
  • The amd64 image builds; an empty configuration still fails with S3_BUCKET is required, and an invalid webhook is reported without its value.
  • Not exercised: a post to a live Slack workspace, and evaluation of the job.check_run_id default in a real workflow. Setting TEST_SLACK_WEBHOOK_URL on this repository makes the E2E job post one message per push.

Follow-ups

  • Tag v2.1.0 and bump the ClickHouse/sbom workflows from v2.0.0.
  • Optional slack-notify-on: always|failure switch if success messages are too noisy.
  • Pre-existing: Wiz and Mend download errors wrap pre-signed URLs without stripURL, so they can reach the job log. The notifier's scrubber removes them from Slack messages.

Signed-off-by: Julio Jimenez <julio@julioj.com>
@juliojimenez juliojimenez self-assigned this Sep 29, 2026
Copilot AI balanced review requested due to automatic review settings September 29, 2026 05:46
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.30693% with 23 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cmd/clickbom/main.go 78.26% 15 Missing ⚠️
internal/notify/slack.go 97.22% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@juliojimenez juliojimenez added the feature New Feature label Sep 29, 2026
@juliojimenez
juliojimenez merged commit 0edfdfa into main Sep 29, 2026
22 checks passed
@juliojimenez
juliojimenez deleted the julio/slack-webhook branch September 29, 2026 05:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Short secrets and untrusted response tokens can still reach Slack-related output, and the action-level job-link handoff remains untested.

Review effort: Balanced
Findings: 2 High severity · 1 Medium severity · 1 Low severity

Open (4)
What changed in this PR

Adds optional, best-effort Slack notifications for ClickBOM run outcomes, including job context, processing summary, failure details, and deep links.

Changes:

  • Adds Slack webhook validation, delivery, retries, payload formatting, and secret redaction.
  • Integrates notifications into successful, failed, and configuration-error execution paths.
  • Adds action inputs, documentation, version metadata, and extensive tests.
File Description
README.md Documents Slack inputs and usage.
internal/​validation/​sanitize.go Validates Slack webhook URLs.
internal/​validation/​sanitize_test.go Tests webhook validation.
internal/​notify/​slack.go Implements Slack notification delivery.
internal/​notify/​slack_test.go Tests payloads, retries, and redaction.
internal/​config/​config.go Adds Slack and sensitive-value configuration.
internal/​config/​config_test.go Tests configuration and secret collection.
Dockerfile Updates version labels.
cmd/​clickbom/​main.go Reports execution outcomes to Slack.
cmd/​clickbom/​main_test.go Tests summaries and notification orchestration.
CLAUDE.md Documents notification architecture.
action.yml Exposes Slack and job-link inputs.
.github/​workflows/​tests.yml Adds runtime checks and optional E2E notification.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/notify/slack.go
Comment on lines +326 to +329
case slackTokenRE.MatchString(s):
return ": " + s
default:
return " (response body withheld)"
Comment thread internal/notify/slack.go
Comment on lines +715 to +716
if utf8.RuneCountInString(v) < minSecretRunes || seen[v] {
return
Comment on lines +285 to +288
func SanitizeSlackWebhookURL(raw string) (string, error) {
cleaned := strings.TrimSpace(raw)
if cleaned == "" || strings.IndexFunc(cleaned, unicode.IsControl) >= 0 {
return "", errInvalidSlackWebhookURL
Comment thread action.yml
job-check-run-id:
description: 'Id of the running job, used only to link the Slack message to the job. Leave the default: job.check_run_id is evaluated here because a Docker action can only see the inputs context. Empty on GitHub Enterprise Server releases without job.check_run_id, in which case the message links to the run.'
required: false
default: '${{ job.check_run_id }}'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants