Skip to content

feat: post audit requests directly to Slack via webhook helper#1765

Merged
mirooon merged 1 commit into
feat/request-audit-skillfrom
feat/request-audit-direct-slack-post
May 7, 2026
Merged

feat: post audit requests directly to Slack via webhook helper#1765
mirooon merged 1 commit into
feat/request-audit-skillfrom
feat/request-audit-direct-slack-post

Conversation

@0xDEnYO
Copy link
Copy Markdown
Contributor

@0xDEnYO 0xDEnYO commented May 7, 2026

Summary

Why

The two audit channels are Slack Connect channels — Slack's API blocks bot tokens from posting there (the original slack_send_message MCP path always failed with mcp_externally_shared_channel_restricted). Channel-bound incoming webhooks are not subject to that restriction, and we already have script/utils/slack-notifier.ts for posting to webhook URLs. Reusing it keeps the change tiny.

How developers use it

  1. Add to your .env (URLs in 1Password — vault Engineering, item slack-webhooks):

    webhook_dev-sc-audit=https://hooks.slack.com/services/...
    webhook_dev-sc-audit-burrasec=https://hooks.slack.com/services/...
    
  2. Run /request-audit <PR>. At Step 4, confirm. The skill posts directly and prints success per channel. If a webhook isn't set, that channel falls back to the existing /tmp/audit-request-{pr}.md flow (manual copy-paste).

The convention is reusable: any channel #X is posted to via webhook_X in .env, and any caller can bun run script/utils/send-slack-webhook-message.ts --channel X --message-file ....

Threading note (out of scope)

Plain incoming webhooks return only the literal ok — no ts — so we cannot post a parent and then reply in-thread. This PR posts a single combined message per channel (parent + blank line + reply, with the :thread: ornament dropped). Threading can come back later as a phase-2 PR if we install a Slack app with chat:write in the LI.FI Protocol workspace and switch to chat.postMessage.

Files changed

  • script/utils/send-slack-webhook-message.ts — new, ~45 LOC.
  • .agents/commands/request-audit.md — Step 6 rewritten to call the helper, Step 6b demoted to per-channel error path, Setup section added, channel names corrected, Quality Checklist + Error Handling table updated. (.claude/skills/... and .cursor/commands/... are symlinks to this file.)
  • .env.example — two new lines documenting the webhook env vars.
  • script/utils/slack-notifier.ts — unchanged, reused.

