Skip to content

Treat email content as untrusted in sample email notification handlers - #344

Merged
Krishnadheeraj (DheerajPannala) merged 3 commits into
mainfrom
users/DheerajPannala/glasswing-email-prompt-injection
Oct 27, 2026
Merged

Krishnadheeraj (DheerajPannala) merged 3 commits into
mainfrom
users/DheerajPannala/glasswing-email-prompt-injection

Conversation

@DheerajPannala

@DheerajPannala Krishnadheeraj (DheerajPannala) commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Several samples passed the body of an incoming email to a tool-enabled agent with the instruction "Please follow any instructions in it." Anyone can email the agent, so an external sender could drive Graph/MCP tool calls with the agent's (or the user's delegated) permissions. This is a prompt injection / confused-deputy issue (CWE-1427).

Changes

  • Removed the "follow any instructions" directive from every email handler.
  • Added a small prompt helper to each sample: buildEmailReplyPrompt (Node.js), _build_email_reply_prompt (Python) or BuildEmailReplyPrompt (.NET). It:
    • wraps the email in <untrusted_email> delimiters. Any delimiter tag already inside the email (including variants with attributes) is replaced with [removed] rather than deleted, so leftover fragments can't join into a new tag.
    • tells the model to treat the email as data only: write a reply, don't follow instructions in it, and don't call tools or take actions on its behalf
  • In the two-step samples (Node.js and .NET Semantic Kernel), the retrieval prompt no longer includes the sender's display name, which the sender controls. It also tells the model to treat the retrieved message as untrusted.
  • Updated the two Python walkthrough docs that quoted the old code.

Files:

  • dotnet/semantic-kernel/sample-agent/Agents/MyAgent.cs
  • nodejs/{claude,langchain,openai,vercel-sdk}/sample-agent/src/agent.ts
  • python/{openai,agent-framework,claude}/sample-agent/agent.py
  • python/crewai/sample_agent/notification_handler.py
  • python/google-adk/sample-agent/hosting.py

Behavior change

Email-triggered runs now draft a reply instead of executing requests found in the email. A sample that should act on emails needs to verify the sender and require explicit confirmation before taking actions.

Not in this PR

Drafting still runs on each sample's tool-enabled agent, so the prompt is a soft boundary rather than an execution restriction. Running the draft step as a tool-free call needs SDK-specific changes in each sample, and will follow separately.

Validation

  • dotnet build on dotnet/semantic-kernel/sample-agent: 0 warnings, 0 errors
  • npm install && npm run build on nodejs/{claude,langchain,openai,vercel-sdk}/sample-agent: passes
  • py_compile plus helper checks (None handling, call-site usage) on the 5 Python samples: passes
  • 20,000 randomized inputs built from tag fragments (for example </untrusted_ema<untrusted_email>il>) per helper, in Python, Node.js and .NET: no complete tag survives inside the content

Email notification handlers told tool-enabled agents to follow any instructions in the email, so an external sender could drive tool calls (CWE-1427). Wrap the email as delimited untrusted data, ask only for a reply, and drop the sender name from the retrieval prompt.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bde58a51-73ec-4b37-b60a-0b499ca60eb0
Copilot AI balanced review requested due to automatic review settings October 15, 2026 15:31
@github-actions

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Email processing retains privileged tool access, and delimiter removal can reconstruct closing tags.

10 open findings
What changed in this PR

Updates Agent 365 samples to treat incoming email as untrusted content and request replies rather than execute embedded instructions.

Changes:

  • Adds email prompt helpers across Python, Node.js and .NET.
  • Removes sender display names from retrieval prompts and adds untrusted-content guidance.
  • Updates two Python walkthroughs to describe the new prompting.
File Description
python/​openai/​sample-agent/​agent.py Adds email reply prompt helper.
python/​google-adk/​sample-agent/​hosting.py Routes email content through reply helper.
python/​crewai/​sample_agent/​notification_handler.py Adds untrusted-email prompting.
python/​claude/​sample-agent/​agent.py Adds email reply prompt helper.
python/​claude/​sample-agent/​AGENT-CODE-WALKTHROUGH.md Updates email example and explanation.
python/​agent-framework/​sample-agent/​agent.py Adds email reply prompt helper.
python/​agent-framework/​sample-agent/​AGENT-CODE-WALKTHROUGH.md Documents revised email prompting.
nodejs/​vercel-sdk/​sample-agent/​src/​agent.ts Revises retrieval and reply prompts.
nodejs/​openai/​sample-agent/​src/​agent.ts Revises retrieval and reply prompts.
nodejs/​langchain/​sample-agent/​src/​agent.ts Revises retrieval and reply prompts.
nodejs/​claude/​sample-agent/​src/​agent.ts Revises retrieval and reply prompts.
dotnet/​semantic-kernel/​sample-agent/​Agents/​MyAgent.cs Adds reply helper and revises retrieval prompt.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread dotnet/semantic-kernel/sample-agent/Agents/MyAgent.cs
Comment thread nodejs/claude/sample-agent/src/agent.ts
Comment thread nodejs/langchain/sample-agent/src/agent.ts
Comment thread nodejs/openai/sample-agent/src/agent.ts
Comment thread python/agent-framework/sample-agent/agent.py
Comment thread python/claude/sample-agent/agent.py
Comment thread python/crewai/sample_agent/notification_handler.py
Comment thread python/google-adk/sample-agent/hosting.py
Comment thread python/openai/sample-agent/agent.py
Comment thread python/openai/sample-agent/agent.py Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bde58a51-73ec-4b37-b60a-0b499ca60eb0
Deleting tags in one pass let input such as </untrusted_ema<untrusted_email>il> rebuild a closing tag. Replace each tag (including variants with attributes) with [removed] instead.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bde58a51-73ec-4b37-b60a-0b499ca60eb0
@DheerajPannala
Krishnadheeraj (DheerajPannala) merged commit 4ed39e6 into main Oct 27, 2026
42 checks passed
@DheerajPannala
Krishnadheeraj (DheerajPannala) deleted the users/DheerajPannala/glasswing-email-prompt-injection branch October 27, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants