Untrusted HTML hosting

Host untrusted HTML safely: a practical isolation model

Learn how to isolate executable user HTML with separate origins, iframe sandboxing, mediated storage, upload controls, lifecycle policy, and residual-risk review.

Author
Slidesfly
Reviewed by
Slidesfly product team
Published
Updated
Review method
Reviewed against current Slidesfly code and production HTTP behavior, live search results, and official MDN and OWASP guidance on 2026-08-24.

15 min read

To host untrusted HTML safely, keep it off the application origin, run it in a sandboxed iframe with the fewest capabilities required, mediate every stored-object request through current access policy, validate uploads and archives before storage, and maintain reporting, quarantine, expiry, and deletion controls. No single layer makes arbitrary HTML risk-free.

Download the untrusted HTML hosting security review to evaluate an implementation or vendor. It turns the model below into 34 evidence-bearing PASS / FAIL / N/A checks; “we use a sandbox” is not enough evidence to pass.

Define the untrusted HTML threat model

Untrusted HTML is markup, CSS, JavaScript, media, or archive content whose publisher is not allowed to inherit the privileges of the hosting application. It may come from an unknown uploader, a customer, a coding agent, a compromised account, or a well-intentioned author who accidentally bundled a secret or unsafe dependency.

The word untrusted describes the boundary, not the person's motive. A useful model assumes the artifact may try to:

  • read application cookies, browser storage, DOM content, or account data;
  • navigate or visually imitate a login, billing, or consent surface;
  • send form data or network requests to an external recipient;
  • open popups, trigger downloads, register persistence, or abuse browser permissions;
  • reach another customer's content through a shared origin or guessed storage path;
  • exhaust CPU, memory, storage, decompression, or request capacity;
  • remain available after deletion, expiry, visibility change, version replacement, or quarantine.

The browser's same-origin policy restricts how documents from different scheme, host, and port tuples interact. It is a critical isolation primitive, but it is not a complete hosting policy: cross-origin pages can still make requests, navigate, communicate through approved channels, mislead viewers, or consume resources.

Map threats to controls and residual risk

ThreatPrimary controlEvidence to requestResidual risk
Read account cookies or DOMSeparate origin plus sandbox without allow-same-originExact serving origins and rendered iframe attributesBrowser or configuration defects; sensitive data deliberately passed into the frame
Replace or imitate trusted UINo top navigation; clear reader chrome; separate originSandbox tokens, visible URL, phishing-report pathIn-frame social engineering remains possible
Read another upload's storageOpaque or per-artifact origin; private object accessStorage policy and cross-artifact browser testShared remote APIs or overly broad CORS can reconnect artifacts
Serve content after policy changePolicy gateway or short-lived authorizationDelete/expire/quarantine/version testCaches, stale replicas, or fail-open dependencies
ZIP traversal or decompression abusePath normalization, file-count and expanded-size limitsAdversarial archive tests before extractionParser vulnerabilities and resource exhaustion below the ceiling
Malicious links or scriptsScript/network policy, scanning, reports, quarantineBlock/flag rules, asynchronous checks, operator responseNovel domains, first-seen abuse, false negatives, false positives
Secret disclosure by publisherPreflight, warnings, narrow access policyArtifact review and clean-session visibility testThe host cannot make an embedded reusable credential safe

The control column is deliberately plural. If the design depends on one perfect scanner, one secret URL, or one iframe token, a bypass in that layer becomes a full compromise.

Choose sanitization or isolation

“Render HTML” can mean two different jobs, and they require different answers.

User needCorrect modelWhat changes
Show comments, descriptions, or rich text inside the trusted applicationSanitize to an explicit allowlist, encode for the output context, and keep scripts disabledDangerous elements, attributes, URLs, and behavior are removed or rewritten
Preserve a complete HTML presentation, prototype, report, or agent artifactIsolate the document in a separate browsing context and originThe artifact remains executable, but loses application privileges
Display plain text or codeUse a text sink such as textContentMarkup is displayed rather than interpreted

The OWASP XSS Prevention Cheat Sheet recommends safe sinks and HTML sanitization where applications intentionally render untrusted HTML inside their own DOM. It also describes Content Security Policy as defense in depth, not the primary fix for an injection flaw.

