Outlook web add-ins are a weird security boundary.

They look like normal web apps because they are built with HTML, CSS, and JavaScript. But they run inside Outlook, interact with mailbox data, and often get trusted more than they should. That mix is exactly why XSS in Outlook web add-ins deserves special attention.

If you already know how to prevent XSS in a regular web app, you are not done. Outlook add-ins add their own constraints, APIs, hosting model, and trust assumptions. Some of those help. Some make things worse.

This guide compares the main XSS risk areas, the common mitigation options, and the tradeoffs you actually deal with when shipping add-ins.

Why XSS in Outlook add-ins is different

An Outlook web add-in is still a web page, usually loaded in an embedded browser surface. If an attacker gets script execution inside that surface, the impact is not just “some DOM got popped.”

The add-in may have access to:

  • email subject and body
  • sender and recipient metadata
  • appointment details
  • tokens or auth state for your backend
  • Office.js methods that trigger actions in the mailbox context

That means an XSS bug can become:

  • mailbox data theft
  • account pivoting into your backend APIs
  • malicious email content rewriting
  • phishing inside a trusted enterprise workflow

I’ve seen teams treat add-ins like “just a sidebar UI.” That mindset causes sloppy rendering code, weak CSP, and dangerous trust in mailbox content.

The biggest XSS sources in Outlook add-ins

Here’s the short version: mailbox data is user-controlled data, even when it came from “inside the company.”

That includes:

  • message subjects
  • HTML email bodies
  • calendar descriptions
  • display names
  • attachment names
  • values returned from your own backend if they were originally derived from email content

The dangerous part is that Outlook add-ins often exist to inspect, summarize, or transform this content. So developers naturally render it.

Option 1: Render mailbox content as plain text

This is the safest default.

If you get a message body or subject and display it using text-only sinks, your XSS risk drops hard.

const subjectEl = document.getElementById("subject");
subjectEl.textContent = item.subject || "";
```text

For larger chunks of content:

```javascript
const previewEl = document.getElementById("preview");
previewEl.textContent = emailBody;

Pros

  • Simple and reliable
  • No sanitizer complexity
  • Works well with untrusted subjects, names, and snippets
  • Hard to misuse once adopted consistently

Cons

  • You lose formatting
  • HTML emails become ugly or less useful
  • Product teams often hate the UX downgrade

If your feature does not truly need rich HTML rendering, I would pick this every time.

Option 2: Render sanitized HTML

Sometimes plain text is not enough. Maybe your add-in highlights parts of an email, preserves formatting for review, or shows generated summaries with links and emphasis.

Then you need sanitization.

import DOMPurify from "dompurify";

const clean = DOMPurify.sanitize(emailHtml, {
  USE_PROFILES: { html: true }
});

document.getElementById("content").innerHTML = clean;
```text

#### Pros

- Preserves formatting
- Better UX for email-heavy workflows
- More flexible for annotation and review features

#### Cons

- Easy to get wrong
- Sanitizer config drift becomes a security problem
- Developers often reintroduce risk with post-sanitize DOM manipulation
- Rich HTML still creates attack surface through allowed tags and attributes

This approach is fine, but only if you treat sanitization as a security control, not a formatting helper.

A common failure looks like this:

```javascript
const clean = DOMPurify.sanitize(emailHtml);
container.innerHTML = clean;

// later
container.querySelectorAll("a").forEach(a => {
  a.setAttribute("target", "_blank");
});

That code looks harmless. But any post-processing step that touches sanitized markup needs review. You can accidentally re-enable risky behavior if you inject attributes, parse embedded data, or concatenate strings into the DOM.

Comparing dangerous DOM patterns

Some XSS bugs in add-ins are just classic web mistakes. The Outlook part only increases the blast radius.

innerHTML

container.innerHTML = item.subject;
```text

#### Pros

- Fast to write
- Flexible for templating

#### Cons

- Dangerous with untrusted data
- Easy footgun
- Usually unnecessary

My opinion: ban it by default.

### `textContent`

```javascript
container.textContent = item.subject;

Pros

  • Safe for untrusted strings
  • Cheap and easy
  • Great default

Cons

  • No HTML formatting

Use this unless you can justify something else.

DOM API construction

const li = document.createElement("li");
const strong = document.createElement("strong");
strong.textContent = senderName;

