104 lines
3.9 KiB
Bash
Executable File
104 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
"$ROOT/scripts/build_cpp.sh" >/dev/null
|
|
mkdir -p "$ROOT/reports"
|
|
MOCK_PORT=19481
|
|
MCPD_PORT=19480
|
|
"$ROOT/build/cpp/mock_mcp_server" --port "$MOCK_PORT" >"$ROOT/reports/security_mock.log" 2>&1 &
|
|
MOCK_PID=$!
|
|
"$ROOT/build/cpp/mcpd" --port "$MCPD_PORT" --upstream "http://127.0.0.1:$MOCK_PORT/rpc" >"$ROOT/reports/security_mcpd.log" 2>&1 &
|
|
MCPD_PID=$!
|
|
cleanup() {
|
|
kill "$MCPD_PID" "$MOCK_PID" >/dev/null 2>&1 || true
|
|
wait "$MCPD_PID" "$MOCK_PID" 2>/dev/null || true
|
|
rm -f "$ROOT/reports/security_large_payload.json"
|
|
}
|
|
trap cleanup EXIT
|
|
# Wait until mcpd has bound its HTTP port before invoking the client.
|
|
for _ in $(seq 1 50); do
|
|
if curl -fsS "http://127.0.0.1:$MCPD_PORT/healthz" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
|
|
CLIENT="$ROOT/build/cpp/oh_mcp_client"
|
|
ENDPOINT="http://127.0.0.1:$MCPD_PORT/rpc"
|
|
pass=0
|
|
fail=0
|
|
|
|
record() {
|
|
local name="$1" expected="$2" actual="$3"
|
|
if [[ "$actual" == "$expected" ]]; then
|
|
printf '[PASS] %-28s expected=%s actual=%s\n' "$name" "$expected" "$actual"
|
|
pass=$((pass+1))
|
|
else
|
|
printf '[FAIL] %-28s expected=%s actual=%s\n' "$name" "$expected" "$actual"
|
|
fail=$((fail+1))
|
|
fi
|
|
}
|
|
|
|
status_of_client() {
|
|
set +e
|
|
"$@" >/tmp/ohmcp_security_body.json 2>/tmp/ohmcp_security_err.log
|
|
local code=$?
|
|
set -e
|
|
if [[ "$code" == "0" ]]; then
|
|
echo 200
|
|
return
|
|
fi
|
|
if grep -q 'unauthorized agent' /tmp/ohmcp_security_body.json; then echo 401; return; fi
|
|
if grep -q 'tool not allowed' /tmp/ohmcp_security_body.json; then echo 403; return; fi
|
|
if grep -q 'replay detected' /tmp/ohmcp_security_body.json; then echo 409; return; fi
|
|
echo "exit-$code"
|
|
}
|
|
|
|
# 1. no token: pass empty token, mcpd should reject.
|
|
NO_TOKEN_STATUS=$(status_of_client "$CLIENT" --endpoint "$ENDPOINT" --token "" --raw call-tool amap.maps_weather city=苏州)
|
|
record "no token rejected" 401 "$NO_TOKEN_STATUS"
|
|
|
|
# 2. bad token.
|
|
BAD_TOKEN_STATUS=$(status_of_client "$CLIENT" --endpoint "$ENDPOINT" --token bad-token --raw call-tool amap.maps_weather city=苏州)
|
|
record "bad token rejected" 401 "$BAD_TOKEN_STATUS"
|
|
|
|
# 3. ACL: weather-token can only call weather, not hotel.
|
|
ACL_STATUS=$(status_of_client "$CLIENT" --endpoint "$ENDPOINT" --token weather-token --raw call-tool amap.hotel_search city=苏州)
|
|
record "acl denied" 403 "$ACL_STATUS"
|
|
|
|
# 4. nonce replay: first succeeds, second with same nonce is rejected.
|
|
FIRST_NONCE_STATUS=$(status_of_client "$CLIENT" --endpoint "$ENDPOINT" --token demo-token --nonce replay-001 --raw call-tool amap.maps_weather city=苏州)
|
|
SECOND_NONCE_STATUS=$(status_of_client "$CLIENT" --endpoint "$ENDPOINT" --token demo-token --nonce replay-001 --raw call-tool amap.maps_weather city=苏州)
|
|
record "nonce first accepted" 200 "$FIRST_NONCE_STATUS"
|
|
record "nonce replay rejected" 409 "$SECOND_NONCE_STATUS"
|
|
|
|
# 5. payload limit: send >256KB JSON body directly with curl.
|
|
python3 - <<PY
|
|
import json
|
|
payload={"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"amap.maps_weather","arguments":{"city":"苏州","blob":"x"*300000}}}
|
|
open('$ROOT/reports/security_large_payload.json','w').write(json.dumps(payload))
|
|
PY
|
|
PAYLOAD_STATUS=$(curl -s -o /tmp/ohmcp_large_body.json -w '%{http_code}' -H 'Content-Type: application/json' -H 'X-Agent-Token: demo-token' --data-binary "@$ROOT/reports/security_large_payload.json" "$ENDPOINT")
|
|
record "large payload rejected" 413 "$PAYLOAD_STATUS"
|
|
|
|
STATS=$(curl -s "http://127.0.0.1:$MCPD_PORT/stats")
|
|
printf '%s' "$STATS" > "$ROOT/reports/security_stats.json"
|
|
cat > "$ROOT/reports/security_summary.json" <<JSON
|
|
{
|
|
"pass": $pass,
|
|
"fail": $fail,
|
|
"tests": {
|
|
"no_token": "$NO_TOKEN_STATUS",
|
|
"bad_token": "$BAD_TOKEN_STATUS",
|
|
"acl_denied": "$ACL_STATUS",
|
|
"nonce_first": "$FIRST_NONCE_STATUS",
|
|
"nonce_replay": "$SECOND_NONCE_STATUS",
|
|
"large_payload": "$PAYLOAD_STATUS"
|
|
}
|
|
}
|
|
JSON
|
|
printf 'summary: pass=%s fail=%s report=%s\n' "$pass" "$fail" "$ROOT/reports/security_summary.json"
|
|
if [[ "$fail" != "0" ]]; then
|
|
exit 1
|
|
fi
|