Lethal Trifecta
Simon Willison's prompt-injection threat model in three axes: access to private data, exposure to untrusted input, and the ability to transmit data outbound. A server that has all three is a complete exfiltration chain on its own. Two of three is still fragile — one more server on your stack closes the loop.
The composed trifecta
The thing single-server grading misses: the trifecta is most often composed across a stack, not present in one server. Three MCPs that each look fine in isolation can combine into a complete exfiltration chain. Examples from realistic agent stacks:
Reads your local repo (private). Fetches arbitrary URLs from PR descriptions or external docs (untrusted). Posts summaries to Slack (outbound). Each component is mid-risk; together, full chain.
Vault holds private notes and credentials. Tavily search returns attacker-shaped web pages. Gmail can send. A poisoned search result instructs the agent to email vault contents — every component looked benign on its own.
AWS server holds account creds. GitHub issues and PR bodies are attacker-controlled. Cloudflare workers can deploy outbound code paths. An issue body that says "rotate keys to attacker-server.com" hits all three.
Full trifecta (39)
Standalone exfiltration chains. Each of these can be hit with a single poisoned input. Sandbox them or audit any agent that uses them.
- mcp/discord3/3
Servers full of strangers + DMs + outbound. Trifecta with extra steps; bot tokens often have admin scope.
PrivateUntrustedOutbound - mcp/mattermost-host3/3
Same shape as Slack. Self-hosted does not change the trifecta surface, only who the attacker has to be.
PrivateUntrustedOutbound - mcp/github-official3/3
Reads private repos, consumes attacker-controlled content via issues and PR bodies, can write to repos and call external APIs. Classic trifecta surface — treat any issue body as potentially hostile input.
PrivateUntrustedOutbound - mcp/sentry3/3
Error payloads can include attacker-controlled strings (ingested untrusted input), project data is private, outbound via API.
PrivateUntrustedOutbound - mcp/linear3/3
Issue bodies and comments are user-written across any team member. Private project data exposed. Outbound via issue updates and comments. Trifecta.
PrivateUntrustedOutbound - mcp/gitlab3/3
Same shape as the GitHub server. Issues and merge-request bodies are attacker-controlled; pipelines and tokens give outbound.
PrivateUntrustedOutbound - mcp/google-calendar3/3
Event invites carry attacker-controlled descriptions; private events are private; sending invites is outbound. Often overlooked.
PrivateUntrustedOutbound - mcp/grafana3/3
Log lines and dashboard panels carry user-controlled strings. Dashboard JSON includes datasource credentials. Webhooks are outbound.
PrivateUntrustedOutbound - mcp/gmail3/3
Reads inbox (private), processes attacker-controlled email bodies (untrusted), can send email (outbound). Trifecta.
PrivateUntrustedOutbound - mcp/shopify3/3
Customer data is private; product reviews and customer messages are mixed-trust; outbound is the entire admin API.
PrivateUntrustedOutbound - mcp/nostr3/3
Public-by-design, but the relay model means the agent ingests notes from anyone. Same trifecta as Twitter.
PrivateUntrustedOutbound - mcp/bluesky-social3/3
DMs make this private; replies and quote-posts make untrusted-input wide; posting closes the loop.
PrivateUntrustedOutbound - mcp/tiktok3/3
Comment streams and DMs are mixed-trust; posting and DM-replying are outbound. Trifecta.
PrivateUntrustedOutbound - mcp/ms-3653/3
The Microsoft suite as one server is the broadest trifecta in the catalogue. Email, calendar, files, contacts, all bundled.
PrivateUntrustedOutbound - mcp/github3/3
Same trifecta surface as the official server. Issues, PRs, and repo contents are mixed-trust.
PrivateUntrustedOutbound - mcp/slack3/3
Reads channels and DMs (private), ingests messages from anyone in the workspace (untrusted), can post and DM (outbound). Trifecta.
PrivateUntrustedOutbound - mcp/google-drive3/3
Private documents, shared-with-you files may contain injected content, and the API supports sending invitations or sharing. Trifecta risk is real.
PrivateUntrustedOutbound - mcp/jira3/3
Same shape as Linear — private tickets, mixed-trust comments, outbound updates.
PrivateUntrustedOutbound - mcp/notion3/3
Shared pages can embed untrusted content. Database access is private. API supports writes.
PrivateUntrustedOutbound - mcp/imessage3/3
Reads private messages, processes attacker-shaped content from any sender, can send. Personal-device trifecta — same exfiltration shape as Slack but with your contacts.
PrivateUntrustedOutbound - mcp/whatsapp3/3
Identical shape to imessage. Group chats and unknown-number messages compound the untrusted-input axis.
PrivateUntrustedOutbound - mcp/telegram3/3
Bots and channels mean a much wider attacker surface than imessage. Treat any forwarded message as hostile.
PrivateUntrustedOutbound - mcp/facebook-pages3/3
Page mentions and message-the-page features are wide-open untrusted-input vectors. Outbound posting closes the chain.
PrivateUntrustedOutbound - mcp/circleci3/3
Build logs include arbitrary script output (untrusted), build environments hold secrets (private), pipelines can call out (external). The CI surface is a sleeper trifecta.
PrivateUntrustedOutbound - mcp/buildkite3/3
Same shape as CircleCI. Pipeline definitions read user-controlled YAML; agents run with privileged tokens.
PrivateUntrustedOutbound - mcp/figma3/3
Private design files, comments from collaborators (some not vetted), outbound via webhooks and embeds.
PrivateUntrustedOutbound - mcp/figma-dev-mode3/3
Inherits the figma trifecta plus access to code-export endpoints.
PrivateUntrustedOutbound - mcp/liveblocks3/3
Real-time collaborative state means anyone in the room can plant content the agent reads. Plus auth tokens. Plus outbound.
PrivateUntrustedOutbound - mcp/airbnb3/3
Reservations and guest messages are private. Guest-written messages are untrusted. The platform sends and receives on your behalf.
PrivateUntrustedOutbound - mcp/attio3/3
CRM trifecta. Contact records private. Inbound emails / form submissions untrusted. Outbound API broad.
PrivateUntrustedOutbound - mcp/hubspot3/3
Same trifecta shape as attio. Marketing automation surface adds breadth on the outbound axis.
PrivateUntrustedOutbound - mcp/salesforce3/3
Enterprise CRM. Largest outbound surface in the catalogue. Same trifecta logic as the others.
PrivateUntrustedOutbound - mcp/intercom3/3
Live customer-support chat is designed to ingest hostile-by-default messages. Trifecta is the default operating mode.
PrivateUntrustedOutbound - mcp/google-sheets3/3
Sheets shared by anyone can carry hostile cell content. Private sheets are private. Sharing and exporting is outbound.
PrivateUntrustedOutbound - mcp/twilio3/3
Inbound SMS and voice are untrusted by definition. Address book is private. SMS/voice send is outbound.
PrivateUntrustedOutbound - mcp/posthog3/3
Event payloads are user-generated. Person properties hold private data. Webhook destinations and exports go outbound.
PrivateUntrustedOutbound - mcp/roblox3/3
Massive UGC surface ingests anything. Account and game-state private. APIs go outbound. Use only in sandboxed agents.
PrivateUntrustedOutbound - mcp/raindrop3/3
Bookmark titles and notes are user content; URLs in bookmarks fetch arbitrary pages. Outbound via Raindrop sync.
PrivateUntrustedOutbound - mcp/strava3/3
Private routes and bio. Comments and segment names are user-generated. Outbound posts to clubs and feeds.
PrivateUntrustedOutbound
Two-of-three (52)
Closer to the trifecta than they look. Pair any of these with a server scoring on the missing axis and you have a complete chain — usually a server you already have installed.
- mcp/stripe2/3
Customer data is private; outbound API calls. Does not ingest untrusted web-style content in normal use.
PrivateUntrustedOutbound - mcp/browserbase2/3
Cloud headless browser. Same untrusted-input shape as Puppeteer with persistent sessions.
PrivateUntrustedOutbound - mcp/apify2/3
Scraping platform. Same axes; output is whatever the actor returned.
PrivateUntrustedOutbound - mcp/aws2/3
Broad cloud creds in scope. No untrusted-input ingestion in pure SDK usage. 2/3 only because nothing reads attacker content directly.
PrivateUntrustedOutbound - mcp/kubernetes2/3
Cluster control with kubeconfig is private; outbound to the API server. Pod logs can carry untrusted strings if you read them — push to 3/3 for log-reading workloads.
PrivateUntrustedOutbound - mcp/tavily2/3
Same shape. Tavily caches pages it found, which means stale poisoned content can keep coming back.
PrivateUntrustedOutbound - mcp/kagi2/3
Same shape. Lenses can constrain the attack surface a bit but do not change the axis count.
PrivateUntrustedOutbound - mcp/cloudflare2/3
DNS, sites, workers — broad outbound. Private API tokens. Add a fetch MCP and you have a trifecta.
PrivateUntrustedOutbound - mcp/desktop-commander2/3
Local desktop control = local private state + outbound capability via apps it can drive.
PrivateUntrustedOutbound - mcp/mcp-installer2/3
Installs npm packages — effectively arbitrary code execution. Treat as a trifecta even though only two axes are nominally true. Anything it installs inherits the agent's entire trust scope.
PrivateUntrustedOutbound - mcp/openapi2/3
Fetches and parses arbitrary OpenAPI specs. Spec contents can carry social-engineering content for the model to follow.
PrivateUntrustedOutbound - mcp/firecrawl2/3
Loads arbitrary URLs and renders them. Highest-quality untrusted-input vector after Puppeteer.
PrivateUntrustedOutbound - mcp/vercel2/3
Project secrets private; deployment API outbound; build logs can carry untrusted strings (push to 3/3 if you read them).
PrivateUntrustedOutbound - mcp/youtube-transcript2/3
Transcript text is user-uploaded. Outbound to YouTube on each pull. Mild but not zero.
PrivateUntrustedOutbound - mcp/giphy2/3
GIF metadata is user-supplied. Image content can carry payloads in alt text.
PrivateUntrustedOutbound - mcp/openstreetmap2/3
User-generated map data; mostly clean but place names and tags have been used for graffiti before.
PrivateUntrustedOutbound - mcp/dune-analytics2/3
On-chain data is public; query results travel outbound. Small input-axis risk from labels.
PrivateUntrustedOutbound - mcp/voicemode2/3
Local microphone audio is private; outbound to the speech-to-text API.
PrivateUntrustedOutbound - mcp/biothings2/3
Same.
PrivateUntrustedOutbound - mcp/arxiv-latex2/3
Same plus LaTeX macros expand the attack surface marginally.
PrivateUntrustedOutbound - mcp/pubmed-simple2/3
Same shape as NCBI.
PrivateUntrustedOutbound - mcp/hackernews-pskill92/3
Comments and submissions are attacker-controlled. Outbound when posting. The HN crowd is generally good but not fully trustable.
PrivateUntrustedOutbound - mcp/gistpad2/3
Gists can be public from anyone; private gists are private (some configs flip this to trifecta).
PrivateUntrustedOutbound - mcp/k8s-alexei2/3
Same shape as kubernetes.
PrivateUntrustedOutbound - mcp/prometheus2/3
Metric labels and series can carry attacker strings if exporters are misconfigured. Mostly 2/3.
PrivateUntrustedOutbound - mcp/brave-search2/3
Returns arbitrary web snippets (untrusted) and uses the Brave API (outbound). No private data access, so attackers need a second server in the stack to complete the chain.
PrivateUntrustedOutbound - mcp/supabase2/3
DB read/write plus outbound to the Supabase API. No untrusted-input ingestion in normal use; trifecta needs another server.
PrivateUntrustedOutbound - mcp/hf-space2/3
Spaces are user-uploaded code that returns user-shaped output. Outbound on each call. Pair with any private-data MCP for full exposure.
PrivateUntrustedOutbound - mcp/twitter-twikit2/3
Public tweets and replies are untrusted; outbound on every post. Account takes care of one private leg in some configs.
PrivateUntrustedOutbound - mcp/duckduckgo2/3
Search results are arbitrary web content. Outbound to DDG. Add any MCP that touches private state for a complete chain.
PrivateUntrustedOutbound - mcp/exa2/3
Same shape as DDG, with neural-ranked results that may rank attacker pages higher when they target the query.
PrivateUntrustedOutbound - mcp/perplexity2/3
Same shape. Synthesised answers reduce the visible-attack surface but the underlying input remains untrusted.
PrivateUntrustedOutbound - mcp/searxng2/3
Self-hosted meta-search. Same axes; you choose your upstreams.
PrivateUntrustedOutbound - mcp/scrapeless2/3
CAPTCHA-bypassing scraper. Untrusted-input by design, outbound to the SaaS.
PrivateUntrustedOutbound - mcp/markdownify2/3
Fetches a URL, returns markdown. The fetch is outbound; the body is untrusted. Common 2/3 in research stacks.
PrivateUntrustedOutbound - mcp/markitdown2/3
Same shape as markdownify with broader format support.
PrivateUntrustedOutbound - mcp/webpage-screenshot2/3
Screenshot tools render arbitrary URLs and return image content. Same axes.
PrivateUntrustedOutbound - mcp/arxiv-search2/3
Curated source so the untrusted-input risk is much lower than open web. Still nonzero — abstracts can carry payloads.
PrivateUntrustedOutbound - mcp/ncbi2/3
Public papers, curated. Low-but-nonzero on the input axis.
PrivateUntrustedOutbound - mcp/paperswithcode2/3
Same.
PrivateUntrustedOutbound - mcp/biomcp2/3
Same.
PrivateUntrustedOutbound - mcp/producthunt2/3
Submissions and comments are user-generated. Posting is outbound.
PrivateUntrustedOutbound - mcp/context72/3
Pulls library docs from registered sources. Generally curated; small input-axis risk in the long tail.
PrivateUntrustedOutbound - mcp/bgg2/3
Reviews and forum-style comments are user content.
PrivateUntrustedOutbound - mcp/discogs2/3
Submissions are user-edited. Mild ongoing risk.
PrivateUntrustedOutbound - mcp/ccxt2/3
Crypto exchange API keys are private and high-value. No untrusted ingestion in normal use, but a 2/3 with very high blast radius if compromised.
PrivateUntrustedOutbound - mcp/unsplash2/3
Image alt text is user-supplied. Outbound on download.
PrivateUntrustedOutbound - mcp/azure2/3
Same shape as AWS.
PrivateUntrustedOutbound - mcp/terraform2/3
State files contain secrets. Outbound to provider APIs. Untrusted-input only via remote modules.
PrivateUntrustedOutbound - mcp/heroku2/3
Same shape as Vercel.
PrivateUntrustedOutbound - mcp/ssh-mcp2/3
Remote files private; commands outbound. Untrusted only if you `cat` adversarial files into the model.
PrivateUntrustedOutbound - mcp/rabbitmq2/3
Queue contents may include private payloads; publishing is outbound. No web-style input ingestion.
PrivateUntrustedOutbound
One-of-three (57)
Single-axis servers. Sensitive in their own right (especially the private-data ones) but not exploitable until combined with two more.
- mcp/fetch1/3
Fetches arbitrary URLs — attacker can serve prompt-injection payloads. No private data or persistent outbound path by itself.
PrivateUntrustedOutbound - mcp/neon1/3
Hosted Postgres; private.
PrivateUntrustedOutbound - mcp/clickhouse1/3
Analytical DB; usually private.
PrivateUntrustedOutbound - mcp/mongodb1/3
Read-write to a private DB. Trifecta needs a second server.
PrivateUntrustedOutbound - mcp/bigquery1/3
Cloud DB; queries return private rows. Wraps GCP creds.
PrivateUntrustedOutbound - mcp/qdrant1/3
Vector DB; private.
PrivateUntrustedOutbound - mcp/docker1/3
Container control reads local file paths and config; doesn't talk out by itself.
PrivateUntrustedOutbound - mcp/milvus1/3
Same.
PrivateUntrustedOutbound - mcp/aws-postgres1/3
Same.
PrivateUntrustedOutbound - mcp/mysql1/3
Same.
PrivateUntrustedOutbound - mcp/redis1/3
Cache contents may include sensitive payloads. Otherwise private-only.
PrivateUntrustedOutbound - mcp/resend1/3
Email sender only. Cannot read inbox.
PrivateUntrustedOutbound - mcp/aws-dynamodb1/3
Same.
PrivateUntrustedOutbound - mcp/obsidian1/3
Vault is private. Read-only by default. The single most concentrated private-data sink in many setups.
PrivateUntrustedOutbound - mcp/weaviate1/3
Same.
PrivateUntrustedOutbound - mcp/react-analyzer1/3
Reads local source. Single-axis.
PrivateUntrustedOutbound - mcp/chess-com1/3
Public game data; outbound.
PrivateUntrustedOutbound - mcp/esxi1/3
Hypervisor management, local network.
PrivateUntrustedOutbound - mcp/opgg1/3
Public game stats.
PrivateUntrustedOutbound - mcp/sonos1/3
Local audio control; nothing leaves the network in normal use.
PrivateUntrustedOutbound - mcp/exif1/3
Reads metadata from local images; can include attacker-crafted EXIF strings. No outbound in pure read mode.
PrivateUntrustedOutbound - mcp/climatiq1/3
Same.
PrivateUntrustedOutbound - mcp/wolframalpha1/3
Computed answers; outbound on each query.
PrivateUntrustedOutbound - mcp/mcp-cli1/3
Generic shell. Reads local state.
PrivateUntrustedOutbound - mcp/local-rag1/3
Local document index.
PrivateUntrustedOutbound - mcp/piapi1/3
Image generation passthrough.
PrivateUntrustedOutbound - mcp/neo4j1/3
Graph DB; usually private.
PrivateUntrustedOutbound - mcp/chroma1/3
Local vector DB. Private content.
PrivateUntrustedOutbound - mcp/mcp-alchemy1/3
Generic SQL bridge. Inherits the DB risk.
PrivateUntrustedOutbound - mcp/obsidian-markuspfundstein1/3
Same as obsidian.
PrivateUntrustedOutbound - mcp/iterm1/3
Terminal control. Reads stdout content.
PrivateUntrustedOutbound - mcp/portainer1/3
UI on top of Docker. Same risk.
PrivateUntrustedOutbound - mcp/mcp-graphql1/3
Generic GraphQL caller. 1/3 unless you give it private endpoints (then 2/3).
PrivateUntrustedOutbound - mcp/filesystem1/3
Reads local files but does not ingest web content or transmit outbound. No trifecta by itself; becomes dangerous when paired with an MCP that has the other two axes.
PrivateUntrustedOutbound - mcp/puppeteer1/3
Loads and executes arbitrary web pages. Highest-risk untrusted-input vector in the catalogue. Pair with caution.
PrivateUntrustedOutbound - mcp/postgres1/3
Direct DB access. Attackers need another axis to exfiltrate. Still high-stakes.
PrivateUntrustedOutbound - mcp/sqlite1/3
Local DB read/write. Same profile as filesystem.
PrivateUntrustedOutbound - mcp/memory1/3
Persists user context; a private-data sink in a prompt-injection chain.
PrivateUntrustedOutbound - mcp/git1/3
Local repo access. Commit messages could be mildly attacker-shaped in multi-author repos, but not a typical untrusted-input surface.
PrivateUntrustedOutbound - mcp/dicom1/3
Medical imaging is sensitive private data. No untrusted input or outbound by default.
PrivateUntrustedOutbound - mcp/apple-notes-bear1/3
Local notes only. Single-axis private data sink — pair with anything outbound for risk.
PrivateUntrustedOutbound - mcp/apple-reminders1/3
Same as notes-bear.
PrivateUntrustedOutbound - mcp/oura1/3
Health metrics private; in default mode the MCP only reads.
PrivateUntrustedOutbound - mcp/mongodb-lens1/3
Same.
PrivateUntrustedOutbound - mcp/duckdb1/3
Local DB.
PrivateUntrustedOutbound - mcp/snowflake1/3
Same shape as BigQuery.
PrivateUntrustedOutbound - mcp/sqlite-jparker1/3
Local SQLite. Identical to the official sqlite server.
PrivateUntrustedOutbound - mcp/mem01/3
User-memory store. Pure private-data sink.
PrivateUntrustedOutbound - mcp/knowledge-graph1/3
Local KG store.
PrivateUntrustedOutbound - mcp/ghidra1/3
Reverse engineering of local binaries; sensitive but local-only by default.
PrivateUntrustedOutbound - mcp/alpha-vantage1/3
Public market data; outbound on every query. No private or untrusted axis.
PrivateUntrustedOutbound - mcp/aviationstack1/3
Same.
PrivateUntrustedOutbound - mcp/bitcoin1/3
Public chain queries.
PrivateUntrustedOutbound - mcp/whale-tracker1/3
Public chain data.
PrivateUntrustedOutbound - mcp/national-rail-uk1/3
Public schedule data.
PrivateUntrustedOutbound - mcp/runescape1/3
Public game data.
PrivateUntrustedOutbound - mcp/unichat1/3
Generic LLM chat passthrough.
PrivateUntrustedOutbound
Clear (18)
No axes flagged. Stack these freely; they are inert against prompt-injection chains.
Prompt injection is unsolved. If your agent can read secrets, ingest untrusted content, and send data outbound, any hostile snippet in its input window can exfiltrate whatever it can see. Delv grades each axis independently so you can decide which MCPs belong in a sandboxed environment and which are safe to leave on all the time.
Grading methodology: editorial review of each server's declared permissions, public documentation, source where available, and the typical user workflow. Null axes mean the server hasn't been graded yet. Flag corrections.
The framing is from Simon Willison's writing on prompt injection — credit where it belongs.