Sanitization is not a transparent way to host an arbitrary interactive document. Removing scripts, event handlers, forms, embeds, or URLs can break the artifact and produce a file different from the one its author reviewed. Conversely, placing a sanitized comment system in a powerful iframe is usually unnecessary complexity. Decide whether behavior must survive before choosing the control.

Separate trusted and untrusted origins

Do not serve uploaded executable HTML from the same origin as login, account, billing, admin, API, or other customer content. A path such as app.example.com/uploads/a.html is still same-origin with app.example.com/account; paths are not security boundaries.

A safer shape is:

app.example.com        trusted UI, sessions, writes, account APIs
content.example.net    reader shell and/or mediated artifact delivery
private object store   no unauthenticated public bucket path

Using a separate registrable domain can further reduce the consequences of cookie scope and future subdomain mistakes. Whatever naming scheme you choose, document which origin owns sessions and which origin is allowed to execute uploaded code.

MDN's iframe reference warns that sandboxing is ineffective if an attacker can open the same content outside the sandbox and recommends serving potentially hostile content from a separate origin. It also warns against combining allow-scripts and allow-same-origin for same-origin embedded content, because the embedded document can remove the sandbox attribute.

Keep the content origin low privilege

  • Do not set application session cookies for the content origin.
  • Do not expose account, billing, administrative, or write APIs there.
  • Do not let an internal host override or preview header work in production.
  • Treat every CORS response as an explicit data-release decision, not a substitute for authorization.
  • Avoid a raw-file URL that viewers can open without the intended isolation or access check.
  • Test custom domains, embed mode, previews, downloads, and “open in new tab” as separate paths.

Origin separation limits blast radius; it does not prevent phishing inside the artifact, outbound network requests, excessive computation, or sensitive content supplied by the publisher.

Sandbox the runtime with the fewest capabilities

An iframe sandbox begins with restrictions and selectively restores capabilities through tokens. MDN documents that omitting allow-same-origin gives the framed resource a special origin that fails same-origin checks, while omitting allow-top-navigation prevents it from replacing the top-level reader page.

Start with an empty sandbox and add only a capability the artifact actually needs:

<iframe sandbox="allow-scripts" title="User-supplied document"></iframe>

Interactive presentations usually need JavaScript. Forms, popups, downloads, modals, clipboard, fullscreen, orientation, or pointer lock are separate decisions. Record why each restored token or Permissions Policy feature is required, what abuse it enables, and how it was tested.

CapabilityBenefitSecurity or product costReview question
allow-scriptsRuns transitions, charts, and controlsEnables arbitrary computation and network-capable codeCan the artifact function without it?
allow-formsSubmits interactive formsCan send viewer-entered data to another endpointIs destination and consent visible?
allow-popupsOpens external links in a new contextEnables popup abuse and opener/referrer concernsAre links user-initiated and isolated?
allow-popups-to-escape-sandboxLets the destination behave normallyThe new page no longer inherits frame restrictionsIs the destination clearly outside the hosted artifact?
allow-same-originRestores normal origin behavior and storageCan reconnect the frame to shared-origin data and weaken same-origin sandboxingIs a unique origin guaranteed per artifact?
allow-top-navigationLets the frame replace the readerEnables forced navigation and phishing flowsWhy should uploaded content control the viewer tab?

The HTTP Content-Security-Policy: sandbox directive can apply similar restrictions to a resource response. CSP also helps constrain scripts, frames, connections, images, fonts, forms, and framing, but a policy must match the actual delivery architecture. A broad connect-src, wildcard CORS rule, or unsandboxed raw path can undo the intent of a narrow reader shell.

Treat message channels as untrusted input

If parent and frame communicate with postMessage, keep the protocol small. The parent should validate message type, shape, size, and—where the frame has a stable origin—the exact origin and window reference. Never evaluate a message as code or insert it into a trusted DOM with an unsafe sink. An opaque-origin frame may serialize its origin as null, so avoid sending secrets and bind the exchange to the specific window reference and a minimal command set.

