- 人工智能
- AI Agent
- AI 技能
- 后端
- 前端
【免费下载链接】skillhub
Self-hosted, open-source agent skill registry for enterprises. Publish & version skill packages, govern with RBAC and audit logs, deploy on-premise with Docker or Kubernetes.
本文基于 SkillHub 仓库中的 安全扫描后端运行指南,完整拆解其围绕skill-scanner构建的后端安全扫描链路:从发布流程中版本状态如何流转到SCANNING、local与upload两种运行模式的取舍、后端全部配置项与环境变量,到 Redis Stream 下的重试与终态失败语义,并逐条给出仓库内的源码与清单佐证,帮助你在私有化环境中正确启用、验证和排障这条扫描链路。
一、扫描链路的整体定位与发布流程集成
SkillHub 当前支持的是一条backend-only(纯后端)的安全扫描链路:扫描动作不经过前端,发布(publish)流程在检测到扫描器启用后,自动把新版本转入扫描状态,扫描完成后落库审计结果,再交回既有人工审核(review)工作流。
Scanner 镜像锁定cisco-ai-skill-scanner==2.1.0,使用 glibc 基础 Linux 运行时,发布镜像支持linux/amd64与linux/arm64两种架构。
发布流程的七步流转如下:
- publish 请求进入
SkillPublishService - 若扫描器已启用,版本状态进入
SCANNING - 后端向 Redis Stream 投递一个
ScanTask ScanTaskConsumer消费任务并调用skill-scanner- 扫描结果写入
security_audit表 - 版本转入
PENDING_REVIEW;若重试全部耗尽则进入SCAN_FAILED - 人工审核仍走既有的 review 工作流
其中领域服务入口位于 SkillPublishService,状态常量SCAN_FAILED、PENDING_REVIEW等均在其中定义。整条链路的调用关系可从 scanner/README.md 中的架构图确认:
SkillHub Backend ↓ SecurityScanService.triggerScan() ↓ Redis Stream (skillhub:scan:requests) ↓ ScanTaskConsumer ↓ SkillScannerAdapter ↓ SkillScannerService (HTTP Client) ↓ Cisco skill-scanner API需要注意的是,前端不在本文范围内:前端应通过专门的审计查询 API 获取审计详情,而不是期望在既有 review 详情载荷里内嵌扫描数据。
二、两种运行模式:local 与 upload
SkillHub 后端支持两种调用 Scanner 的运行模式,核心区别在于包体如何交给扫描器:
| 模式 | 调用接口 | 传递方式 | 适用场景 |
|---|---|---|---|
local | POST /scan | 传递文件系统路径 | 本地开发且与 Scanner 共享文件系统 |
upload | POST /scan-upload | 上传包归档 | 官方 Compose / Kubernetes / 任何服务拆分部署 |
local 模式的前置条件
local模式要求 SkillHub 与skill-scanner把同一目录挂载到同一路径,并且 Scanner 侧必须允许该根目录——对标准路径需要设置:
SKILL_SCANNER_ALLOWED_ROOTS=/tmp/skillhub-scans这个/tmp/skillhub-scans并非任意取值:后端消费者 ScanTaskConsumer 中硬编码了SCAN_TEMP_DIR = /tmp/skillhub-scans,当任务携带bundleKey(对象存储中的包)时,消费者会把包拉取到该目录下的临时.zip文件再交给扫描器,并在任务结束/失败时清理该目录(清理逻辑还包含路径前缀校验,只删除该目录内部内容)。
upload 模式与官方部署
官方 Compose 与 Kubernetes 部署统一使用upload模式。例如 compose.release.yml 中 server 容器固定注入:
SKILLHUB_SECURITY_SCANNER_ENABLED: ${SKILLHUB_SECURITY_SCANNER_ENABLED:-true} SKILLHUB_SECURITY_SCANNER_URL: http://skill-scanner:8000 SKILLHUB_SECURITY_SCANNER_MODE: upload后端适配层 SkillScannerAdapter 在发起扫描时会记录当前mode,两条路径最终汇聚到同一个SecurityScanner抽象,因此对上游业务代码而言模式切换是透明的。
三、后端配置全量说明
3.1 应用属性
skillhub.security段默认值由 SkillScannerProperties 定义,文档给出的完整配置示例如下(注意enabled: false是关闭态示例):
skillhub: security: scanner: enabled: false base-url: http://localhost:8000 health-path: /health scan-path: /scan-upload mode: local connect-timeout-ms: 5000 read-timeout-ms: 900000 retry-max-attempts: 3 stream: key: skillhub:scan:requests group: skillhub-scanners reclaim-min-idle: PT16M max-unavailable-age: PT1H结合源码补充几个取值含义:
enabled:属性类中默认值为true,即不配置时扫描默认开启,需显式置false关闭;read-timeout-ms:默认900000(900 秒),对应一次完整扫描的服务端读超时;retry-max-attempts:默认3,与ScanTaskConsumer的默认maxRetryAttempts一致;scan-path:默认/scan-upload,即官方部署默认走 upload 接口。
application.yml 中该段全部由环境变量驱动,且stream段还有几个文档未列举但实际生效的项,带源码注释:
stream: # Keep temporary scanner outages recoverable, but do not retain Redis Pending entries forever. max-unavailable-age: ${SKILLHUB_SECURITY_STREAM_MAX_UNAVAILABLE_AGE:PT1H} key: ${SKILLHUB_SCAN_STREAM_KEY:skillhub:scan:requests} group: ${SKILLHUB_SCAN_STREAM_GROUP:skillhub-scanners} reclaim-enabled: ${SKILLHUB_SCAN_STREAM_RECLAIM_ENABLED:true} # Must exceed scanner read-timeout so an active task is not reclaimed prematurely. reclaim-min-idle: ${SKILLHUB_SCAN_STREAM_RECLAIM_MIN_IDLE:PT16M} reclaim-batch-size: ${SKILLHUB_SCAN_STREAM_RECLAIM_BATCH_SIZE:20} reclaim-interval: ${SKILLHUB_SCAN_STREAM_RECLAIM_INTERVAL:PT30S}注释明确了约束:reclaim-min-idle必须大于 scanner 读超时,否则一个仍在正常执行的扫描任务会被提前重新认领(reclaim)。这就是PT16M(960 秒)与 900 秒读超时之间的时序关系来源。
3.2 关键环境变量
后端侧重要环境变量(均可通过 Spring 属性绑定覆盖上表默认值):
SKILLHUB_SECURITY_SCANNER_ENABLEDSKILLHUB_SECURITY_SCANNER_URLSKILLHUB_SECURITY_SCANNER_MODESKILLHUB_SECURITY_SCANNER_READ_TIMEOUTSKILLHUB_SCAN_STREAM_KEYSKILLHUB_SCAN_STREAM_GROUPSKILLHUB_SCAN_STREAM_RECLAIM_MIN_IDLESKILLHUB_SECURITY_STREAM_MAX_UNAVAILABLE_AGE
Scanner 侧可选环境变量:
SKILL_SCANNER_LLM_API_KEYSKILL_SCANNER_LLM_BASE_URLSKILL_SCANNER_LLM_MODELSKILLHUB_SCANNER_MAX_CONCURRENT_SCANS(默认1)SKILLHUB_SCANNER_HARD_TIMEOUT_SECONDS(默认930)SKILLHUB_SCANNER_MAX_UPLOAD_SIZE_BYTES(默认110100480,即 105 MiB)
Scanner 端对这些变量的解析可在 skillhub_scanner_app.py 中直接确认,例如并发上限_MAX_CONCURRENT_SCANS = max(1, int(os.getenv("SKILLHUB_SCANNER_MAX_CONCURRENT_SCANS", "1")))与硬超时_HARD_TIMEOUT_SECONDS = max(1, int(os.getenv("SKILLHUB_SCANNER_HARD_TIMEOUT_SECONDS", "930")))。
LLM 三个变量缺失时,Scanner 仍会以非 LLM 分析器运行,因此最小部署可以不配置任何密钥。
3.3 默认超时序的三层防线
文档定义的默认超时顺序是这条链路稳定性的核心设计:
- Server 读超时 900 秒(
read-timeout-ms: 900000)——后端先放弃等待; - Scanner 硬超时 930 秒(
SKILLHUB_SCANNER_HARD_TIMEOUT_SECONDS)——扫描器进程自杀; - Pending 消息回收 960 秒(
reclaim-min-idle: PT16M)——Redis 侧最后兜底。
硬超时会以退出码124结束 Scanner 进程,Compose 或 Kubernetes 会将其重启,同时对应的 Redis pending 任务按失败语义重试。三层超时逐级递增,保证"谁先发现超时,谁负责清理",避免出现任务悬挂。
四、Kubernetes 部署要点
当前仓库清单假设skillhub-server与skillhub-scanner是两个独立 Deployment,二者不共享可写的包目录,因此 Kubernetes 环境必须使用upload模式:
SKILLHUB_SECURITY_SCANNER_MODE=upload SKILLHUB_SECURITY_SCANNER_URL=http://skillhub-scanner:8000相关清单(位于 deploy/k8s/base 目录):
- scanner-deployment.yaml
- services.yaml
- backend-deployment.yaml
- configmap.yaml
从源码结构看各清单的具体职责:
- configmap.yaml 提供
skill-scanner-enabled: "true"、skill-scanner-url: http://skillhub-scanner:8000、skill-scanner-mode: upload、skill-scanner-read-timeout: "900000"以及skill-scanner-max-concurrent-scans: "1"、skill-scanner-hard-timeout-seconds: "930"、skill-scanner-max-upload-size-bytes: "110100480"等键; - backend-deployment.yaml 通过
configMapKeyRef把skill-scanner-*键映射为SKILLHUB_SECURITY_SCANNER_ENABLED/URL/MODE/READ_TIMEOUT与SKILLHUB_SCAN_STREAM_RECLAIM_MIN_IDLE环境变量; - scanner-deployment.yaml 将
SKILL_SCANNER_LLM_*三个 LLM 变量声明为可选(optional: true)的 secret 引用,并发/超时/上传大小三个运维参数走 ConfigMap;就绪与存活探针均指向/health(readiness 初始延迟 10 秒、liveness 初始延迟 20 秒)。
Scanner Service 默认是internal-only(集群内部可见),后端通过集群 DNSskillhub-scanner:8000访问,不经过 Ingress 暴露。Helm 安装路径下,charts/skillhub/templates/scanner-deployment.yaml 与 server-deployment.yaml 提供等价的变量注入。
五、滚动升级到 Scanner 2.1.0
Server 与 Scanner 的 HTTP 契约升级必须严格按以下顺序进行:
- 在旧 Scanner 仍在运行时,先部署兼容版 Server;
- 排空并下线所有旧 Server 实例(包括在途的扫描请求);
- 将 Scanner 升级到 2.1.0;
- 在恢复常规流量前,验证
/health与一次 upload 模式扫描。
不要让旧版 Server 直接对接 Scanner 2.1.0。在任意扫描模式的混版滚动期间,应保持 AI Defense 关闭(SKILLHUB_SCANNER_USE_AI_DEFENSE=false,这也是默认值,见 application.yml 中ai-defense: ${SKILLHUB_SCANNER_USE_AI_DEFENSE:false})。如果必须在旧 Scanner 下线前保持 AI Defense 启用,应在旧 Scanner 的环境中用其自身版本支持的环境变量直接配置凭据。任何情况下都不要把 AI Defense 密钥放在 URL 查询参数或请求体中。
仓库还提供了契约级自动化校验:scripts/tests/scanner-2-1-contract-test.sh 用于回归验证 2.1.0 的接口契约,滚动升级前可作为 CI 检查项参考。
六、验证扫描服务与端到端检查
6.1 验证 Scanner 服务本身
仓库自带验证脚本 verify-scanner.sh:
sh scripts/verify-scanner.sh http://localhost:8000 sh scripts/verify-scanner.sh http://localhost:8000 /path/to/skill.zip脚本内部按三步执行(可直接阅读 scripts/verify-scanner.sh 确认):
- 健康检查:
GET /health,失败立即退出; - 分析器清单:
GET /analyzers,不可用时降级为仅健康检查并告警; - 上传冒烟测试:提供样本包时执行
POST /scan-upload(-F "file=@$SAMPLE_PACKAGE"),并校验响应中包含scan_id/scanId。
6.2 启用功能后的后端检查清单
- 发布一个测试包;
- 确认版本状态变为
SCANNING; - 确认
security_audit表产生了新记录(该表由 V35 迁移 创建,后续经 V36、V38 与 V48 等迁移演进,任务投递表则来自 V44 scan_task_outbox); - 确认版本最终进入
PENDING_REVIEW或SCAN_FAILED; - 调用
GET /api/v1/skills/{skillId}/versions/{versionId}/security-audit验证审计可查。
七、审计查询 API 与响应语义
审计数据的统一入口为 SecurityAuditController:
GET /api/v1/skills/{skillId}/versions/{versionId}/security-audit可选查询参数scannerType用于按扫描器类型过滤(源码中对应findLatestActiveByVersionIdAndScannerType)。响应为审计记录列表,字段包括:
scanIdscannerTypeverdictisSafemaxSeverityfindingsCountfindingsscanDurationSecondsscannedAtcreatedAt
源码中还额外返回failureReason字段(对应 V48 迁移新增的列),用于在终态失败时向运维与授权用户暴露失败原因。
权限模型
该接口的访问控制从 SecurityAuditController 的canViewAudit可以确认:
- 平台角色
SUPER_ADMIN或SKILL_ADMIN直接放行; - 所属 namespace 的
ADMIN/OWNER放行; - 其余用户走
visibilityChecker.canAccess按 skill 可见性判断; - 未认证(
principal == null)一律拒绝。
此外,同一路由下还挂载了POST .../security-audit/retry端点,用于对失败扫描发起人工重试。
isSafe 的精确语义
isSafe: true表示扫描未发现高风险问题,并不表示扫描没有产出任何 finding:低严重度 finding 可能仍然存在,findingsCount可能非零。因此 UI 将该状态渲染为No high-risk findings(未发现高风险问题),而不做无条件的安全保证。这一语义与后端策略默认值policy.fail-on-severity: high(见 SkillScannerProperties 中Policy内部类,preset默认balanced)保持一致。
八、失败语义:从"自动重试"到"可见的终态失败"
扫描任务的失败处理由通用消费者 AbstractStreamConsumer 提供骨架,ScanTaskConsumer通过覆写钩子实现扫描专属语义。文档定义的行为逐条对应源码:
| 行为 | 源码依据 |
|---|---|
重试由AbstractStreamConsumer统一处理 | retryMessage()重新publishScanTask,并带上递增的retryCount |
| 连接失败、HTTP 429、HTTP 5xx 保持 pending 等待自动恢复 | shouldDeferFailure():isScannerUnavailable(error) && !hasUnavailableRecoveryExpired(payload)时挂起而非失败 |
超过max-unavailable-age的不可用任务标记SCAN_FAILED、确认并移除 | shouldRetry()在恢复窗口过期后返回false,进入markFailed()→processScanFailure() |
超时在 pending reclaim 时评估,终态处理发生在约一个reclaim-min-idle加一个reclaim-interval之后 | reclaim 参数见 application.yml;maxUnavailableAge默认PT1H(ScanTaskConsumer 中DEFAULT_MAX_UNAVAILABLE_AGE = Duration.ofHours(1)) |
| 终态失败在审计响应中保留失败原因 | finalFailureReason()区分"扫描器未能在配置超时内恢复"与"重试耗尽"两类文案,落入failureReason |
其他终态失败在重试耗尽后把版本标记为SCAN_FAILED | markFailed()调用securityScanService.processScanFailure() |
| 扫描失败后仍会创建 review 任务,避免包永远卡死 | 保持既有人工审核路径完整 |
几个值得注意的实现细节(均来自 ScanTaskConsumer):
- 并发防重:每个任务先取 Redisson 分布式锁
skillhub:scan:processing:{taskId},拿不到锁时抛出ConcurrentScanInProgressException走"重新入队"路径,防止 reclaim 出的重复消息在活跃扫描仍持有任务锁时把唯一的持久投递 ACK 掉; - 幂等:处理前经
isTaskAlreadyProcessed(taskId)双重检查(锁外一次、锁内一次),已处理的任务直接跳过; - 双来源取包:payload 优先使用
bundleKey(从对象存储拉取到/tmp/skillhub-scans临时目录),否则回退到skillPath直接路径;二者皆缺时抛出IllegalStateException; - 任务年龄判定:
createdAtMillis优先取消息字段,缺失时从 Redis 消息 ID 前缀解析毫秒时间戳,并容忍最多 5 分钟的时钟偏移(MAX_CLOCK_SKEW)。
相关测试覆盖见 ScanTaskConsumerTest、ScanTaskConsumerPathSafetyTest(验证临时路径清理安全边界)与 SkillScannerAdapterTest。
这套设计的效果是:人工审核路径始终完整,扫描器故障只是"可见的延迟"而不是"静默的丢失"——失败原因可查、重试可手动触发、超限任务有明确终态。
附录:快速排障对照表
| 现象 | 可能原因 | 排查入口 |
|---|---|---|
版本长期停在SCANNING | Scanner 不可用,任务被 defer 等待恢复 | 检查/health、Redis pending 队列;确认未超max-unavailable-age |
任务进入SCAN_FAILED且原因为"did not recover before the configured timeout" | Scanner 持续不可用超过 1 小时恢复窗口 | 恢复 Scanner 后调用 retry 端点 |
| 扫描被提前 reclaim | reclaim-min-idle小于读超时 | 核对SKILLHUB_SCAN_STREAM_RECLAIM_MIN_IDLE≥ 读超时 |
local模式 Scanner 拒绝扫描 | 根目录未在允许列表 | 设置SKILL_SCANNER_ALLOWED_ROOTS=/tmp/skillhub-scans且两端挂载同一路径 |
| 上传被拒 | 包超过 105 MiB 默认上限 | 调整SKILLHUB_SCANNER_MAX_UPLOAD_SIZE_BYTES |
- 人工智能
- AI Agent
- AI 技能
- 后端
- 前端
【免费下载链接】skillhub
Self-hosted, open-source agent skill registry for enterprises. Publish & version skill packages, govern with RBAC and audit logs, deploy on-premise with Docker or Kubernetes.
相关推荐
Mise 在 Windows 安装 Node.js 报"拒绝访问"怎么办:三条排查路径
Mise 在 Windows 安装 Node.js 报"拒绝访问"怎么办:三条排查路径 在 Windows 上用 Mise 安装 Node.js 时,如果命令停
开发工具CLIRustFS Scanner 后台维护扫描循环:架构解析、运行时控制与调优实战
RustFS Scanner 后台维护扫描循环:架构解析、运行时控制与调优实战 RustFS Scanner 是 RustFS(开源、兼容 S3 的高性能对象存
后端对象存储分布式存储Apache Ossie AI上下文实战指南:instructions、synonyms与examples怎么写
Apache Ossie AI上下文实战指南:instructions、synonyms与examples怎么写 Apache Ossie 是跨行业推动的语义元
后端数据建模数据集成
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考