Datasette Apps 将沙盒 HTML 应用带入 Datasette

Simon Willison··作者 Simon Willison

关键信息

这些应用运行在 iframe 沙盒中,并配有严格的 CSP,无法访问 cookies、localStorage,也不能向外部主机发起 HTTP 请求。该插件本身并不依赖 LLM,不过 Simon Willison 提到,这种应用形态很适合由现代 LLM 生成。

资讯摘要

Simon Willison 发布了 datasette-apps,这是一个新的 Datasette 插件,用于直接在 Datasette 内托管自定义的 HTML+JavaScript 应用。 这些应用是自包含的,并且运行在 Datasette 网站中的一个严格受限的 iframe 沙盒里。 在这个沙盒内,它们可以渲染 HTML、CSS 和 JavaScript,并且可以针对底层 Datasette 数据库执行只读 SQL 查询。 如果实例配置了存储查询,这些应用还可以执行写入查询。 Willison 表示,这套方案同时使用了 iframe 限制和注入的内容安全策略(CSP),以防止应用访问 cookies、localStorage 或向外部网络发起请求。

这样做的目标是降低有缺陷或恶意应用外泄私有数据的风险。 他还解释说,这个功能最初是为 Datasette Agent 尝试构建一种类似 Claude Artifacts 的机制,但他很快意识到,这种沙盒模式的用途远不止聊天界面中的自定义应用。 这次发布也延续了他长期以来对 HTML 工具的实验,并把这些实验提升为 Datasette 生态中的核心能力。 用户可以通过 GitHub 登录 agent.datasette.io 的演示实例来试用这一功能。

Datasette Apps 将沙盒 HTML 应用带入 Datasette

资讯正文

<p>今天,我们为 Datasette 发布了一个新的插件 <a href="https://github.com/datasette/datasette-apps">datasette-apps</a>,同时在 Datasette 项目博客上发布了这篇<a href="https://datasette.io/blog/2026/datasette-apps/">发布公告</a>。那篇文章讲的是“是什么”,而我想在这里再展开一点,解释一下“为什么”。</p>

<h4 id="the-tl-dr">简而言之</h4>

<p>Datasette Apps 是自包含的 HTML+JavaScript 应用,运行在托管于你的 Datasette 应用中的、限制非常严格的 <code>&lt;iframe&gt;</code> 沙箱里。它们可以使用 JavaScript 对 Datasette 中的数据执行只读 SQL 查询;如果你通过<a href="https://datasette.io/blog/2026/sql-write-queries/">一些存储查询</a>进行配置,它们也可以执行写入查询。</p>

<p>这里有一个<a href="https://agent.datasette.io/-/apps/01kvdp1d26g8trye3r4gc3yy9c">非常简单的示例</a>,以及一个<a href="https://agent.datasette.io/-/apps/01ktvyaejhk07zskdx2tewxppe">更复杂的自定义时间线示例</a>——后者看起来是这样的:</p>

<p><img alt="一张网页应用的截图,标题为“Datasette timeline”,右上角有“All apps”“Edit app”和“Pin”按钮,下方还有一个“Full screen”按钮。在一个带边框的面板内,标题“Datasette timeline”位于搜索框上方,搜索框中显示“Search news, blog posts and releases…”,其下有三个已勾选的复选框,分别标注为 News、Blog 和 Releases。再往下显示“Showing 200 of 1,953 items”,随后是可滚动的时间线条目列表。每个条目都有彩色标签(蓝色“BLOG”或绿色“RELEASE”)、日期、蓝色链接标题和一段说明文字。可见的条目包括一篇日期为 2026-06-11、标题为“Datasette 1.0a33 with JSON extras in the API”的“BLOG”文章,一条日期为 2026-06-11、标题为“datasette 1.0a33”的“RELEASE”,以及一条日期为 2026-06-09、标题为“llm 0.32a3”的“RELEASE”,每条都有正文和一个“▶ Show more”切换项。底部的另一个面板显示一个折叠的“▶ 2 log entries”切换项。" src="https://static.simonwillison.net/static/2026/datasette-timeline-app.jpg" /></p>

<p>这些应用可以运行 JavaScript 并渲染 HTML 和 CSS。它们在访问权限方面受到限制——它们运行在 <code>&lt;iframe sandbox="allow-scripts allow-forms"&gt;</code> 中,这使它们无法访问 cookies 或 localStorage;同时它们还会注入一个 CSP 头部(感谢<a href="https://simonwillison.net/2026/Apr/3/test-csp-iframe-escape/">这项研究</a>),这会阻止它们向外部主机发出 HTTP 请求,从而防止恶意或有缺陷的应用泄露私有数据。</p>

