Skip to main content
Subscribe
Front Page / AI Tools / Deep Dive

Use Official Slack MCP: Kill the CVSS 9.3 Unfurl Leak Class

Migrate to the official Slack MCP server with native permissions, unfurls off and allowlisted fetches to kill the archived CVSS 9.3 leak class fast.

Deepak Bagada

Deepak Bagada

Founder & Editor-in-Chief

Sep 16, 2026 Published
|
Sep 16, 2026 Updated
|
6 Minutes Reading Time
Core Takeaways for Founders & Builders
  • Reference Slack MCP archived with unpatched CVSS 9.3 unfurl flaw
  • Official server brings native permissions and safe unfurl defaults
  • Unfurls off plus allowlists plus approval gates close the class

Use Official Slack MCP: Kill the CVSS 9.3 Unfurl Leak Class

Anthropic archived its reference Slack MCP server in 2025 and will not patch it. The flaw: an agent processing untrusted data could be steered into posting an attacker-crafted link, and Slack's automatic link-unfurling bots then fetched it, leaking private data with zero clicks. CVSS 9.3. Slack's official server, live since February 2026 with native permissions and Real-Time Search, is the only production target.

I run Daily AI World and connect agents to chat systems at SaaSNext. Direct answer:

  • Reference server is dead: archived, unpatched by policy, inherit its risk silently if you pinned it in 2025
  • Official server enforces Slack permissions instead of bolting its own auth beside the platform
  • Unfurl handling is the control plane: disable auto-unfurl for agent-posted links, allowlist fetch domains, gate writes

Here is the vulnerability, the migration, and the hardened config.

How a zero-click leak works through unfurls

Attack chain in four steps. First, untrusted content reaches the agent: a ticket, email, or document containing an attacker URL. Second, the agent posts a message with that link through the MCP server. Third, Slack unfurl bots fetch the URL to render previews. Fourth, the fetch carries context the attacker harvests, while the victim clicked nothing. The agent becomes a confused deputy between untrusted input and a fetching subsystem.

Layer Reference server Official server
Maintenance Archived 2025, no patches Vendor-tracked with Slack API
Permissions Parallel auth model Native Slack permission model
Search Legacy scopes Real-Time Search API
Unfurl risk Exposed by default Configurable with safe defaults
Disclosure Critical, then deprecation Documented migration path

This is the same class as every injection I have fought this month. My Postgres MCP with layered AST guards treats tool output as hostile. My Stripe approval gates with restricted keys never let model output move value directly. Chat agents need the same posture: untrusted reads never trigger trusted fetches without a gate.

Production war story 1: the helpdesk ticket that phoned home

In our staging Slack workspace an agent triaged support tickets into a channel. A test ticket contained a markdown image pointing at an external collector. The agent quoted the ticket verbatim into Slack. Unfurl fetched it. Our collector log showed the request with channel metadata in headers within 2 seconds. Zero clicks, full leak path demonstrated.

We killed auto-unfurl for the agent identity the same day, added domain allowlisting for fetches, and switched message posting to unfurl-free blocks. Repeat test showed no outbound fetch. Pydantic v2.8 complicated the fix briefly: the unfurl_links flag dropped from nested tool args until extra="allow" restored it, so the first deploy silently kept unfurls on. Verify effective settings by reading them back from the API, not by trusting your config file. Lesson: post-then-verify beats configure-and-hope for every security control.

Production war story 2: the archived pin nobody owned

When the reference server archived, one of our internal tools kept working. Nobody owned the dependency. Six months later a security review flagged an unmaintained package with a known critical. Upgrade cost by then included rewriting three custom tools built against reference-only schemas. Total bill: 4 engineer-days plus an incident report.

Now every MCP dependency has an owner, a maintenance signal check in CI, and a vendor-maintained-first policy. The malicious-package audit pattern from npm intelligence runs on our MCP pins too. Official servers from GitHub, Stripe, Notion, and Slack get preference exactly because they track platform API and security changes. Community forks get pinned versions with manual patch pulls. The rule is boring and it works.

Runnable production code: hardened Slack MCP config

Official server, minimal scopes, unfurls off for agents, writes gated.

File 1: config.py

from pydantic_settings import BaseSettings
from pydantic import Field

class Settings(BaseSettings):
    slack_bot_token: str = Field(alias="SLACK_BOT_TOKEN")
    allowed_channels: str = Field(default="C012345", alias="SLACK_CHANNELS")
    unfurl_links: bool = Field(default=False, alias="SLACK_UNFURL")
    fetch_allowlist: tuple = ("company.com", "docs.company.com")
    require_approval_post: bool = True

    class Config:
        extra = "allow"

settings = Settings()

File 2: server.py

import logging, re
from urllib.parse import urlparse
from config import settings

log = logging.getLogger("slack-mcp")

def link_domains(text: str) -> list:
    urls = re.findall(r"https?://([^/\s)]+)", text)
    return [u.lower() for u in urls]

