fix(bootstrap): enforce manifest-pinned sources
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -23,6 +23,7 @@ Desktop.ini
|
||||
*.tmp
|
||||
*.bak
|
||||
*.log
|
||||
implementation-notes.md
|
||||
|
||||
# User-specific config
|
||||
.claude/
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
"servicePort": 23816,
|
||||
"docsUrl": "https://github.com/Mouseww/anything-analyzer",
|
||||
"canAutoInstall": true,
|
||||
"pinnedCommit": "0ed4791688e5186da051c85eb7ddfe4639e14fd2",
|
||||
"verificationMode": "service-or-registration"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
"servicePort": 23816,
|
||||
"docsUrl": "https://github.com/Mouseww/anything-analyzer",
|
||||
"canAutoInstall": true,
|
||||
"pinnedCommit": "0ed4791688e5186da051c85eb7ddfe4639e14fd2",
|
||||
"verificationMode": "service-or-registration"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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" <<STUB
|
||||
#!/usr/bin/env bash
|
||||
if [[ "\${1:-}" == "-" && "\${2:-}" == "23816" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\${1:-}" == "-c" && "\${2:-}" == "import pwn" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
exec "$REAL_PYTHON" "\$@"
|
||||
STUB
|
||||
chmod +x "$STUB_BIN/python3"
|
||||
|
||||
manifest_value() {
|
||||
"$REAL_PYTHON" - "$MANIFEST" "$1" "$2" <<'PY'
|
||||
import json, pathlib, sys
|
||||
manifest = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding='utf-8'))
|
||||
capability = next(item for item in manifest['capabilities'] if item['name'] == sys.argv[2])
|
||||
value = capability.get(sys.argv[3], '')
|
||||
print(value if isinstance(value, str) else json.dumps(value, separators=(',', ':')))
|
||||
PY
|
||||
}
|
||||
|
||||
run_bootstrap() {
|
||||
env \
|
||||
PATH="$STUB_BIN:/usr/bin:/bin" \
|
||||
HOME="$SCRATCH/home" \
|
||||
CALL_LOG="$CALL_LOG" \
|
||||
REVERSE_SKILL_TOOLS_DIR="$SCRATCH/tools" \
|
||||
CLAUDE_MCP_CONFIG="$SCRATCH/home/mcp.json" \
|
||||
bash "$BOOTSTRAP" "$@"
|
||||
}
|
||||
|
||||
run_bootstrap_with_failing_pipx() {
|
||||
env \
|
||||
PATH="$STUB_BIN:/usr/bin:/bin" \
|
||||
HOME="$SCRATCH/home" \
|
||||
CALL_LOG="$CALL_LOG" \
|
||||
STUB_FAIL_COMMAND=pipx \
|
||||
REVERSE_SKILL_TOOLS_DIR="$SCRATCH/tools" \
|
||||
CLAUDE_MCP_CONFIG="$SCRATCH/home/mcp.json" \
|
||||
bash "$BOOTSTRAP" "$@"
|
||||
}
|
||||
|
||||
run_kali_bootstrap() {
|
||||
env \
|
||||
PATH="$STUB_BIN:/opt/homebrew/bin:/usr/bin:/bin" \
|
||||
HOME="$SCRATCH/home" \
|
||||
CALL_LOG="$CALL_LOG" \
|
||||
bash "$KALI_BOOTSTRAP" "$@"
|
||||
}
|
||||
|
||||
failures=0
|
||||
check_log_line() {
|
||||
if ! grep -Fqx "$1" "$CALL_LOG"; then
|
||||
printf 'missing argv: %s\n' "$1" >&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'
|
||||
@@ -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 通道
|
||||
|
||||
Reference in New Issue
Block a user