cap-ecosystem

CAP after browser-side XSLT: representation, rendering and content negotiation

Browser XSLT removal changes human presentation, not CAP. A publisher-focused guide to rendering, HTTP negotiation and practical migration.

Ian Ibbotson, CTO, Alert-Hub. Technical reference accompanying a 30-minute talk. Verified 4 October 2026.

Download the presentation: PowerPoint (.pptx) · PDF.

Also available: speaker/rehearsal script, implementation examples and sources.

Chromium removing browser-side XSLT does not threaten the Common Alerting Protocol itself. It removes one mechanism commonly used to turn CAP XML into a human-readable document. Existing CAP documents remain CAP documents. The architectural question for publishers is where presentation should happen.

This article examines the choices available to organisations publishing their own CAP feeds. It gives particular attention to server rendering with HTTP content negotiation, while recognising that separate URLs, client applications and machine-only endpoints can also be appropriate. Alert-Hub appears as one inspected implementation, rather than a required service.

1. What Chromium is changing

Chrome’s current published plan removes native XSLT in Stable version 158, scheduled for 17 November 2026. It covers both transformation through an XSLT xml-stylesheet instruction and the JavaScript XSLTProcessor API. Temporary origin-trial and enterprise-policy extensions are planned to end with Chrome 176 on 17 August 2027. Recheck the Chrome Developers migration notice and Chrome Platform Status entry before relying on a release date.

XML support remains. The processing instruction can still associate CSS with XML. XSLT transforms document structure; CSS styles it. Keeping CSS support does not preserve an XSLT-generated HTML page. Other engines have their own plans, so do not assume this is a synchronised, universal browser switch.

A temporary browser exception or extension may help a controlled internal environment. A public warning page needs a presentation path that visitors can use without arranging those exceptions. Changing the CAP exchange format is not necessary to address this browser feature removal.

2. Why CAP publishers care

A familiar document begins like this:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="cap.xsl"?>
<alert xmlns="urn:oasis:names:tc:emergency:cap:1.2">
  <!-- CAP content omitted in this abbreviated example -->
</alert>

A supporting browser receives XML, fetches cap.xsl, executes the transformation and displays the result. The server has sent XML and a presentation instruction. It has not sent an HTML page.

A machine consumer can read the CAP without executing the stylesheet. The CAP 1.2 specification defines the alert structure and semantics independently of this browser presentation mechanism. Removing a transformation step does not alter the sender, identifier, message type or information blocks.

Human access still matters. People follow shared alert links, check instructions and inspect feeds during incidents. A loss of readable presentation is an operational issue even when the machine exchange continues correctly. The browser’s exact fallback may vary; publishers should test their own URLs rather than promise that everyone will see a particular XML viewer.

3. Presentation was an implicit dependency

The browser-XSLT pattern delegates part of publishing to the recipient’s browser. It depends on a compatible processor, an available stylesheet and permitted access to any resources the stylesheet uses. That arrangement can work well, but its ownership is easy to overlook.

A migration makes the responsibility visible. Who creates the readable document? What happens when rendering fails? Which version of the alert does the page describe? How does the publisher verify that the warning’s meaning survives the transformation?

Keep CAP as the authoritative interchange representation. Test presentation separately. Introducing an HTML default carelessly can disrupt a consumer that previously received XML, even though Chromium’s change itself did not affect that consumer.

4. A resource can have multiple representations

Consider a fictional URL:

https://alerts.example.org/alerts/12345

It can identify an alert resource. A response can represent that resource as CAP XML or HTML. This is the distinction described in HTTP Semantics, resources and representations.

CAP supplies machine-readable alert information. HTML supplies human-readable information about the same alert. Generate both from the same authoritative version. Preserve status, instructions, languages, affected areas and update/cancellation relationships. A renderer that drops an information block or mislabels a test alert is semantically wrong, however polished its page looks.

A CAP identifier is not automatically an HTTP URL. Publishers still need an addressing policy. Likewise, an alert resource is distinct from a collection. An RSS or Atom feed can enumerate links to many individual CAP alerts. Label a collection with its actual media type rather than calling every XML response application/cap+xml.

5. Content negotiation in HTTP

A client can express its preference through Accept:

GET /alerts/12345 HTTP/1.1
Host: alerts.example.org
Accept: application/cap+xml
HTTP/1.1 200 OK
Content-Type: application/cap+xml; charset=UTF-8
Vary: Accept

<alert xmlns="urn:oasis:names:tc:emergency:cap:1.2">...</alert>

The same URL can respond to Accept: text/html with Content-Type: text/html. The header requests a representation; the response type describes what actually arrived. A request’s Content-Type describes a request body, so it does not select the GET response format.

Use a framework parser or negotiation facility. Real Accept fields contain media ranges and quality values:

