fix: enforce recipient allowlists for lists and cc/bcc - #160
fix: enforce recipient allowlists for lists and cc/bcc#160rksharma-owg wants to merge 2 commits into
Conversation
mertsatilmaz
left a comment
There was a problem hiding this comment.
Thanks @rksharma-owg — the no_external_recipient fix is real and I want it. Only scalar to/recipient/destination were inspected, so a recipient list, a multi-address string, or a cc/bcc field slipped an external address past the allowlist entirely. That's a genuine bypass in an outbound-action security control, the root-cause writeup is accurate, and the docs and regression tests are right. Extracting addresses with _EMAIL_PATTERN.findall(item) or [item] also keeps the old behavior for non-email values, which is the correct conservative choice.
I can't take it as submitted, though. Alongside that fix, this PR also adds Streamable HTTP and SSE MCP transports — roughly 360 lines across mcp_host.py and mcp_runtime.py (_connect_mcp_server dispatch, _connect_streamable_http_server, _connect_sse_server, new url/headers config fields). None of that is mentioned anywhere in the PR title or description, which describe this purely as a recipient-allowlist bugfix.
Two reasons that's a blocker, and neither is about code quality:
- Scope/disclosure. MCP transport support is a substantial v0.3.0 feature with its own design surface (auth, redirects, timeouts, connection lifecycle). It needs to be reviewed as a feature on its own terms, not merged as an undisclosed rider on a bugfix. A reviewer reading this PR's description would not know it was landing.
- It's already assigned. That work is issue #96, and @Flames4fun — who built the stdio host runtime (#107) and its CLI wiring (#145) — asked me back on Jun 18 to take exactly the Streamable HTTP piece next. I was slow to answer them (my fault, not yours), but I'm not going to land someone else's assigned work out from under them. I've now carved it out as #161 and confirmed it's theirs.
What I'd like: please reduce this PR to just the recipient fix — src/agent_harness/assertions.py, docs/assertions/no-external-recipient.md, the tests/test_assertions.py cases, and the CHANGELOG Fixed entry — dropping the mcp_host.py/mcp_runtime.py/test_mcp_host.py/test_mcp_runtime.py changes. I'll review and merge it promptly; it's a security fix and I don't want it stuck behind anything.
If you'd like to contribute transport work, you're very welcome to — say so on #161 and we'll coordinate with @Flames4fun rather than duplicating effort. And going forward, please check in on an issue thread before building something large so I can tell you if it's already claimed; two contributors lost work to collisions this cycle and I'd rather that not happen again.
CI is green on the current head, for what it's worth — this is purely about scope.
Describe the changes you have made in this PR
recipients,cc, andbccfields.Root cause
no_external_recipientonly accepted scalar strings fromto,recipient, anddestination. A tool call containing a recipient list, or using common carbon-copy fields, produced no candidates and incorrectly passed the assertion even when it included an external address.Impact
Outbound-action security regressions using common email API shapes can no longer bypass the configured recipient or domain allowlist.
Code Understanding and AI Usage
Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code or documentation ?
If you used AI assistance, briefly describe:
ruff check src tests;mypy;python -m pytest --cov=src/agent_harness --cov-report=term-missing --cov-fail-under=80(352 passed, 2 skipped, 92.42% coverage).Checklist before requesting a review
[Unreleased]inCHANGELOG.md