Skip to main content

6 posts tagged with "podium"

View All Tags

Introducing the html template literal

· 5 min read

The latest release of Podium 5 introduces the html template literal to help reduce the risk of cross-site scripting (XSS) vulnerabilities in your applications, both layouts and podlets.

Passing strings directly to podiumSend is still supported, but is now considered deprecated.

// Use the new named export `html`
import Layout, { html } from "@podium/layout";
import express from "express";

const app = express();

const layout = new Layout({
name: "my-layout",
pathname: "/",
});

const myPodlet = layout.client.register({
name: "my-podlet",
uri: "http://localhost:7100/manifest.json",
});

app.use(layout.middleware());

app.get(layout.pathname(), async (req, res) => {
const incoming = res.locals.podium;
const response = await myPodlet.fetch(incoming);

incoming.podlets = [response];

// Use the `html` template literal on the content you pass to `podiumSend`
res.podiumSend(html`
<div>This is the layout's HTML content</div>
<!--
Note that you need to pass in the response object here,
not response.content – otherwise the podlet HTML gets escaped!
-->
${response}
`);
});

Risk of cross-site scripting

Up until now Podium has not included any safety measures against cross-site scripting, instead leaving that up to application developers. Unfortunately, it's easy to forget to account for malicious inputs, especially when many meta-frameworks do this type of mitigation for you by default.

In the example below an attacker can construct a link where the id parameter closes the div tag and injects a malicious script instead.

app.get("/:id", async (req, res) => {
const { id } = req.params;
const { publicPathname } = res.locals.podium.context;
const { step } = req.query;

res.podiumSend(`
<!--
Don't do this! Here transactionId is passed raw in the HTML output,
introducing a cross-site scripting vulnerability. `transactionId`
must be escaped first before being used this way.
-->
<div data-id=${transactionId}>
</div>
`);
});

How the html template literal mitigates XSS

Under the hood html uses the escape-html library on the values that get passed to the template. This way you can include inputs from the request in the output and not end up with unexpected HTML.

app.get("/:id", async (req, res) => {
const { id } = req.params;
const { publicPathname } = res.locals.podium.context;
const { step } = req.query;

// Now `transactionId` is run through `escape-html` before it's safely included in the document
res.podiumSend(html`
<div data-id=${transactionId}>
</div>
`);
});

There are two exceptions that don't get escaped:

Pass in the whole podlet response

Since we trust the HTML content coming from podlets the html template literal looks for the response type from a Podium client fetch call and includes them without escaping.

app.get(layout.pathname(), async (req, res, next) => {
const incoming = res.locals.podium;

const [aResponse, bResponse] = await Promise.all([
podletA.fetch(incoming),
podletB.fetch(incoming),
]);

incoming.podlets = [aResponse, bResponse];

res.podiumSend(html`
<!-- NB! Don't send in aResponse.content, otherwise it gets escaped! -->
${aResponse}
${bResponse}
`);
});

Syntax highlighting and completions in editors

Several frameworks have a similar html template literal, for example Lit.

Editor plugins for Lit typically include HTML features in strings that have the html template literal. In other words you can install lit-plugin for VS Code to get syntax highlighting and completions in Podium html template literals.

Use escape for API inputs

The html template literal is designed for podiumSend, but an application may include one or more APIs that don't return HTML. For them, use escape, available for both layouts and podlets.

app.get("/:id", async (req, res) => {
const safeId = escape(req.params.id);
});

Deprecating string inputs to podiumSend

We wanted to get this in the hands of developers without breaking existing APIs. However, in the interest of security-by-default we are now deprecating string-based inputs to podiumSend.

Please start using the html template literal to make future upgrades of Podium simpler.

app.get("/", (req, res) => {
// This is now deprecated!
res.podiumSend(`<h2>Hello world</h2>`);
});
app.get("/", (req, res) => {
// Use the html template literal to future-proof
res.podiumSend(html`<h2>Hello world</h2>`);
});

Using the html template literal with view frameworks

If you use view frameworks like React or Lit to render your markup on the server they typically already include some XSS hardening. To tell Podium you trust the generated HTML, use DangerouslyIncludeUnescapedHTML, available for both layouts and podlets.

app.get("/", (req, res) => {
const trustedHtml = await render();
const body = new DangerouslyIncludeUnescapedHTML({ __content: trustedHtml });
res.podiumSend(html`${body}`);
});

Version 5.0.0

· 4 min read
Richard Walker
Maintainer

The Podium team is pleased to annouce and make available the release of version 5.0.0 of Podium. 🥳 🎉

This is a breaking change from the v4.x line but since most of the changes are either under the hood or the removal of deprecations, upgrading, except in a couple notible exceptions, should be pretty simple for most users and may require no changes at all.

Version 4.0.0

· 5 min read
Trygve Lie
Lead maintainer

We are very happy to release version 4.0.0 of Podium. Both @podium/layout and @podium/podlet are now available at v4.0.0.

Version 4.0.0 attempts to be almost entirely backwards compatible with version 3.0.0.