Accept: text/html;q=0.9, application/cap+xml;q=0.5

Here HTML has the stronger preference. q=0 excludes a representation. A more specific range determines a candidate’s quality before a broader wildcard: text/html;q=0, */*;q=1 does not make HTML acceptable. Parameter matching and equal-quality tie-breaking also deserve tests. Do not implement selection as header.Contains("html"). The relevant rules are in RFC 9110, Accept.

Accept: */* means any media type is acceptable. It does not identify a browser. An absent field similarly needs a documented default. For an existing CAP endpoint, retaining CAP as the default can preserve clients that send no useful preference. Browser navigations typically offer HTML explicitly, so they can receive HTML without changing that machine default.

When no supported type is acceptable, this package’s framework examples return 406 Not Acceptable. HTTP also permits ignoring the preference, so strict rejection is a chosen policy, not a universal requirement. Document whichever behaviour you adopt.

Test a real service, or use one of the local examples:

curl -i -H 'Accept: application/cap+xml' http://localhost:8080/alerts/123
curl -i -H 'Accept: text/html' http://localhost:8080/alerts/123
curl -i -H 'Accept: */*' http://localhost:8080/alerts/123
curl -i -H 'Accept: application/json' http://localhost:8080/alerts/123

For negotiated responses, add Vary: Accept while preserving other existing Vary values. Validate the CDN’s cache key and revalidation behaviour. A URL-only cache can serve the wrong representation. Use representation-appropriate ETags: an HTML template revision may change HTML bytes while CAP stays identical. See HTTP caching, selecting stored responses.

Try the offline simulation: content-negotiation demonstration. It shows requests, selection decisions, headers and fictional representations. It supports both header-first and fixed-suffix policies. It does not make live HTTP requests or identify its policy as a standard.

Some publishers offer /alerts/123.xml and /alerts/123.html. Those suffixes do not automatically perform HTTP negotiation. The server decides their meaning.

A header-first design can apply explicit Accept preference > suffix > default, treating an uninformative wildcard as permission to use the suffix. Another design uses a fixed representation URL: .xml always returns XML and .html always returns HTML. That is explicit selection by URL. Either needs clear documentation, especially when headers conflict with suffixes.

Avoid breaking links people use to download CAP. An ordinary browser follows links using its normal Accept header, which may prefer HTML. If you need an explicit CAP download, provide a fixed-format route and accurately label it. The examples use /alerts/123/cap for that purpose. Explicit URLs also offer an alternative when a static host cannot negotiate.

Use canonical links deliberately and expose alternate representations where helpful. Do not assume a canonical HTML link proves that CAP’s identifier or signature refers to that URL.

7. Server rendering

Server rendering produces HTML before sending the response. A publisher can use a template/view, a reusable CAP renderer or an XSLT processor. Selection and rendering are separate responsibilities: HTTP chooses HTML; a rendering component produces it.

A template is attractive when the application already has a view layer. A shared renderer can centralise common CAP presentation rules. XSLT can preserve existing transformation logic. None automatically guarantees accessibility. Review reading order, language declarations, keyboard use, colour contrast and how urgently people can understand the instructions.

The initial HTML should remain useful without JavaScript. Add interactive maps or details as enhancement where necessary, without hiding essential warning text behind a client runtime. Search indexing depends on more than server rendering, including access policy and robots configuration.

8. Existing XSLT investment can survive

The basic migration is straightforward:

CAP + approved XSLT
        ↓
server transformation
        ↓
HTML response

The execution location changes; CAP meaning need not. Test processor compatibility, namespace handling, relative asset URLs, output encoding and the full range of your publisher’s messages. Browser-era XSLT commonly targets version 1.0; choose and test the processor rather than assuming all stylesheet versions behave alike.

Cache approved stylesheet bytes and, where appropriate, compiled forms. Record their hash, provenance, approval state and renderer version. Refresh through a bounded process rather than fetching a stylesheet during every public request. A changed remote file should not silently gain the previous file’s approval.

Define failure paths. An unavailable stylesheet may trigger a reviewed default renderer. A transformation error may instead require a clearly logged failure. Never make a broken transformation look like a successful, complete warning. Monitor fallback use and give operators enough context to investigate.

Explore execution location: XSLT migration demonstration. This is a diagram simulation, not an XSLT engine or compatibility test.

9. A new security boundary

Executing arbitrary publisher-supplied XSLT on your server is not inherently safe. Move the trust boundary deliberately, covering both retrieval and execution.

Retrieval and SSRF. A stylesheet URL derived from untrusted XML can target loopback, private networks, cloud metadata or services that the publisher should not reach. Resolve relative references against a validated source URL. Allowlist schemes and destinations, reject credentials, validate DNS/IP results and recheck redirects. Enforce egress policy so DNS changes cannot evade validation. Bound redirects, fetch duration and compressed/decompressed size. See the OWASP SSRF Prevention Cheat Sheet.

