FlawOpen

flawopen.com/AI Security/microsoft-365-copilot-echoleak-prompt-injection-may-2025

● CVE-2025-32711 · CVSS 9.3 · Critical
FlawOpen Security Research

EchoLeak: Copilot Injection CVE-2025-32711

CVE-2025-32711 let a crafted email inject prompts into Copilot without a click. Microsoft patched the service; no customer action is required.[2], [3]

💡 Plain English Explainer (ELI5)

Imagine an assistant that summarizes letters and a preview system that automatically loads illustrations in its answer. A hostile letter tricks the assistant into placing a reference to a private detail in an illustration; the preview system follows that reference without anyone clicking. EchoLeak crossed the boundary between untrusted email, the assistant’s answer, and an automatic external fetch. The defense is to stop untrusted content from authorizing a fetch and to check every preview at a separate policy boundary.[3], [4]

Core Concepts & Subsystem Terms

Indirect prompt injection
Instructions embedded in data an AI system reads, such as email or documents, that try to redirect the system from the user’s intended task.[4], [3]
CWE-74: Injection
CWE-74: Injection. Microsoft’s CVE record lists this classification and a CVSS 3.1 score of 9.3 Critical. NVD separately shows its own 7.5 High enrichment; these are different assessments, not a contradiction in the vulnerability identifier.[2], [5]
Source-to-sink path
The auditable path from untrusted content (the source), through model processing, to an action or disclosure channel (the sink).[3]
Microsoft 365 Copilot
Microsoft’s work assistant that can use permitted Microsoft 365 context to answer and assist with tasks.[1]
Least privilege
Giving an identity, tool, or workflow only the access needed for its current task, and no broader standing access.[4]

Framework mappings

Step-by-Step Attack Flow

Step 1

Untrusted email enters context

The published case study says Copilot had to process or retrieve the attacker-controlled email during ordinary use. Delivery alone did not trigger the chain; the user did not need to open the email or click anything.[3], [6]

Step 2

Content competes with task instructions

The researchers report that the injected content evaded Copilot's XPIA classifier, while reference-style Markdown in the generated answer escaped external-link redaction.[3], [6]

Step 3

Authorized data becomes reachable

The answer could place information already available to the victim in a remote image reference. The client fetched the image automatically, so no image click was needed.[3], [6]

Step 4

Egress is the decisive boundary

The paper reports that an allowlisted Microsoft Teams preview service fetched the external image destination, crossing the intended content-security boundary. Microsoft later released a server-side fix; its guidance says no customer action is required.[3], [1], [2], [6]

Source Code: Flaw vs. Secure Implementation

FlawOpen teaching model, not Microsoft source code or its patch. The vulnerable branch models an answer renderer auto-loading a model-selected reference. The defensive branch accepts only a ServerValidatedPreview produced by a trusted server-side resolver, checks the server-owned destination policy and non-private classification, and asks a trusted approval policy to verify approval bound to the exact response and destination. These are explicit preconditions: Python types alone do not make a value trusted, so production must enforce the resolver and policy boundaries in server-controlled code. The code performs no network request and does not reproduce EchoLeak’s payload or internal implementation. The approval is also bound to the data classification.[3], [4]

UNPATCHED FLAW
# ABSTRACT RISK MODEL: models an automatic preview decision; performs no network request.
from dataclasses import dataclass

@dataclass(frozen=True)
class PreviewRequest:
    response_id: str
    destination_id: str
    provenance: str
    contains_private_data: bool

def vulnerable_preview_decision(request):
    return True
HARDENED SECURE PATCH
# ABSTRACT RISK MODEL: models an automatic preview decision; performs no network request.
from dataclasses import dataclass

@dataclass(frozen=True)
class ServerValidatedPreview:
    response_id: str
    destination_id: str
    provenance: str
    data_classification: str

class ServerPolicy:
    def destination_is_allowed(self, destination_id):
        raise NotImplementedError

    def verifies_exact_approval(self, approval, *, response_id, destination_id, data_classification):
        raise NotImplementedError

# PRECONDITION: trusted server code alone creates this validated value.
def authorize_preview(request: ServerValidatedPreview, approval, server_policy):
    if request.provenance != 'trusted-catalog' or request.data_classification != 'public':
        return False
    if not server_policy.destination_is_allowed(request.destination_id):
        return False
    return server_policy.verifies_exact_approval(
        approval, response_id=request.response_id, destination_id=request.destination_id,
        data_classification=request.data_classification
    )

Engineering & System Hardening Checklist

Sources

← AI Security Hub Directory →