Skip to content

Commit 9113c9e

Browse files
committed
Harden validate defaults for remote $ref resolution
1 parent 2aa9f7a commit 9113c9e

File tree

6 files changed

+96
-6
lines changed

6 files changed

+96
-6
lines changed

README.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,18 @@ Usage
5454

5555
.. code-block:: python
5656
57-
validate(instance, schema, cls=OAS32Validator, **kwargs)
57+
validate(instance, schema, cls=OAS32Validator, allow_remote_references=False, **kwargs)
5858
5959
The first argument is always the value you want to validate.
6060
The second argument is always the OpenAPI schema object.
6161
The ``cls`` keyword argument is optional and defaults to ``OAS32Validator``.
6262
Use ``cls`` when you need a specific validator version/behavior.
6363
Common forwarded keyword arguments include ``registry`` (reference context)
6464
and ``format_checker`` (format validation behavior).
65+
By default, ``validate`` uses a local-only empty registry to avoid implicit
66+
remote ``$ref`` retrieval. To resolve external references, pass an explicit
67+
``registry``. Set ``allow_remote_references=True`` only if you explicitly
68+
accept jsonschema's default remote retrieval behavior.
6569

6670
To validate an OpenAPI schema:
6771

SECURITY.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ If you believe you have found a security vulnerability in the repository, please
66

77
**Please do not report security vulnerabilities through public GitHub issues.**
88

9-
Instead, please report them directly to the repository maintainer.
9+
Instead, please report them directly to the repository maintainers or use GitHub's private vulnerability reporting flow
10+
(``Security Advisories``) to report security issues privately:
11+
https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/report-a-vulnerability/privately-reporting-a-security-vulnerability
1012

1113
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
1214