Processor capabilities. Review xsl:include, xsl:import, document(), extension functions, external entities and output-to-file facilities. Restrict file and network access during stylesheet compilation and execution. Configure XML input parsing separately. A secure parser setting is not a complete XSLT sandbox. In Java, JAXP’s security guide describes secure processing, limits and external-access properties. PHP’s XSLT security preferences provide resource controls. .NET’s XslCompiledTransform requires deliberate settings and resolvers. Verify the exact engine and runtime you deploy, including native-image/AOT support where applicable.

Availability. Restrict input and output size, memory, CPU and concurrency. Recursion and expansion can consume substantial resources without accessing the network. A timeout on the calling thread may leave transformation work running. Use a process/worker boundary with an enforceable termination mechanism where hard limits are required. Bound compiled caches as well as individual jobs.

HTML output. A stylesheet can emit scripts, external asset references, unsafe links or misleading content. Trust in an alert publisher does not automatically justify executing their generated JavaScript in your site’s origin. Review or sanitise output and choose an appropriate CSP and isolation boundary. Templates must escape alert fields too.

Lifecycle. Retain audit records, define expiry and refresh policy and fail visibly. Where automated fetches encounter a human-verification challenge, do not bypass it. A bounded administrative upload with validation and approval can be a legitimate alternative. It still needs the same execution controls.

10. Small framework implementations

The downloadable examples include a shared, fictional CAP 1.2 fixture with status=Test, complete projects and run instructions. They return the fixture unchanged as CAP and derive a small escaped HTML view from it. They do not fetch remote documents or execute remote stylesheets. All use CAP for an absent header or a bare wildcard, and document their HTTP contract.

.NET 10 / ASP.NET Core

Controller output formatters let MVC negotiate a typed alert value. Configure browser header handling explicitly and offer only the representations this small app supports:

builder.Services.AddControllers(options =>
{
    options.RespectBrowserAcceptHeader = true;
    options.ReturnHttpNotAcceptable = true;
    options.OutputFormatters.Clear(); // Standalone teaching app only.
    options.OutputFormatters.Add(new AlertFormatter("application/cap+xml"));
    options.OutputFormatters.Add(new AlertFormatter("text/html"));
});

AlertFormatter derives from TextOutputFormatter, declares UTF-8 support and writes CAP or HTML for the same DemoAlert type. The controller returns Ok(alert). A small middleware policy uses typed MediaTypeHeaderValue matching to preserve exact q=0 exclusions before MVC, including exclusions beneath wildcards. Unmodified framework defaults did not enforce those cases in our tests. See the complete Program.cs, Microsoft’s formatter documentation and response formatting guidance.

Do not globally remove formatters in an existing multi-purpose API. Register narrowly applicable CAP/HTML formatters alongside its other formats. Ordinary XML serialization of an arbitrary object does not establish CAP validity. A manual ContentResult is useful for a fixed-format route, but bypasses formatter negotiation; it requires a separate selection policy if used for the negotiated endpoint.

Java / Spring Boot 4.1.1 and Spring MVC

The controller declares both representations for one resource:

@GetMapping(value = "/alerts/{id}",
            produces = {"application/cap+xml", "text/html"})
ResponseEntity<Alert> get(@PathVariable String id) {
    if (!id.equals("123")) return ResponseEntity.notFound().build();
    return ResponseEntity.ok().header("Vary", "Accept")
        .header("Cache-Control", "no-store").body(alert);
}

An AbstractHttpMessageConverter<Alert> writes the selected representation. A WebMvcConfigurer registers it and a ContentNegotiationStrategy which uses Spring’s header parser, then resolves effective quality for the two offered types. This makes CAP the default and preserves q=0 exclusions, which the unmodified defaults did not consistently enforce in testing. The converter retains the original CAP bytes and uses escaped values in HTML. Returning a view name or an arbitrary XML-serialised Java object would be a different design. The complete CapDemo.java demonstrates the converter and configuration. Consult Spring’s mapping, content negotiation and message converter documentation.

PHP 8.4 / Symfony HttpFoundation 8.1.8

HttpFoundation parses the field and exposes matching quality values, including wildcard fallbacks:

$accept = AcceptHeader::fromString(
    $request->headers->get('Accept') ?: '*/*');
$selected = null;
$best = 0.0;
foreach (['application/cap+xml', 'text/html'] as $type) {
    $quality = $accept->get($type)?->getQuality() ?? 0.0;
    if ($quality > $best) {
        $best = $quality;
        $selected = $type;
    }
}

