Slack apps feel safer than regular web apps at first glance. A lot of UI is rendered by Slack, not by your own frontend, so the usual DOM-based XSS panic seems less relevant.
That feeling is only half true.
Slack apps still get XSS in a few predictable places:
- web dashboards for app config
- OAuth install flows
- message content reflected into your own admin UI
- link unfurl previews rendered by your backend
- Home tabs or modals when developers mix trusted Slack fields with untrusted external data
- any custom web view opened from a Slack app
The tricky part is that Slack removes some classes of frontend mistakes while leaving others completely intact. So the right question is not “can Slack apps get XSS?” It’s “which Slack surfaces reduce XSS risk, and which ones just move it somewhere else?”
The short version
Here’s the comparison I use when reviewing Slack apps.
| Surface | XSS Risk | Pros | Cons |
|---|---|---|---|
| Slack-rendered Block Kit messages | Low to medium | Slack controls rendering, limited HTML | Developers still inject unsafe content into linked web pages or external previews |
| Home tabs and modals | Low to medium | Structured UI, no arbitrary HTML | Unsafe interpolation in text fields, dangerous links, bad assumptions about escaping |
| Slash command responses | Medium | Simple payloads, predictable format | Reflected user input often ends up in app dashboards or logs |
| OAuth/install pages | High | Fully under your control | Standard web XSS risk, plus state and tenant confusion bugs |
| Admin/config dashboards | High | Flexible UI | Usually the weakest point; message content and workspace data become attacker-controlled input |
| Link unfurl backends | Medium to high | Rich previews improve UX | HTML scraping, metadata reflection, and template injection are common |
| Custom web views from Slack links | High | Full product experience | You own the browser attack surface again |
If your Slack app includes any normal web pages, assume your XSS exposure is basically the same as a SaaS app, with extra attacker-controlled text flowing in from Slack events.
1. Slack-rendered UI: safer, but not magic
Block Kit, modals, and Home tabs are the best thing Slack gives you from an XSS perspective.
Why? Because you are not shipping raw HTML into a browser you control. You send structured JSON, and Slack renders it. That removes a whole class of “innerHTML and pray” mistakes.
Pros
- No arbitrary HTML rendering in Slack surfaces
- Structured components reduce template injection mistakes
- Lower exposure to classic stored XSS in the chat UI itself
Cons
- Developers assume all embedded data is safe everywhere
- Links and external actions still jump into browser-controlled surfaces
- Text formatting can still be abused for phishing-style payloads even if not full XSS
A common bad pattern is trusting data just because it appeared in Slack first.
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Customer note: <script>alert(1)</script>"
}
}
Slack won’t execute that script in the message UI. Good.
Then someone reuses the same note inside an internal admin page:
noteContainer.innerHTML = customerNote;
Now you have stored XSS in your own dashboard, sourced from Slack-originated data. I’ve seen this exact failure mode more than once: “it came from Slack, so we thought it was sanitized.” Wrong trust boundary.
2. Home tabs and modals: good structure, easy false confidence
Home tabs and modals are generally safer than rolling your own frontend. I like them because they force discipline. But they also create a dangerous mindset: “Slack is handling security for us.”
Slack is handling rendering in Slack. It is not handling your backend, your templates, your logs viewer, or your support tooling.
Pros
- Tight schema for content
- Fewer opportunities for arbitrary script execution
- Easier to reason about than custom HTML UIs
Cons
- User-controlled values often flow into other systems
- Developers misuse
mrkdwnand link formatting - App actions often open unsafe URLs or web pages
Watch out for dynamic links:
const url = userProfile.website; // attacker-controlled
blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: `<${url}|Open profile>`
}
});
This may not become JavaScript execution inside Slack, but it can still become a delivery mechanism to a hostile page. If that page is yours, and you reflect query params unsafely, the Slack app just became the entry point.
Validate URLs hard. Allow only https: and known hosts where possible.
function safeExternalUrl(input) {
const url = new URL(input);
if (url.protocol !== "https:") throw new Error("invalid protocol");
if (!["app.example.com", "example.com"].includes(url.hostname)) {
throw new Error("invalid host");
}
return url.toString();
}
3. OAuth and install flows: this is where the real XSS usually lives
If I had to bet on one Slack app surface containing XSS, I’d pick the install flow or admin dashboard before anything inside Slack itself.
These pages are just web apps. They tend to be built quickly, touched rarely, and trusted too much because “the main product is in Slack.”
Pros
- Full control over UX
- Easier debugging and instrumentation
- Can use standard hardened web frameworks
Cons
- Full browser attack surface
- Query params, state values, team names, and error messages are often reflected
- Teams frequently skip CSP because these pages seem “small”
Classic example:
app.get("/slack/install/error", (req, res) => {
const reason = req.query.reason || "Unknown error";
res.send(`<p>Install failed: ${reason}</p>`);
});
That’s reflected XSS waiting to happen.
Safer version:
import escapeHtml from "escape-html";
app.get("/slack/install/error", (req, res) => {
const reason = req.query.reason || "Unknown error";
res.send(`<p>Install failed: ${escapeHtml(reason)}</p>`);
});
Better yet, use templates with auto-escaping and a strict CSP. If you need CSP implementation details, this is one of the few cases where I’d point people to https://csp-guide.com.
For Slack install and callback pages, I’d start with a policy close to this:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
base-uri 'none';
object-src 'none';
frame-ancestors 'none';
form-action 'self';
If your framework injects inline scripts, fix that instead of weakening the policy with 'unsafe-inline'.
4. Admin dashboards: the most underestimated Slack app XSS sink
Most Slack apps have some internal or customer-facing admin screen:
- workspace settings
- message history
- approval queues
- support tools
- audit viewers
- notification logs
This is where attacker-controlled Slack data gets replayed into HTML.
Pros
- Powerful operational visibility
- Easier than doing everything in Slack UI
- Supports richer workflows
Cons
- Stored XSS is extremely common
- Internal-only dashboards are often poorly hardened
- Teams sanitize outgoing content but not incoming event data
Bad pattern:
function EventRow({ event }) {
return <div dangerouslySetInnerHTML={{ __html: event.text }} />;
}
Good pattern:
function EventRow({ event }) {
return <div>{event.text}</div>;
}
If you really must render formatted content, sanitize with a well-maintained HTML sanitizer and keep allowed tags tiny. Personally, I try hard to avoid rendering any HTML derived from Slack event payloads.
Also remember that names, channel topics, profile fields, custom status text, and message content are all attacker-controlled in many workspace threat models.
5. Link unfurls and previews: rich UX, messy trust boundaries
Unfurls are useful, but they tempt developers into scraping external pages, parsing metadata, then reflecting that data into templates or dashboards.
Pros
- Better user experience
- Nice way to surface app context in Slack
- Usually server-side, so fewer direct browser sinks
Cons
- Metadata from third-party pages is untrusted
- Preview generators often use unsafe string interpolation
- Internal debug pages display scraped HTML or titles without escaping
Example of a sloppy preview template:
res.send(`
<h1>${page.title}</h1>
<p>${page.description}</p>
`);
If your scraper ingests hostile markup from a page title or Open Graph field, you’re done.
Treat all scraped metadata like user input. Escape on output. Always.
6. Custom web views: maximum flexibility, maximum responsibility
Some Slack apps push users into a full browser app via buttons or links. That is often the right product decision. It is also where your XSS risk returns to normal web-app levels.
Pros
- Full UI freedom
- Easier complex workflows
- Better for large forms and data-heavy views
Cons
- Every standard XSS class is back
- Slack users may trust these pages more than they should
- OAuth/session transitions create extra edge cases
If your app opens custom pages, the baseline controls are not optional:
- framework auto-escaping
- no unsafe DOM sinks like
innerHTML - strict CSP
- output encoding by context
- URL validation
- cookie hardening
- template separation from untrusted data
Official Slack docs are still worth reading for the platform behavior and payload formats: https://api.slack.com/docs
What I’d choose in practice
If the goal is reducing XSS risk in a Slack app, here’s the rough preference order I’d use:
Best for low XSS exposure
Slack-native surfaces like Block Kit, Home tabs, and modals
Pros
- Safer rendering model
- Less browser complexity
- Harder to accidentally execute script
Cons
- Limited UI flexibility
- Still easy to mishandle the same data elsewhere
Best for flexibility with manageable risk
Slack-native UI plus a small hardened web admin
Pros
- Good product balance
- Lets you keep dangerous rendering surfaces small
- Easier to review and secure
Cons
- Requires discipline around data flow
- Dashboard often becomes the weak point
Highest risk
Heavy reliance on custom web pages and internal HTML dashboards
Pros
- Fast to build if your team already knows web UI
- Unlimited UX options
Cons
- Standard XSS problems everywhere
- Slack event data becomes a stored-XSS source
- Usually needs the most security engineering effort
My opinionated checklist
If I’m reviewing a Slack app for XSS, I ask these first:
- Do any Slack event fields get rendered into HTML anywhere?
- Are install, OAuth, and error pages protected by a real CSP?
- Are admin tools treated like production apps, or like throwaway internal pages?
- Are outbound links restricted to safe schemes and trusted hosts?
- Does any code use
innerHTML,dangerouslySetInnerHTML, or server-side string concatenation for templates? - Are scraped preview fields escaped before display?
- Does the team understand that “rendered safely in Slack” does not mean “safe everywhere else”?
That last point is the one that bites people most. Slack reduces XSS risk inside Slack-rendered UI. It does not sanitize your architecture.
If you keep untrusted data in structured Slack surfaces and avoid replaying it into your own HTML, you cut the risk dramatically. If you build sprawling dashboards, install pages, and preview tools around your Slack app, you’re back in familiar XSS territory—just with more confusing trust boundaries.