mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: release QA hardening across processing, media, security, and CI gates (#649)
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.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "SnapOtter 的安全加固指南。涵盖容器安全、网络隔离、Docker 密钥、Kubernetes 部署和合规产物。"
|
||||
i18n_source_hash: 986f7658430c
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 05d4a7e4d409
|
||||
i18n_source_hash: 9ff337fa0417
|
||||
i18n_provenance: machine
|
||||
i18n_output_hash: 5cdae11497f9
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 安全与加固 {#security-hardening}
|
||||
@@ -11,133 +12,42 @@ SnapOtter 完全在你自己的基础设施上处理文件。它默认发送匿
|
||||
|
||||
容器以专用的非 root 用户(`snapotter`)运行,除最低必需集之外的所有 Linux 权能都被丢弃。完整的漏洞披露政策和安全架构,请参阅 GitHub 上的 [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md)。
|
||||
|
||||
## 容器加固 {#container-hardening}
|
||||
## 容器硬化 {#container-hardening}
|
||||
|
||||
[默认 docker-compose.yml](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose.yml) 包含生产环境的安全加固。以下是对每个选项及其重要性的逐项说明:
|
||||
规范的 [CPU](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose.yml) 和 [GPU](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose-gpu.yml) Compose 文件是事实来源。不要将缩写示例复制到生产中;从您验证的发布标签部署文件。
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
ports:
|
||||
# Bind to localhost only for internet-facing deployments:
|
||||
- "127.0.0.1:1349:1349"
|
||||
volumes:
|
||||
- SnapOtter-data:/data
|
||||
- SnapOtter-workspace:/tmp/workspace
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_PASSWORD=change-me-immediately
|
||||
- RATE_LIMIT_PER_MIN=1000
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
两个堆栈都应用以下控制:
|
||||
|
||||
# --- Resource limits ---
|
||||
mem_limit: 6g # Prevents runaway memory from crashing the host
|
||||
memswap_limit: 6g # No swap - fail fast instead of degrading the host
|
||||
cpus: 4 # Cap CPU usage to 4 cores
|
||||
pids_limit: 512 # Prevents fork bombs
|
||||
- 内存、交换、CPU 和 PID 限制包含失控的本机处理。
|
||||
- 每个服务都会放弃所有 Linux 功能。该应用程序仅添加回 `CHOWN, SETUID, SETGID, DAC_OVERRIDE, FOWNER, KILL` 来实现卷所有权、单向 `gosu` 身份删除以及优雅的信号转发。 PostgreSQL 和 Redis 仅接收其官方入口点所需的子集。
|
||||
- `security_opt: [no-new-privileges:true]` 防止应用程序、PostgreSQL 和 Redis 容器中的进程获得额外权限。这仍然与 `gosu` 兼容:入口点以 root 身份开始,准备卷,并且仅下降到专用的 `snapotter` 用户。
|
||||
- PostgreSQL 和 Redis 图像输入由摘要固定。应用程序同样应该固定到经过验证的发布标签或摘要,而不是 `latest`。
|
||||
- 健康检查、有界 JSON 日志轮换、持久的 Redis AOF 和重启策略在规范文件中集中定义。
|
||||
|
||||
# --- Capability restrictions ---
|
||||
cap_drop:
|
||||
- ALL # Drop ALL Linux capabilities first
|
||||
cap_add:
|
||||
- CHOWN # Needed for volume permission setup
|
||||
- SETUID # Needed for gosu privilege drop (root -> snapotter)
|
||||
- SETGID # Needed for gosu privilege drop
|
||||
- DAC_OVERRIDE # Needed for volume permission setup
|
||||
- FOWNER # Needed for volume permission setup
|
||||
对于面向互联网的部署,将端口 1349 绑定到环回并在维护的反向代理处终止 TLS。生成唯一的 PostgreSQL 和 Redis 凭据,将机密存储在受保护的文件或机密管理器中,并立即更改初始管理员密码。
|
||||
|
||||
# --- Logging ---
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "50m" # Rotate logs at 50 MB
|
||||
max-file: "5" # Keep 5 rotated log files
|
||||
### 为什么 `read_only` 没有设置 {#why-read-only-is-not-set}
|
||||
|
||||
# --- Health check ---
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "--max-time", "5", "http://localhost:1349/api/v1/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 60s
|
||||
retries: 3
|
||||
未设置 `read_only: true`,因为 PUID/PGID 重新映射在启动时写入 `/etc/passwd` 和 `/etc/group`。如果您使用 Docker 的 `--user` 标志或 Kubernetes `runAsUser` 而不是 PUID/PGID,则可以安全地启用只读根文件系统。
|
||||
|
||||
shm_size: "2gb" # Required for Python ML shared memory
|
||||
restart: unless-stopped
|
||||
## 网络隔离{#network-isolation}
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 15s
|
||||
文件处理是本地的,但默认安装**不是无出口系统**。启用遥测功能时,匿名产品分析使用 PostHog,崩溃报告使用 Sentry。设置 `SNAPOTTER_TELEMETRY=0`(或在“设置”>“系统”>“隐私”下禁用分析)以关闭两者。 SnapOtter 绝不会在这些事件中包含上传的文件、文件名、OCR 输出、文档文本或其他文件内容。
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- SnapOtter-redisdata:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
SnapOtter-data:
|
||||
SnapOtter-workspace:
|
||||
SnapOtter-pgdata:
|
||||
SnapOtter-redisdata:
|
||||
```
|
||||
|
||||
### 为何不设置 `no-new-privileges` {#why-no-new-privileges-is-not-set}
|
||||
|
||||
`security_opt: [no-new-privileges:true]` 被有意省略。入口点以 root 启动以修复卷的所有权,然后通过 [gosu](https://github.com/tianon/gosu) 降权到 `snapotter` 用户,而这需要 setuid。一旦降权完成,进程就以 `snapotter` 运行,除上面列出的五项外的所有权能都被移除。
|
||||
|
||||
如果你使用 Kubernetes 或 Docker 的 `--user` 标志直接以非 root 运行(绕过 gosu),那么启用 `no-new-privileges` 是安全的。
|
||||
|
||||
### 为何不设置 `read_only` {#why-read-only-is-not-set}
|
||||
|
||||
没有设置 `read_only: true`,因为 PUID/PGID 重映射会在启动时写入 `/etc/passwd` 和 `/etc/group`。如果你使用 Docker 的 `--user` 标志或 Kubernetes 的 `runAsUser` 而非 PUID/PGID,你可以安全地启用只读根文件系统。
|
||||
|
||||
## 网络隔离 {#network-isolation}
|
||||
|
||||
在正常运行期间,容器**不发起任何出站网络连接**。所有文件处理都使用捆绑的库在本地进行。
|
||||
|
||||
```
|
||||
Browser --> Reverse Proxy (TLS) --> SnapOtter container --> (nothing)
|
||||
```
|
||||
|
||||
唯一的例外是 **AI 模型下载**:当用户通过 UI 安装 AI 功能包时,容器会从 Hugging Face 下载预构建的包归档,外加来自 GitHub Releases、Google Storage 和 PyPI 的少量单独模型文件。这些下载每个包只发生一次,并存储在 `/data` 卷中。
|
||||
其他出站流量是功能驱动的:AI 捆绑包/模型安装下载签名发布输入; URL导入获取用户请求的公共URL;并显式配置的 OIDC、SAML、OpenTelemetry、webhooks、S3 兼容存储或类似集成会联系管理员选择的目标。运行时模型下载默认处于禁用状态。仅在明确选择启用自动回退下载时设置 `SNAPOTTER_ALLOW_MODEL_DOWNLOAD=1`。[离线捆绑导入](/zh-CN/guide/deployment) 可以在没有运行时模型出口的情况下提供 AI 功能。
|
||||
|
||||
**防火墙建议:**
|
||||
|
||||
| 场景 | 出站规则 |
|
||||
|设想|出站规则|
|
||||
|---|---|
|
||||
| 隔离网络(无 AI) | 阻止容器的所有出站流量 |
|
||||
| 需要 AI 包 | 安装期间允许对 `huggingface.co`、`*.xethub.hf.co`、`cdn-lfs.huggingface.co`、`github.com`、`objects.githubusercontent.com`、`storage.googleapis.com`、`pypi.org`、`files.pythonhosted.org` 的 HTTPS,之后阻止 |
|
||||
| AI 安装完成后 | 阻止所有出站流量 - 模型已在本地缓存 |
|
||||
|气隙|设置`SNAPOTTER_TELEMETRY=0`和`SNAPOTTER_ALLOW_MODEL_DOWNLOAD=0`,使用离线AI捆绑导入,禁用URL导入和外部集成,然后阻止出口|
|
||||
|默认遥测|允许浏览器/网络日志列出的 PostHog 和 Sentry 端点;如果策略不允许,则禁用遥测|
|
||||
|需要 AI 捆绑包|安装过程中,允许HTTPS到`huggingface.co, *.xethub.hf.co, cdn-lfs.huggingface.co, github.com, objects.githubusercontent.com, storage.googleapis.com, pypi.org, files.pythonhosted.org`;然后阻止这些主机|
|
||||
|外部集成|仅允许管理员准确配置的 OIDC/SAML/OTLP/webhook/对象存储目标|
|
||||
|
||||
包归档由 Hugging Face 的 Xet 存储提供,它通过 `*.xethub.hf.co` 端点并行传输,正是它让数 GB 的包下载变得快速。如果你的防火墙允许 `huggingface.co` 但阻止 `*.xethub.hf.co`,安装仍会成功,但会回退到较慢的单流下载,所以请将 Xet 主机加入允许列表以保持在快速路径上。完全离线的安装可以跳过这一切,改用 [离线包导入](/zh-CN/guide/deployment)。
|
||||
捆绑包档案由 Hugging Face 的 Xet 存储提供,该存储通过 `*.xethub.hf.co` 端点并行传输,这使得多 GB 捆绑包下载速度更快。如果您的防火墙允许 `huggingface.co` 但阻止 `*.xethub.hf.co`,安装仍然会成功,但会回退到较慢的单流下载,因此将 Xet 主机列入白名单以保持快速路径。完全离线安装可以跳过所有这些并使用[离线捆绑导入](/zh-CN/guide/deployment)。
|
||||
|
||||
关于反向代理配置(Nginx、Traefik、Caddy、Cloudflare Tunnels),请参阅 [部署指南](/zh-CN/guide/deployment#reverse-proxy)。
|
||||
有关反向代理配置(Nginx、Traefik、Caddy、Cloudflare Tunnels),请参阅[部署指南](/zh-CN/guide/deployment#reverse-proxy)。
|
||||
|
||||
## Docker 密钥 {#docker-secrets}
|
||||
|
||||
@@ -257,83 +167,101 @@ spec:
|
||||
|
||||
## 备份与恢复 {#backup-and-recovery}
|
||||
|
||||
持久化状态分布在两个卷中:
|
||||
生产 Compose 堆栈定义了四个卷。在进行协调备份之前停止入口并让活动作业完成,以便 PostgreSQL、Redis 和文件状态描述相同的时间点。
|
||||
|
||||
| 卷 | 内容 | 是否关键? |
|
||||
|体积|内容|康复治疗|
|
||||
|---|---|---|
|
||||
| `SnapOtter-pgdata` | PostgreSQL 数据库(用户、设置、流水线、任务、审计日志) | 是 |
|
||||
| `/data`(应用卷) | 用户上传的文件、AI 模型、Python venv | 部分(见下文) |
|
||||
|`SnapOtter-pgdata`|PostgreSQL 用户、设置、管道、作业、文件元数据和审核日志|批判的;使用快速故障逻辑转储进行可移植恢复|
|
||||
|`SnapOtter-data`|保存的库对象、日志和 AI 状态 (`/data/files, /data/logs, /data/ai, /data/ai/venv`)|备份整个卷;为了节省空间,故意省略所有 AI 状态并重新安装其捆绑包|
|
||||
|`SnapOtter-redisdata`|Redis AOF 用于持久的 BullMQ 队列状态|暂停应用程序并强制`SAVE`后备份;需要准确地恢复排队的工作|
|
||||
|`SnapOtter-workspace`|临时对象存储键 (`/tmp/workspace/uploads, /tmp/workspace/outputs`)|所有作业清空或取消后不要进行备份;当工作处于活动状态时切勿丢弃它|
|
||||
|
||||
在 `/data` 卷内:
|
||||
Compose 通常在卷名称前加上项目名称作为前缀。从已安装的容器中解析真实的源卷,而不是假设显示名称(例如 `SnapOtter-data`)是 Docker 卷名称。
|
||||
|
||||
| 路径 | 内容 | 是否关键? |
|
||||
|---|---|---|
|
||||
| `/data/uploads/`、`/data/outputs/` | 用户文件和处理结果 | 是 |
|
||||
| `/data/ai/` | 下载的 AI 模型文件 | 否(可重新下载) |
|
||||
| `/data/venv/` | Python 虚拟环境 | 否(启动时重建) |
|
||||
### 数据库备份{#database-backup}
|
||||
|
||||
### 数据库备份 {#database-backup}
|
||||
|
||||
在栈运行期间使用 `pg_dump` 备份数据库:
|
||||
使用 PostgreSQL 的自定义存档格式并在将备份视为完整之前验证存档:
|
||||
|
||||
```bash
|
||||
# Dump the database
|
||||
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql
|
||||
docker exec SnapOtter-postgres \
|
||||
pg_dump --format=custom --no-owner -U snapotter snapotter > snapotter.dump
|
||||
test -s snapotter.dump
|
||||
docker exec -i SnapOtter-postgres pg_restore --list < snapotter.dump >/dev/null
|
||||
|
||||
# Restore into a fresh database
|
||||
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter
|
||||
# Restore only into a fresh/disposable target first; any SQL error fails the command.
|
||||
docker exec -i SnapOtter-postgres \
|
||||
pg_restore --exit-on-error --clean --if-exists --no-owner \
|
||||
-U snapotter -d snapotter < snapotter.dump
|
||||
```
|
||||
|
||||
或者,停止栈并对 `SnapOtter-pgdata` 卷做快照:
|
||||
通过将每个备份恢复到隔离堆栈、检查数据库记录和文件校验和以及启动应用程序来测试每个备份。存储库的 `tests/qa/backup-restore-drill.sh` 会针对显式 `QA_IMAGE` 自动执行该发布门。
|
||||
|
||||
如果您的平台采用崩溃一致的卷快照,请首先停止整个堆栈,并将所有关键卷快照为一组。来自正在运行的容器的原始 PostgreSQL 数据目录副本不是受支持的逻辑备份。
|
||||
|
||||
### 文件和队列备份 {#file-and-queue-backup}
|
||||
|
||||
在捕获文件和队列卷之前暂停应用程序。使用 `docker inspect` 解析实际卷名称,强制 Redis 保留其当前状态,并在保留所有权和权限的情况下进行归档:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
|
||||
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
|
||||
docker stop SnapOtter
|
||||
docker exec SnapOtter-redis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning SAVE
|
||||
docker stop SnapOtter-redis
|
||||
|
||||
DATA_VOLUME="$(docker inspect SnapOtter --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')"
|
||||
REDIS_VOLUME="$(docker inspect SnapOtter-redis --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')"
|
||||
|
||||
install -d -m 700 backup
|
||||
docker run --rm -v "$DATA_VOLUME:/source:ro" -v "$PWD/backup:/backup" \
|
||||
alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce tar czf /backup/snapotter-data.tar.gz -C /source .
|
||||
docker run --rm -v "$REDIS_VOLUME:/source:ro" -v "$PWD/backup:/backup" \
|
||||
alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce tar czf /backup/snapotter-redis.tar.gz -C /source .
|
||||
sha256sum backup/snapotter-*.tar.gz > backup/SHA256SUMS
|
||||
```
|
||||
|
||||
### 用户文件备份 {#user-files-backup}
|
||||
应用前重启Redis。如果您有意排除 `/data/ai`,请删除整个 AI 子树,而不是保留不带模型或虚拟环境的 `installed.json` 记录。保持备份文件加密、访问受控,并与运行 SnapOtter 的主机分开。
|
||||
|
||||
```bash
|
||||
# Snapshot the app data volume (excluding re-downloadable AI models)
|
||||
docker run --rm -v SnapOtter-data:/data -v $(pwd)/backup:/backup \
|
||||
alpine tar czf /backup/snapotter-files.tar.gz \
|
||||
--exclude='ai' --exclude='venv' -C /data .
|
||||
```
|
||||
## 合规工件 {#compliance-artifacts}
|
||||
|
||||
AI 模型跨所有包总计约 24 GB。由于它们可重新下载,请从备份中排除 `/data/ai/` 和 `/data/venv/` 以节省空间。只有数据库和用户文件是关键的。
|
||||
每个 SnapOtter 版本都包含以下安全工件:
|
||||
|
||||
## 合规产物 {#compliance-artifacts}
|
||||
|
||||
每个 SnapOtter 发布都包含以下安全产物:
|
||||
|
||||
| 产物 | 格式 | 获取位置 |
|
||||
| 人工制品 | 格式 | 在哪里可以找到它 |
|
||||
|---|---|---|
|
||||
| SBOM(CycloneDX) | JSON | [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases) 资产:`snapotter-v{version}-sbom.cdx.json` |
|
||||
| SBOM(SPDX) | JSON | [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases) 资产:`snapotter-v{version}-sbom.spdx.json` |
|
||||
| 漏洞扫描 | Trivy JSON | [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases) 资产:`snapotter-v{version}-trivy.json` |
|
||||
| 漏洞扫描 | SARIF | [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security) 选项卡 |
|
||||
| 静态分析 | CodeQL(JS/TS + Python) | [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security) 选项卡,每周 + 每 PR 运行 |
|
||||
| 依赖审查 | GitHub 原生 | 每 PR 检查,在新增高危项时失败 |
|
||||
| Python 依赖审计 | pip-audit | 每次推送的 CI 运行日志 |
|
||||
| 安全政策 | Markdown | 仓库中的 [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) |
|
||||
| 依赖更新 | Dependabot | 针对 npm、pip、Docker、Actions 的自动化每周 PR |
|
||||
| 释放主体绑定 | 规范 JSON + GitHub 证明 | [GitHub发布](https://github.com/snapotter-hq/SnapOtter/releases)资产:`snapotter-v{version}-release-subjects.json` |
|
||||
| 归档 SBOM | CycloneDX 和 SPDX JSON | 释放资产:`snapotter-v{version}-archive-linux-{arch}-sbom.{cdx,spdx}.json` |
|
||||
| 图片 SBOM | CycloneDX 和 SPDX JSON | 释放资产:`snapotter-v{version}-image-linux-{arch}-sbom.{cdx,spdx}.json` |
|
||||
| 漏洞扫描 | Trivy JSON | 发布具有匹配 `archive-linux-{arch}` 或 `image-linux-{arch}` 前缀的资产 |
|
||||
| 漏洞扫描 | SARIF | [GitHub 安全](https://github.com/snapotter-hq/SnapOtter/security) 选项卡 |
|
||||
| 静态分析 | CodeQL (JS/TS + Python) | [GitHub 安全](https://github.com/snapotter-hq/SnapOtter/security) 选项卡,每周运行 + 每个 PR |
|
||||
| 依赖性审查 | GitHub 本机 | 按 PR 检查,高严重性添加失败 |
|
||||
| Python依赖审计 | pip-audit | CI 在每次推送时运行日志 |
|
||||
| 安全政策 | Markdown | 存储库中的 [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) |
|
||||
| 依赖项更新 | Dependabot | npm、pip、Docker、Actions 的自动每周 PR |
|
||||
|
||||
**运行你自己的扫描:**
|
||||
**运行您自己的扫描:**
|
||||
|
||||
从发布中下载 SBOM,并用你偏好的工具扫描它:
|
||||
下载发布主题清单并验证它是否已由发布工作流程证明:
|
||||
|
||||
```bash
|
||||
gh attestation verify snapotter-v2.1.0-release-subjects.json \
|
||||
--repo snapotter-hq/SnapOtter \
|
||||
--signer-workflow snapotter-hq/SnapOtter/.github/workflows/release.yml
|
||||
```
|
||||
|
||||
清单中分别记录了 `releaseTag`、`releaseCommit` 和 `workflowTriggerCommit`。验证 `releaseCommit` 是否是从不可变标记中剥离的提交,然后验证存档、映像、SBOM 的 SHA-256 摘要,或根据 `subjects` 中的条目进行扫描。这种区别是有意为之的:签出新创建的发布提交不会更改工作流的 OIDC 凭证中的提交标识。
|
||||
|
||||
您还可以直接扫描下载的 SBOM 或图像:
|
||||
|
||||
```bash
|
||||
# Scan with Grype using the CycloneDX SBOM
|
||||
grype sbom:snapotter-v1.17.2-sbom.cdx.json
|
||||
grype sbom:snapotter-v2.1.0-image-linux-amd64-sbom.cdx.json
|
||||
|
||||
# Scan with Trivy using the SPDX SBOM
|
||||
trivy sbom snapotter-v1.17.2-sbom.spdx.json
|
||||
trivy sbom snapotter-v2.1.0-image-linux-amd64-sbom.spdx.json
|
||||
|
||||
# Scan the Docker image directly
|
||||
trivy image snapotter/snapotter:1.17.2
|
||||
trivy image snapotter/snapotter:2.1.0
|
||||
```
|
||||
|
||||
::: info
|
||||
SBOM 和漏洞扫描反映的是该次发布所发布的确切镜像。部署后安装的 AI 模型包不包含在 SBOM 中,因为它们是在运行时下载的。
|
||||
::: info
|
||||
图像 SBOMs 和扫描反映了针对该版本发布的具体架构特定图像。存档 SBOMs 和扫描分别描述预建存档。部署后安装的 AI 模型包不包含在这些 SBOMs 中,因为它们是在运行时下载的。
|
||||
:::
|
||||
|
||||
Reference in New Issue
Block a user