* feat: add public-source threat intelligence * fix(routing): assign threat-intel to R44, not ADF R42 Keep analysis-decision-framework R42 as experimental YARA. Public-source IOC / OSINT route is R44. Restore 5 benchmark cases. --------- Co-authored-by: kriptoburak <kriptoburak@users.noreply.github.com>
420 lines
17 KiB
Bash
420 lines
17 KiB
Bash
#!/usr/bin/env bash
|
||
# refresh-tool-index.sh — generic Linux/macOS tool index refresh
|
||
#
|
||
# This script is intentionally detection-only. It does not install tools.
|
||
# Output defaults:
|
||
# skills/tool-index.md
|
||
# skills/tool-index.json
|
||
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
SKILL_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||
REPO_ROOT="$(cd "$SKILL_ROOT/.." && pwd)"
|
||
|
||
OUTPUT_MD="${1:-${SKILL_ROOT}/tool-index.md}"
|
||
OUTPUT_JSON="${2:-${SKILL_ROOT}/tool-index.json}"
|
||
GENERATED_AT="$(date '+%Y-%m-%d %H:%M:%S %z')"
|
||
UNAME_S="$(uname -s 2>/dev/null || echo unknown)"
|
||
UNAME_R="$(uname -r 2>/dev/null || echo unknown)"
|
||
|
||
case "$UNAME_S" in
|
||
Darwin) PLATFORM="macos" ;;
|
||
Linux) PLATFORM="linux" ;;
|
||
*) PLATFORM="unknown" ;;
|
||
esac
|
||
|
||
has_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||
cmd_path() { command -v "$1" 2>/dev/null || true; }
|
||
|
||
run_version() {
|
||
local cmd="$1"; shift
|
||
if ! has_cmd "$cmd"; then
|
||
echo ""
|
||
return 0
|
||
fi
|
||
"$cmd" "$@" 2>&1 | head -n 1 | tr '\n' ' ' | sed 's/[[:space:]]*$//' || true
|
||
}
|
||
|
||
file_exists_any() {
|
||
local p
|
||
for p in "$@"; do
|
||
if [[ -e "$p" ]]; then
|
||
echo "$p"
|
||
return 0
|
||
fi
|
||
done
|
||
echo ""
|
||
}
|
||
|
||
brew_prefix() {
|
||
if has_cmd brew; then
|
||
brew --prefix 2>/dev/null || true
|
||
fi
|
||
}
|
||
|
||
install_hint() {
|
||
local name="$1"
|
||
case "$PLATFORM:$name" in
|
||
linux:java) echo "apt: sudo apt install openjdk-17-jdk" ;;
|
||
linux:node) echo "apt/nvm: sudo apt install nodejs npm; prefer NodeSource or nvm for newer Node" ;;
|
||
linux:python3) echo "apt: sudo apt install python3 python3-venv python3-pip pipx" ;;
|
||
linux:jadx) echo "GitHub release: download jadx ZIP to ~/tools/jadx" ;;
|
||
linux:apktool) echo "apt or jar: sudo apt install apktool; or official apktool.jar" ;;
|
||
linux:adb) echo "apt or Android platform-tools: sudo apt install adb" ;;
|
||
linux:frida) echo "pipx: pipx install frida-tools" ;;
|
||
linux:r2|linux:radare2) echo "GitHub/source preferred; apt if available" ;;
|
||
linux:ghidra) echo "GitHub release ZIP or Flatpak; Java required" ;;
|
||
linux:burpsuite) echo "manual installer/jar; then load burp-mcp-full jar" ;;
|
||
linux:jshookmcp) echo "npx: npx -y @jshookmcp/jshook@0.3.4" ;;
|
||
linux:reqable-mcp) echo "npx: npx -y reqable-mcp-server@1.0.1; install Reqable desktop separately" ;;
|
||
linux:jeb-pro) echo "manual licensed install: https://www.pnfsoftware.com/jeb/" ;;
|
||
linux:anything-analyzer) echo "git clone + pnpm install + pnpm dev" ;;
|
||
|
||
linux:nuclei) echo "GitHub release or go install; apt may be unavailable" ;;
|
||
linux:seclists) echo "git clone https://github.com/danielmiessler/SecLists ~/tools/SecLists" ;;
|
||
|
||
macos:java) echo "brew: brew install openjdk" ;;
|
||
macos:node) echo "brew/nvm: brew install node; or nvm" ;;
|
||
macos:python3) echo "brew: brew install python; then pipx/venv" ;;
|
||
macos:jadx) echo "brew: brew install jadx" ;;
|
||
macos:apktool) echo "brew: brew install apktool" ;;
|
||
macos:adb) echo "brew: brew install android-platform-tools" ;;
|
||
macos:frida) echo "pipx: pipx install frida-tools" ;;
|
||
macos:r2|macos:radare2) echo "brew: brew install radare2" ;;
|
||
macos:ghidra) echo "brew: brew install ghidra or brew install --cask ghidra" ;;
|
||
macos:burpsuite) echo "brew cask/manual: brew install --cask burp-suite" ;;
|
||
macos:jshookmcp) echo "npx: npx -y @jshookmcp/jshook@0.3.4" ;;
|
||
macos:reqable-mcp) echo "npx: npx -y reqable-mcp-server@1.0.1; install Reqable desktop separately" ;;
|
||
macos:jeb-pro) echo "manual licensed install: https://www.pnfsoftware.com/jeb/" ;;
|
||
macos:anything-analyzer) echo "git clone + corepack enable + pnpm install + pnpm dev" ;;
|
||
macos:nuclei) echo "brew: brew install nuclei" ;;
|
||
macos:seclists) echo "git clone https://github.com/danielmiessler/SecLists ~/tools/SecLists" ;;
|
||
|
||
linux:binwalk) echo "apt: sudo apt install binwalk" ;;
|
||
linux:yara) echo "apt: sudo apt install yara" ;;
|
||
linux:pwntools) echo "pipx: pipx install pwntools" ;;
|
||
|
||
macos:binwalk) echo "brew: brew install binwalk" ;;
|
||
macos:yara) echo "brew: brew install yara" ;;
|
||
macos:pwntools) echo "pipx: pipx install pwntools" ;;
|
||
linux:xquik-mcp|macos:xquik-mcp) echo "remote MCP: register https://xquik.com/mcp in the selected host, then complete OAuth" ;;
|
||
*) echo "see PLATFORMS.md and docs/platforms/${PLATFORM}.md" ;;
|
||
esac
|
||
}
|
||
|
||
# name|skill|purpose|commands|version command args|extra path probes
|
||
TOOLS=(
|
||
"java|core-runtime|Java runtime for jadx/apktool/Burp/Ghidra|java|java -version|"
|
||
"python3|core-runtime|Python runtime for helper scripts and pipx tools|python3|python3 --version|"
|
||
"pipx|core-runtime|Isolated Python CLI installer|pipx|pipx --version|"
|
||
"node|core-runtime|Node.js runtime for MCP bridges|node|node --version|"
|
||
"npm|core-runtime|Node package manager|npm|npm --version|"
|
||
"npx|core-runtime|Run npm MCP packages|npx|npx --version|"
|
||
"jadx|apk-reverse|APK Java/Kotlin decompiler|jadx|jadx --version|$HOME/tools/jadx/bin/jadx"
|
||
"apktool|apk-reverse|APK decode and rebuild|apktool|apktool --version|"
|
||
"adb|apk-reverse|Android device bridge|adb|adb version|$HOME/tools/android-platform-tools/platform-tools/adb"
|
||
"frida|reverse-engineering|Dynamic instrumentation CLI|frida|frida --version|$HOME/.local/bin/frida"
|
||
"frida-ps|reverse-engineering|Frida process listing|frida-ps|frida-ps --version|$HOME/.local/bin/frida-ps"
|
||
"r2|radare2|radare2 CLI analysis|r2|r2 -v|"
|
||
"rabin2|radare2|Binary metadata extraction|rabin2|rabin2 -v|"
|
||
"ghidra|reverse-engineering|Ghidra reverse-engineering suite|ghidraRun|ghidraRun --version|$HOME/tools/ghidra/ghidraRun;/Applications/Ghidra.app"
|
||
"idapro|ida-reverse|IDA Pro commercial reverse-engineering suite|idat|idat -v|/opt/idapro/idat;/Applications/IDA Professional.app;/Applications/IDA Free.app"
|
||
"burpsuite|burp-mcp|BurpSuite desktop application|burpsuite|burpsuite --version|/Applications/Burp Suite Professional.app;/Applications/Burp Suite Community Edition.app"
|
||
"graphviz|diagram-generator|Graphviz diagram rendering|dot|dot -V|"
|
||
"plantuml|diagram-generator|PlantUML diagram rendering|plantuml|plantuml -version|"
|
||
"nmap|pentest-tools|Network scanner|nmap|nmap --version|"
|
||
"sqlmap|pentest-tools|SQL injection testing tool|sqlmap|sqlmap --version|"
|
||
"ffuf|pentest-tools|Web fuzzer|ffuf|ffuf -V|"
|
||
"hashcat|pentest-tools|Password recovery|hashcat|hashcat --version|"
|
||
"nuclei|pentest-tools|Template-based vulnerability scanner|nuclei|nuclei -version|"
|
||
"binwalk|firmware-pentest|Firmware extraction and analysis|binwalk|binwalk --version|"
|
||
"seclists|pentest-tools|Security wordlists|none|none|$HOME/tools/SecLists;/usr/share/seclists"
|
||
"jshookmcp|js-reverse|JS/CDP/Hook MCP capability (requires registration + npx runtime)|none|none|"
|
||
"reqable-mcp|pentest-tools|Reqable MCP capability (requires registration + npx runtime)|none|none|"
|
||
"xquik-mcp|threat-intelligence|Remote public X threat-intelligence MCP (requires registration + OAuth)|none|none|"
|
||
"jeb-pro|apk-reverse|Commercial Android/ARM decompiler (manual licensed install)|jeb,jeb_wincon|jeb --version|$HOME/tools/JEB/jeb;$HOME/JEB/jeb;/opt/jeb/jeb"
|
||
"anything-analyzer|browser-automation|Browser/HTTP analyzer MCP project|none|none|$HOME/tools/anything-analyzer;$REPO_ROOT/../anything-analyzer"
|
||
"burp-mcp-full|burp-mcp|Local Burp MCP extension and stdio bridge|none|none|$REPO_ROOT/burp-mcp-full/mcp-bridge.js"
|
||
"yara|malware-analysis|Malware rule matching engine|yara|yara --version|"
|
||
"pwntools|reverse-engineering|CTF pwn exploit development framework|pwn|pwn --version|"
|
||
)
|
||
|
||
records_tmp="$(mktemp)"
|
||
trap 'rm -f "$records_tmp"' EXIT
|
||
|
||
{
|
||
echo "# Tool Index"
|
||
echo ""
|
||
echo "- Generated at: $GENERATED_AT"
|
||
echo "- Platform: $PLATFORM ($UNAME_S $UNAME_R)"
|
||
echo "- Script: \`skills/scripts/refresh-tool-index.sh\`"
|
||
echo "- Note: This script detects tools only. It does not install tools."
|
||
echo ""
|
||
echo "| Tool | Skill | Purpose | Available | Path | Version | Source | Install hint |"
|
||
echo "|---|---|---|---|---|---|---|---|"
|
||
} > "$OUTPUT_MD"
|
||
|
||
for entry in "${TOOLS[@]}"; do
|
||
IFS='|' read -r name skill purpose commands version_spec probes <<< "$entry"
|
||
|
||
available="no"
|
||
path=""
|
||
version=""
|
||
source=""
|
||
|
||
if [[ "$commands" != "none" ]]; then
|
||
IFS=',' read -ra cmd_list <<< "$commands"
|
||
for cmd in "${cmd_list[@]}"; do
|
||
if has_cmd "$cmd"; then
|
||
available="yes"
|
||
path="$(cmd_path "$cmd")"
|
||
source="command"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
if [[ "$available" == "no" && -n "${probes:-}" ]]; then
|
||
IFS=';' read -ra probe_list <<< "$probes"
|
||
for probe in "${probe_list[@]}"; do
|
||
if [[ -e "$probe" ]]; then
|
||
available="yes"
|
||
path="$probe"
|
||
source="path-probe"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
if [[ "$version_spec" != "none" ]]; then
|
||
read -r ver_cmd ver_arg1 ver_arg2 <<< "$version_spec"
|
||
if has_cmd "$ver_cmd"; then
|
||
version="$(run_version "$ver_cmd" "${ver_arg1:-}" "${ver_arg2:-}")"
|
||
fi
|
||
fi
|
||
|
||
[[ -z "$path" ]] && path="—"
|
||
[[ -z "$version" ]] && version="—"
|
||
[[ -z "$source" ]] && source="—"
|
||
hint="$(install_hint "$name")"
|
||
|
||
echo "| $name | $skill | $purpose | $available | $path | $version | $source | $hint |" >> "$OUTPUT_MD"
|
||
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$name" "$skill" "$purpose" "$available" "$path" "$version" "$source" "$hint" >> "$records_tmp"
|
||
done
|
||
|
||
{
|
||
echo ""
|
||
echo "---"
|
||
echo ""
|
||
echo "## Next steps"
|
||
echo ""
|
||
case "$PLATFORM" in
|
||
linux)
|
||
echo "- Read \`docs/platforms/linux.md\` for ordinary Linux setup."
|
||
echo "- If the host is Kali, read \`kali/README-kali.md\` instead."
|
||
;;
|
||
macos)
|
||
echo "- Read \`docs/platforms/macos.md\` for Homebrew and app-bundle setup."
|
||
;;
|
||
*)
|
||
echo "- Read \`PLATFORMS.md\` and choose the closest platform guide."
|
||
;;
|
||
esac
|
||
echo "- Register MCP servers in your Agent client; tool availability does not imply MCP registration."
|
||
} >> "$OUTPUT_MD"
|
||
|
||
# --- Capability status view -------------------------------------------------
|
||
# Parity with skills/scripts/refresh-tool-index.ps1. Registration is discovered
|
||
# across supported host adapters rather than assuming one default AI client.
|
||
|
||
MANIFEST_PATH="$SCRIPT_DIR/bootstrap-manifest.json"
|
||
CLAUDE_MCP_CONFIG_PATH_FOR_CAP="${CLAUDE_MCP_CONFIG:-$HOME/.claude/mcp.json}"
|
||
CODEX_MCP_CONFIG_PATH_FOR_CAP="${CODEX_CONFIG_PATH:-$HOME/.codex/config.toml}"
|
||
CAP_RECORDS_TMP="$(mktemp)"
|
||
trap 'rm -f "$records_tmp" "$CAP_RECORDS_TMP"' EXIT
|
||
|
||
if [[ -f "$MANIFEST_PATH" ]]; then
|
||
python3 - "$MANIFEST_PATH" "$CLAUDE_MCP_CONFIG_PATH_FOR_CAP" "$CODEX_MCP_CONFIG_PATH_FOR_CAP" "$records_tmp" "$CAP_RECORDS_TMP" <<'PY'
|
||
import json, pathlib, re, socket, sys, urllib.request
|
||
|
||
manifest_path, claude_config_path, codex_config_path, tool_records_path, out_path = sys.argv[1:6]
|
||
|
||
try:
|
||
manifest = json.loads(pathlib.Path(manifest_path).read_text(encoding='utf-8'))
|
||
except Exception:
|
||
manifest = {'capabilities': []}
|
||
capabilities = manifest.get('capabilities', [])
|
||
|
||
registered_names = set()
|
||
try:
|
||
mcp_data = json.loads(pathlib.Path(claude_config_path).read_text(encoding='utf-8'))
|
||
registered_names.update(mcp_data.get('mcpServers', {}).keys())
|
||
except Exception:
|
||
pass
|
||
try:
|
||
codex_text = pathlib.Path(codex_config_path).read_text(encoding='utf-8')
|
||
pattern = re.compile(r'^\s*\[mcp_servers\.([^\].]+)\]\s*$', re.MULTILINE)
|
||
registered_names.update(pattern.findall(codex_text))
|
||
except Exception:
|
||
pass
|
||
|
||
tool_available = {}
|
||
try:
|
||
with open(tool_records_path, encoding='utf-8') as f:
|
||
for line in f:
|
||
parts = line.rstrip('\n').split('\t')
|
||
if len(parts) >= 4:
|
||
tool_available[parts[0]] = (parts[3] == 'yes')
|
||
except Exception:
|
||
pass
|
||
|
||
def tcp_open(port, timeout=1.0):
|
||
try:
|
||
s = socket.socket(); s.settimeout(timeout)
|
||
s.connect(('127.0.0.1', int(port))); s.close()
|
||
return True
|
||
except Exception:
|
||
return False
|
||
|
||
def mcp_http_handshake(port, timeout=3):
|
||
try:
|
||
body = json.dumps({"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}).encode()
|
||
req = urllib.request.Request(
|
||
f"http://127.0.0.1:{int(port)}/mcp",
|
||
data=body,
|
||
headers={"Content-Type": "application/json"},
|
||
method="POST",
|
||
)
|
||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||
return resp.status == 200
|
||
except Exception:
|
||
return False
|
||
|
||
rows = []
|
||
for cap in capabilities:
|
||
name = cap.get('name')
|
||
if not name:
|
||
continue
|
||
bootstrap_kind = cap.get('bootstrapKind', '')
|
||
can_auto_install = bool(cap.get('canAutoInstall', False))
|
||
mcp_names = cap.get('mcpNames') or []
|
||
service_port = cap.get('servicePort')
|
||
verification_mode = cap.get('verificationMode', '')
|
||
|
||
registered = any(n in registered_names for n in mcp_names) if mcp_names else False
|
||
service_online = False
|
||
mcp_http_verified = False
|
||
if service_port:
|
||
service_online = tcp_open(service_port)
|
||
if service_online:
|
||
mcp_http_verified = mcp_http_handshake(service_port)
|
||
|
||
tool_ready = bool(tool_available.get(name, False))
|
||
runtime_ready = bool(tool_available.get('npx', False)) if bootstrap_kind == 'npm-mcp' else tool_ready
|
||
|
||
if mcp_names:
|
||
if verification_mode == 'registration-only':
|
||
ready = registered
|
||
elif verification_mode == 'service-and-registration':
|
||
ready = registered and service_online
|
||
elif verification_mode == 'service-or-registration':
|
||
ready = registered or service_online
|
||
elif bootstrap_kind == 'npm-mcp':
|
||
ready = registered and runtime_ready
|
||
else:
|
||
ready = registered or tool_ready
|
||
else:
|
||
ready = tool_ready
|
||
|
||
rows.append({
|
||
'name': name,
|
||
'tool_available': tool_ready,
|
||
'runtime_available': runtime_ready,
|
||
'ready': ready,
|
||
'mcp_registered': registered if mcp_names else None,
|
||
'service_online': service_online if service_port else None,
|
||
'mcp_http_verified': mcp_http_verified if service_port else None,
|
||
'can_auto_install': can_auto_install,
|
||
'bootstrap_kind': bootstrap_kind,
|
||
})
|
||
|
||
with open(out_path, 'w', encoding='utf-8') as f:
|
||
json.dump(rows, f, ensure_ascii=False)
|
||
PY
|
||
|
||
{
|
||
echo ""
|
||
echo "---"
|
||
echo ""
|
||
echo "## 能力状态视图 (Capability Status)"
|
||
echo ""
|
||
echo "| 能力 | 工具可用 | Ready | MCP 已注册 | 服务在线 | MCP HTTP | 可自动安装 | 安装方式 |"
|
||
echo "|------|---------|-------|-----------|---------|----------|-----------|---------|"
|
||
} >> "$OUTPUT_MD"
|
||
|
||
python3 - "$CAP_RECORDS_TMP" "$OUTPUT_MD" <<'PY'
|
||
import json, sys
|
||
rows = json.loads(open(sys.argv[1], encoding='utf-8').read())
|
||
def yn(v):
|
||
return '✓' if v else '✗'
|
||
def opt(v):
|
||
if v is True: return '✓'
|
||
if v is False: return '—'
|
||
return '—'
|
||
with open(sys.argv[2], 'a', encoding='utf-8') as out:
|
||
for r in rows:
|
||
out.write(
|
||
f"| {r['name']} | {yn(r['tool_available'])} | {yn(r['ready'])} | "
|
||
f"{opt(r['mcp_registered'])} | {opt(r['service_online'])} | "
|
||
f"{opt(r['mcp_http_verified'])} | {yn(r['can_auto_install'])} | "
|
||
f"{r['bootstrap_kind'] or '—'} |\n"
|
||
)
|
||
out.write("\n> ✓ = 是 | ✗ = 否 | — = 不适用或未检测。npm-mcp 的 Ready 使用 MCP 注册状态 + npx runtime;npx 本身不会让某个 MCP capability 变成工具可用。\n\n")
|
||
PY
|
||
else
|
||
CAP_RECORDS_TMP=""
|
||
fi
|
||
# --- end capability status view ---------------------------------------------
|
||
|
||
python3 - "$records_tmp" "$OUTPUT_JSON" "$GENERATED_AT" "$PLATFORM" "$UNAME_S $UNAME_R" "${CAP_RECORDS_TMP:-}" <<'PY'
|
||
import json, sys
|
||
records_path, output_json, generated_at, platform, uname, cap_records_path = sys.argv[1:7]
|
||
tools=[]
|
||
with open(records_path, encoding='utf-8') as f:
|
||
for line in f:
|
||
name, skill, purpose, available, path, version, source, hint = line.rstrip('\n').split('\t')
|
||
tools.append({
|
||
'name': name,
|
||
'skill': skill,
|
||
'purpose': purpose,
|
||
'available': available == 'yes',
|
||
'path': None if path == '—' else path,
|
||
'version': None if version == '—' else version,
|
||
'source': None if source == '—' else source,
|
||
'install_hint': hint,
|
||
})
|
||
capabilities = []
|
||
if cap_records_path:
|
||
try:
|
||
with open(cap_records_path, encoding='utf-8') as f:
|
||
capabilities = json.load(f)
|
||
except Exception:
|
||
capabilities = []
|
||
with open(output_json, 'w', encoding='utf-8') as f:
|
||
json.dump({
|
||
'generated_at': generated_at,
|
||
'platform': platform,
|
||
'uname': uname,
|
||
'script': 'skills/scripts/refresh-tool-index.sh',
|
||
'tools': tools,
|
||
'capabilities': capabilities,
|
||
}, f, ensure_ascii=False, indent=2)
|
||
PY
|
||
|
||
echo "✅ Tool index refreshed"
|
||
echo " markdown=$OUTPUT_MD"
|
||
echo " json=$OUTPUT_JSON"
|
||
echo " platform=$PLATFORM"
|
||
echo " tools=${#TOOLS[@]}"
|