Haiku 5.5
Native agent · Needs credentials · full use untestedOperation / attempted scope limited checks
Agent-reported scope: Run the whatsapp-mcp server and have an MCP client read WhatsApp messages through its tools
Run details
- run
- finereli--whatsapp-mcp-claude-haiku-5-5-1792785224818035339
- date
- Oct 23, 2026
- time to working
- not recorded
- product hosting / execution
- Not recorded
- recorded operation scope
- limited checks
- model
- claude-haiku-5-5
- agent / version
- claude-code / 2.1.300 (Claude Code)
- platform
- linux
- reported cost
- $0.030159
- Recorded source
- https://github.com/finereli/whatsapp-mcp
- Backend
- hyperv
- Tool transport
- mcp-assigned-vm
- Requested source pin (not an observed revision)
- {"commit": "64f1b3017931e4f6fe33eec4f95999a10f839b99", "url": "https://github.com/finereli/whatsapp-mcp"}
- Test version
- readme-bdc1b9b3c0d2673aef87e076d14084a8123ddcf09d520c1ee2e364a37c3f4bb9
- revision
- 64f1b3017931e4f6fe33eec4f95999a10f839b99
Prerequisite: full use remains untested. This is not a repo defect.
No Cloudflare account and no phone for the WhatsApp QR link in this disposable guest.
Step: Deploy to Cloudflare and link WhatsApp (README 'Set it up', steps 1-3 and 'Link WhatsApp')
No Cloudflare account and no phone for the WhatsApp QR link in this disposable guest.
Safety: Review signals: 1 connection review, 6 known vulnerability.
Review signals: 1 connection review, 6 known vulnerability.
Reviewed as expected for a test run: resolver address lookups that send no data.
Repository processes attempted these non-loopback connections; proxies can hide the final hostname. 104.18.2.57:443 3 attributed processes.
OSV reports a known vulnerability in this dependency version. 2 distinct locations. 6 findings.
OSV scanned supported lockfiles; unpinned dependencies and unsupported formats are outside this check.
A short run cannot establish that code is safe.
Connections show IP addresses; proxies can hide the final hostname.
Tracing covers dispatched commands and their children, not the whole OS; guest root can interfere with tracing.
Install / setup / connection checks
[
{
"status": "PASS",
"details": {
"working": false,
"kind": "mcp_http",
"url": "http://127.0.0.1:8787/mcp",
"hosting": "local",
"transport": "streamable-http",
"scope": "local MCP initialize + tools/list",
"argv": [
"npm",
"run",
"dev",
"--",
"--port",
"8787",
"--ip",
"127.0.0.1"
],
"needs_credentials": true,
"credentials_error": "Remote MCP authentication required (HTTP 401)"
},
"check": "credentials_error",
"optional": false
}
]Answers recorded for this run
- The server is read-only and cannot send WhatsApp messages: Needs an OAuth token from /authorize, which only completes after a WhatsApp phone is linked (src/index.ts proceed(): token is returned only when isPaired()). No Cloudflare account and no phone in this guest.
Source: It can't send anything.
Input: none (no send tool in the MCP tool list; no authenticated call possible here)
Expected: cannot be checked without an authenticated MCP session
Observed: Needs an OAuth token from /authorize, which only completes after a WhatsApp phone is linked (src/index.ts proceed(): token is returned only when isPaired()). No Cloudflare account and no phone in this guest.
- Setup needs a Cloudflare Workers Paid account, Node 20+, and a phone: Prerequisites missing in this disposable guest: no Cloudflare account, no Workers Paid plan, no phone to scan the WhatsApp QR code. Node 22 was installed and npm install succeeded; the deploy step (npm run deploy / wrangler deploy) was not run.
Source: You need a Cloudflare account on the Workers Paid plan (see the note below), Node 20 or later, and your phone.
Input: none
Expected: deploy and link with a real account and phone
Observed: Prerequisites missing in this disposable guest: no Cloudflare account, no Workers Paid plan, no phone to scan the WhatsApp QR code. Node 22 was installed and npm install succeeded; the deploy step (npm run deploy / wrangler deploy) was not run.
- Read tools top up from WhatsApp when the stored copy is older than ten minutes: Needs a linked WhatsApp account and an authenticated MCP session; both require a phone scan and a Cloudflare deployment, neither available here.
Source: Every read tool first tops up from WhatsApp if the stored copy is more than ten minutes old.
Input: none
Expected: a read tool returns messages after a pull
Observed: Needs a linked WhatsApp account and an authenticated MCP session; both require a phone scan and a Cloudflare deployment, neither available here.
Examples
- The repo's own offline test suite passes: command completed successfully
Source: package.json scripts.test
Input: none
Expected: all tests pass
Command: npm test
Observed: comes first ok last thing THEY said, not me ok unnamed count ok save reports saved and which have chats ok named chat leaves the unnamed list ok new name shows in chat list ok sender name used in messages ok empty name removes it ok group message sender resolves to custom name ok toJid("+972 52-421-9320", undefined) ok toJid("052-421-9320", 972) ok toJid("052-421-9320", undefined) ok toJid("00972524219320", undefined) ok toJid("524219320", 972) ok toJid("<email>", undefined) ok toJid("abc", 972) ok toJid("+1 (818) 917-7834", 972) all passed
Cycles
- Daily pull of WhatsApp messages at 04:00 UTC: Missing prerequisite: the account must be linked to WhatsApp before the cron pulls anything; no Cloudflare deployment and no phone here.
Source: wrangler.jsonc
Input: none
Expected: a daily pull of stored messages
Observed: Missing prerequisite: the account must be linked to WhatsApp before the cron pulls anything; no Cloudflare deployment and no phone here.
Local or hosted: both
Suggested next step (agent-reported): Document a local development path in the README (npm run dev plus a .dev.vars password) and state clearly that the MCP token is only issued after the WhatsApp phone link is complete. Otherwise the local run can't reach any tool.
What the agent did
Run the whatsapp-mcp server and have an MCP client read WhatsApp messages through its tools
cd <guest-path> && ls -la && cat README.md | head -200 && id -u && python3 --version && git --version; ls /inputs 2>/dev/null; ls <guest-path> 2>/dev/null
cd <guest-path> && cat package.json && cat wrangler.jsonc && ls .coldpass scripts src test local-export && node --version; npm --version; which uv; cat .gitignore; ls -la .coldpass
(sudo -n apt-get update -qq && sudo -n DEBIAN_FRONTEND=noninteractive apt-get install -y -qq nodejs npm) 2>&1 | tail -5; node --version; npm --version
cd <guest-path> && export NODE_USE_ENV_PROXY=1 && npm install 2>&1 | tail -20; echo EXIT=$?
sudo -n npm i -g n 2>&1 | tail -2 && sudo -n n 22 2>&1 | tail -2; hash -r; which node; node --version; npm --version
cd <guest-path> && rm -rf node_modules && export NODE_USE_ENV_PROXY=1 && npm install 2>&1 | tail -15; echo EXIT=${PIPESTATUS[0]}; ls vendor 2>/dev/null
cd <guest-path> && npm test 2>&1 | tail -25; echo EXIT=${PIPESTATUS[0]}; npm run typecheck 2>&1 | tail -10; echo TC=${PIPESTATUS[0]}
cd <guest-path> && (timeout 90 npx wrangler dev --port 8787 --ip 127.0.0.1 > /tmp/wrangler-dev.log 2>&1 &) ; sleep 60; cat /tmp/wrangler-dev.log | tail -40
cd <guest-path> && pkill -f "wrangler dev" ; (nohup npx wrangler dev --port 8787 --ip 127.0.0.1 > /tmp/wrangler-dev.log 2>&1 &); sleep 25; curl -s -i -X POST http://127.0.0.1:8787/mcp -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' | head -20; echo; curl -s http://127.0.0.1:8787/ | head -c 600; echo; grep -n "oauth\|OAuth\|authorize\|token" src/index.ts | head -40
cd <guest-path> && (nohup npx wrangler dev --port 8787 --ip 127.0.0.1 > /tmp/wrangler-dev.log 2>&1 &); sleep 30; tail -3 /tmp/wrangler-dev.log; curl -s -i -X POST http://127.0.0.1:8787/mcp -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' | head -12; echo; curl -s http://127.0.0.1:8787/ | head -c 400; echo; grep -n "authMode\|OWNER_PASSWORD\|/token\|/authorize" src/index.ts src/auth.ts | head -30
cd <guest-path> && sed -n 55,140p src/auth.ts; grep -n "login\|password" src/index.ts | head -40
cd <guest-path> && sed -n 85,135p src/index.ts; sed -n 165,185p src/index.ts; grep -n "consent\|<form" src/pages.ts | head
cd <guest-path> && PW=$(python3 -c "import secrets;print(secrets.token_urlsafe(24))") && printf 'OWNER_PASSWORD=%s\n' "$PW" > .dev.vars && chmod 600 .dev.vars && for p in $(pgrep -f "[w]rangler dev"); do kill $p; done; sleep 3; (nohup npx wrangler dev --port 8787 --ip 127.0.0.1 > /tmp/wrangler-dev.log 2>&1 &); sleep 30; tail -2 /tmp/wrangler-dev.log; curl -s -X POST http://127.0.0.1:8787/register -H 'content-type: application/json' -d '{"client_name":"coldpass-probe","redirect_uris":["http://127.0.0.1:9999/cb"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"]}' | head -c 600; echo; git -C . status 2>/dev/null | head -2; git check-ignore .dev.vars
cd <guest-path> && ls -la .dev.vars 2>&1; (ss -ltnp 2>/dev/null | grep 8787) ; PIDS=$(ss -ltnp 2>/dev/null | grep ':8787 ' | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u); echo "pids=$PIDS"; for p in $PIDS; do kill $p; done; sleep 3; curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8787/ || echo down
cd <guest-path> && (nohup npx wrangler dev --port 8787 --ip 127.0.0.1 > /tmp/wrangler-dev.log 2>&1 &); sleep 30; tail -1 /tmp/wrangler-dev.log; curl -s -X POST http://127.0.0.1:8787/register -H 'content-type: application/json' -d '{"client_name":"coldpass-probe","redirect_uris":["http://127.0.0.1:9999/cb"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"]}' | head -c 400; echo; git check-ignore .dev.vars
mkdir -p <guest-path> && cat > <guest-path> <<'EOF'
import base64, hashlib, html, http.cookiejar, json, os, re, secrets, sys, urllib.parse, urllib.request
BASE = "http://127.0.0.1:8787"
REDIRECT = "http://127.0.0.1:9999/cb"
PW = None
for line in open("<guest-path>"):
if line.startswith("OWNER_PASSWORD="):
PW = line.strip().split("=", 1)[1]
jar = http.cookiejar.CookieJar()
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *a, **k):
return None
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar), NoRedirect)
def req(url, data=None, headers=None, method=None):
r = urllib.request.Request(url, data=data, headers=headers or {}, method=method)
try:
resp = opener.open(r)
return resp.status, dict(resp.headers), resp.read().decode()
except urllib.error.HTTPError as e:
return e.code, dict(e.headers), e.read().decode()
client_id = json.loads(open("<guest-path>").read())["client_id"]
verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode().rstrip("=")
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=")
q = urllib.parse.urlencode({"response_type": "code", "client_id": client_id, "redirect_uri": REDIRECT,
"code_challenge": challenge, "code_challenge_method": "S256", "state": "s1", "scope": "whatsapp"})
st, h, body = req(f"{BASE}/authorize?{q}")
print("authorize", st, file=sys.stderr)
m = re.search(r'<form[^>]*action="([^"]+)"', body)
action = html.unescape(m.group(1))
if action.startswith("/"):
action = BASE + action
print("signin action", action[:120], file=sys.stderr)
st, h, body = req(action)
print("signin GET", st, file=sys.stderr)
form = urllib.parse.urlencode({"password": <secret>}).encode()
st, h, body = req(action, data=form, headers={"content-type": "application/x-www-form-urlencoded"})
print("password POST", st, h.get("Location", "")[:120], file=sys.stderr)
loc = h.get("Location")
if not loc:
sys.exit("no redirect after password: " + body[:300])
# follow redirects manually until we reach the client redirect with code
for _ in range(6):
if loc.startswith(REDIRECT):
break
if loc.startswith("/"):
loc = BASE + loc
st, h, body = req(loc)
loc = h.get("Location")
if not loc:
sys.exit("chain ended: %s %s" % (st, body[:300]))
qs = urllib.parse.parse_qs(urllib.parse.urlparse(loc).query)
code = qs["code"][0]
tok_body = urllib.parse.urlencode({"grant_type": "authorization_code", "code": code, "code_verifier": verifier,
"client_id": client_id, "redirect_uri": REDIRECT}).encode()
st, h, body = req(f"{BASE}/token", data=tok_body, headers={"content-type": "application/x-www-form-urlencoded"})
print("token", st, file=sys.stderr)
tok = json.loads(body)
print(tok["access_token"])
EOF
cd <guest-path> && curl -s -X POST http://127.0.0.1:8787/register -H 'content-type: application/json' -d '{"client_name":"coldpass-probe","redirect_uris":["http://127.0.0.1:9999/cb"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"]}' > <guest-path> && python3 -I <guest-path> > <guest-path>; echo EXIT=$?; wc -c <guest-path>
cd <guest-path> && sed -n 8,30p src/pages.ts; grep -n "startSignIn" -A12 src/auth.ts | head -30; grep -n "function proceed" -A10 src/index.ts
cd <guest-path> && wc -l check.py && grep -n "credentials_error\|checks\|def \|kind\|needs_\|stays_running\|mcp_http" check.py | head -120
cd <guest-path> && sed -n 440,480p check.py; sed -n 788,856p check.py
cd <guest-path> && sed -n 185,200p src/index.ts; grep -n "async pull" -A12 src/whatsapp-do.ts | head -30; grep -n "isPaired" -A3 src/whatsapp-do.ts | head -8
cd <guest-path> && sed -i 's/m = re.search(r.<form\[^>\]\*action="(\[^"\]+)"., body)/m = re.search(r'"'"'href="(\/login\/password[^"]+)"'"'"', body)/' get_token.py && grep -n "re.search" get_token.py && cd <guest-path> && python3 -I <guest-path> > <guest-path> 2><guest-path>; echo EXIT=$?; cat <guest-path> | tail -5; grep -c "Pair\|pair" <guest-path>; curl -s -X GET 'http://127.0.0.1:8787/pair/status' -o /dev/null -w '%{http_code}\n'; PIDS=$(ss -ltnp 2>/dev/null | grep ':8787 ' | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u); for p in $PIDS; do kill $p; done; sleep 3; curl -s -o /dev/null -w 'after-stop %{http_code}\n' http://127.0.0.1:8787/ || echo "server stopped"
cd <guest-path> && rm -f .dev.vars && python3 - <<'EOF'
import json, os
W="<guest-path>"
README=open(os.path.join(W,"README.md"),encoding="utf-8").read()
q1="It can't send anything."
q2="You need a Cloudflare account on the Workers Paid plan (see the note below), Node 20 or later, and your phone."
q3="Every read tool first tops up from WhatsApp if the stored copy is more than ten minutes old."
for q in (q1,q2,q3): print(q in README, q)
claims=[
{"kind":"claim","claim":"The server is read-only and cannot send WhatsApp messages",
"source":q1,"input":"none (no send tool in the MCP tool list; no authenticated call possible here)",
"expected":"cannot be checked without an authenticated MCP session",
"testable":False,
"reason":"Needs an OAuth token from /authorize, which only completes after a WhatsApp phone is linked (src/index.ts proceed(): token is returned only when isPaired()). No Cloudflare account and no phone in this guest."},
{"kind":"claim","claim":"Setup needs a Cloudflare Workers Paid account, Node 20+, and a phone",
"source":q2,"input":"none",
"expected":"deploy and link with a real account and phone",
"testable":False,
"reason":"Prerequisites missing in this disposable guest: no Cloudflare account, no Workers Paid plan, no phone to scan the WhatsApp QR code. Node 22 was installed and npm install succeeded; the deploy step (npm run deploy / wrangler deploy) was not run."},
{"kind":"claim","claim":"Read tools top up from WhatsApp when the stored copy is older than ten minutes",
"source":q3,"input":"none",
"expected":"a read tool returns messages after a pull",
"testable":False,
"reason":"Needs a linked WhatsApp account and an authenticated MCP session; both require a phone scan and a Cloudflare deployment, neither available here."},
]
json.dump(claims,open(os.path.join(W,".coldpass/claims.json"),"w"),indent=2,ensure_ascii=False)
probe={"checks":[{"check":"credentials_error","kind":"mcp_http",
"argv":["npm","run","dev","--","--port","8787","--ip","127.0.0.1"],
"url":"http://127.0.0.1:8787/mcp","cwd":"<guest-path>"}]}
json.dump(probe,open(os.path.join(W,".coldpass/probe.json"),"w"),indent=2)
examples=[{"kind":"example","claim":"The repo's own offline test suite passes",
"source":"package.json scripts.test",
"input":"none","expected":"all tests pass","argv":["npm","test"],
"cwd":"<guest-path>","env":{},"expected_text":"all passed"}]
json.dump(examples,open(os.path.join(W,".coldpass/examples.json"),"w"),indent=2)
cycles={"detected":True,"checks":[{"claim":"Daily pull of WhatsApp messages at 04:00 UTC",
"source":"wrangler.jsonc",
"quote":"\"triggers\": { \"crons\": [\"0 4 * * *\"] }",
"schedule":"cron 0 4 * * * (04:00 UTC daily)",
"method":"not run: the scheduled() handler in src/index.ts only pulls when isPaired() is true, and this guest has no paired account; wrangler dev's local scheduled endpoint would be the substitute",
"input":"none","expected":"a daily pull of stored messages",
"reason":"Missing prerequisite: the account must be linked to WhatsApp before the cron pulls anything; no Cloudflare deployment and no phone here."}]}
json.dump(cycles,open(os.path.join(W,".coldpass/cycles.json"),"w"),indent=2,ensure_ascii=False)
report={"outcome":"needs_credentials","fault_owner":"prerequisite",
"operation":"Run the whatsapp-mcp server and have an MCP client read WhatsApp messages through its tools",
"operation_scope":"Local guest only. Needs a Cloudflare account on the Workers Paid plan for deployment, and a real phone to scan the WhatsApp linking QR code. Nothing was deployed or linked.",
"steps":["Install Node 20+ (Ubuntu apt nodejs is 18; installed Node 22 via npm i -g n && n 22)",
"npm install (includes scripts/vendor-bridge.mjs, postinstall: OK)",
"npm test (offline suite: all passed)",
"npm run typecheck (tsc --noEmit: OK)",
"npx wrangler kv namespace create OAUTH_KV (not run: needs Cloudflare login)",
"npx wrangler secret put OWNER_PASSWORD (not run: needs Cloudflare login)",
"npm run deploy (not run: needs Cloudflare account)",
"Open deployed /, link WhatsApp by QR (not run: needs phone)",
"claude mcp add --transport http whatsapp <url>/mcp (not run: no deployed URL)"],
"deviations":["apt nodejs 18.19 failed the Baileys and wrangler engine checks; installed Node 22.23.4 with npm i -g n",
"README has no local run instructions; used package.json script 'npm run dev' (wrangler dev) on 127.0.0.1:8787 for local checks",
"Local OAuth attempt: wrote a random OWNER_PASSWORD to .dev.vars (wrangler local secret file, deleted afterwards), registered an OAuth client and started the consent flow; my helper's regex did not match the consent link, so I stopped there. The token path is gated on WhatsApp pairing per src/index.ts proceed() (read from source, not observed).",
"No browser used; no external services contacted except the local dev server."],
"stuck_step":"Deploy to Cloudflare and link WhatsApp (README 'Set it up', steps 1-3 and 'Link WhatsApp')",
"main_blocker":"No Cloudflare account and no phone for the WhatsApp QR link in this disposable guest.",
"plain_failure":"The server can only be used after it is deployed to a Cloudflare account and linked to a real WhatsApp phone. This guest has neither, so I could run the code locally and confirm it refuses unauthenticated requests, but I could not read any messages.",
"suggested_fix":"Document a local development path in the README (npm run dev plus a .dev.vars password) and state clearly that the MCP token is only issued after the WhatsApp phone link is complete. Otherwise the local run can't reach any tool."}
os.makedirs(os.path.join(W,".coldpass"),exist_ok=True)
json.dump(report,open(os.path.join(W,".coldpass/report.json"),"w"),indent=2,ensure_ascii=False)
print("written")
EOF
ls -la .coldpass; <env-var>=<guest-path> python3 -I <guest-path> --validate --replay 2>&1 | tail -60; echo CHECK_EXIT=${PIPESTATUS[0]}
ss -ltnp 2>/dev/null | grep -E ':8787|wrangler' || echo "no server listening on 8787"; pgrep -af "[w]rangler|workerd" | head -3 || true; ls -la <guest-path> | head -3; test -f <guest-path> && echo "BAD root report" || echo "no root report.json"
for p in $(ps -eo pid,args | awk '$2 ~ /^(npm|sh|node|workerd)$/ && /wrangler|workerd/ {print $1}'); do echo "kill $p: $(ps -o args= -p $p | cut -c1-80)"; kill $p 2>/dev/null; done; sleep 3; ps -eo pid,args | grep -E "[w]rangler dev|[w]orkerd" | cut -c1-120 || true; echo done
cd <guest-path> && <env-var>=<guest-path> python3 -I <guest-path> --validate --replay > <guest-path> 2>&1; echo EXIT=$?; python3 -I -c "import json;d=json.load(open('<guest-path>'));print('valid',d.get('valid'),'problems',d.get('problems'),'replay',d.get('replay',{}).get('status'))"run JSON · checker evidence · agent report + check · transcript excerpt unavailable · error excerpt unavailable