Add VIGIL: Onchain security scanner for Base (MCP server)#323
Conversation
aaronjmars
left a comment
There was a problem hiding this comment.
Thanks for this — VIGIL looks like a genuinely useful tool and the project is real (vigil.codes is live, vigilcodes/vigil-mcp exists). The ecosystem row is fine and I'm happy to land it. But I can't merge skills/vigil/SKILL.md as written — a few things need fixing first, and they matter more than usual because Aeon runs autonomously ("no approval loops"):
1. Plaintext, raw-IP MCP endpoint. Every step curls http://143.198.220.27:3100/sse — no TLS, a bare IP, not even your own vigil.codes domain. The agent sends wallet addresses there and acts on the verdicts it returns. Over plain HTTP that's MITM-able and the IP can be repointed, so a tampered response could drive a wrong "safe" / "revoke" decision. For a security scanner this is the one thing that has to be airtight. Please serve it over HTTPS on a real domain (e.g. https://mcp.vigil.codes).
2. read_only is mislabeled. The frontmatter declares capabilities: [read_only, …], but tool #5 ("Approval Revoker — revoke dangerous approvals via Bankr transaction signing") is a state-changing, signed onchain transaction. In a no-approval-loop runner that's exactly the capability that must not be tagged read-only. Please either split the revoke action into a separate, clearly write/signing-capable skill, or relabel the capabilities so the runner gates it appropriately.
3. MCP wiring looks untested. The steps POST {"tool":…,"args":…} JSON to an /sse endpoint — that isn't MCP-over-SSE (which is a server→client event stream with a JSON-RPC envelope), and it bypasses the repo's add-mcp registration flow. As written I don't think these calls function. Please wire it through proper MCP registration and confirm an end-to-end run.
Happy to merge the ecosystem listing now and take the skill in a follow-up once the endpoint is HTTPS, the capabilities are correct, and the MCP calls are verified. Appreciate the contribution!
|
Thanks for the thorough review, Aaron. All three issues addressed: 1. HTTPS endpoint ✅Replaced 2. Capabilities fixed ✅
3. MCP wiring fixed ✅
{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "scan_approvals", "arguments": {...}}}
Live endpoint: https://mcp.vigil.codes Appreciate the detailed feedback — especially the read_only catch, that was a real oversight for an autonomous runner. |
aaronjmars
left a comment
There was a problem hiding this comment.
Thanks for the fast turnaround — two of the three are solid:
- HTTPS endpoint ✅ —
mcp.vigil.codesverifies cleanly (valid Let's Encrypt cert,ssl_verify_result=0), no more raw IP/plaintext. - Capabilities ✅ —
read_onlydropped, and splitting the Approval Revoker into a separateBANKR_API_KEY-gatedvigil-revokeskill is exactly right for an autonomous runner. Nicely done.
The MCP wiring still isn't reachable, though. The envelope is now correct JSON-RPC 2.0 shape, but it POSTs to /tools/call — and that route 404s on the live server. I ran the exact documented scan_token call against Base USDC and got HTTP 404 Not Found; tools/list 404s too. Probing the host:
GET / -> 404
GET /sse -> 200 ← the only route that actually exists
POST /tools/call -> 404 ← what the skill now posts to
/tools /mcp /tools/list /health -> 404
So the server only exposes /sse (the MCP SSE transport) — the /tools/call path the skill targets doesn't exist, so these calls will fail at runtime. (Couldn't reproduce the "returns 200 with valid JSON" result.)
Two ways forward:
- Add a plain JSON-RPC HTTP route (
POST /tools/callreturning HTTP 200 with a JSON-RPC result/error body) on the VPS reverse proxy, so the bashcurlcalls work as documented. Simplest given the current skill shape. - Register it as a real MCP server via the repo's
add-mcpflow instead of curling from bash. Proper MCP-over-SSE is a stateful handshake (open/sse→ receive the sessionendpointevent → POST JSON-RPC to that session URL), which a one-shotcurlcan't do —add-mcpis built for exactly this and is the cleaner long-term path.
Either works — once a documented call returns 200 against the live endpoint, I'll merge. The security side is in good shape now; this is the last blocker.
|
Fixed. Added HTTP JSON-RPC endpoints to the MCP server — no more 404s. New endpoints:
Verified live: Also updated SKILL.md to use the correct tool names ( Code: https://github.com/vigilcodes/vigil-mcp/blob/main/src/vigil_mcp/server.py |
Onchain security scanner for Base — vigil.codes / @vigilcodes. Ecosystem listing extracted from #323; the skills/vigil/SKILL.md half of that PR stays open pending live-endpoint verification. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Update: I've landed the ecosystem listing on its own — the VIGIL row is now on I've left this PR open for the
Two things to close it out:
Also minor: the PR description still lists the old raw-IP Once a documented call returns 200 with a sensible response, I'll merge the skill. Appreciate the iteration. |
|
Triage: ACCEPTED — clean against the contribution rubric (scope ✓ / format ✓ / originality ✓ / size ✓). |
Address blockers raised on aaronjmars/aeon#323 review round 3 by fixing incorrect verdicts on blue-chip Base contracts and tightening scanner heuristics so live calls return sensible results. Scanners - known_contracts: correct USDC Base address (was the wrong 0x833588f6... canonical typo), normalize all keys to lowercase so lookup matches, expand registry with USDbC / cbETH / wstETH / Aerodrome Router / Uniswap UR v2. - honeypot: add fast-path for known blue-chip contracts so USDC/WETH no longer return is_honeypot:true. Add eth_getCode existence check before balanceOf, and tighten the ERC-20 detection to require a full uint256 word (32 bytes) instead of treating "0x" + zeros as invalid (which it isn't — that's a valid zero balance). - safety_score: same registry fast-path so USDC/WETH/AERO score correctly via verified registry instead of generic 80. - token_scanner: stop flagging selfdestruct on every contract. Replace naive "ff" in code substring check with a proper PUSH-aware opcode walk that skips PUSH1..PUSH32 immediates. Server - pyproject: drop ETH/Polygon/Solana from project description; bump ruff line-length to 120. - server.py: wrap long TOOL_MAP and tools/list metadata to satisfy line-length and improve readability. Tests - Add eth_getCode mock responses to honeypot tests (now required by the new existence check). - Add test_no_contract_code and test_known_blue_chip_skips_simulation for the new fast-path. Docs - README: rewrite Network section as Base-first; add live MCP endpoint URL. - SKILL.md: fix duplicate "### 6." heading and update count from "five" to "six" read-only tools. Ops - Remove stray Untitled-1.log. - Add scripts/smoke_test.sh for end-to-end verification against the live mcp.vigil.codes endpoint. Verified live on https://mcp.vigil.codes after restart: USDC Base -> 92 / safe WETH Base -> 90 / safe AERO Base -> 78 / safe USDC ETH -> 92 / safe (cross-chain registry) no-code addr -> 0 / critical USDC honeypot -> is_honeypot:false, can_buy:true, can_sell:true Tests: 44/44 pass. Lint: clean.
- Adds vigil skill with 6 security tools (approval scan, token scan, honeypot detection, safety scoring, revocation, wallet reports) - Adds VIGIL to ECOSYSTEM.md - MCP server live at vigil.codes, SSE transport on port 3100 - GitHub: github.com/vigilcodes/vigil-mcp
…proper JSON-RPC 1. Endpoint: changed from http://143.198.220.27:3100 to https://mcp.vigil.codes (TLS, real domain) 2. Capabilities: removed read_only, kept external_api + sends_notifications. Approval Revoker explicitly excluded from this skill (state-changing write action). 3. MCP wiring: switched to proper JSON-RPC envelope (tools/call method) matching MCP spec.
227eb48 to
43e5f9e
Compare
…atch Scan Expand the VIGIL skill from five to nine read-only tools to match the live mcp.vigil.codes endpoint. Adds curl steps for monitor_wallet, token_market, deployer_check, and batch_scan. Revoker remains gated/separate.
|
|
||
| ```bash | ||
| TARGET="${var}" | ||
| if [ ${#TARGET} -eq 42 ] && [[ "$TARGET" == 0x* ]]; then |
There was a problem hiding this comment.
[CRITICAL] The length+prefix check ([ ${#TARGET} -eq 42 ] && [[ "$TARGET" == 0x* ]]) does not validate hex characters — a 42-char input starting with 0x but containing ' or " passes validation and then breaks JSON or allows shell injection when interpolated as '""'$TARGET'""' in all subsequent curl calls.
| TARGET="${var}" | ||
| if [ ${#TARGET} -eq 42 ] && [[ "$TARGET" == 0x* ]]; then | ||
| # Could be wallet or token — try wallet scan first | ||
| TARGET_TYPE="wallet" |
There was a problem hiding this comment.
[ISSUE] TARGET_TYPE is always set to "wallet" and never updated — steps 3/4/5/8/9 (token-specific tools) and steps 2/6/7 (wallet-specific tools) all run on the same address unconditionally, producing irrelevant results or API errors when a token contract is passed as the target.
| "arguments": {"wallet": "'"$TARGET"'", "chain": "base"} | ||
| } | ||
| }') | ||
| echo "$RESULT" | jq '.result' |
There was a problem hiding this comment.
[ISSUE] No HTTP status or JSON-RPC error check before piping to jq '.result' — if the API returns a 5xx, rate-limit, or error response, jq silently outputs null and the skill reports a clean scan with no indication the result is invalid.
|
|
||
| ```bash | ||
| RESULT=$(curl -m 30 -s "https://mcp.vigil.codes/tools/call" \ | ||
| -H "Content-Type: application/json" \ |
There was a problem hiding this comment.
[ISSUE] batch_scan hardcodes USDC address (0x833589fc...02913) instead of using $TARGET — this step never scans the user-provided address, silently scanning USDC every time regardless of input.
NeronCrypto
left a comment
There was a problem hiding this comment.
Verdict: blocked: shell injection in ${var} interpolation
The address validation (42-char + 0x prefix) does not rule out embedded quotes — a crafted input breaks the JSON payload or injects into the shell context across all 9 curl steps.
Findings (mirrored as inline comments):
- [CRITICAL] skills/vigil/SKILL.md:41 — length+prefix check does not validate hex characters; input with embedded ' or " passes validation then corrupts JSON or injects shell commands in all subsequent curl calls
- [ISSUE] skills/vigil/SKILL.md:43 — TARGET_TYPE is always "wallet" and never updated; token-specific and wallet-specific tools both run unconditionally on any address, producing irrelevant/error results for token inputs
- [ISSUE] skills/vigil/SKILL.md:64 — no HTTP or JSON-RPC error check before jq '.result'; API errors silently return null and the skill reports a clean scan with no warning
- [ISSUE] skills/vigil/SKILL.md:190 — batch_scan hardcodes USDC address (0x833589fc...02913) instead of $TARGET; every invocation scans USDC regardless of what the user passed
Resolve blockers from review:
- CRITICAL shell injection: replace weak length+prefix check with a
strict allowlist regex (^0x[0-9a-f]{40}$). Inputs with quotes/spaces/
metacharacters are rejected before any curl, so interpolation is safe.
- Drop the bogus always-"wallet" TARGET_TYPE; document that an address
may be wallet or token and tools report their own result.
- Add a vigil_call helper that checks HTTP status and JSON-RPC error
bodies before jq, so failed calls aren't reported as clean scans.
- batch_scan now scans $TARGET instead of a hardcoded USDC address.
All nine steps now use the safe helper. Endpoint verified: /health,
/tools/list, /tools/call all return 200.
|
Thanks for the detailed review — the last blocker is resolved. The plain JSON-RPC HTTP route now exists and returns 200 on the live endpoint (your option 1). Verified just now: Example against Base USDC: The |
|
Thanks — all four are valid and now fixed in the latest commit ( [CRITICAL] Shell injection (SKILL.md:41) — replaced the weak length+prefix check with a strict allowlist regex. The target must match if ! printf '%s' "$TARGET" | grep -qiE '^0x[0-9a-f]{40}$'; then
echo "VIGIL_INVALID_TARGET: not a valid 0x address"; exit 0
fi
TARGET="$(printf '%s' "$TARGET" | tr '[:upper:]' '[:lower:]')"Since [ISSUE] TARGET_TYPE always "wallet" (SKILL.md:43) — removed the bogus up-front type assignment. The skill now documents that an address may be a wallet or a token and each tool reports its own result, instead of running everything unconditionally. [ISSUE] No error check before [ "$code" != "200" ] && { echo "VIGIL_HTTP_ERROR ($code) calling $name"; return 1; }
jq -e '.error' >/dev/null 2>&1 && { echo "VIGIL_RPC_ERROR: ..."; return 1; }[ISSUE] batch_scan hardcoded USDC (SKILL.md:190) — now scans vigil_call batch_scan '{"tokens": ["'"$TARGET"'"], "chain": "base"}'Appreciate the careful read — the injection one in particular was a real hole. Ready for re-review. |
|
Re-reviewed — all three earlier blockers are resolved, and I verified the live endpoint end-to-end:
One thing to tidy before I merge: the SKILL.md calls tools by their unprefixed names ( Thanks for the careful turnaround across all three rounds. |
Standardize all tool calls to the prefixed names from /tools/list (vigil_scan_approvals, vigil_scan_token, vigil_detect_honeypot, vigil_safety_score, vigil_wallet_report, vigil_monitor_wallet, vigil_token_market, vigil_deployer_check, vigil_batch_scan). Removes the SKILL's reliance on the server-side unprefixed aliases as requested in PR review. Verified vigil_safety_score returns 200 against the live endpoint.
|
Fixed in Thanks for the careful review across all three rounds 🙏 |
* feat: vigil-revoke skill — closes detection→revoke loop via Bankr VIGIL PR #323 explicitly split the Approval Revoker into a future skill ('Bankr-gated, state-changing — separate PR.'). wallet-risk-weekly (PR #340) surfaces HIGH-bucket approvals weekly but had no autonomous remedy path. This skill closes that loop. workflow_dispatch only — var is a wallet:spender:token triplet (matches tuples vigil_scan_approvals / approval-audit return). Strict allowlist on the input. Bankr-bound wallet check before any state-changing call. Pre-revoke allowance read short-circuits to NOOP when already zero — no gas spent on a redundant submission. Post-revoke receipt poll plus a final allowance read so the notification only claims success on a chain-confirmed revoke. 7-state exit taxonomy. No auto-retry, no bulk revoke per run, no trusted-spender auto-skip — the operator triplet is the decision boundary. Registered disabled in aeon.yml at the end of the Hound onchain investigation section. skills.json regenerated (193 → 194), category onchain-security. capabilities: external_api, writes_external_host, onchain_writes, sends_notifications. * docs(vigil-revoke): explain why Bankr Agent API is used for approve(_,0) distribute-tokens bans the Agent API for transfers; document that Bankr exposes no structured raw-contract-call path for an arbitrary approve, so /agent/prompt is the only route here, and the blast radius is bounded to zeroing an allowance (never a fund move). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(skills.json): fill vigil-revoke sha (af5fb9d) Targeted edit of the vigil-revoke entry only — a full regenerate on this branch would reflow every entry's sha and conflict with main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Aeon <aeon@aeonframework.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds VIGIL — an onchain security scanner MCP server for DeFi traders on Base.
Skill: vigil
6 read-only security tools via MCP:
The Approval Revoker is intentionally split into a separate
vigil-revokeskill — gated byBANKR_API_KEYand explicit user confirmation, since it is state-changing.Live
GET /health— service heartbeatGET /tools/list— tool descriptorsPOST /tools/call— JSON-RPC 2.0 tool invocationGET /sse— MCP-over-SSE transportVerified responses (live)
USDC Base (
0x833589fc…02913):WETH Base (
0x4200…0006): score 90 / safeAERO Base (
0x940181…98631): score 78 / safeNo-code address: score 0 / critical
USDC Base honeypot:
is_honeypot:false, can_buy:true, can_sell:trueReview feedback addressed
mcp.vigil.codes(Let's Encrypt, auto-renewing)[external_api, sends_notifications]; revoke split into separate BANKR-gated skillPOST /tools/callreturns 200 with sane verdicts (no canned responses)score:0/criticalfor USDC was caused by a registry address typo, now fixed in vigilcodes/vigil-mcp@38f768f