SA-4 SSRF protections implemented.
This commit is contained in:
+15
-13
@@ -648,19 +648,20 @@ def test_link_submission_posts_to_enabled_mastodon_plugin():
|
||||
try:
|
||||
headers = login_headers()
|
||||
base_url = f'http://127.0.0.1:{server.server_port}'
|
||||
assert client.put('/api/user/plugins/mastodon', headers=headers, json={
|
||||
'instance': base_url,
|
||||
'access_token': 'test-token',
|
||||
'post_prefix': 'From my #LinkLog: ',
|
||||
}).status_code == 200
|
||||
assert client.put('/api/admin/plugins/mastodon', headers=headers, json={'enabled': True}).status_code == 200
|
||||
with patch('backend.app.services.plugin_manager.validate_public_instance', return_value=base_url):
|
||||
assert client.put('/api/user/plugins/mastodon', headers=headers, json={
|
||||
'instance': base_url,
|
||||
'access_token': 'test-token',
|
||||
'post_prefix': 'From my #LinkLog: ',
|
||||
}).status_code == 200
|
||||
assert client.put('/api/admin/plugins/mastodon', headers=headers, json={'enabled': True}).status_code == 200
|
||||
|
||||
response = client.post('/api/links', headers=headers, json={
|
||||
'title': 'A useful page',
|
||||
'url': 'https://example.com/useful',
|
||||
'comment': 'Worth sharing',
|
||||
'tags': ['#python', '#web'],
|
||||
})
|
||||
response = client.post('/api/links', headers=headers, json={
|
||||
'title': 'A useful page',
|
||||
'url': 'https://example.com/useful',
|
||||
'comment': 'Worth sharing',
|
||||
'tags': ['#python', '#web'],
|
||||
})
|
||||
|
||||
assert response.status_code == 201
|
||||
assert received['path'] == '/api/v1/statuses'
|
||||
@@ -683,7 +684,8 @@ def test_mastodon_post_without_title_omits_source_line():
|
||||
plugin = MastodonPlugin()
|
||||
plugin.initialize({'instance': 'https://mastodon.example', 'access_token': 'test-token'})
|
||||
|
||||
with patch('backend.app.services.plugin_manager.urlopen', return_value=response) as open_url:
|
||||
with patch('backend.app.services.plugin_manager.open_no_redirect', return_value=response) as open_url, \
|
||||
patch('backend.app.services.plugin_manager.validate_public_instance', return_value='https://mastodon.example'):
|
||||
result = plugin.handle_event({
|
||||
'url': 'https://example.com/useful',
|
||||
'title': '',
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
## Copyright © 2026 Olaf Kolkman
|
||||
## SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from unittest.mock import patch
|
||||
from urllib.error import HTTPError
|
||||
from urllib.request import Request
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.app.services.mastodon_security import open_no_redirect, validate_public_instance
|
||||
|
||||
|
||||
@pytest.mark.parametrize('instance', [
|
||||
'http://mastodon.example',
|
||||
'https://127.0.0.1',
|
||||
'https://[::ffff:127.0.0.1]',
|
||||
'https://user:password@mastodon.example',
|
||||
])
|
||||
def test_mastodon_instance_rejects_unsafe_urls(instance):
|
||||
with pytest.raises(ValueError):
|
||||
validate_public_instance(instance)
|
||||
|
||||
|
||||
def test_mastodon_instance_rejects_private_dns_result():
|
||||
with patch('backend.app.services.mastodon_security.socket.getaddrinfo', return_value=[(2, 1, 6, '', ('10.0.0.5', 443))]):
|
||||
with pytest.raises(ValueError, match='public IP'):
|
||||
validate_public_instance('https://mastodon.example')
|
||||
|
||||
|
||||
def test_mastodon_outbound_redirects_are_rejected():
|
||||
request = Request('https://mastodon.example/api/v1/statuses')
|
||||
with patch('backend.app.services.mastodon_security.NO_REDIRECT_OPENER.open', side_effect=HTTPError(request.full_url, 302, 'Redirects are not allowed', {}, None)):
|
||||
with pytest.raises(HTTPError, match='Redirects are not allowed'):
|
||||
open_no_redirect(request)
|
||||
Reference in New Issue
Block a user