<p>Datasette Apps 最初是我尝试为 <a href="https://datasette.io/blog/2026/datasette-agent/">Datasette Agent</a> 构建一种 Claude Artifacts 机制的产物,但我很快意识到,这种沙箱化模式的意义远不止于在聊天界面里添加自定义应用,于是我把它提升为 Datasette 生态系统中的一个独立顶层概念。</p>

它们也是一种很有趣的方式,可以把我<a href="https://tools.simonwillison.net/">多年用 vibe-coded HTML 工具做实验</a>的成果,变成我主项目的核心功能之一!

你可以在 <a href="https://agent.datasette.io/">agent.datasette.io</a> 的演示实例上,通过 GitHub 登录来试用 Datasette Apps。

为什么要做这个?

自从最早的版本开始,Datasette 就通过其 JSON API 提供了一个灵活的后端,用于创建自定义 HTML 应用。

我最早的 Datasette 项目之一,是我在 Eventbrite 工作时做的一个文档内部搜索引擎——它的工作方式是按 cron 从不同系统导入文档到 SQLite 中,然后通过一个 Datasette 实例将它们提供出来,并配套一个自定义的 HTML+JavaScript 搜索界面,直接查询 Datasette API。

我当时让客户端 JavaScript 去构造 SQL 查询,最初这只是个工程上的玩笑,但结果却成为一种<em>非常高效</em>的应用迭代方式!

那个项目,加上我<a href="https://simonwillison.net/2025/Dec/10/html-tools/">构建 HTML 工具集合</a>的经验,以及我<a href="https://simonwillison.net/2024/Oct/21/claude-artifacts/">对 Claude Artifacts 的实验</a>,让我确信:把一个类似 Datasette 的后端加入到一个自包含的 HTML 前端中,是一种惊人强大的组合。

想象一下,如果 Claude Artifacts 能访问一个持久化的关系型数据库,它会变得多有用。这就是我正在用 Datasette Apps 打造的东西!

Datasette Apps 里的巧妙想法

在构建这个项目的过程中,我想到并验证了几种我认为很有生命力的思路和模式。

<code>&lt;iframe sandbox="allow-scripts" srcdoc="..."&gt;</code> + <code>&lt;meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob;"&gt;</code>

这就是让 Datasette Apps 之所以可行的关键组合。我需要在一个高度敏感的域名上运行不受信任的 HTML 和 JavaScript——经过身份验证的 Datasette 实例可能包含各种私密数据。<code>sandbox=</code> 属性让我能够以一种无法与父应用交互的方式运行这些不受信任的代码——它不能读取 DOM,也不能访问 cookies,或者从 <code>localStorage</code> 中窃取秘密。不过,它仍然可以使用 <code>fetch()</code> 之类的接口从其他域加载内容(或者外传数据)。但后来我发现,如果你在 HTML 页面的开头放上一个 <code>&lt;meta http-equiv="Content-Security-Policy"&gt;</code> 头部,就可以<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP">设置额外的策略</a>,从而限制对其他域的访问。我原本担心恶意 JavaScript 会改写或移除这个头部,但事实证明<a href="https://github.com/simonw/research/tree/main/test-csp-iframe-escape#readme">这样做行不通</a>——一旦设置完成,CSP 策略对于该 frame 的内容就是不可变的。

使用 <code>postMessage()</code> 和 <code>MessageChannel()</code> 限制 API

在把那些 iframe 限制到几乎什么有趣的事都做不了之后,接下来的挑战是再把它们开放回来,让它们能够运行一组白名单操作,首先是针对指定数据库的只读 SQL 查询。

我用 <code>postMessage()</code> 做出了第一个版本,它允许子 iframe 向父窗口发送消息。我设计了一套简单的协议,用于请求父窗口执行 SQL 查询——父窗口随后可以先验证该查询是否针对一个在白名单中的数据库,然后再执行它。

我记得某个 LLM 工具,大概是 GPT-5.5,建议说如果 iframe 以某种方式从不受信任的域加载了额外代码,那么仅靠 <code>postMessage()</code> 可能会被利用。我不认为这适用于 Datasette Apps,但我也信奉纵深防御,所以我<a href="https://gist.github.com/simonw/0b29f301c2007808314eb04675c66916">让 GPT-5.5 帮我</a>把传输方式迁移到了基于 <a href="https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel"><code>MessageChannel()</code></a> 的方案。

<code>MessageChannel()</code> 的优势在于,如果页面跳转到别处,通道会自动关闭,从而消除从不受信任的外部页面发送命令并被执行的任何可能性。

