datasette/datasette-apps
GitHub: datasette/datasette-apps
datasette-apps 是 Datasette 插件,用于在其平台内创建、托管和管理沙箱化的 HTML/JS 应用及插件注册的外部应用,并提供目录浏览、权限控制和受控数据访问。
Stars: 28 | Forks: 2
# datasette-apps
[](https://pypi.org/project/datasette-apps/)
[](https://github.com/datasette/datasette-apps/releases)
[](https://github.com/datasette/datasette-apps/actions/workflows/test.yml)
[](https://github.com/datasette/datasette-apps/blob/main/LICENSE)
创建驻留在 Datasette 内部的应用
## 安装
在与 Datasette 相同的环境中安装此插件。
阅读 [使用 Datasette Apps 在 Datasette 内部托管应用](https://datasette.io/blog/2026/datasette-apps/) 以了解更多关于此项目的信息。
```
datasette install datasette-apps
```
## 用法
此插件在 `/-/apps` 引入了一个新界面,用于浏览和搜索可用的应用。
有两种类型的应用:
- 由本插件管理的 HTML 和 JavaScript 应用,它们运行在沙箱中,沙箱限制其只能进行经批准的 Datasette 数据访问,并阻止与外部站点的通信,除非这些主机已被明确允许。
- 由其他插件提供的应用,这些应用使用 Python 和 HTML/JavaScript 编写,它们不受限制,但能实现更多功能
此插件允许你创建和修改 HTML 应用,并提供了一个插件钩子,使其他插件能够将自己的 Python 应用添加到系统中。
- `/-/apps` 允许你浏览可用的应用
- `/-/apps/ULID` 用于与全屏 HTML 应用交互
- `/-/apps/create` 用于创建新应用
- `/-/apps/ULID/edit` 用于编辑现有应用
已登录用户会在 Datasette 的右上角菜单中看到一个“Apps”链接。
由此插件管理的 HTML 应用使用小写的单调递增 ULID 作为其 ID,并将每次编辑作为新行记录在 `app_revisions` 中。
### 沙箱应用
存储的应用渲染在沙箱化的 iframe 内部。插件将 Content Security Policy 注入到 iframe 的 `srcdoc` 中。
除非应用配置了确切的 `https://` 来源,否则直接的网络访问将被阻止。这些来源同样被允许用于远程图像、外部 script 标签和外部样式表链接/样式元素。本地主机来源永远不会被允许。允许使用 `data:` 和 `blob:` 图像 URL 进行本地文件预览。
在应用上配置这些来源受到严格限制,因为来源可用于从应用中泄露私密数据。拥有 `apps-set-csp` 权限的用户可以在创建和编辑页面使用自由格式的字段设置任何有效的 `https://` 来源。其他用户只能从由 `allowed_csp_origins` 插件设置配置的允许列表中进行选择(如果存在)——否则他们根本无法配置网络访问。在编辑已具有不在该允许列表中的来源的应用时——例如由特权用户先前添加的来源——可以保留或删除这些现有来源,但不能添加新的任意来源。
要配置允许列表:
```
plugins:
datasette-apps:
allowed_csp_origins:
- https://cdn.jsdelivr.net
- cdnjs.cloudflare.com
```
没有协议的条目将被视为 `https://` 来源。
iframe 桥接器会将 JavaScript 错误、未处理的 Promise 拒绝、CSP 违规、资源加载失败、fetch 请求失败、`console.error()` 调用以及失败的 Datasette 数据查询报告回父页面。应用页面会在 iframe 上方的一个小型可展开错误面板中显示这些信息。它还会捕获 `console.log()` 消息和 Datasette 数据助手调用,并显示在 iframe 下方折叠的日志面板中。
此桥接器还将沙箱化 iframe 内部的 `history.replaceState()`、`history.pushState()`、`history.back()`、`history.forward()` 和 `history.go()` 替换为空操作函数,从而避免尝试管理 URL 状态的应用引发浏览器错误。
### 数据访问
存储的应用可以使用注入的 `datasette.query(database, sql, params)` 助手查询 Datasette 数据。
它们还可以使用 `datasette.storedQuery(database, query, params)` 运行允许列表中的存储查询。iframe 使用 `postMessage` 将这些请求发送到父页面,父页面将其转发到应用范围内的查询 endpoint。
应用具有在编辑页面上配置的允许列表。如果请求的数据库或存储查询被允许,该请求将使用当前 actor 转发到 Datasette 自身的 JSON API,因此 Datasette 的常规 SQL 和查询权限仍然适用。
失败的存储查询尝试,包括尝试调用不在允许列表中的查询或当前 actor 无法运行的查询,都将显示在应用页面的错误面板中。
存储查询权限是通过创建和编辑页面上的选择器配置的。该选择器会搜索 Datasette 的 `/-/queries.json?q=search-term` API,并将选定的查询存储为 `database-name/query-name` 字符串。移除查询需使用选定查询旁边的 `x` 按钮。在页面保存之前,添加和移除操作不会被应用。
### 权限
该插件注册了 Datasette 权限:`create-app`、`view-app`、`edit-app`、`delete-app`、`manage-app-access` 和 `apps-set-csp`。存储应用的所有者始终可以查看、编辑、删除和管理其自己的应用。标记为私密的应用仅对其所有者可见,即使其他用户拥有广泛的 `view-app` 权限授予。
创建存储应用需要明确的 `create-app` 权限授予。要将其授予所有已登录用户:
```
permissions:
create-app:
id: "*"
```
`apps-set-csp` 权限控制谁可以在应用上设置任意 CSP 来源 —— 参见[沙箱应用](#sandboxed-apps)。默认情况下无人拥有此权限。要将其授予特定用户:
```
permissions:
apps-set-csp:
id: admin
```
删除存储应用会将其从目录中隐藏,并禁用对其页面和查询 API 的访问。其 `app_revisions` 数据行将保留在数据库中,以便数据库管理员在需要时进行恢复。
非私密的应用可由具有 `view-app` 权限的 actor 查看。要让所有已登录用户都能查看所有非私密应用,请配置:
```
permissions:
view-app:
id: "*"
```
由插件注册的外部应用默认不私密,因此除非注册插件提供了自己的权限规则,否则它们也需要 `view-app` 权限。
已登录用户可以从目录和各个存储应用页面上置顶应用。置顶的应用将优先显示在 `/-/apps` 上,并且最近使用的三个置顶应用会通过 `top_homepage()` 显示在 Datasette 主页上。
`/-/apps` 目录支持搜索和分页。
### 应用编写
创建和编辑页面包含可复制的 LLM prompt。该 prompt 会在浏览器中根据当前表单状态进行组装,因此对选定的数据库、存储查询和网络来源的未保存更改会立即反映出来。它解释了沙箱环境、`datasette.query()` 和 `datasette.storedQuery()` 桥接器、CSP 限制、当前的 schema 摘要,以及仅为该应用选定的存储查询。
创建和编辑页面使用 Datasette 现有内置的 CodeMirror 编辑器作为 HTML 源码 textarea。
编辑页面包含一个私密复选框、SQL 查询数据库访问权限、存储查询访问权限以及允许的网络来源。网络来源控件对于拥有 `apps-set-csp` 权限的用户来说是一个自由格式列表;如果配置了 `allowed_csp_origins` 允许列表,则显示为一组复选框;在其他情况下则保持隐藏。
### 来自插件的外部应用
Datasette 插件可以注册独立于 HTML 沙箱系统的自带应用。这允许具有更复杂要求的应用依然能参与围绕应用注册表构建的其他功能。
非 HTML 格式存储的应用称为**外部应用**。它们可以使用 `Registry` 类进行添加和移除。
此示例在启动时注册了一个额外的应用:
```
from datasette import hookimpl
from datasette_apps import Registry
@hookimpl
async def startup(datasette):
await Registry(datasette).add_app(
id="myplugin:example",
name="Example plugin app",
description="A plugin-owned app that appears in /-/apps",
path="/-/myplugin-example",
source="myplugin",
)
```
以下是根据 ID 从目录中移除单个外部应用的方法:
```
await Registry(datasette).remove_app("myplugin:example")
```
## 开发
要在本地设置此插件,请首先检出代码。你可以像这样确认它是否可用:
```
cd datasette-apps
# 确认插件可见
uv run datasette plugins
```
要运行测试:
```
uv run pytest
```
标签:Datasette, 多模态安全, 插件, 数据可视化, 沙箱, 逆向工具