li.appendChild(strong);
li.appendChild(document.createTextNode(`: ${subject}`));
list.appendChild(li);
```text

#### Pros

- Safer than string-built HTML
- Explicit structure
- Good for component rendering without a framework

#### Cons

- Verbose
- Developers sometimes fall back to string concatenation out of frustration

For small add-ins, this pattern ages better than people expect.

## Framework auto-escaping: good, but not magic

If your Outlook add-in uses React, Vue, Angular, or similar, you get some protection from default escaping.

React example:

function MailSubject({ subject }) { return

{subject}

; }


That is much safer than manual string rendering.

But the usual escape hatches are still dangerous.

function HtmlPreview({ html }) { return <div dangerouslySetInnerHTML={{ __html: html }} />; }


#### Pros of framework templating

- Safer defaults
- Less accidental DOM XSS
- Easier to standardize patterns across the team

#### Cons

- Dangerous escape hatches still exist
- Third-party UI components may inject raw HTML
- Developers often trust server-rendered HTML too much

If you use a framework, create a single reviewed component for sanitized HTML rendering and make everything else text-only by default.

## CSP in Outlook add-ins: useful, but constrained

Content Security Policy still matters for Outlook web add-ins, especially when you want a backstop against inline script execution and surprise script sources.

A good CSP can reduce exploitability when someone slips unsafe rendering into the codebase.

Example:

Content-Security-Policy: default-src ‘self’; script-src ‘self’; style-src ‘self’ ‘unsafe-inline’; img-src ‘self’ data: https:; connect-src ‘self’ https://api.example.com; frame-ancestors https://outlook.office.com; base-uri ’none’; object-src ’none’;


#### Pros

- Strong defense-in-depth
- Limits script execution paths
- Helps catch bad assumptions during testing
- Useful for hardening add-in pages and task panes

#### Cons

- Outlook add-in hosting requirements can limit how strict you can be
- Legacy libraries may force weaker policies
- Inline event handlers and ad-hoc scripts become painful to maintain

You should still do it. Just expect a little friction.

For CSP implementation details and rollout patterns, [csp-guide.com](https://csp-guide.com) is useful. For Office add-in hosting behavior and platform requirements, stick to the official Microsoft documentation.

## Comparing sanitization strategies

### Centralized sanitizer wrapper

```javascript
import DOMPurify from "dompurify";

export function renderTrustedHtml(el, dirtyHtml) {
  const clean = DOMPurify.sanitize(dirtyHtml, {
    ALLOWED_TAGS: ["b", "i", "em", "strong", "p", "br", "ul", "ol", "li", "a"],
    ALLOWED_ATTR: ["href"],
    ALLOW_DATA_ATTR: false
  });

  el.innerHTML = clean;
}

Pros

  • Consistent policy
  • Easier to review
  • Easier to test

Cons

  • Can become a bottleneck if every feature wants exceptions
  • Teams are tempted to keep widening allowed tags

This is the best practical setup for most teams.

Per-feature sanitizer rules

Pros

  • Flexible for different UI needs
  • Can preserve more formatting where required

Cons

  • Hard to audit
  • Rules drift over time
  • Security posture becomes inconsistent

I avoid this unless the product is unusually complex.

Outlook-specific trust mistakes

These are the ones I see most often.

“The email came from our tenant, so it’s trusted”

Nope.

Internal accounts get compromised. Display names are spoofable. Calendar content is user input. Treat mailbox-derived content as hostile.

“Office.js data is safe because it came from the API”

Also nope.

The API is not a sanitizer. If it returns message content, that content still needs output encoding or sanitization.

“We sanitized on the server”

Good. Sanitize again if needed for the client rendering context.

Different contexts need different defenses. A server-side filter does not guarantee safety for DOM insertion.

Safer patterns for add-in architecture

If I were setting standards for an Outlook add-in team, I’d keep it pretty strict:

  1. Default all mailbox-derived strings to textContent
  2. Allow HTML rendering only through one reviewed sanitizer wrapper
  3. Ban direct innerHTML outside that wrapper
  4. Add a CSP that blocks inline scripts and unknown script sources
  5. Review any feature that displays email body HTML as a higher-risk component
  6. Treat auth tokens and mailbox data in the add-in as sensitive even if the UI seems minor

You can even enforce some of this with lint rules.

Example policy wrapper:

export function setText(el, value) {
  el.textContent = value ?? "";
}

export function setSafeHtml(el, html) {
  const clean = DOMPurify.sanitize(html, {
    USE_PROFILES: { html: true }
  });
  el.innerHTML = clean;
}
```text

Then make direct DOM sinks a code review violation.

## Best option by use case

### Showing subjects, names, metadata

Use plain text.

**Best choice:** `textContent`

### Showing snippets or previews

Use plain text unless formatting is a hard requirement.

**Best choice:** plain text first, sanitized HTML only if justified

### Rendering full email body content

Use a strict sanitizer and isolate the feature mentally as high risk.

**Best choice:** centralized sanitization plus CSP

### Building interactive add-in UIs

Use a framework with auto-escaping, but lock down raw HTML escape hatches.

**Best choice:** framework templating plus reviewed safe HTML component

## The practical takeaway

The real comparison is not “can Outlook add-ins get XSS?” Of course they can.

The useful comparison is between teams that treat mailbox content as hostile and teams that treat it as business data. The first group usually ends up with boring, effective defenses. The second group ends up debugging why a calendar description executed script in a trusted enterprise tool.

For Outlook web add-ins, my bias is simple:

- text by default
- sanitized HTML rarely
- CSP always if you can
- zero trust for mailbox content

That approach is not glamorous, but it keeps your add-in from becoming the easiest XSS target in the mail client.