If nothing matches with positive quality, return 406. Otherwise construct a Response with the selected type and Vary: Accept. Equal qualities use this example’s documented CAP-first server order. This compact example offers two unparameterised UTF-8 types; expand and test the policy before offering parameterised variants. The complete index.php includes method/path handling, safe escaping and a fixed CAP link. See HttpFoundation documentation and the AcceptHeader implementation.

These are teaching servers, not production CAP systems. They intentionally omit storage, feed pagination, publisher authentication, signatures and operational caching. Production reads should retrieve/filter the required alerts in the database, never load broad history and filter it in memory.

11. One observed implementation: the Alert-Hub reflector

In our aggregation work we implemented this pattern for reflected feeds and individual alerts. Relevant resources are the aggregator console, Mexico SMN source page, upstream feed and public reflector.

Direct checks on 4 October 2026 found:

Resource/requestObserved result
Unsuffixed feed, Accept: text/htmlHTML, Vary: Accept
Unsuffixed feed, Accept: application/rss+xmlRSS XML, Vary: Accept
Unsuffixed feed, Accept: application/xml or */*RSS XML labelled application/rss+xml
Unsuffixed feed, Accept: application/cap+xml406
Individual alert, Accept: application/cap+xmlCAP XML, Vary: Accept
Same individual alert, Accept: text/htmlHTML, Vary: Accept
.xml, with conflicting HTML preferenceXML fixed by suffix
.html, with conflicting XML/CAP preferenceHTML fixed by suffix

The feed is a collection, not itself a CAP alert. The upstream response was Atom XML in our check. An RSS reflector of that collection can expose individual CAP resources without claiming the collection is CAP.

The deployed policy is fixed suffix first; otherwise Accept; otherwise default. This differs from an Accept-first proposal. The live-check record records the distinction and reproduction commands. Response contents change as the source publishes and expires warnings; choose an individual link from the current feed rather than relying on a historical UUID.

Local source inspection confirms a stylesheet registry, approval and caching, execution of approved publisher XSLT on the server, and bundled/template renderers when no approved stylesheet is selected. It does not prove that the particular HTML observed used publisher XSLT. Nor does the presence of a default renderer prove that every transformation failure automatically falls back.

The console and source pages returned an access challenge to command-line retrieval. Their page contents were not verified. The public reflector was directly testable. These qualifications distinguish observed deployment behaviour from code capabilities.

Other publishers need not copy the reflector. The useful evidence is that representation negotiation and rendering can operate together in a real service.

12. Reasonable migration choices

ChoiceUseful whenResponsibility/trade-off
CAP/XML onlyEndpoint serves machines and humans have another routeDocument that boundary and keep public information discoverable
Separate HTML/CAP URLsExisting website or static publishing pipelineLink representations and keep versions aligned
Client JavaScriptRich client interaction is already requiredMaintain runtime, CORS, accessibility and failure states
Server renderingReliable initial HTML is desirableOperate a renderer and test semantic fidelity
Existing XSLT on serverStylesheets retain valueReview compatibility, retrieval and execution security
Content negotiationOne resource should serve several client typesDefine preferences, defaults, explicit links and caching

These choices overlap. Server XSLT is server rendering. Negotiation selects representations, rather than generating them. A publisher can negotiate HTML while retaining an explicit CAP URL for download clients.

13. Migration checklist

  1. Inventory xml-stylesheet, XSLTProcessor, alert links, feeds and archives.
  2. Define the human experience and preserve existing machine contracts.
  3. Choose addressing and rendering separately. Document missing/wildcard defaults.
  4. Test stylesheet reuse or choose a template/default renderer.
  5. Validate CAP and compare human meaning across languages, updates and cancellations.
  6. Test Accept, quality values, q=0, unsupported formats and explicit URLs.
  7. Verify Vary, validators and the deployed CDN with both representations.
  8. Review retrieval, processor limits, generated HTML and accessibility.
  9. Exercise failures with browser XSLT disabled. Monitor the migration.

Use the shared smoke test against each local example, then extend it for your actual consumers. Preserve signed alert bytes where required. Do not suggest that the HTML view itself carries a CAP signature or replaces independent validation.

14. Presentation becomes explicit

CAP remains the interoperable alert representation. Human presentation remains necessary. What changes is where that presentation is produced.

Server rendering with HTTP negotiation provides a coherent option: a URL identifies the alert, CAP and HTML represent it, and clients request what they can use. The organisation still owns defaults, cache behaviour, semantic fidelity and renderer safety. Different publishers can reasonably choose different strategies.

Presentation resources: PowerPoint (.pptx) · PDF · rehearsal notes.

Explore the negotiation demo and execution-location demo, then use the examples and source register to inspect and reproduce the pattern.

For discussion, email Ian Ibbotson or join the Telegram CAP community, where I also hang out.