Tiles are composable web docs and apps that can safely be used in arbitrary contexts and assembled from multiple sources to carry out complex user interface tasks. Their composability, security, and privacy properties make them ideal for use in social media, chat, agents, and malleable software.
The web is a powerful platform that makes it possible to create high-quality content and applications. It would be desirable to be able to use it for social media posts, so that they could feature arbitrary content, and to be able to compose multiple small web apps together by having them talk to one another. Unfortunately, the dominant web security model is a poor fit for such tasks because it makes it way too easy to exfiltrate data, and is generally a privacy nightmare.
Tiles change this by offering an environment in which arbitrary web content can be executed, while protecting against the egress of data by simply locking down the tile to whatever content is pre-listed in its manifest. The embedding app or context mediates all communication. This makes it possible to safely embed tiles in social media, chats, LLM interactions, or to compose them with one another.
While the lack of network interaction is limiting, tile execution contexts can then make tiles powerful all the same. For instance, consider a poll tile in a group chat app: when one participant votes, their tile instance posts the vote to a communication channel provided by the chat application. The chat app then broadcasts this vote to all other participants' tile instances, keeping the poll results synchronized.
A tile manifest is simply a MASL document, using the Bundle Mode. The manifest
must have a name and a resources entry, and resources must
include a / entry for the root.
That's all that's needed. The mechanism for obtaining the manifest depends on the context that the tile is loaded from, as detailed in the following sections.
A key use case for tiles is publishing them on the AT Protocol ([[at]]).
Publishing a tile on AT is conceptually simple: every entry in resources must get uploaded
as a blob to the relevant PDS and then the tile itself must be posted to the account's repository as a
record containing the manifest.
The details of the record and the MASL variant used on AT are specified in the relevant appendix.
Please pay particular attention to the details provided there about the manner in which resources
need to point to blobs in order to operate correctly in an AT context.
Tiles can be stored as files by packing them into CARballs ([[car]]). All the resources
must be in the CAR, and the CAR header must be the MASL metadata, along with the
roots and version fields that CAR requires ([[masl]]).
As a convention, the tile carball may be named with a .tile extension. No particular media
type is recommended at this time, the preferred way of publishing tiles online being over AT.
In order to guarantee that tiles run in as safe and private a manner as possible, the web context into which they are loaded must meet restrictions that correspond to the following headers:
content-security-policy:
default-src 'self' blob: data:;
script-src 'self' blob: data: 'unsafe-inline' 'wasm-unsafe-eval';
script-src-attr 'none';
style-src 'self' blob: data: 'unsafe-inline';
form-src 'self';
manifest-src 'none';
object-src 'none';
base-uri 'none';
sandbox allow-downloads
allow-forms
allow-modals
allow-popups
allow-popups-to-escape-sandbox
allow-same-origin
allow-scripts
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: cross-origin
origin-agent-cluster: ?1
permissions-policy: interest-cohort=(), browsing-topics=()
referrer-policy: no-referrer
x-content-type-options: nosniff
x-dns-prefetch-control: off
In the current state of browser technology, loading tiles in a web context requires setting up an origin that is unique to the tile (currently it is random, though it may become derived from its CID), which in turn requires touching the network. It is recommended to use a server that is trusted by the user for this purpose (i.e. one that cannot learn more than what the embedding context already knows).
Because the lack of network access is restrictive, tiles can be granted additional contextual access. A future version of this specification will add support for chat channels (to sync between tile instances in a chat) and an intent-based way for tiles to call one another.
The design of web tiles can be surprising to people who are used to typical apps. A core part of the model is that, while they can have access to relatively powerful capabilities, they are completely cut off from network access beyond loading their own pre-declared dependencies (and barring gating access through specific user action).
This can feel restrictive, but two things mitigate those restrictions. First, depending on context, tiles can be granted access to functions that allow them to perform things that regular pages cannot, such as communicating with other instances of the same tile in a chat or invoking other tiles. And second — and most importantly — this restriction makes new applications possible. In many cases it's simply inconceivable to run a full-fledged web frame simply because it could exfiltrate too much sensitive data too easily. But since tiles have very constrained exfiltration paths, they are safe for use in private environments.
To better understand this, we've made a decision tree that maps the design space for embedded apps and docs. You'll notice the tree omits whether users should be prompted for access to powerful capabilities as a load-bearing component of access to anything dangerous, including exfiltration. This is intentional: relying on users to correctly evaluate permission requests for powerful capabilities has repeatedly failed in practice.
Note that lexica are not flexible enough to be able to describe the resources map.
Importantly, whereas regular MASL uses a CID to reference content, when in an AT record it does
not use the cid type but rather the blob type ([[masl]], [[cid]], [[at]]). This is because a PDS
will not pay particular attention to a CID but will need an uploaded blob to be referenced by
a blob type in a record.
This has important implications with how MASL needs to be used over AT in order to work correctly
with deployed PDSs. An example resources entry for a root HTML document looks as
follows:
{
…
"resources": {
"/": {
"src": {
"$type": "blob",
"ref": {
"$link":"bafkreicknles2uzv7sruakcbjtn5pnoc4rqmgkvnavuq4raxexjvcn7osq"
},
"size": 20,
"mimeType": "application/octet-stream"
},
"content-type": "text/html"
},
…
}
}
A few things are worth noting in the above:
$type is required.ref is what's used to link to the CID.size is required by the PDS and it must be correct (in bytes).mimeType is required by at least some PDS versions and implementations. Note
that it doesn't need to match the actual content-type that is provided
separately. In fact, in some PDS versions it cannot match it in many cases. Some
implementations also sniff an uploaded blob, store it under the type they recognise, and
refuse a record whose reference names a different mimeType. It is recommended
to upload with application/octet-stream and to use the blob reference that
com.atproto.repo.uploadBlob returns, mimeType included, as the
resource's src.
The full lexicon is:
{
"id": "ing.dasl.masl",
"description": "Lexicon for DASL (https://dasl.ing/) types used on AT, notably for Web Tiles.",
"defs": {
"masl": {
"type": "object",
"description": "MASL metadata as defined in https://dasl.ing/masl.html",
"properties": {
"name": {
"type": "string",
"description": "The name for the tile, can be a title or app name",
"maxLength": 1000,
"maxGraphemes": 100
},
"description": {
"type": "string",
"description": "Short overview of the content",
"maxLength": 3000,
"maxGraphemes": 300
},
"categories": {
"type": "array",
"description": "Tags categorising the tile",
"items": {
"type": "string"
}
},
"background_color": {
"type": "string",
"description": "A colour for the background of the tile"
},
"icons": {
"type": "array",
"description": "Icons for the tile",
"items": {
"type": "object",
"required": ["src"],
"properties": {
"src": { type: "string" }, // has to be in resources
"sizes": { type: "string" },
"purpose": { "type": "string" }
}
}
},
"screenshots": {
"type": "array",
"description": "Screenshots, can be used for banner or card images",
"items": {
"type": "object",
"required": ["src"],
"properties": {
"src": { "type": "string" },
"sizes": { "type": "string" },
"label": { "type": "string" }
}
}
},
"sizing": {
"type": "object",
"description": "Requesting sizing properties for the content",
"properties": {
"width": {
"type": "integer",
"mininum": 1
},
"height": {
"type": "integer",
"mininum": 1
}
},
"required": ["width", "height"]
},
"resources": {
"type": "unknown",
"description": "A mapping of path to object with a CID src and HTTP headers"
},
"short_name": {
"type": "string",
"description": "A name, in case the basic name cannot fit"
},
"theme_color": {
"type": "string",
"description": "Theme colour"
},
"prev": {
"type": "cid-link",
"description": "In case there are multiple versions of this tile, this is the CID of the previous one"
},
// CAR compatibility
"version": {
"type": "integer",
"description": "The CAR version — avoid using this",
"const": 1
},
"roots": {
"type": "array",
"description": "The CAR roots — avoid using this",
"items": { "type": "cid-link" }
},
},
"required": ["name", "resources"]
},
"main": {
"type": "record",
"description": "A tile, instantiating MASL metadata into a record",
"key": "tid",
"record": {
"type": "object",
"required": ["cid", "tile", "createdAt"],
"properties": {
"cid": {
"type": "string",
"description": "The DRISL CID of the MASL for the tile",
"format": "cid"
},
"tile": {
"type": "ref",
"description": "The MASL content",
"ref": "ing.dasl.masl#masl"
},
"createdAt": {
"type": "string",
"description": "Timestamp",
"format": "datetime"
}
}
}
}
}
}