From e8faca63eddbd15983a698ef2dc689ef26f2454b Mon Sep 17 00:00:00 2001 From: Atirna <288419661+atirna@users.noreply.github.com> Date: Thu, 13 Aug 2026 02:30:36 +0530 Subject: [PATCH] fix(bootstrap): enforce manifest-pinned sources --- .github/workflows/ci.yml | 1 + .gitignore | 1 + kali/scripts/bootstrap-manifest.json | 1 + kali/scripts/bootstrap-reverse.sh | 15 +- skills/scripts/bootstrap-manifest.json | 1 + skills/scripts/bootstrap-reverse.ps1 | 27 +-- skills/scripts/bootstrap-reverse.sh | 165 +++++++++++--- skills/scripts/test-bootstrap-manifest.sh | 238 ++++++++++++++++++++ skills/scripts/verify-routing-coherence.ps1 | 6 +- 9 files changed, 397 insertions(+), 58 deletions(-) create mode 100644 skills/scripts/test-bootstrap-manifest.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e86139a..6cbfa17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,7 @@ jobs: run: | set -euo pipefail bash skills/scripts/test-routing.sh + bash skills/scripts/test-bootstrap-manifest.sh scratch="$(mktemp -d)" trap 'rm -rf "$scratch"' EXIT diff --git a/.gitignore b/.gitignore index 8073f7e..7254c4a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ Desktop.ini *.tmp *.bak *.log +implementation-notes.md # User-specific config .claude/ diff --git a/kali/scripts/bootstrap-manifest.json b/kali/scripts/bootstrap-manifest.json index 226774b..6232906 100644 --- a/kali/scripts/bootstrap-manifest.json +++ b/kali/scripts/bootstrap-manifest.json @@ -156,6 +156,7 @@ "servicePort": 23816, "docsUrl": "https://github.com/Mouseww/anything-analyzer", "canAutoInstall": true, + "pinnedCommit": "0ed4791688e5186da051c85eb7ddfe4639e14fd2", "verificationMode": "service-or-registration" }, { diff --git a/kali/scripts/bootstrap-reverse.sh b/kali/scripts/bootstrap-reverse.sh index c8cf1f6..e6357e3 100644 --- a/kali/scripts/bootstrap-reverse.sh +++ b/kali/scripts/bootstrap-reverse.sh @@ -22,6 +22,7 @@ CAPABILITIES=() START_SERVICES=false SKIP_REFRESH=false MANUAL_REQUIRED=false +FAILED=false LAST_CAPABILITY_MANUAL=false for arg in "$@"; do @@ -630,11 +631,10 @@ start_anything_analyzer() { fi local repo_dir="$HOME/tools/anything-analyzer" - - if [[ ! -d "$repo_dir" ]]; then - log_info "克隆 anything-analyzer ..." - git clone https://github.com/Mouseww/anything-analyzer.git "$repo_dir" - fi + local repo commit + repo=$(manifest_field anything-analyzer repoUrl) + commit=$(manifest_field anything-analyzer pinnedCommit) + install_git_commit "$repo" "$commit" "$repo_dir" || return 1 if ! command -v pnpm &>/dev/null; then npm install -g pnpm @@ -681,6 +681,7 @@ for cap in "${CAPABILITIES[@]}"; do fi else RESULTS+=("{\"name\":\"$cap\",\"status\":\"failed\"}") + FAILED=true fi done @@ -691,7 +692,9 @@ if [[ "$SKIP_REFRESH" != "true" ]]; then fi final_exit_code=0 -if [[ "$MANUAL_REQUIRED" == "true" ]]; then +if [[ "$FAILED" == "true" ]]; then + final_exit_code=1 +elif [[ "$MANUAL_REQUIRED" == "true" ]]; then final_exit_code=2 fi diff --git a/skills/scripts/bootstrap-manifest.json b/skills/scripts/bootstrap-manifest.json index 6d7b8e5..6457dc0 100644 --- a/skills/scripts/bootstrap-manifest.json +++ b/skills/scripts/bootstrap-manifest.json @@ -127,6 +127,7 @@ "servicePort": 23816, "docsUrl": "https://github.com/Mouseww/anything-analyzer", "canAutoInstall": true, + "pinnedCommit": "0ed4791688e5186da051c85eb7ddfe4639e14fd2", "verificationMode": "service-or-registration" }, { diff --git a/skills/scripts/bootstrap-reverse.ps1 b/skills/scripts/bootstrap-reverse.ps1 index 32e0b60..59d0f2d 100644 --- a/skills/scripts/bootstrap-reverse.ps1 +++ b/skills/scripts/bootstrap-reverse.ps1 @@ -781,6 +781,13 @@ function Start-AnythingAnalyzerService { return } + $repoDir = [string]$Definition.installDir + $checkoutDefinition = [pscustomobject]@{ + repo = [string]$Definition.repoUrl + pinnedCommit = [string]$Definition.pinnedCommit + } + Ensure-GitCloneInstall -Definition $checkoutDefinition -TargetPath $repoDir | Out-Null + Ensure-Pnpm $vsBuildToolsError = '' if (Test-ReverseIsWindows) { @@ -793,26 +800,6 @@ if (Test-ReverseIsWindows) { } } - $repoDir = @($Definition.startupDirCandidates) | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1 - if ([string]::IsNullOrWhiteSpace($repoDir)) { - $installDir = $Definition.installDir - $gh = Get-FirstCommandPath -Names @('gh') - $git = Get-FirstCommandPath -Names @('git') - if ($gh) { - & $gh repo clone 'Mouseww/anything-analyzer' $installDir - } - elseif ($git) { - & $git clone $Definition.repoUrl $installDir - } - else { - throw 'Cannot clone anything-analyzer because neither gh nor git is available.' - } - if ($LASTEXITCODE -ne 0) { - throw 'Failed to clone anything-analyzer.' - } - $repoDir = $installDir - } - $pnpm = Get-NodeCommandPath -Name 'pnpm' if ([string]::IsNullOrWhiteSpace($pnpm)) { throw 'pnpm is not available after installation.' diff --git a/skills/scripts/bootstrap-reverse.sh b/skills/scripts/bootstrap-reverse.sh index 57ca7aa..74c1133 100644 --- a/skills/scripts/bootstrap-reverse.sh +++ b/skills/scripts/bootstrap-reverse.sh @@ -27,6 +27,7 @@ if [[ -z "$TOOLS_ROOT" || "$TOOLS_ROOT" == "/" || "$TOOLS_ROOT" == "$HOME" ]]; t exit 2 fi MCP_CONFIG_PATH="${CLAUDE_MCP_CONFIG:-$HOME/.claude/mcp.json}" +MANIFEST_PATH="$SCRIPT_DIR/bootstrap-manifest.json" UNAME_S="$(uname -s 2>/dev/null || echo unknown)" case "$UNAME_S" in @@ -39,6 +40,7 @@ START_SERVICES=false SKIP_REFRESH=false LIST_ONLY=false MANUAL_REQUIRED=false +FAILED=false LAST_CAPABILITY_MANUAL=false CAPABILITIES=() @@ -67,6 +69,26 @@ json_escape() { ensure_dir() { mkdir -p "$1"; } +manifest_field() { + local capability="$1" + local field="$2" + python3 - "$MANIFEST_PATH" "$capability" "$field" <<'PY' +import json, pathlib, sys +manifest = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding='utf-8')) +for capability in manifest.get('capabilities', []): + if capability.get('name') == sys.argv[2]: + value = capability.get(sys.argv[3]) + if value is None or value == '': + raise SystemExit(1) + if isinstance(value, (dict, list)): + print(json.dumps(value, separators=(',', ':'))) + else: + print(value) + raise SystemExit(0) +raise SystemExit(1) +PY +} + safe_remove_install_dir() { local target="$1" local tmp_target="${2:-}" @@ -351,6 +373,39 @@ install_github_release() { log_ok "installed $repo to $dest" } +install_git_commit() { + local repo="$1" + local commit="$2" + local install_dir="$3" + + if [[ -d "$install_dir/.git" ]]; then + local current + current=$(git -C "$install_dir" rev-parse HEAD 2>/dev/null || true) + if [[ "$current" != "$commit" ]]; then + log_err "Existing checkout is not at pinned commit $commit: $install_dir" + log_err "Move it aside explicitly, then retry; bootstrap will not overwrite local changes." + return 1 + fi + return 0 + fi + if [[ -e "$install_dir" ]]; then + log_err "Install path exists but is not a git checkout: $install_dir" + return 1 + fi + + ensure_dir "$(dirname "$install_dir")" + git init --quiet "$install_dir" + git -C "$install_dir" remote add origin "$repo" + git -C "$install_dir" fetch --depth 1 origin "$commit" + git -C "$install_dir" checkout --quiet --detach FETCH_HEAD + local resolved + resolved=$(git -C "$install_dir" rev-parse HEAD) + if [[ "$resolved" != "$commit" ]]; then + log_err "Pinned checkout verification failed for $repo: expected $commit, got $resolved" + return 1 + fi +} + write_mcp_server() { local name="$1" local json_payload="$2" @@ -444,12 +499,14 @@ ensure_jeb_pro() { ensure_jadx() { if has_cmd jadx; then log_ok "jadx ready: $(cmd_path jadx)"; return 0; fi ensure_java_runtime - local tag="v1.5.6" - local sha="545ea2be9c242511bc145755cf4bda2485ade42966e096f8b4d3da2a230e8974" - local re='^jadx-1\.5\.6\.zip$' + local repo re tag sha + repo=$(manifest_field jadx repo) + re=$(manifest_field jadx assetRegex) + tag=$(manifest_field jadx releaseTag) + sha=$(manifest_field jadx assetSha256) case "$PLATFORM" in - macos) install_brew jadx || install_github_release skylot/jadx "$re" "$TOOLS_ROOT/jadx" "$tag" "$sha" ;; - linux) install_github_release skylot/jadx "$re" "$TOOLS_ROOT/jadx" "$tag" "$sha" ;; + macos) install_brew jadx || install_github_release "$repo" "$re" "$TOOLS_ROOT/jadx" "$tag" "$sha" ;; + linux) install_github_release "$repo" "$re" "$TOOLS_ROOT/jadx" "$tag" "$sha" ;; esac } @@ -462,10 +519,12 @@ ensure_apktool() { if install_apt apktool; then return 0; fi ensure_dir "$TOOLS_ROOT/apktool" local meta url digest jar wrapper - local tag="v3.0.2" - local sha="eee4669a704a14e0623407e6701b0b91887e61e1e4049cb7a82833e14ae8b5fd" - local re='^apktool_3\.0\.2\.jar$' - meta=$(latest_github_asset_meta iBotPeaches/Apktool "$re" "$tag") + local repo tag sha re + repo=$(manifest_field apktool repo) + tag=$(manifest_field apktool releaseTag) + sha=$(manifest_field apktool assetSha256) + re=$(manifest_field apktool assetRegex) + meta=$(latest_github_asset_meta "$repo" "$re" "$tag") url=$(printf '%s' "$meta" | cut -f1) digest=$(printf '%s' "$meta" | cut -f2) jar="$TOOLS_ROOT/apktool/apktool.jar" @@ -482,40 +541,56 @@ ensure_apktool() { ensure_frida_tools() { ensure_python_runtime if has_cmd frida && has_cmd frida-ps; then log_ok "frida-tools ready"; return 0; fi - pipx install frida-tools || pipx upgrade frida-tools + local package + package=$(manifest_field frida pipPackage) + pipx install --force "$package" || return 1 export PATH="$HOME/.local/bin:$PATH" } ensure_idalib_mcp() { ensure_python_runtime if has_cmd ida-pro-mcp; then log_ok "ida-pro-mcp ready: $(cmd_path ida-pro-mcp)"; return 0; fi - pipx install 'git+https://github.com/mrexodia/ida-pro-mcp.git' || pipx upgrade ida-pro-mcp + local source + source=$(manifest_field idalib-mcp pipSource) + pipx install --force "$source" || return 1 export PATH="$HOME/.local/bin:$PATH" log_warn "Post-install: run 'ida-pro-mcp --install', choose Streamable HTTP + Global, then restart IDA Pro." } ensure_jshookmcp() { ensure_node_runtime - write_mcp_server "jshook" '{"command":"npx","args":["-y","@jshookmcp/jshook@0.3.4"],"env":{"JSHOOK_BASE_PROFILE":"search"}}' + local package + package=$(manifest_field jshookmcp npmPackage) + write_mcp_server "jshook" "$(python3 - "$package" <<'PY' +import json, sys +print(json.dumps({'command':'npx','args':['-y',sys.argv[1]],'env':{'JSHOOK_BASE_PROFILE':'search'}})) +PY +)" } ensure_reqable_mcp() { ensure_node_runtime - write_mcp_server "reqable-mcp" '{"command":"npx","args":["-y","reqable-mcp-server@1.0.1"]}' + local package + package=$(manifest_field reqable-mcp npmPackage) + write_mcp_server "reqable-mcp" "$(python3 - "$package" <<'PY' +import json, sys +print(json.dumps({'command':'npx','args':['-y',sys.argv[1]]})) +PY +)" log_warn "Reqable MCP requires the separately installed Reqable desktop application and its local API." } ensure_anything_analyzer() { + local dir="$TOOLS_ROOT/anything-analyzer" + local repo commit + repo=$(manifest_field anything-analyzer repoUrl) + commit=$(manifest_field anything-analyzer pinnedCommit) + if ! has_cmd git; then + case "$PLATFORM" in macos) install_brew git ;; linux) install_apt git ;; esac + fi + install_git_commit "$repo" "$commit" "$dir" || return 1 ensure_node_runtime ensure_pnpm - local dir="$TOOLS_ROOT/anything-analyzer" - if [[ ! -d "$dir/.git" ]]; then - if ! has_cmd git; then - case "$PLATFORM" in macos) install_brew git ;; linux) install_apt git ;; esac - fi - rm -rf "$dir" - git clone https://github.com/Mouseww/anything-analyzer "$dir" - fi write_mcp_server "anything-analyzer" '{"url":"http://localhost:23816/mcp"}' if $START_SERVICES; then (cd "$dir" && pnpm install && nohup pnpm dev >/tmp/anything-analyzer.log 2>&1 &) @@ -574,7 +649,9 @@ ensure_adb() { ensure_agent_browser() { ensure_node_runtime if has_cmd agent-browser; then log_ok "agent-browser ready"; return 0; fi - npm install -g agent-browser + local package + package=$(manifest_field agent-browser npmPackage) + npm install -g "$package" || return 1 if has_cmd npx; then npx playwright install chromium || true; fi local setup="$SKILL_ROOT/browser-automation/scripts/setup.sh" if [[ -x "$setup" ]]; then "$setup" --skip-browser-install || true; fi @@ -582,6 +659,9 @@ ensure_agent_browser() { ensure_ghidra_mcp() { ensure_java_runtime + local repo regex + repo=$(manifest_field ghidra-mcp repo) + regex=$(manifest_field ghidra-mcp assetRegex) case "$PLATFORM" in macos) if ! has_cmd ghidraRun && [[ ! -d /Applications/Ghidra.app ]]; then @@ -590,7 +670,7 @@ ensure_ghidra_mcp() { ;; linux) if ! has_cmd ghidraRun; then - install_github_release NationalSecurityAgency/ghidra '^ghidra_.*_PUBLIC_.*\.zip$' "$TOOLS_ROOT/ghidra" || \ + install_github_release "$repo" "$regex" "$TOOLS_ROOT/ghidra" || \ manual_required ghidra-mcp "Install Ghidra from GitHub release or Flatpak, then configure ghidra-mcp if used." fi ;; @@ -600,15 +680,26 @@ ensure_ghidra_mcp() { ensure_seclists() { local dir="$TOOLS_ROOT/SecLists" - if [[ -d "$dir/.git" || -d /usr/share/seclists ]]; then log_ok "SecLists ready"; return 0; fi + if [[ -d /usr/share/seclists ]]; then log_ok "SecLists ready"; return 0; fi if ! has_cmd git; then case "$PLATFORM" in macos) install_brew git ;; linux) install_apt git ;; esac; fi - git clone https://github.com/danielmiessler/SecLists "$dir" + local repo commit + repo=$(manifest_field seclists repo) + commit=$(manifest_field seclists pinnedCommit) + install_git_commit "$repo" "$commit" "$dir" || return 1 } ensure_proxycat() { ensure_python_runtime if has_cmd proxycat; then log_ok "proxycat ready"; return 0; fi - pipx install git+https://github.com/honmashironeko/ProxyCat.git || manual_required proxycat "Clone/install ProxyCat manually; verify command 'proxycat'." + local repo commit + repo=$(manifest_field proxycat repo) + commit=$(manifest_field proxycat pinnedCommit) + pipx install "git+${repo}@${commit}" || { + manual_required proxycat "Clone/install ProxyCat manually; verify command 'proxycat'." + LAST_CAPABILITY_MANUAL=true + MANUAL_REQUIRED=true + return 0 + } } ensure_burpsuite_mcp() { @@ -649,7 +740,10 @@ ensure_pentestswarm() { if ! has_cmd go; then case "$PLATFORM" in macos) install_brew go ;; linux) install_apt golang-go ;; esac fi - if go install github.com/Armur-Ai/Pentest-Swarm-AI/cmd/pentestswarm@v0.1.0; then + local go_package docker_image + go_package=$(manifest_field pentestswarm goPackage) + docker_image=$(manifest_field pentestswarm dockerImage) + if go install "$go_package"; then local go_bin go_bin="$(go env GOBIN 2>/dev/null || true)" if [[ -z "$go_bin" ]]; then @@ -663,8 +757,12 @@ ensure_pentestswarm() { log_warn "pentestswarm installed but no executable was found in GOBIN/GOPATH; trying Docker fallback" fi if has_cmd docker; then - write_mcp_server "pentestswarm" '{"command":"docker","args":["run","--rm","-i","ghcr.io/armur-ai/pentestswarm:v0.1.0","mcp","serve"]}' - log_warn "pentestswarm Go install failed or produced no runnable binary; registered Docker fallback ghcr.io/armur-ai/pentestswarm:v0.1.0" + write_mcp_server "pentestswarm" "$(python3 - "$docker_image" <<'PY' +import json, sys +print(json.dumps({'command':'docker','args':['run','--rm','-i',sys.argv[1],'mcp','serve']})) +PY +)" + log_warn "pentestswarm Go install failed or produced no runnable binary; registered Docker fallback $docker_image" else manual_required pentestswarm "Install Go 1.24+ or Docker, then install Pentest-Swarm-AI and ensure pentestswarm is on PATH." fi @@ -689,7 +787,9 @@ ensure_yara() { ensure_pwntools() { ensure_python_runtime if python3 -c "import pwn" 2>/dev/null; then log_ok "pwntools ready"; return 0; fi - pipx install pwntools || python3 -m pip install --user pwntools + local package + package=$(manifest_field pwntools pipPackage) + pipx install "$package" || python3 -m pip install --user "$package" || return 1 } status_json_line() { @@ -779,6 +879,7 @@ for cap in "${EXPANDED[@]}"; do fi else status_json_line "$cap" "failed" "see $(platform_doc)" >> "$RESULTS_FILE" + FAILED=true fi done @@ -787,7 +888,9 @@ if ! $SKIP_REFRESH; then fi FINAL_EXIT_CODE=0 -if $MANUAL_REQUIRED; then +if $FAILED; then + FINAL_EXIT_CODE=1 +elif $MANUAL_REQUIRED; then FINAL_EXIT_CODE=2 fi diff --git a/skills/scripts/test-bootstrap-manifest.sh b/skills/scripts/test-bootstrap-manifest.sh new file mode 100644 index 0000000..f2677b4 --- /dev/null +++ b/skills/scripts/test-bootstrap-manifest.sh @@ -0,0 +1,238 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BOOTSTRAP="$SCRIPT_DIR/bootstrap-reverse.sh" +MANIFEST="$SCRIPT_DIR/bootstrap-manifest.json" +KALI_BOOTSTRAP="$SCRIPT_DIR/../../kali/scripts/bootstrap-reverse.sh" +REAL_PYTHON="$(command -v python3)" +SCRATCH="$(mktemp -d /tmp/reverse-bootstrap-test-XXXXXX)" +trap 'rm -rf "$SCRATCH"' EXIT + +STUB_BIN="$SCRATCH/bin" +CALL_LOG="$SCRATCH/calls.log" +mkdir -p "$STUB_BIN" "$SCRATCH/home" "$SCRATCH/tools" + +cat > "$STUB_BIN/command-stub" <<'STUB' +#!/usr/bin/env bash +name="$(basename "$0")" +{ + printf '%s' "$name" + for arg in "$@"; do printf '|%s' "$arg"; done + printf '\n' +} >> "$CALL_LOG" + +if [[ "${STUB_FAIL_COMMAND:-}" == "$name" ]]; then + exit 1 +fi + +if [[ "$name" == "git" ]]; then + if [[ "${1:-}" == "init" ]]; then + target="${!#}" + mkdir -p "$target/.git" + printf '%s\n' 'unpinned-head' > "$target/.stub-head" + elif [[ "${1:-}" == "-C" && "${3:-}" == "fetch" ]]; then + printf '%s\n' "${7:-}" > "$2/.stub-fetch" + elif [[ "${1:-}" == "-C" && "${3:-}" == "checkout" ]]; then + cat "$2/.stub-fetch" > "$2/.stub-head" + elif [[ "${1:-}" == "-C" && "${3:-}" == "rev-parse" ]]; then + cat "$2/.stub-head" + fi +fi +exit 0 +STUB +chmod +x "$STUB_BIN/command-stub" +for command_name in git node npm npx pipx pnpm sleep; do + ln -s command-stub "$STUB_BIN/$command_name" +done + +cat > "$STUB_BIN/python3" <&2 + failures=$((failures + 1)) + fi +} + +: > "$CALL_LOG" +run_bootstrap frida --skip-refresh >/dev/null +frida_package="$(manifest_value frida pipPackage)" +check_log_line "pipx|install|--force|$frida_package" + +: > "$CALL_LOG" +run_bootstrap idalib-mcp --skip-refresh >/dev/null +idalib_source="$(manifest_value idalib-mcp pipSource)" +check_log_line "pipx|install|--force|$idalib_source" + +: > "$CALL_LOG" +set +e +run_bootstrap_with_failing_pipx idalib-mcp --skip-refresh >/dev/null 2>&1 +idalib_fail_exit=$? +set -e +if [[ "$idalib_fail_exit" -eq 0 ]]; then + printf 'idalib-mcp reported success after its pinned install failed\n' >&2 + failures=$((failures + 1)) +fi +check_log_line "pipx|install|--force|$idalib_source" +if [[ "$(wc -l < "$CALL_LOG" | tr -d ' ')" -ne 1 ]]; then + printf 'idalib-mcp attempted an unpinned fallback after pinned install failure\n' >&2 + failures=$((failures + 1)) +fi + +: > "$CALL_LOG" +run_bootstrap agent-browser --skip-refresh >/dev/null +agent_package="$(manifest_value agent-browser npmPackage)" +check_log_line "npm|install|-g|$agent_package" + +: > "$CALL_LOG" +run_bootstrap seclists --skip-refresh >/dev/null +seclists_repo="$(manifest_value seclists repo)" +seclists_pin="$(manifest_value seclists pinnedCommit)" +seclists_dir="$SCRATCH/tools/SecLists" +check_log_line "git|-C|$seclists_dir|remote|add|origin|$seclists_repo" +check_log_line "git|-C|$seclists_dir|fetch|--depth|1|origin|$seclists_pin" + +: > "$CALL_LOG" +run_bootstrap proxycat --skip-refresh >/dev/null +proxycat_repo="$(manifest_value proxycat repo)" +proxycat_pin="$(manifest_value proxycat pinnedCommit)" +check_log_line "pipx|install|git+${proxycat_repo}@${proxycat_pin}" + +: > "$CALL_LOG" +run_bootstrap pwntools --skip-refresh >/dev/null +pwntools_package="$(manifest_value pwntools pipPackage)" +check_log_line "pipx|install|$pwntools_package" + +: > "$CALL_LOG" +run_bootstrap anything-analyzer --start-services --skip-refresh >/dev/null +anything_repo="$(manifest_value anything-analyzer repoUrl)" +anything_pin="$(manifest_value anything-analyzer pinnedCommit)" +anything_dir="$SCRATCH/tools/anything-analyzer" +if [[ -z "$anything_pin" ]]; then + printf 'anything-analyzer is missing pinnedCommit in bootstrap-manifest.json\n' >&2 + failures=$((failures + 1)) +else + check_log_line "git|init|--quiet|$anything_dir" + check_log_line "git|-C|$anything_dir|remote|add|origin|$anything_repo" + check_log_line "git|-C|$anything_dir|fetch|--depth|1|origin|$anything_pin" + check_log_line "git|-C|$anything_dir|checkout|--quiet|--detach|FETCH_HEAD" + check_log_line "git|-C|$anything_dir|rev-parse|HEAD" +fi +check_log_line 'pnpm|install' +check_log_line 'pnpm|dev' + +if [[ -n "$anything_pin" ]]; then + checkout_line="$(grep -nF "git|-C|$anything_dir|checkout|--quiet|--detach|FETCH_HEAD" "$CALL_LOG" | cut -d: -f1 | head -n1)" + install_line="$(grep -nF 'pnpm|install' "$CALL_LOG" | cut -d: -f1 | head -n1)" + if [[ -z "$checkout_line" || -z "$install_line" || "$checkout_line" -ge "$install_line" ]]; then + printf 'anything-analyzer dependencies ran before the pinned checkout\n' >&2 + failures=$((failures + 1)) + fi +fi + +: > "$CALL_LOG" +mkdir -p "$anything_dir/.git" +printf '%s\n' 'different-commit' > "$anything_dir/.stub-head" +set +e +run_bootstrap anything-analyzer --start-services --skip-refresh >/dev/null 2>&1 +mismatch_exit=$? +set -e +if [[ "$mismatch_exit" -eq 0 ]]; then + printf 'anything-analyzer accepted an existing checkout at a different commit\n' >&2 + failures=$((failures + 1)) +fi +if grep -Eq '^pnpm\|(install|dev)$' "$CALL_LOG"; then + printf 'anything-analyzer ran dependencies from an unpinned existing checkout\n' >&2 + failures=$((failures + 1)) +fi + +if (( BASH_VERSINFO[0] >= 4 )); then + : > "$CALL_LOG" + kali_dir="$SCRATCH/home/tools/anything-analyzer" + rm -rf "$kali_dir" + set +e + run_kali_bootstrap anything-analyzer --start-services --skip-refresh >"$SCRATCH/kali-bootstrap.out" 2>&1 + set -e + check_log_line "git|init|-q|$kali_dir" + check_log_line "git|-C|$kali_dir|remote|add|origin|$anything_repo" + check_log_line "git|-C|$kali_dir|fetch|--depth|1|origin|$anything_pin" + check_log_line "git|-C|$kali_dir|checkout|-q|--detach|FETCH_HEAD" + check_log_line 'pnpm|install' + check_log_line 'pnpm|dev' + + : > "$CALL_LOG" + mkdir -p "$kali_dir/.git" + printf '%s\n' 'different-commit' > "$kali_dir/.stub-head" + set +e + run_kali_bootstrap anything-analyzer --start-services --skip-refresh >/dev/null 2>&1 + kali_mismatch_exit=$? + set -e + if [[ "$kali_mismatch_exit" -eq 0 ]]; then + printf 'Kali anything-analyzer accepted an existing checkout at a different commit\n' >&2 + failures=$((failures + 1)) + fi + if grep -Eq '^pnpm\|(install|dev)$' "$CALL_LOG"; then + printf 'Kali anything-analyzer ran dependencies from an unpinned existing checkout\n' >&2 + failures=$((failures + 1)) + fi +fi + +if [[ "$failures" -ne 0 ]]; then + if [[ -f "$SCRATCH/kali-bootstrap.out" ]]; then cat "$SCRATCH/kali-bootstrap.out" >&2; fi + printf '%s\n' 'captured argv:' >&2 + cat "$CALL_LOG" >&2 + exit 1 +fi + +printf '%s\n' 'bootstrap manifest source regression passed' diff --git a/skills/scripts/verify-routing-coherence.ps1 b/skills/scripts/verify-routing-coherence.ps1 index 54469fa..8788539 100644 --- a/skills/scripts/verify-routing-coherence.ps1 +++ b/skills/scripts/verify-routing-coherence.ps1 @@ -389,6 +389,7 @@ if (Test-Path -LiteralPath $kaliManifest) { # --- supply-chain pin gate: auto-install download sources MUST be pinned --- # 统一判定:pinnedVersion / pinnedCommit / pinPolicy 三选一; # github-release-* 额外接受 assetSha256 / preferApiDigest(GitHub 官方发布资产哈希)。 +# local-http-mcp 只有在不获取外部源码时才可免 pin。 $pinKinds = @('pip-package', 'npm-mcp', 'npm-global', 'go-install', 'git-clone') foreach ($mf in @($skillsManifest, $kaliManifest)) { if (-not (Test-Path -LiteralPath $mf)) { continue } @@ -401,7 +402,10 @@ foreach ($mf in @($skillsManifest, $kaliManifest)) { 'github-release-zip' { $hasPin = $hasPin -or $cap.assetSha256 -or $cap.preferApiDigest } 'github-release-jar-wrapper' { $hasPin = $hasPin -or $cap.assetSha256 } 'github-release-tar' { $hasPin = $hasPin -or $cap.assetSha256 -or $cap.preferApiDigest } - 'local-http-mcp' { $hasPin = $true } # 本地服务,不下载 + 'local-http-mcp' { + $fetchesExternalSource = $cap.repoUrl -or $cap.repo + $hasPin = (-not $fetchesExternalSource) -or $cap.pinnedCommit -or $cap.pinnedVersion + } 'winget-package' { $hasPin = $hasPin } # winget-latest 属于 pinPolicy 'apt-package' { $hasPin = $true } # 发行版仓库自带(Kali 侧) 'docker-image' { $hasPin = $true } # fallback 通道