Mediate storage and every content-delivery path

Sandboxing the visible reader is insufficient if a predictable object URL exposes the same HTML without policy or if a public bucket keeps old versions reachable. Storage and delivery need their own authorization boundary.

The OWASP File Upload Cheat Sheet recommends allowlisting required extensions, validating rather than trusting the request Content-Type, limiting filenames and size, storing files on a separate server or outside the web root, and mapping public access through an application handler.

A content request should answer all of these questions before bytes leave storage:

  1. Does the artifact still exist and remain active?
  2. Is this the current version or an explicitly authorized historical version?
  3. Has it expired, been deleted, or entered quarantine?
  4. Does its visibility allow this unauthenticated path?
  5. Does a password, allowlist, or private policy require a separate authorization flow?
  6. If the policy service is unavailable, does delivery fail closed?

Public and unlisted content can use a gateway that checks current state on every request. Protected content can use a short-lived signed URL after viewer authorization. Both designs must control cache lifetime, object naming, historical versions, partial content, and every asset in a multi-file bundle—not only index.html.

Verify revocation, not just initial access

Run transitions, then read back the same stable path in a clean session:

TransitionExpected result
Active → expiredContent path stops serving the artifact
Active → quarantinedReader and raw asset paths deny access
Public/unlisted → protectedUnauthenticated path no longer returns bytes
Version 1 → version 2Stable reader resolves current version; guessed old path is denied
Active → deletedReader, entry file, and bundled assets become unavailable according to policy
Policy dependency unavailableService returns an unavailable/denied state, not stale content

An HTTP 200 on the day of publication proves only that one path worked at that moment. Security depends on later state changes reaching every serving path.

Validate uploads and archives before serving them

Validation should confirm the artifact contract and reduce abuse; it should not be marketed as a guarantee that arbitrary JavaScript is harmless.

For a single HTML file, check at least:

  • accepted extension and a plausible HTML marker;
  • compressed/request and decoded byte limits;
  • filename handling independent from object keys;
  • disallowed remote runtime patterns required by the product policy;
  • risk signals for triage without pretending a keyword list is a complete malware detector;
  • final content type plus X-Content-Type-Options: nosniff on delivery.

For an archive, add:

  • a required root entry file such as index.html;
  • normalized POSIX-style paths with no absolute paths, backslashes, null bytes, or .. traversal;
  • duplicate-path handling that makes the scanned bytes equal the served bytes;
  • file-count, compressed-size, and total expanded-size ceilings checked before unbounded extraction;
  • per-file and total plan limits after extraction;
  • scanning of every executable or text asset, not only the root HTML;
  • safe MIME mapping for every served extension.

OWASP calls out ZIP bombs and advises considering size after decompression. Archive validation also has a time-of-check/time-of-use requirement: if two entries normalize to the same storage key, the scanner and object writer must agree about which bytes win.

Scanning, isolation, and moderation solve different problems

LayerGood atDoes not prove
Structural validationRejecting malformed artifact shapes and known archive hazardsThe JavaScript is benign
Blocklists and reputationCatching known indicators and domainsA new or obfuscated payload is safe
Browser isolationRemoving application privileges from rendered codeThe content cannot phish or exfiltrate data supplied inside it
Reporting and moderationResponding after abusive content is observedEvery harmful artifact is found before first view
Quotas and rate limitsReducing availability and cost abuseA small artifact cannot be harmful

Keep logging and retry states truthful. A scanner timeout is “unscanned” or “unavailable,” not a clean verdict.

Enforce lifecycle, monitoring, and response policy

A hosting system remains responsible after the first successful render. At minimum, document:

  • visibility states and who may change them;
  • expiry semantics and the clock used to enforce them;
  • update, version-history, restore, and historical-object behavior;
  • deletion from the reader, gateway, cache, object store, analytics, and backups;
  • user report categories and a visible report path;
  • automatic and manual quarantine states;
  • operator alerts, triage ownership, appeal, and recovery;
  • vulnerability disclosure and security-contact instructions.

