A release-readiness QA pass over the whole product. The commits split into defects a user would hit and gates that were reporting green while measuring nothing. ## Fixes that change behaviour Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so request.ip came from a client-set header and a forged X-Forwarded-For got past the login limiter. The default is now a private-network trust list. A transient Postgres outage stranded in-flight jobs, leaving finished output on disk with no row pointing at it. A reconciler now resolves those rows and adopts the bytes rather than dropping the work. A Redis connection that moved to a new address wedged every read-blocked consumer, so completions stopped signalling while health still answered 200. Socket timeouts plus subscriber pings recover it. Installing more than one AI bundle left the shared venv multi-versioned and silently broke three tools. The installer now reconciles distributions to one version each. Converting an image to JXL at quality 1 through 4 returned a 500, because libjxl 0.7 rejects the distance those values compute. The quality is floored at what the encoder honours. A missing ffmpeg was also reported to the user as a corrupt upload; it now says the engine is unavailable. RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at 0.22.2, and the release scan was split so it can fail on an unfixed critical instead of hiding it behind ignore-unfixed. ## Gates that could not fail Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs build; coverage discarded its whole report on any failing test; the lint gate skipped root tests, scripts, and two workspaces; and several generated matrices counted a host missing ffmpeg as a passing tool. Each now measures what it claims. Full evidence and the outstanding release items are tracked locally and are not part of this branch.
11 KiB
description, i18n_source_hash, i18n_provenance, i18n_output_hash, i18n_hash_version
| description | i18n_source_hash | i18n_provenance | i18n_output_hash | i18n_hash_version |
|---|---|---|---|---|
| 在 SnapOtter 中管理用户、内置角色与自定义角色、权限、API 密钥、团队、会话以及审计日志。 | bea8955f3aff | human | 586307e9d95e | 2 |
用户、角色与权限
SnapOtter 提供三个内置角色、17 项细粒度权限,并支持带可选按工具访问控制的自定义角色。本页涵盖完整的授权模型、API 密钥作用域、团队管理以及审计日志。
::: tip 相关页面 OIDC / SSO | SAML SSO | SCIM 配置 | 安全与加固 :::
用户
创建用户
管理员可以通过管理面板或 POST /api/auth/register 端点创建用户。每个用户都有用户名、角色、团队分配以及一个可选的电子邮件地址。
默认管理员
首次启动时,SnapOtter 会创建一个默认管理员账户。凭据来自环境变量:
| Variable | Default | Description |
|---|---|---|
DEFAULT_USERNAME |
admin |
初始管理员账户的用户名 |
DEFAULT_PASSWORD |
admin |
初始管理员账户的密码 |
默认管理员在首次登录时必须更改密码。
身份验证提供方
用户可以通过多种方式进行身份验证:
- 本地 - 存储在 SnapOtter 数据库中的用户名和密码
- OIDC - 任意 OpenID Connect 提供方(参见 OIDC / SSO)
- SAML - SAML 2.0 身份提供方(参见 SAML SSO)
- SCIM - 来自身份提供方的自动化配置(参见 SCIM 配置)
禁用身份验证
设置 AUTH_ENABLED=false 以完全禁用身份验证。在此模式下,所有请求都会使用一个带 admin 角色的合成匿名用户。无需登录。
::: warning 禁用身份验证会向任何能访问该实例的人授予完整的管理员权限。仅在受信任的环境中使用。 :::
内置角色
SnapOtter 包含三个内置角色。它们无法被修改或删除。
管理员(Admin)
全部 17 项权限。对实例拥有完全控制权。
tools:use files:own files:all apikeys:own apikeys:all pipelines:own pipelines:all settings:read settings:write users:manage teams:manage features:manage system:health audit:read compliance:manage webhooks:manage security:manage
编辑者(Editor)
7 项权限。可以使用所有工具并管理所有文件和流水线,但无法访问管理功能。
tools:use files:own files:all apikeys:own pipelines:own pipelines:all settings:read
用户(User)
5 项权限。可以使用工具并管理自己的资源。
tools:use files:own apikeys:own pipelines:own settings:read
权限参考
| Permission | Description |
|---|---|
tools:use |
使用任意处理工具 |
files:own |
查看和管理自己的文件 |
files:all |
查看和管理所有用户的文件 |
apikeys:own |
创建和管理自己的 API 密钥 |
apikeys:all |
查看所有用户的 API 密钥 |
pipelines:own |
创建和管理自己的流水线 |
pipelines:all |
查看和管理所有用户的流水线 |
settings:read |
查看实例设置 |
settings:write |
修改实例设置 |
users:manage |
在参与者的权限范围内创建和管理用户帐户 |
teams:manage |
创建、更新和删除团队 |
features:manage |
安装和管理 AI 功能包 |
system:health |
访问健康与就绪端点 |
audit:read |
查看审计日志并列出角色 |
compliance:manage |
管理 GDPR 生命周期和合规性功能;破坏性用户操作仍受权限限制 |
webhooks:manage |
配置出站 webhook |
security:manage |
管理安全设置(IP 允许列表、SSO 强制) |
自定义角色
拥有 security:manage 权限的管理员可以通过管理面板或角色 API 创建自定义角色。列出角色需要 audit:read。
创建自定义角色
curl -X POST http://localhost:1349/api/v1/roles \
-H "Authorization: Bearer si_..." \
-H "Content-Type: application/json" \
-d '{
"name": "reviewer",
"description": "Can use tools and view all files",
"permissions": ["tools:use", "files:own", "files:all", "settings:read"]
}'
角色名称必须为 2 到 30 个字符,小写字母数字,可包含连字符和下划线。
委派管理边界
所有 17 个权限都可以通过自定义角色委派,但管理权限并不会使该角色等同于内置 admin 角色。 users:manage授权的用户突变、compliance:manage授权的破坏性操作以及security:manage授权的自定义角色管理均受参与者当前权限的约束:
- 内置角色遵循
admin>editor>user;自定义角色位于内置角色下方。 - 目标的权限必须包含在参与者的有效权限中。因此,限定范围的 API 密钥无法行使其范围中省略的权限。
- 目标角色的工具访问权限必须包含在参与者自己的工具访问权限中。
- 当角色记录为
disabled:<original-role>时,已禁用的帐户将根据其原始角色进行检查。 - 删除自定义角色还需要分配内置
user后备的权限;禁用的成员仍然禁用为disabled:user。
全局凭证和配置更加严格:颁发或撤销 SCIM 令牌以及导入实例配置需要具有完整有效管理权限的内置 admin 角色。
工具级权限
自定义角色可以选择性地限制用户可以访问哪些工具。有两种模式可用:
| Mode | Behavior | License requirement |
|---|---|---|
category |
按模态限制(图像、视频、音频、文档、文件) | 无(免费) |
tool |
按单个工具 ID 限制 | 需要 per_tool_permissions 企业功能 |
当设置了 tool 模式但企业功能不可用时,SnapOtter 会优雅降级,允许访问所有工具。
{
"name": "image-only",
"permissions": ["tools:use", "files:own"],
"toolPermissions": {
"mode": "category",
"allowed": ["image"]
}
}
删除自定义角色
当删除自定义角色时,所有分配到该角色的用户会自动重新分配到 user 角色。
团队
团队将用户分组,用于存储和保留管理。首次启动时会创建一个 Default 团队。
| Field | Type | Description |
|---|---|---|
name |
string | 唯一的团队名称(1 到 50 个字符) |
storageQuota |
number | 每团队的存储上限,单位为字节(无需企业版即可使用) |
retentionHours |
number | 在这么多小时之后自动删除输出(需要 team_retention_overrides,企业版) |
legalHold |
boolean | 阻止自动删除团队成员的文件(需要 legal_hold,企业版) |
::: info
Default 团队无法删除。仍有成员的团队无法删除。请先重新分配成员。
:::
API 密钥
用户可以生成 API 密钥用于编程访问。每个密钥使用 si_ 前缀,且仅在创建时展示一次。
作用域权限
API 密钥可以选择性地携带一个 permissions 数组。设置后,一个请求的有效权限是用户角色权限与该密钥作用域权限的交集。这意味着 API 密钥永远无法超出用户自身的权限进行提权。
curl -X POST http://localhost:1349/api/v1/api-keys \
-H "Authorization: Bearer si_..." \
-H "Content-Type: application/json" \
-d '{
"name": "CI pipeline key",
"permissions": ["tools:use", "files:own"],
"expiresAt": "2027-01-01T00:00:00Z"
}'
过期
密钥接受一个可选的 expiresAt 时间戳。过期的密钥会在身份验证时被拒绝。
审计日志
SnapOtter 会在存储于 audit_log 数据库表中的结构化审计日志里记录与安全相关的事件。
查看审计日志
GET /api/v1/audit-log?page=1&limit=50&action=LOGIN_FAILED&from=2026-01-01T00:00:00Z&to=2026-12-31T23:59:59Z
需要 audit:read 权限。支持分页(page、limit)和过滤(action、ip、from、to)。
工具操作审计
::: warning
TOOL_EXECUTED 事件默认不记录。它们可以通过以下两种途径之一选择性开启:
- 将
auditToolOperations管理员设置设为true。 - 持有一份带
audit_export功能的有效许可证(团队版和企业版计划均提供)。
若不满足其中之一,单个工具的执行不会记录到审计日志中。 :::
导出
GET /api/v1/enterprise/audit/export?format=csv&from=2026-01-01T00:00:00Z
需要 audit:read 权限以及 audit_export 企业功能(团队版和企业版计划均提供)。支持 CSV 和 JSON 格式,可按 action、actorId、targetType、targetId、from 和 to 过滤。
防篡改签名
启用后,每条审计日志条目都会用一个从 DATA_ENCRYPTION_KEY 派生的 HMAC 进行签名。这需要:
- 在你的环境中设置
DATA_ENCRYPTION_KEY。 - 启用
tamperResistantAudit管理员设置。 - 一份带
tamper_resistant_audit功能的企业许可证。
保留
设置 AUDIT_RETENTION_DAYS 以自动清除旧条目。默认值为 0,表示条目会无限期保留。
事件参考
| Event | Category |
|---|---|
LOGIN_SUCCESS、LOGIN_FAILED |
身份验证 |
OIDC_LOGIN_SUCCESS、OIDC_LOGIN_FAILED |
身份验证 |
SAML_LOGIN_SUCCESS、SAML_LOGIN_FAILED |
身份验证 |
LOGOUT |
身份验证 |
USER_CREATED、USER_UPDATED、USER_DELETED |
用户管理 |
PASSWORD_CHANGED、PASSWORD_RESET |
用户管理 |
MFA_ENROLLED、MFA_DISABLED、MFA_VERIFIED、MFA_VERIFY_FAILED |
MFA |
MFA_CHALLENGE_ISSUED、MFA_RECOVERY_USED、MFA_RESET |
MFA |
ROLE_CREATED、ROLE_UPDATED、ROLE_DELETED |
角色 |
API_KEY_CREATED、API_KEY_DELETED |
API 密钥 |
SETTINGS_UPDATED、IP_ALLOWLIST_UPDATED |
设置 |
FILE_UPLOADED、FILE_DELETED |
文件 |
TOOL_EXECUTED |
工具(选择性开启) |
SCIM_USER_PROVISIONED、SCIM_USER_UPDATED、SCIM_USER_DEPROVISIONED |
SCIM |
SCIM_GROUP_SYNCED |
SCIM |
LEGAL_HOLD_APPLIED、LEGAL_HOLD_RELEASED |
合规 |
GDPR_EXPORT_INITIATED、GDPR_USER_PURGED、GDPR_TEAM_PURGED |
合规 |
CONFIG_EXPORTED、CONFIG_IMPORTED |
配置 |
会话管理
会话基于 cookie,由 SESSION_DURATION_HOURS 控制(默认值:168 小时 / 7 天)。
角色变更会使会话失效
当管理员更改某个用户的角色时,该用户所有活动会话都会被删除。用户必须重新登录才能获得其新权限。
安全防护
- 最后管理员保护:最后一个剩余的管理员无法被降级到更低的角色。如果你尝试这么做,API 会返回错误。
- 防止自删除:管理员无法通过 API 删除自己的账户。