Skip to content

Python: Harden HttpPlugin request validation#13969

Draft
SergeyMenshykh wants to merge 1 commit intomicrosoft:mainfrom
SergeyMenshykh:fix/python-http-plugin-ssrf
Draft

Python: Harden HttpPlugin request validation#13969
SergeyMenshykh wants to merge 1 commit intomicrosoft:mainfrom
SergeyMenshykh:fix/python-http-plugin-ssrf

Conversation

@SergeyMenshykh
Copy link
Copy Markdown
Member

Improve input validation and request handling in the Python HttpPlugin.

Changes

  • Add explicit opt-in for unrestricted domain access
  • Tighten URL validation logic
  • Improve redirect handling when domain restrictions are configured
  • Add regression tests

- Deny-all by default: add allow_all_domains flag requiring explicit opt-in
- Block redirects when allowed_domains is set to prevent domain bypass
- Add URL scheme validation (http/https only)
- Fix empty hostname bypass and redirect logic edge cases

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 8, 2026 10:11
@SergeyMenshykh SergeyMenshykh requested a review from a team as a code owner May 8, 2026 10:11
@moonbox3 moonbox3 added the python Pull requests for the Python Semantic Kernel label May 8, 2026
@SergeyMenshykh SergeyMenshykh self-assigned this May 8, 2026
@SergeyMenshykh SergeyMenshykh marked this pull request as draft May 8, 2026 10:12
@SergeyMenshykh SergeyMenshykh moved this to In Review in Agent Framework May 8, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the Python HttpPlugin by making outbound HTTP requests opt-in (either via an allowlist of domains or an explicit “allow all domains” switch), tightening URL validation to only permit http/https, and adjusting redirect behavior to reduce SSRF bypass risk.

Changes:

  • Change default behavior to block all requests unless allowed_domains is provided or allow_all_domains=True.
  • Add scheme validation (only http/https) and disable redirects when domain restrictions are configured.
  • Update and expand unit tests to cover the new security behavior and regressions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
python/semantic_kernel/core_plugins/http_plugin.py Enforces default-deny URL policy, restricts URL schemes, and controls redirect behavior based on domain configuration.
python/tests/unit/core_plugins/test_http_plugin.py Updates existing tests for the opt-in behavior and adds regression/security tests for default-deny, scheme filtering, and redirect handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +33 to +34
- When ``allowed_domains`` is set, HTTP redirects are disabled to prevent
redirect-based domain bypass (SSRF).
@@ -179,8 +179,8 @@ async def test_allowed_domains_case_insensitive():


async def test_allowed_domains_none_allows_all():
Comment on lines 102 to +107
self._validate_url(url)

async with aiohttp.ClientSession() as session, session.get(url, raise_for_status=True) as response:
allow_redirects = self.allow_all_domains or self.allowed_domains is None
async with (
aiohttp.ClientSession() as session,
session.get(url, raise_for_status=True, allow_redirects=allow_redirects) as response,
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 88%

✓ Correctness

The PR correctly implements default-deny, scheme validation, and an explicit allow_all_domains opt-in. The redirect-disable logic for domain-restricted configurations is sound. However, the allow_redirects expression in all four HTTP methods contains a vestigial or self.allowed_domains is None clause that carries forward old semantics (where allowed_domains=None meant 'allow all'), contradicting the new default-deny model. While currently unreachable because _validate_url blocks requests first, this expression would incorrectly enable redirects if the deny-all path is ever relaxed.

✓ Security Reliability

This PR is well-designed security hardening for the HttpPlugin. The default-deny posture, scheme validation (blocking file:/, ftp://, etc.), redirect disabling with domain restrictions (SSRF protection), and explicit opt-in for unrestricted access are all sound security improvements. The redirect logic (allow_redirects = self.allow_all_domains or self.allowed_domains is None) is correct across all configuration combinations — in the default case where both are falsy/None, _validate_url blocks before the redirect setting is ever used. No security or reliability issues found.

✓ Test Coverage

The PR adds strong regression tests for the new security defaults (deny-all, scheme blocking, redirect disabling). However, there is an asymetry in redirect-behavior coverage: the 'redirects disabled with allowed_domains' path is verified for all four HTTP methods (GET/POST/PUT/DELETE), but the 'redirects allowed with allow_all_domains=True' path is only verified for GET. Since each method independently computes allow_redirects on a separate line, a typo or copy-paste error in POST/PUT/DELETE would go undetected. This is a non-blocking gap.

✗ Design Approach

The redirect hardening mostly goes in the right direction, but the new flag precedence leaves one SSRF bypass open: when both allowed_domains and allow_all_domains=True are set, the request methods re-enable redirect following even though the class-level security contract says redirects must be disabled whenever allowed_domains is configured.


Automated review by SergeyMenshykh's agents

await plugin.get("https://example.com/path")

_, kwargs = mock_get.call_args
assert kwargs["allow_redirects"] is True
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.

This test only verifies allow_redirects=True for GET. The corresponding 'redirects disabled' tests cover all four methods (GET/POST/PUT/DELETE) individually, but the 'redirects allowed' path only covers GET. Each HTTP method in http_plugin.py computes allow_redirects on its own line, so a copy-paste error in post(), put(), or delete() would go undetected. Consider adding parametrized or individual tests asserting allow_redirects is True for the other three methods when allow_all_domains=True.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests for the Python Semantic Kernel

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

3 participants