docs/references.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ References
44
You can resolve JSON Schema references by passing registry.
55
The ``validate(instance, schema, ...)`` shortcut resolves local references
66
(``#/...``) against the provided ``schema`` mapping.
7+
By default, the shortcut uses a local-only empty registry and does not
8+
implicitly retrieve remote references.
9+
If needed, ``allow_remote_references=True`` enables jsonschema's default
10+
remote retrieval behavior.
711

812
.. code-block:: python
913

docs/validation.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,24 @@ Validate
1010

1111
.. code-block:: python
1212
13-
validate(instance, schema, cls=OAS32Validator, **kwargs)
13+
validate(instance, schema, cls=OAS32Validator, allow_remote_references=False, **kwargs)
1414
1515
The first argument is always the value you want to validate.
1616
The second argument is always the OpenAPI schema object.
1717
The ``cls`` keyword argument is optional and defaults to ``OAS32Validator``.
1818
Use ``cls`` when you need a specific validator version/behavior.
19+
The ``allow_remote_references`` keyword argument is optional and defaults to
20+
``False``.
1921
Common forwarded keyword arguments include:
2022

21-
- ``registry`` for reference resolution context
23+
- ``registry`` for explicit external reference resolution context
2224
- ``format_checker`` to control format validation behavior
2325

26+
By default, ``validate`` uses a local-only empty registry to avoid implicit
27+
remote ``$ref`` retrieval.
28+
Set ``allow_remote_references=True`` only if you explicitly accept
29+
jsonschema's default remote retrieval behavior.
30+
2431
To validate an OpenAPI schema:
2532

2633
.. code-block:: python

openapi_schema_validator/shortcuts.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from jsonschema.exceptions import best_match
66
from jsonschema.protocols import Validator
7+
from referencing import Registry
78

89
from openapi_schema_validator._dialects import OAS31_BASE_DIALECT_ID
910
from openapi_schema_validator._dialects import OAS32_BASE_DIALECT_ID
@@ -16,6 +17,7 @@ def validate(
1617
schema: Mapping[str, Any],
1718
cls: type[Validator] = OAS32Validator,
1819
*args: Any,
20+
allow_remote_references: bool = False,
1921
**kwargs: Any
2022
) -> None:
2123
"""
@@ -33,8 +35,13 @@ def validate(
3335
(``#/...``) are resolved against this mapping.
3436
cls: Validator class to use. Defaults to ``OAS32Validator``.
3537
*args: Positional arguments forwarded to ``cls`` constructor.
38+
allow_remote_references: If ``True`` and no explicit ``registry`` is
39+
provided, allow jsonschema's default remote reference retrieval
40+
behavior.
3641
**kwargs: Keyword arguments forwarded to ``cls`` constructor
37-
(for example ``registry`` and ``format_checker``).
42+
(for example ``registry`` and ``format_checker``). If omitted,
43+
a local-only empty ``Registry`` is used to avoid implicit remote
44+
reference retrieval.
3845
3946
Raises:
4047
jsonschema.exceptions.SchemaError: If ``schema`` is invalid.
@@ -53,8 +60,12 @@ def validate(
5360
check_openapi_schema(cls, schema_dict)
5461
else:
5562
cls.check_schema(schema_dict)
63+
64+
validator_kwargs = kwargs.copy()
65+
if not allow_remote_references:
66+
validator_kwargs.setdefault("registry", Registry())
5667

57-
validator = cls(schema_dict, *args, **kwargs)
68+
validator = cls(schema_dict, *args, **validator_kwargs)
5869
error = best_match(
5970
validator.evolve(schema=schema_dict).iter_errors(instance)
6071
)

tests/unit/test_shortcut.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from unittest.mock import patch
33

44
import pytest
5+
from referencing import Registry
6+
from referencing import Resource
57

68
from openapi_schema_validator import OAS32Validator
79
from openapi_schema_validator import validate
@@ -56,3 +58,63 @@ def test_oas32_validate_does_not_fetch_remote_metaschemas(schema):
5658
validate({"email": "foo@bar.com"}, schema, cls=OAS32Validator)
5759

5860
urlopen.assert_not_called()
61+
62+
63+
def test_validate_blocks_implicit_remote_http_references_by_default():
64+
schema = {"$ref": "http://example.com/remote-schema.json"}
65+
66+
with patch("urllib.request.urlopen") as urlopen:
67+
with pytest.raises(Exception, match="Unresolvable"):
68+
validate({}, schema)
69+
70+
urlopen.assert_not_called()
71+
72+
73+
def test_validate_blocks_implicit_file_references_by_default():
74+
schema = {"$ref": "file:///etc/hosts"}
75+
76+
with patch("urllib.request.urlopen") as urlopen:
77+
with pytest.raises(Exception, match="Unresolvable"):
78+
validate({}, schema)
79+
80+
urlopen.assert_not_called()
81+
82+
83+
def test_validate_local_references_still_work_by_default():
84+
schema = {"$defs": {"Value": {"type": "integer"}}, "$ref": "#/$defs/Value"}
85+
86+
with patch("urllib.request.urlopen") as urlopen:
87+
result = validate(1, schema)
88+
89+
assert result is None
90+
urlopen.assert_not_called()
91+
92+
93+
def test_validate_honors_explicit_registry():
94+
schema = {
95+
"type": "object",
96+
"properties": {"name": {"$ref": "urn:name-schema"}},
97+
}
98+
name_schema = Resource.from_contents(
99+
{
100+
"$schema": "https://json-schema.org/draft/2020-12/schema",
101+
"type": "string",
102+
}
103+
)
104+
registry = Registry().with_resources(
105+
[("urn:name-schema", name_schema)],
106+
)
107+
108+
result = validate({"name": "John"}, schema, registry=registry)
109+
110+
assert result is None
111+
112+
113+
def test_validate_can_allow_implicit_remote_references():
114+
schema = {"$ref": "http://example.com/remote-schema.json"}
115+
116+
with patch("urllib.request.urlopen") as urlopen:
117+
with pytest.raises(Exception):
118+
validate({}, schema, allow_remote_references=True)
119+
120+
assert urlopen.called

0 commit comments

Comments
 (0)