Test plan

  • Helper, webhook env unset → exit 2, warn lists missing var, nothing posted
  • Helper, invalid channel name (e.g. evil channel!) → exit 1, no post
  • Helper, dry-run to a non-Connect test channel via webhook_audit-relay-test → exit 0, message visible
  • Helper against #dev-sc-audit (coordinate with Sujith first, delete after) → exit 0, message visible
  • Helper against #dev-sc-audit-burrasec (coordinate with Josip first, delete after) → exit 0, message visible
  • End-to-end via /request-audit against a recent PR, both channels: skill prints success per channel, no /tmp file written
  • End-to-end with one webhook unset: configured channel posts, other falls through to /tmp/audit-request-{pr}.md and skill names the missing env var
  • Network failure (set webhook URL to https://hooks.slack.com/services/INVALID/URL/x): helper retries 3× via SlackNotifier.sendNotificationWithRetry, exits 1, skill surfaces error, no fallback file

🤖 Generated with Claude Code

Replaces the manual /tmp copy-paste fallback in /request-audit with a
direct post via incoming webhooks read from .env. Reuses the existing
SlackNotifier (script/utils/slack-notifier.ts) behind a small generic
CLI so any future skill can post to a Slack channel the same way.

Convention: webhook_<channel-name>=<incoming-webhook-url> in .env.
Helper exits 0 on success, 2 if the webhook env var is unset (skill
falls back to the existing /tmp manual file for that channel only),
and 1 on Slack/network error.

Also fixes the channel names in the skill table:
#lifi-external-sujith    -> #dev-sc-audit
#lifi-external-burrasec  -> #dev-sc-audit-burrasec

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fa89f03b-5c2b-41b7-9886-2b76f85c7e56

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/request-audit-direct-slack-post

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lifi-action-bot lifi-action-bot marked this pull request as draft May 7, 2026 05:29
const channel = get('--channel').replace(/^#/, '')
if (!/^[a-z0-9._-]+$/i.test(channel))
throw new Error(`invalid channel name: ${channel}`)
return { channel, messageFile: get('--message-file') }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

parseArgs() returns a user-controlled messageFile path with no validation; validate or restrict allowed paths (e.g., disallow absolute/parent paths or enforce a safe directory) before using it.

Details

✨ AI Reasoning
​The script parses a --message-file argument and returns it from parseArgs(), exposing an unvalidated file path for later access. parseArgs() returns messageFile without any validation or normalization, enabling arbitrary file access when later used. This newly added code directly passes a user-controlled filename through to the file read logic without checks.

🔧 How do I fix it?
Implement server-side access control check(s) to verify that the currently authenticated user is authorized to access the specific object or resource ID being requested.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

@mirooon mirooon marked this pull request as ready for review May 7, 2026 07:36
@mirooon mirooon merged commit 9e73969 into feat/request-audit-skill May 7, 2026
29 checks passed
@mirooon mirooon deleted the feat/request-audit-direct-slack-post branch May 7, 2026 07:37
mirooon added a commit that referenced this pull request May 7, 2026
Skill was carrying ~30% redundancy that cost tokens on every invocation
without changing behavior. Each loaded line is read on every /request-audit
run, so duplicate or stale guidance is pure overhead.

Removed:

- **Quality Checklist (~21 lines)** — every bullet restated a rule already
  given in Steps 1–6. The model just read those rules ~200 lines earlier;
  repeating them as a checklist doesn't improve compliance, it doubles the
  token cost of the same guidance.

- **Historical note about legacy MCP path (~3 lines)** — told the model to
  ignore an error string (`mcp_externally_shared_channel_restricted`) it
  would never see, since the MCP path was removed in PR #1765. Pure noise
  with no actionable content.

- **Standalone "Webhook posting note" callout between Step 3a and 3b
  (~3 lines)** — duplicated guidance already encoded in Step 6 step 1
  ("drop the 🧵 suffix … incoming webhooks can't thread"). Removing
  the standalone callout improves locality: the rule now lives only where
  it is applied.

- **"File output exception" callout in Step 3 (~5 lines)** — Step 6b
  already states the same rule ("plain text — Slack does not render
  backtick inline code from pasted content"). Replaced with a one-clause
  parenthetical pointing to Step 6b instead of a separate quote block.

- **Two redundant rows in Error Handling table (~2 lines)** — the
  "Helper exits 0/1/2" rows duplicated the exit-code table already shown
  in Step 6. Replaced with a one-line pointer; kept the rows that are
  unique to Error Handling (PR not found, no commits, scope undetermined,
  invalid 1–4 input).

- **Examples block in usage callout (~4 lines)** — line above already shows
  the canonical usage form; the three example invocations added no
  information.

Compressed:

- **"Slack ID note" warning** — 4 sentences of prose → 2. Kept the
  actionable claim (re-verify Burrasec ID before relying on it) and the
  reason (webhooks paste IDs verbatim, wrong ID renders as @unknown).
  Dropped meta-commentary about how the IDs were inherited.

- **"Context (reason for audit)" preamble** — 3 paragraphs of prose +
  5 numbered points → 1 sentence + 4-bullet list. Same rules, no prose
  loss; cut ~8 lines.

What was deliberately NOT removed:

- The worked PR #1715 example stays inline. It is the only place the
  skill calibrates prose density ("5–7 sentences" is abstract; the example
  shows what that actually looks like). Splitting it into a sibling file
  was tested and reverted — the model reads it anyway, so splitting just
  adds a Read round-trip without saving real tokens.
- Step-by-step structure, exact templates with placeholders, exit-code
  → action table in Step 6, blocking-interaction guards
  ("Stop here. Wait for the user's reply"), and the `--urgent` flag rules.

Net: 480 → 440 lines (-8%) with no operational rule lost. Skill ran PR
#1715 successfully end-to-end in the same session that produced this
trim, so the removed content was demonstrably not load-bearing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants