#!/usr/bin/env bash set -euo pipefail # 运行固定本地 advisory database 的离线漏洞审计。 # # RUSTSEC_ADVISORY_DB 必须由 runner/调用方预先提供;脚本不会联网、clone、 # 更新或写入 advisory database。runner 也应预装并固定 cargo-audit 版本,必要 # 时通过 CARGO_AUDIT_BIN 指向该可执行文件。这样 CI 不会因为自动 fetch DB # 或工具版本漂移而产生不可复核的审计结果。 usage() { cat <<'EOF' 用法: RUSTSEC_ADVISORY_DB=/绝对路径/advisory-db ./scripts/run-cargo-audit.sh 环境变量: RUSTSEC_ADVISORY_DB runner 预先提供的本地 RustSec advisory database 目录,必填。 脚本使用 --no-fetch,不联网、不初始化、不更新此目录。 CARGO_AUDIT_BIN 可选的 cargo-audit 可执行文件路径或 PATH 中的命令名;默认 为 cargo-audit。脚本会向该二进制传入 `audit` 子命令;runner 应预装并固定其版本。 脚本从自身所在 workspace 的上一级解析 Cargo.toml/Cargo.lock,并在该目录运行; 调用者当前目录不会决定审计哪一个 Cargo.lock。 EOF } fail() { echo "cargo-audit gate failed: $*" >&2 exit 1 } if (($# > 0)); then case "$1" in -h|--help) usage exit 0 ;; *) usage >&2 fail "未知参数:$1" ;; esac fi caller_pwd="$(pwd -P)" || fail "无法解析调用目录" script_path="${BASH_SOURCE[0]}" case "$script_path" in /*) ;; *) script_path="$caller_pwd/$script_path" ;; esac script_dir="$(cd -- "$(dirname -- "$script_path")" && pwd -P)" \ || fail "无法解析脚本目录" workspace_root="$(cd -- "$script_dir/.." && pwd -P)" \ || fail "无法解析 workspace 根目录" # 既检查文件存在,也检查 manifest 确实是 workspace manifest,避免把脚本复制 # 到其它 Cargo 项目后静默审计错误的锁文件。 [[ -f "$workspace_root/Cargo.toml" && -r "$workspace_root/Cargo.toml" ]] \ || fail "workspace 根目录缺少可读 Cargo.toml:$workspace_root" [[ -f "$workspace_root/Cargo.lock" && -r "$workspace_root/Cargo.lock" ]] \ || fail "workspace 根目录缺少可读 Cargo.lock:$workspace_root" grep -Eq '^[[:space:]]*\[workspace\][[:space:]]*$' "$workspace_root/Cargo.toml" \ || fail "Cargo.toml 不是 workspace manifest:$workspace_root/Cargo.toml" db_input="${RUSTSEC_ADVISORY_DB:-}" [[ -n "$db_input" ]] || fail "必须设置 RUSTSEC_ADVISORY_DB(runner 提供本地 advisory DB)" case "$db_input" in *$'\n'*|*$'\r'*) fail "RUSTSEC_ADVISORY_DB 不能包含换行" ;; esac if [[ "$db_input" == /* ]]; then advisory_db="$db_input" else # 相对路径也固定解释为 workspace 根目录下的路径,不随调用 cwd 漂移。 advisory_db="$workspace_root/$db_input" fi [[ -d "$advisory_db" ]] \ || fail "advisory DB 不存在或不是目录:$advisory_db" [[ -r "$advisory_db" ]] \ || fail "advisory DB 不可读:$advisory_db" advisory_db="$(cd -- "$advisory_db" && pwd -P)" \ || fail "无法解析 advisory DB 路径:$advisory_db" audit_bin_input="${CARGO_AUDIT_BIN:-cargo-audit}" [[ -n "$audit_bin_input" ]] || fail "CARGO_AUDIT_BIN 不能为空" case "$audit_bin_input" in *$'\n'*|*$'\r'*) fail "CARGO_AUDIT_BIN 不能包含换行" ;; esac if [[ "$audit_bin_input" == */* ]]; then if [[ "$audit_bin_input" == /* ]]; then audit_bin="$audit_bin_input" else # 在 cd 到 workspace 前解析相对可执行文件,避免 cwd 改变后找不到 fake/tool。 audit_bin="$caller_pwd/$audit_bin_input" fi [[ -f "$audit_bin" && -x "$audit_bin" ]] \ || fail "CARGO_AUDIT_BIN 不存在或不可执行:$audit_bin" else audit_bin="$(command -v "$audit_bin_input" 2>/dev/null || true)" [[ -n "$audit_bin" ]] \ || fail "找不到 cargo-audit 工具:$audit_bin_input(请由 runner 预装,或设置 CARGO_AUDIT_BIN)" # PATH 里若使用相对目录(例如 PATH=.:$PATH),command -v 可能返回 # 相对路径;在切换到 workspace 后先固定成调用时的绝对位置。 case "$audit_bin" in /*) ;; *) audit_bin="$caller_pwd/$audit_bin" ;; esac [[ -f "$audit_bin" && -x "$audit_bin" ]] \ || fail "找到的 cargo-audit 不可执行:$audit_bin" fi cd -- "$workspace_root" || fail "无法进入 workspace 根目录:$workspace_root" [[ "$(pwd -P)" == "$workspace_root" ]] \ || fail "workspace 根目录解析漂移:期望 $workspace_root,实际 $(pwd -P)" echo "运行离线 cargo-audit:workspace=$workspace_root advisory_db=$advisory_db" # cargo-audit 的可执行文件本身仍使用 cargo 子命令解析器,因此必须显式传入 # `audit`;省略它会把 --no-fetch 当成顶层参数而失败。 "$audit_bin" audit --no-fetch --db "$advisory_db" --file Cargo.lock