查询和错误的可见日志

如果你访问<a href="https://agent.datasette.io/-/apps/01ktvyaejhk07zskdx2tewxppe">时间线演示</a>并搜索字符串 <code>usercontent</code>,你会拉出一些搜索结果,这些结果嵌入了来自 <code>user-images.githubusercontent.com</code> 域的图片。这个域不在 CSP 白名单中,因此会触发错误。

这些错误会被捕获并传回父框架,然后可以显示在一个有用的错误日志中。这样做是为了通过把原本不可见的问题暴露出来,让应用开发更高效。

我做了一个<a href="https://simonwillison.net/2026/May/13/csp-allow/">实验</a>,演示你甚至可以把这变成一种“一键允许”的机制,根据实际被阻止的内容来构建 CSP 白名单,但我还没有把这个想法集成进 <code>datasette-apps</code>。

SQL 查询也会被可视化记录——滚动到<a href="https://agent.datasette.io/-/apps/01ktvyaejhk07zskdx2tewxppe">时间线页面底部</a>即可看到实际效果。

用于写入操作的存储查询

我希望应用能够有条件地向数据库写入,但这比 SQL 读取要<em>危险得多</em>!

我的解决方案涉及 Datasette 的 <a href="https://docs.datasette.io/en/latest/sql_queries.html#stored-queries">stored queries</a> 功能,它是从“canned queries”重新命名而来,并在最近的 <a href="https://datasette.io/blog/2026/sql-write-queries/">Datasette 1.0a31</a> 中得到了重大升级——这项工作也直接受到了 Datasette Apps 的启发。

用户可以创建一个已存储的写入查询,用于执行插入或更新操作,然后将该特定查询列入允许列表,供某个应用使用。应用内部代码中的用法如下:

我自己也才刚刚开始探索这为我们打开的可能性,但我的目标是支持完全读写、并且以安全方式构建的 Datasette Apps。

复制并粘贴一个提示来构建应用

Datasette Apps 插件本身完全不依赖 LLM,但这些独立应用的形态,正适合由现代 LLM 来编写。

创建应用表单末尾包含一个可复制的提示。这个提示包含模型构建新应用所需知道的一切信息,包括任何所选数据库的 schema。

<p><img alt="‘创建应用’页面下半部分的截图。顶部是 HTML 代码编辑器的末尾(第 35–43 行,关闭 script、body 和 html 标签),以及一个蓝色的‘创建应用’按钮。下方是一个标题为‘使用 AI 构建此应用’的部分,文字说明为:‘在 LLM 聊天中描述你想要的应用,然后把这个提示词复制进去作为上下文,这样它就可以生成或修改应用的 HTML。将结果粘贴到上面的 HTML 编辑器中。’一个蓝色的‘复制提示词’按钮位于‘▼ 显示完整提示词’切换按钮上方。展开的文本框显示提示词:‘构建一个 Datasette HTML 应用。应用名称:Latest news。返回一个完整的单文件 HTML 文档。把 DOCTYPE、CSS 和 JavaScript 放在同一个文件中。此应用将在一个受严格内容安全策略保护的沙盒 iframe 内运行。重要限制:– 默认情况下已禁用直接网络访问。– 应用无法从 Datasette、localhost 或任意来源抓取数据。– 外部 fetch() 请求仅在应用网络访问设置中明确授予的精确 https:// 来源上可用。– 允许从这些相同的精确 https:// 来源加载远程图片。允许使用 data: 和 blob: 图片 URL 进行本地文件预览。– 允许从这些相同的精确 https:// 来源加载外部脚本标签。– 允许从这些相同的精确 https:// 来源加载外部样式表链接和 style 元素。– 在沙盒中,history.replaceState()、history.pushState()、history.back()、history.forward() 和 history.go() 都不起作用。– 即使某个来源已被授予,CORS 仍然适用。请使用此 API 访问数据:– await datasette.query(database, sql, params?)’" src="https://datasette.io/static/blog/2026/create-app-prompt.jpg" /></p>

这意味着你可以点击“复制”,把它粘贴到 ChatGPT、Claude 或 Gemini 里,告诉它你需要什么,而模型很有可能会吐出构建该应用所需的代码。

如果你安装了 <a href="https://agent.datasette.io/">Datasette Agent</a>,你的 AI 助手还将获得工具,不仅可以创建新应用,还可以编辑现有应用,风格类似 Claude Artifacts。

