Skip to content

Commit 7151d75

Browse files
committed
test(jsonld): add tests for SSRF protection in document loader
Adds unit tests for the JSON-LD document loader to verify the new Server-Side Request Forgery (SSRF) protection mechanism. The tests cover the following scenarios: - Attempts to load contexts from loopback and private network addresses are blocked. - Unresolvable hostnames are handled correctly. - Loading from a public address is permitted.
1 parent f306f28 commit 7151d75

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
2+
import socket
3+
from unittest.mock import MagicMock, patch
4+
5+
import pytest
6+
from pyld import jsonld
7+
8+
from apmodel._jsonld import create_document_loader
9+
10+
11+
def test_load_context_from_loopback_address_raises_error():
12+
"""
13+
Tests that attempting to load a JSON-LD context from a loopback address
14+
(127.0.0.1) raises a JsonLdError to prevent SSRF vulnerabilities.
15+
"""
16+
loader = create_document_loader()
17+
url = "http://localhost/context.jsonld"
18+
19+
with patch("socket.gethostbyname", return_value="127.0.0.1"):
20+
with pytest.raises(jsonld.JsonLdError) as excinfo:
21+
loader(url)
22+
assert "Loading from local or private network is not allowed." in str(
23+
excinfo.value
24+
)
25+
26+
27+
def test_load_context_from_private_network_address_raises_error():
28+
"""
29+
Tests that attempting to load a JSON-LD context from a private network
30+
address (192.168.1.1) raises a JsonLdError.
31+
"""
32+
loader = create_document_loader()
33+
url = "http://example.internal/context.jsonld"
34+
35+
with patch("socket.gethostbyname", return_value="192.168.1.1"):
36+
with pytest.raises(jsonld.JsonLdError) as excinfo:
37+
loader(url)
38+
assert "Loading from local or private network is not allowed." in str(
39+
excinfo.value
40+
)
41+
42+
43+
def test_load_context_from_unresolvable_hostname_raises_error():
44+
"""
45+
Tests that attempting to load a JSON-LD context from a hostname that
46+
cannot be resolved raises a JsonLdError.
47+
"""
48+
loader = create_document_loader()
49+
url = "http://this-is-not-a-real-hostname/context.jsonld"
50+
51+
with patch(
52+
"socket.gethostbyname", side_effect=socket.gaierror("not found")
53+
):
54+
with pytest.raises(jsonld.JsonLdError) as excinfo:
55+
loader(url)
56+
assert "Could not resolve hostname." in str(excinfo.value)
57+
58+
59+
@patch("pyld.documentloader.requests.requests_document_loader")
60+
def test_load_context_from_public_address_succeeds(mock_requests_loader_factory):
61+
"""
62+
Tests that loading a JSON-LD context from a public address is allowed
63+
and proceeds as expected.
64+
"""
65+
loader = create_document_loader()
66+
url = "http://schema.org/context.jsonld"
67+
expected_doc = {"@context": "http://schema.org"}
68+
69+
# Mock the inner loader function that the factory returns
70+
mock_inner_loader = MagicMock()
71+
mock_inner_loader.return_value = {
72+
"contextUrl": None,
73+
"documentUrl": url,
74+
"document": expected_doc,
75+
}
76+
# Configure the factory to return our mock loader
77+
mock_requests_loader_factory.return_value = mock_inner_loader
78+
79+
with patch("socket.gethostbyname", return_value="8.8.8.8"):
80+
# Re-create the loader now that the factory is mocked correctly
81+
loader = create_document_loader()
82+
result = loader(url)
83+
assert result["document"] == expected_doc
84+
85+
# Check that the inner loader was called
86+
mock_inner_loader.assert_called_once()
87+

0 commit comments

Comments
 (0)