Monitoring should distinguish a failed content fetch, policy dependency failure, blocked upload, risk flag, reputation hit, user report, and operator quarantine. Combining them into one “security scan failed” metric hides whether viewers are protected or the service is merely unavailable.

Review a hosting system with observable evidence

Ask for behavior you can inspect, not adjectives. This compact scorecard separates architecture claims from evidence.

Review areaPASS evidenceFAIL signal
Trusted boundaryAccount and content origins are named; content origin has no write/account APIsUploads share the session origin or the answer is unspecified
Runtime boundaryExact iframe/CSP sandbox tokens are visible and justified“Sandboxed” without configuration, or arbitrary raw open path
Storage boundaryObjects are private or every public path is policy-mediatedBucket/object URLs bypass current visibility and moderation
Archive boundaryTraversal, duplicate paths, file count, compressed and expanded sizes are testedOnly filename extension is checked
Lifecycle boundaryExpiry, deletion, quarantine, visibility change, and old versions are read backPolicy exists only in dashboard UI
Failure boundaryAuthorization dependency failure does not serve stale contentPolicy errors silently fall back to public bytes
Response boundaryReport, quarantine, disclosure, and owner are documentedNo route from observed abuse to revocation
Claim boundaryLimits and residual risks are explicit“100% safe,” “risk-free,” or certification without scope/evidence

Slidesfly implementation example

Slidesfly's product-specific facts remain on the canonical security architecture page. As of the August 24, 2026 review, the implementation uses separate app and content domains, renders decks in an iframe that omits allow-same-origin and allow-top-navigation, keeps R2 objects private, checks current visibility, expiry, moderation, protection, and version state before public delivery, and rejects unsafe archive paths and oversized expansion.

The review used repository code, tests, the live /security page, its response headers, the agent-readable security.md, and a production request confirming that a write API on the content domain returned 404. These checks validate the named paths and controls; they are not a penetration test, independent certification, or promise that no defect exists.

For artifact preparation, use the HTML presentation preflight checklist. For the full hosting decision, start with the HTML presentation hosting guide. For a reproducible source and reader workflow, use the plain HTML Proof Pack.

Frequently asked questions

Is a sandboxed iframe enough to host untrusted HTML safely?

No. The sandbox controls browser capabilities inside one rendering path. You still need a separate low-privilege origin, mediated storage, upload and archive validation, access policy, revocation, reporting, moderation, availability controls, and tests for raw files, embeds, custom domains, downloads, and failure states.

Should I sanitize untrusted HTML or put it in an iframe?

Sanitize when the application needs safe rich text inside its trusted DOM and executable behavior is not part of the product. Use isolation when a complete HTML document must retain JavaScript and interaction. Sanitization changes the artifact; sandboxing preserves more behavior but requires a stronger origin, storage, and lifecycle boundary.

Can sandboxed HTML still make network requests?

Potentially, yes. Sandbox tokens and the same-origin policy do not automatically block every outbound request. Apply a Content Security Policy and deliberate CORS/server authorization, then test forms, fetches, images, media, fonts, beacons, links, and popups. Never pass secrets into the frame on the assumption that origin isolation prevents exfiltration.

Why omit allow-same-origin from the iframe sandbox?

Without allow-same-origin, the browser treats the framed resource as a special origin that fails same-origin checks, limiting access to shared cookies, storage, and DOM. If an architecture needs normal origin behavior, it should provide a demonstrably unique, low-privilege origin per artifact and review the additional attack surface.

Does an unlisted URL make uploaded HTML safe?

No. Unlisted affects discovery, not code behavior or viewer authorization. Anyone who obtains the URL may be able to open it. Remove secrets before upload, use a verified protected policy for restricted audiences, and ensure that raw storage paths cannot bypass that policy.

How should a host behave when its policy service is down?

Fail closed for content authorization. Return an unavailable or denied response and retry safely; do not serve stale bytes because the policy lookup failed. Record the outage separately from a policy denial so operators can restore availability without weakening authorization.

Run the downloadable 34-check security review before accepting a hosting design. If the artifact itself is ready, continue to the publish-and-verify workflow and verify the returned reader URL in a clean browser session.

Continue with the Quickstart or browse all HTML presentation publishing guides.