Datasette Apps 最初诞生于 4 月,当时名为 <a href="https://github.com/datasette/datasette-agent-edit/commits/b242a8fc2e200d01820dacb5bf9a060f659c3a18/">datasette-agent-artifacts</a>,这是一个我后来改名为 <code>datasette-agent-edit</code> 的插件,目前只保留了它的 <a href="https://simonwillison.net/2026/Jun/7/datasette-agent-edit/">编辑工具</a>。我开发它,是为了作为 <a href="https://datasette.io/blog/2026/datasette-agent/">Datasette Agent</a> 的首批插件之一,以帮助把插件钩子调整到合适的形态。这个最初的原型主要是使用 Claude Code 中的 Claude Opus 4.6 构建出来的。

当我转向 Datasette Apps 时,我开始使用 Codex Desktop 和 GPT-5.5 xhigh 构建的一个<a href="https://github.com/datasette/datasette-apps/commit/fc1e23b801b5845647dcd423d632339648acf19c#diff-de64950fcb0bc622027de0d657eeb322f3520ce502d826813ff7653b51cf6059">计划</a>,这个计划建立在大量对话以及输入了 <code>datasette-agent-artifacts</code> 和我构建的其他原型的基础上。

随后大部分工作都继续使用 Codex 完成,不过在我们短暂获得 <a href="https://simonwillison.net/2026/Jun/9/claude-fable-5/">Claude Fable 5</a> 访问权限的那几天里,我让它对这个产品做了一次安全评估(这一能力不久后会让它被<a href="https://simonwillison.net/2026/Jun/13/us-government-directive-to-suspend-access/">美国政府禁止</a>),它确实发现了一个非常真实的问题。

我当时允许用户为他们的应用将 CSP 主机加入白名单,但 Fable 指出了下面这种攻击:

<li>权限较低的用户在拥有 <code>create-app</code> 权限后,会创建一个应用,该应用查询 SQLite 中所有可用表,并通过他们在 CSP 中加入白名单的主机选择并外传所有数据。</li>

<li>然后,他们诱骗一名能够访问私密数据的管理员用户去访问自己的应用。</li>

<li>……而这个应用现在就可以以<em>那个</em>用户的身份运行查询,并窃取他们的私密数据!</li>

</ol>

这显然是不可接受的。我通过将允许将任何域名加入白名单的能力限制到一个新的 <code>apps-set-csp</code> 权限来修复了这个问题,这项权限只打算授予受信任的工作人员。站点管理员还可以<a href="https://github.com/datasette/datasette-apps#sandboxed-apps">配置 Datasette</a>,提供一个 <code>allowed_csp_origins</code> 列表,然后普通用户就可以从中进行选择。这意味着你可以允许 <code>cdnjs.cloudflare.com</code>,这样你的用户就能构建从 <a href="https://cdnjs.com">cdnjs</a> CDN 加载额外 JavaScript 库的应用。

我对 Datasette Apps 进行了极其细致的审查,尤其是其中与安全相关的部分。关键的沙箱和 CSP 配置基于多个 AI 辅助的原型和测试。

<h4 id="it-s-looking-good-so-far">到目前为止看起来不错</h4>

我对这个初始版本真的很满意。

Datasette 正在超越它作为只读数据服务应用的起源,成长为一个更丰富的工具生态系统,用于在数据被收集之后,基于这些数据做出有用的事情。

Datasette 的根基在数据新闻领域。我一直对这样一个问题很感兴趣:当记者拿到一大堆关于世界的数据之后,<em>下一步</em>会是什么。Datasette 支持探索和发布这些数据。Datasette Agent 通过 AI 辅助来帮助审问这些数据。现在,Datasette Apps 又把这一能力扩展到了构建自定义界面和可视化工具,以帮助挖掘隐藏在其中的故事。

标签:<a href="https://simonwillison.net/tags/iframes">iframes</a>、<a href="https://simonwillison.net/tags/javascript">javascript</a>、<a href="https://simonwillison.net/tags/projects">projects</a>、<a href="https://simonwillison.net/tags/sandboxing">sandboxing</a>、<a href="https://simonwillison.net/tags/ai">ai</a>、<a href="https://simonwillison.net/tags/datasette">datasette</a>、<a href="https://simonwillison.net/tags/generative-ai">generative-ai</a>、<a href="https://simonwillison.net/tags/llms">llms</a>、<a href="https://simonwillison.net/tags/ai-assisted-programming">ai-assisted-programming</a>、<a href="https://simonwillison.net/tags/content-security-policy">content-security-policy</a>

来源与参考

  1. 原始链接
  2. Datasette Apps: Host custom HTML applications inside Datasette

收录于 2026-06-20