Datasette Apps:在 Datasette 中托管自定义 HTML 应用
Datasette Apps: Host custom HTML applications inside Datasette
Datasette 发布插件 `datasette-apps`,允许在 Datasette 实例上运行自包含的 HTML+JavaScript 应用,这些应用运行在 `<iframe sandbox>` 与 CSP 标头构成的严格沙箱中,无法访问 cookie、localStorage 或向外部主机发起 HTTP 请求。应用可通过 `postMessage()` 和 `MessageChannel()` 对指定数据库执行只读 SQL 查询,并支持通过 Datasette 的存储查询(stored queries)执行写入操作。该插件最初源于为 Datasette Agent 构建 Claude Artifacts 机制的尝试,后独立为顶级概念。创建应用的表单末尾包含可复制提示词,便于 LLM(如 ChatGPT、Claude、Gemini)生成代码。安全方面,通过 `apps-set-csp` 权限和 `allowed_csp_origins` 配置限制 CSP 允许列表,防止低权限用户窃取数据。
今天我们为 Datasette 发布了一个新插件 datasette-apps,并在 Datasette 项目博客上发布了这篇发布公告。那篇文章介绍了“是什么”,但我想在这里进一步展开,说明“为什么”。
TL;DR Datasette Apps 是自包含的 HTML+JavaScript 应用程序,运行在 Datasette 应用上严格受限的沙箱中。它们可以使用 JavaScript 对 Datasette 中的数据执行只读 SQL 查询,如果你配置了一些存储查询(stored queries),它们也可以执行写入查询。这里有一个非常简单的示例和一个更复杂的自定义时间线示例——后者看起来像这样:
Apps 允许运行 JavaScript 并渲染 HTML 和 CSS。它们在访问权限上受到限制——它们运行的 <iframe sandbox> 阻止它们访问 cookie 或 localStorage,并且它们还有一个注入的 CSP 标头(感谢这项研究),阻止它们向外部主机发起 HTTP 请求,从而防止恶意或有缺陷的 app 泄露私有数据。
Datasette Apps 最初是我尝试为 Datasette Agent 构建一个 Claude Artifacts 机制,但我很快意识到,这种沙箱模式不仅限于为界面表面添加自定义 app,它的用途要广泛得多,于是我将其提升为 Datasette 生态系统中的一个顶级概念。它们也是一种有趣的方式,将我多年来在 vibe-coded HTML 工具上的实验转化为我主要项目的核心功能!
你可以通过使用 GitHub 登录 agent.datasette.io 演示实例来试用 Datasette Apps。
为什么要构建这个?
从第一个版本开始,Datasette 就通过其 JSON API 提供了一个灵活的 backend,用于创建自定义 HTML 应用。我最早的 Datasette 项目之一是在 Eventbrite 工作时为文档构建的内部搜索引擎——它的工作方式是通过 cron 将不同系统的文档导入 SQLite,然后通过一个 Datasette 实例提供服务,该实例带有一个自定义的 HTML+JavaScript 搜索界面,直接查询 Datasette API。我使用客户端 JavaScript 构建 SQL 查询,这最初只是一个工程玩笑,但后来证明是迭代应用的一种非常高效的方式!
那个项目,加上我构建 HTML 工具集的经验以及我对 Claude Artifacts 的实验,让我确信,为自包含的 HTML 前端添加一个 Datasette 风格的 backend 是一种极其强大的组合。想象一下,如果 Claude Artifacts 能够访问一个持久的关系数据库,它们会变得多么有用。这就是我正在用 Datasette Apps 构建的东西!
Datasette Apps 中的巧妙想法
以下是我在构建这个过程中发现的一些我认为具有持久价值的想法和模式。
<iframe sandbox>+ CSP 标头:这是让 Datasette Apps 首先变得可行的神奇组合。我需要在高度敏感的域名上运行不受信任的 HTML 和 JavaScript——一个经过身份验证的 Datasette 实例可能包含各种私有数据。sandbox=属性让我以一种无法与父应用交互的方式运行不受信任的代码——它无法读取 DOM、访问 cookie 或从 localStorage 窃取秘密。但它可以使用fetch()等从其他域加载内容(或窃取数据)。但是……事实证明,如果你用一个Content-Security-Policy标头启动一个 HTML 页面,你可以设置额外的策略来锁定对其他域的访问。我曾担心恶意 JavaScript 能够更新或删除该标头,但事实证明这行不通——一旦设置,CSP 策略对于该 frame 的内容是不可变的。使用
postMessage()和MessageChannel()锁定 API:在将这些 iframe 锁定到它们无法做任何有趣事情的程度后,挑战在于重新打开它们,使它们能够运行一个允许列表中的操作,从针对指定数据库的只读 SQL 查询开始。我使用postMessage()构建了第一个版本,它允许子 iframe 向父窗口发送消息。我创建了一个简单的协议来请求父窗口运行 SQL 查询——父窗口可以在执行之前验证它是否针对允许列表中的数据库。其中一个 LLM 工具,我想是 GPT-5.5,建议如果 iframe 以某种方式从不信任的域加载额外代码,单独的postMessage()可能会被利用。我不认为这适用于 Datasette Apps,但我也相信纵深防御,所以我让 GPT-5.5 帮助我移植到基于MessageChannel()的传输方式。MessageChannel()的优势在于,如果页面导航到其他地方,通道会自动关闭,从而消除了执行来自不受信任外部页面的命令的任何可能性。查询和错误的可见日志:如果你导航到时间线演示并搜索字符串
usercontent,你会拉入一些搜索结果,这些结果嵌入了来自user-images.githubusercontent.com域的图片。该域不在 CSP 允许列表中,因此会触发错误。这些错误会被捕获并传回父 frame,在那里它们可以显示在有用的错误日志中。这旨在通过暴露原本不可见的问题来提高开发 app 的效率。我构建了一个实验,演示你甚至可以将其转变为一个一键允许机制,根据哪些内容出错来构建 CSP 允许列表,但我还没有将该想法集成到datasette-apps中。SQL 查询也会被可见地记录——滚动到时间线页面的底部即可看到实际效果。用于写入操作的存储查询:我希望 app 能够有条件地写入数据库,但这比 SQL 读取更危险!我的解决方案涉及 Datasette 的存储查询(stored queries)功能,该功能从“canned queries”重新命名,并在最近的 Datasette 1.0a31 中进行了重大升级——这项工作直接受到 Datasette Apps 的启发。用户可以创建一个执行 insert 或 update 的存储写入查询,然后针对某个 app 的使用将该特定查询加入允许列表。app 内部代码的使用方式如下:
const result = await datasette.storedQuery("todos", "add_todo", { title: "Buy milk", due_date: "2026-06-20", priority: "high", completed: false });我刚刚开始探索这解锁的可能性,但我的目标是支持构建为 Datasette Apps 的安全读写应用程序。
复制粘贴提示词来构建 app:Datasette Apps 插件完全不依赖 LLM,但这些自包含的 app 非常适合由现代 LLM 编写。创建 app 的表单末尾包含一个可复制的提示词。这个提示词包含了模型构建新 app 所需的一切信息,包括任何选定数据库的 schema。这意味着你可以点击“复制”,将其粘贴到 ChatGPT、Claude 或 Gemini 中,告诉它你需要什么,模型很可能会吐出构建该 app 所需的代码。如果你安装了 Datasette Agent,你的 AI 助手还将获得创建新 app 和编辑现有 app 的工具,类似于 Claude Artifacts 的风格。
借助大量 AI 辅助构建
Datasette Apps 最初于四月份以 datasette-agent-artifacts 的形式诞生,这是一个我后来重命名为 datasette-agent-edit 的插件,只保留了其编辑工具。我将其构建为 Datasette Agent 的首批插件之一,以帮助将插件钩子调整到正确的形状。第一个原型主要使用 Claude Code 中的 Claude Opus 4.6 构建。
当我转向 Datasette Apps 时,我首先使用 Codex Desktop 和 GPT-5.5 xhigh 制定了一个计划,基于广泛的对话并输入了 datasette-agent-artifacts 和我构建的其他原型。后续的大部分工作都坚持使用 Codex,但在我们能够访问 Claude Fable 5 的短短几天里,我让它对该产品进行了一次安全评估(这项能力不久后就会导致它被美国政府封禁),它发现了一个非常真实的问题。
我允许用户为他们的 app 将 CSP 主机加入允许列表,但 Fable 指出了以下攻击:一个拥有 create-app 权限的低权限用户创建一个 app,该 app 查询 SQLite 以获取所有可用表,并选择所有数据,然后将其窃取到他们通过 CSP 加入允许列表的主机。然后,他们诱骗一个有权访问私有数据的管理员用户访问他们的 app。……然后该 app 可以以该用户的身份运行查询并窃取他们的私有数据!这显然是不可接受的。我通过将对任何域进行允许列表的能力限制在一个新的 apps-set-csp 权限中来解决这个问题,该权限仅供受信任的工作人员使用。站点管理员还可以使用一个 allowed_csp_origins 列表来配置 Datasette,普通用户随后可以选择这些来源。这意味着你可以做诸如允许 cdnjs.cloudflare.com 之类的事情,你的用户将能够构建从 cdnjs CDN 加载额外 JavaScript 库的 app。
我已经非常仔细地审查了 Datasette Apps,特别是与安全相关的部分。关键的沙箱和 CSP 配置基于多个 AI 辅助的原型和测试。
目前看来不错
我对这个初始版本非常满意。Datasette 正在超越其作为提供只读数据应用的起源,发展成为一个更丰富的生态系统,其中包含各种工具,用于在数据收集后利用这些数据做有用的事情。Datasette 的根源在于数据新闻。我一直对一个问题感兴趣:当记者拿到大量关于世界的数据转储后,接下来会发生什么?Datasette 支持探索和发布这些数据。Datasette Agent 增加了借助 AI 进行审问的能力。现在,Datasette Apps 将其扩展到构建自定义界面和可视化,以帮助解锁隐藏在数据中的故事。
标签:iframes, javascript, projects, sandboxing, ai, datasette, generative-ai, llms, ai-assisted-programming, content-security-policy