# refuse
**为 [`refuse-cli`](https://github.com/RefuseHQ/refuse-cli) 提供支持的可自托管后端,用于阻止安装存在漏洞的包。**
[](https://github.com/RefuseHQ/refuse/actions/workflows/ci.yaml)
[](https://github.com/RefuseHQ/refuse/actions/workflows/codeql.yml)
[](LICENSE)
[](https://github.com/RefuseHQ/refuse/pkgs/container/refuse)
[](https://github.com/RefuseHQ/refuse/releases)
一个小型 HTTP 服务,接收公共漏洞 + 元数据源 —— [OSV](https://osv.dev/)、[deps.dev](https://deps.dev/)、[CISA KEV](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)、[FIRST EPSS](https://www.first.org/epss/)、[GitHub Security Advisories](https://github.com/advisories)、[Wolfi](https://github.com/wolfi-dev/advisories) —— 存入本地 SQLite 数据库,并回答如下问题:
- *`lodash@4.17.10` 有漏洞吗?*
- *`requests` 的最低安全升级版本是什么?*
- *这个 `package-lock.json` 中的 250 个包里是否有已知的恶意包?*
- *这个 Dockerfile 是否安装了包含 CVE 的 apt 包?*
预期调用方是 [`refuse-cli`](https://github.com/RefuseHQ/refuse-cli) —— 一个小巧的 shim,包装了 `npm`、`pnpm`、`yarn`、`pip`、`cargo`、`gem`、`bun`、`go`,并拒绝为已知的恶意包运行安装。该服务器也是标准的 REST API,因此 `curl`、CI 步骤或任何其他客户端都可以使用它。
## 运行位置
一个长期运行的 HTTP 服务 —— 选择一台主机:
- **笔记本电脑** —— `docker run`,绑定到 localhost。匿名模式即可。
- **团队 VM** —— 同一镜像位于你的反向代理之后,通过 API key 锁定。
- **托管** —— [refuse.dev](https://refuse.dev) 为你运行相同的镜像。
CLI 端集成(PATH shim、pre-commit、GitHub Actions、Docker 构建)位于 [`refuse-cli`](https://github.com/RefuseHQ/refuse-cli) 中。
## 功能说明
- **REST API** —— `POST /api/v1/check/{package, batch, lockfile, dockerfile, workflow}` 和 `suggest-safe-version`。JSON 输入/输出,可选 bearer 认证。
- **数据摄入** 在进程内运行:OSV 约每 5 分钟一次,deps.dev 约每 15 分钟一次,每日执行 KEV / EPSS / GHSA / Wolfi 丰富化。所有源均为公开,运行时不会向除此之外发起调用。
- **内嵌式 SQLite**(WAL 模式),一个容器,一个卷。
- 位于 `/ui/` 的 **管理界面** 用于查看数据源健康状况、手动触发摄入以及 API key 的 CRUD。
## 快速开始
### 1. 启动带有持久化卷的服务器
```
docker run -d --name refuse -p 8080:8080 \
-v refuse-data:/data \
ghcr.io/refusehq/refuse:latest
```
### 2. 观察冷启动数据播种(约 3 分钟)
```
docker logs -f refuse
```
首次启动会流式传输 OSV 的批量存档(一次遍历所有生态系统 —— npm、PyPI、Maven、Go、crates.io、RubyGems ……以及 OSV 团队发布的每一个 Debian / Ubuntu / Alpine / RHEL 发行版),同时并行处理 KEV、EPSS、GHSA 和 Wolfi。进度条展示了当前状态:
```
refuse: ingest[osv:bulk ] [██████████░░░░░░░░░░] 50% • 75000 records • 1m30s
refuse: ingest[kev ] [████████████████████] 100% • 1542/1542 entries
refuse: ingest[epss ] [████████████████████] 87% • 245678 rows scored
refuse: ingest[ghsa ] ✓ done — 100 records in 2s (cursor saved)
refuse: ingest[wolfi ] ✓ done — 1247 records across 412 packages in 4s
```
在此一次性引导之后,每 5 分钟的增量更新只需几秒钟。
### 3. 等待 `/readyz`
```
curl http://localhost:8080/readyz
```
在数据源进行引导时返回带有 `pending_sources` 列表的 `503` 状态码,并在每个必需的数据源至少完成一次遍历后返回 `200`:
```
{
"ready": true,
"ready_sources": ["osv", "kev", "epss", "ghsa_direct", "wolfi"],
"pending_sources": [],
"osv_ecosystems_done": 26,
"osv_ecosystems_total": 26
}
```
### 4. 将 [`refuse-cli`](https://github.com/RefuseHQ/refuse-cli) 指向它
```
refuse config set server_url http://localhost:8080
refuse install # drop PATH shims into ~/.refuse/bin
npm install lodash@4.17.10
# refuse: blocked — CVE-2019-10744 (critical); 建议 4.17.21
```
也支持直接的 REST 调用 —— 适用于不希望使用该 shim 的任何客户端:
```
curl -sX POST http://localhost:8080/api/v1/check/package \
-H 'Content-Type: application/json' \
-d '{"ecosystem":"npm","name":"lodash","version":"4.17.10"}' | jq .
```
或者浏览
查看仪表板。
## 生产环境部署 (compose)
```
services:
refuse:
image: ghcr.io/refusehq/refuse:latest
ports: ["8080:8080"]
volumes: ["./data:/data"]
restart: unless-stopped
environment:
REFUSE_REQUIRE_KEY: "true"
REFUSE_ADMIN_TOKEN: "${REFUSE_ADMIN_TOKEN:?set it before starting}"
# REFUSE_GITHUB_TOKEN: ghp_... (optional, raises GHSA ingest rate limit)
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8080/healthz').then(r=>process.exit(r.ok?0:1))"]
interval: 30s
retries: 3
```
请查看 [`docker/docker-compose.with-key.yml`](docker/docker-compose.with-key.yml) 获取带有注释的相同配置。
## 配置说明
一切均由环境变量驱动。默认值选择了安全的配置,因此无需任何标志即可运行 `docker run`。
| 变量 | 默认值 | 用途 |
| --- | --- | --- |
| `REFUSE_PORT` | `8080` | HTTP 监听端口 |
| `REFUSE_DB_PATH` | `/data/refuse.db` | SQLite 文件路径 |
| `REFUSE_REQUIRE_KEY` | `false` | 在 `/api/v1/check/*` 上要求 `Authorization: Bearer rfs_…` |
| `REFUSE_ADMIN_TOKEN` | *(未设置)* | 用于管理界面 + key CRUD 的静态 bearer |
| `REFUSE_OSV_FREQUENCY` | `5` | OSV 增量运行间隔(分钟) |
| `REFUSE_DEVS_DEV_FREQUENCY` | `15` | deps.dev 运行间隔(分钟) |
| `REFUSE_ENRICHMENT_CRON` | `0 5 * * *` | 用于 KEV/EPSS/GHSA/Wolfi 的 Cron 表达式 |
| `REFUSE_BOOTSTRAP_ON_EMPTY` | `true` | 如果数据库为空,首次启动时同步拉取 OSV |
| `REFUSE_DISABLE_INGEST` | `false` | 只读镜像模式(用于预播种快照) |
| `REFUSE_GITHUB_TOKEN` | *(未设置)* | 可选的 GH token,可提高 GHSA-direct 的速率限制 |
| `REFUSE_CARD_CACHE_SIZE` | `5000` | 构建的 `VulnCard`s 的 LRU 条目数 |
| `REFUSE_CARD_CACHE_TTL_SECONDS` | `600` | 该 LRU 的 TTL |
| `REFUSE_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` |
| `REFUSE_CORS_ORIGIN` | `*` | `/api/v1/check/*` 上的 CORS allow-origin |
完整参考:[`docs/configuration.md`](docs/configuration.md)。
## API 概览
| | |
| --- | --- |
| `GET /healthz` | 存活探针 |
| `POST /api/v1/check/package` | 单包漏洞检查 |
| `POST /api/v1/check/batch` | 并行检查多个包 |
| `POST /api/v1/check/lockfile` | 解析并扫描整个 lockfile |
| `POST /api/v1/check/dockerfile` | 解析并扫描基础镜像 + RUN 行 |
| `POST /api/v1/check/workflow` | 扫描 GitHub Actions 的 `uses:` 条目 |
| `POST /api/v1/suggest-safe-version` | 为受影响的包提供最低安全升级版本 |
| `GET /api/admin/stats` | 数据库行数统计(管理员 token) |
| `GET /api/admin/sources` | 每个摄入源的最后一次运行 / 上次成功记录 |
| `POST /api/admin/ingest/{osv,deps-dev,enrichment}` | 手动触发 |
| `GET/POST/DELETE /api/keys[/:id]` | API key 的 CRUD |
| `GET /ui/` | 面向自托管运维人员的管理仪表板 |
完整 schema 参考:[`docs/api.md`](docs/api.md)。
## refuse 家族
| | 它是什么 | 何时使用 |
| --- | --- | --- |
| **[refuse](https://github.com/RefuseHQ/refuse)**(本仓库) | 可自托管的 HTTP 服务器 | 当你想要自己的后端时 —— 无论是因为气隙隔离、本地部署还是其他原因 |
| **[refuse-cli](https://github.com/RefuseHQ/refuse-cli)** | 包装了 `npm` / `pip` / `cargo` / ... 的 PATH shim | 当你想在开发机器或 CI 上在安装发生之前阻止它们时 |
两者共享解析器、版本比较器和基于 OSV 派生的数据模型。
## 对比
| | refuse | OSV-scanner | Trivy / Grype | Dependency-Track | guarddog | npq |
| --- | --- | --- | --- | --- | --- | --- |
| 开源协议 | Apache-2.0 | Apache-2.0 | Apache-2.0 | Apache-2.0 | Apache-2.0 | MIT |
| 独立服务器 | ✅ | — | 部分 | ✅ | — | — |
| **通过 PATH shim 进行安装时拦截**(配合 [`refuse-cli`](https://github.com/RefuseHQ/refuse-cli)) | ✅ | — | — | — | — | ✅ |
| **Dockerfile 解析(基础镜像 + RUN)** | ✅ | — | ✅(机制不同) | — | — | — |
| OSV 数据 | ✅ | ✅ | ✅ | ✅ | — | — |
| KEV / EPSS 丰富化 | ✅ | — | 部分 | ✅ | — | — |
| GitHub Actions `uses:` 扫描 | ✅ | — | 部分 | — | — | — |
| 启发式恶意包检测 | 部分 | — | — | — | ✅ | 部分 |
| 单容器部署 | ✅ | n/a | ✅ | ✅ | n/a | n/a |
`refuse` 的核心差异在于 **安装时拦截** —— 在运行前拒绝安装,而不是事后报告依赖树的状态。CLI shim 使这种拦截对开发者或 CI 步骤是透明的。如果你已经在运行 Trivy 或 Dependency-Track 进行安装后监控,refuse 可以与它们互补:结构化的拒绝记录(包、版本、原因、OSV id)可以无缝接入任一 pipeline。
## 自托管指南
### 单个开发者(笔记本电脑)
```
mkdir -p ~/refuse-data
docker run -d --name refuse \
-p 8080:8080 -v ~/refuse-data:/data \
--restart unless-stopped \
ghcr.io/refusehq/refuse:latest
# 安装 CLI (从 refuse-cli repo)
brew install refusehq/tap/refuse
refuse config set server_url http://localhost:8080
refuse install
```
在此场景下使用匿名模式即可 —— 服务器仅绑定到 `localhost`。
### 团队集群(真实域名,持久化卷)
位于带有 TLS 的 nginx/Caddy 之后:
```
services:
refuse:
image: ghcr.io/refusehq/refuse:latest
volumes: ["/srv/refuse:/data"]
environment:
REFUSE_REQUIRE_KEY: "true"
REFUSE_ADMIN_TOKEN: "${REFUSE_ADMIN_TOKEN}"
REFUSE_GITHUB_TOKEN: "${REFUSE_GITHUB_TOKEN}"
REFUSE_CORS_ORIGIN: "https://your-app.example.com"
restart: unless-stopped
```
将 DNS 记录指向该主机,在你的反向代理中终结 TLS,并定期轮换 `REFUSE_ADMIN_TOKEN`。通过 cron `cp` 备份 `/srv/refuse/refuse.db` —— SQLite WAL 使热拷贝安全可行。
详细指南:[`docs/self-hosting.md`](docs/self-hosting.md)。
## 从源码构建
```
git clone https://github.com/RefuseHQ/refuse.git
cd refuse
pnpm install
pnpm typecheck && pnpm test
pnpm --filter @refuse-oss/server dev # hot reload on src/
# 本地构建 Docker 镜像
make docker
make docker-run # → http://localhost:8080
```
仓库结构:
```
apps/server/ # Hono server, REST API, tools, ingest, UI
src/
config.ts # env validation
db/ # SQLite client + D1-shape facade + migrations
http/ # router, REST, auth, admin
tools/ # the six check_* tools
ingest/ # cron scheduler + OSV/deps.dev/KEV/EPSS/GHSA/Wolfi
cards/ # VulnCard reader (LRU on top of SQLite)
ui/static/ # tiny vanilla SPA
packages/
shared/ # zod schemas, ecosystem normalization
versions/ # version matchers (semver, pypi, maven, dpkg, …)
docker/ # Dockerfile + compose examples + entrypoint
```
请查看 [ARCHITECTURE.md](./ARCHITECTURE.md) 了解更深入的说明。
## 安全性
安全策略:[SECURITY.md](./SECURITY.md)。请通过 [hello@refuse.dev](mailto:hello@refuse.dev) 或 [GitHub 私密漏洞报告](https://github.com/RefuseHQ/refuse/security/advisories/new) 私下报告。
## 致谢
基于 [OSV.dev](https://osv.dev/)、[deps.dev](https://deps.dev/)、[CISA KEV 目录](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)、[FIRST EPSS](https://www.first.org/epss/)、[GitHub Security Advisories](https://github.com/advisories) 和 [Wolfi advisories](https://github.com/wolfi-dev/advisories) 构建。
## 许可证
[Apache License 2.0](./LICENSE) © RefuseHQ.