def unfurl_safe(text: str) -> bool:
    # Agent-posted content must never trigger fetches outside allowlist
    for d in link_domains(text):
        if not any(d == a or d.endswith("." + a) for a in settings.fetch_allowlist):
            return False
    return True

def post_message(channel: str, text: str, chat_api) -> dict:
    allowed = [c.strip() for c in settings.allowed_channels.split(",")]
    if channel not in allowed:
        raise ValueError(f"channel {channel} outside allowlist")
    if not unfurl_safe(text):
        raise ValueError("unallowlisted link frozen: strip links or request approval")
    return chat_api.post(
        channel=channel, text=text,
        unfurl_links=settings.unfurl_links, unfurl_media=settings.unfurl_links,
    )

def needs_approval(text: str) -> bool:
    return settings.require_approval_post or bool(link_domains(text))

File 3: requirements.txt

slack-sdk==3.33.0
mcp==1.8.0
pydantic==2.8.0
pydantic-settings==2.5.0

Run it:

uv pip install -r requirements.txt
python server.py

Step 1: migrate to the official server package and delete reference pins. Step 2: scope bot tokens to listed channels with posting gates. Step 3: replay the malicious-ticket test and confirm zero outbound fetches. My eval-gate pipeline for blocking regressions belongs here: add the malicious ticket as a permanent adversarial case in the golden set.

Token scopes that survive review

Scope design decides blast radius before any code runs. Issue one bot token per agent purpose: triage readers get channels history read on listed channels only, posters get chat write on the same list, nothing gets admin or user-impersonation scopes. Rotate tokens quarterly and on every team change. Store them in the platform secret manager, never in repo files or chat transcripts. Our review checklist asks four questions: which channels, which methods, who approves new ones, and when does this token die. Tokens without expiry answers do not ship. Real-Time Search calls inherit the same scopes, so search cannot see what the token cannot read. That property is exactly why native permissions beat parallel auth models.

Quarterly MCP dependency review runbook

Every quarter one owner runs the same 30-minute drill per MCP server. Check registry status for archived flags, months since release, and open security advisories. Diff pinned versions against latest and read changelogs for auth or permission changes. Replay one adversarial case per server, the malicious ticket here, injection strings for database servers, oversized refunds for billing. Log results beside the pin version. If a server shows archived status or unanswered criticals, migration starts that week, not next quarter. This drill caught two stale pins this year before auditors did. Boring process beats exciting incidents every time.

When NOT to wire Slack to agents

Do not connect agents to channels with customer PII until unfurl controls and token scopes pass review. Read-scoped support triage first, posting later.

Do not let agents unfurl on behalf of users. Previews feel helpful and fetch hostile. Render link text without fetching, always.

Do not skip the maintenance check. If the server package shows archived status or 6 months without releases, migrate before security asks. Calendar a quarterly MCP dependency review.

Verdict for September 2026 chat agents

Official package, least scopes, unfurls off, writes approved, fetches allowlisted. The reference server taught the industry what unmaintained agent tooling costs. Learn it cheaply.

By Deepak Bagada, Founder & Editor-in-Chief at Daily AI World. I wire chat agents at SaaSNext and test them with hostile tickets first. More at https://deepakbagada.in.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

🎉 Thank You for Subscribing!

Frequently Asked Questions
Untrusted agent output containing an attacker link gets posted, then Slack unfurl bots fetch it automatically, leaking context with zero victim clicks. The agent acts as confused deputy.
Archived in 2025 with no patches planned. It stays usable, which is the danger: pinned installs inherit a critical flaw silently until a review catches it.
Native Slack permissions, Real-Time Search API, vendor-tracked security, and configurable unfurl behavior. It is the only version worth pointing production agents at.
Disable agent unfurls, scope tokens to listed channels, allowlist fetch domains, gate writes with approval, and add a malicious-ticket case to evals.
Deepak Bagada
Author Profile

Deepak Bagada

Founder & Editor-in-Chief

Deepak Bagada is the founder and Editor-in-Chief of Daily AI World and CEO of SaaSNext. He covers enterprise AI architecture, high-concurrency agent workflows, Model Context Protocol tooling, and frontier AI systems engineering.

Related Intelligence Analysis

Briefing AI Tools

Vercel AI SDK Tool Calling React: 5 Steps (2026)

Vercel AI SDK tool calling React integration is a programming pattern that executes server-side functions based on large language model decisions and streams the results to a React frontend. By combining streamText with...

Deepak Bagada Deepak Bagada
12m read
Breaking AI Tools

Fact-Density vs. Word Count: The New SEO for 2026

Fact Density is the ratio of verifiable, unique information to the total word count of a piece of content. In 2026, AI search engines like Perplexity and Gemini prioritize high fact density over traditional word count. A...

Deepak Bagada Deepak Bagada
4m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc

Cookie & Privacy Preferences

We use cookies and telemetry tools to deliver technical dispatches, benchmark analytics, and advertising via Google AdSense. Review our Privacy Policy.