sdcvalidator 4.5.1__tar.gz → 4.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/PKG-INFO +1 -1
  2. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/pyproject.toml +1 -1
  3. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/__init__.py +1 -1
  4. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/mcp_server.py +23 -3
  5. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/PKG-INFO +1 -1
  6. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_mcp_server.py +52 -0
  7. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/LICENSE +0 -0
  8. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/README.md +0 -0
  9. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/setup.cfg +0 -0
  10. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/cli.py +0 -0
  11. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/constants.py +0 -0
  12. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/converters.py +0 -0
  13. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/error_classifier.py +0 -0
  14. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/error_mapper.py +0 -0
  15. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/exceptions.py +0 -0
  16. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/instance_modifier.py +0 -0
  17. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/schema_checker.py +0 -0
  18. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/validator.py +0 -0
  19. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator/xsd11_restriction.py +0 -0
  20. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/SOURCES.txt +0 -0
  21. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/dependency_links.txt +0 -0
  22. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/entry_points.txt +0 -0
  23. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/requires.txt +0 -0
  24. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/top_level.txt +0 -0
  25. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_converters.py +0 -0
  26. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_error_classifier.py +0 -0
  27. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_error_mapper.py +0 -0
  28. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_instance_modifier.py +0 -0
  29. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_recovery.py +0 -0
  30. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_schema_checker.py +0 -0
  31. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_validator.py +0 -0
  32. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_xsd11_restriction.py +0 -0
  33. {sdcvalidator-4.5.1 → sdcvalidator-4.5.2}/tests/test_xsi_type.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sdcvalidator
3
- Version: 4.5.1
3
+ Version: 4.5.2
4
4
  Summary: SDC4 structural validator with ExceptionalValue recovery — thin wrapper over xmlschema
5
5
  Author: Axius SDC, Inc.
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sdcvalidator"
7
- version = "4.5.1"
7
+ version = "4.5.2"
8
8
  description = "SDC4 structural validator with ExceptionalValue recovery — thin wrapper over xmlschema"
9
9
  license = "Apache-2.0"
10
10
  requires-python = ">=3.10"
@@ -14,7 +14,7 @@ Thin wrapper over xmlschema with two-tier error classification and the SDC4
14
14
  "quarantine-and-tag" recovery pattern (ISO 21090 ExceptionalValues).
15
15
  """
16
16
 
17
- __version__ = "4.5.1"
17
+ __version__ = "4.5.2"
18
18
 
19
19
  # etree_tostring is re-exported from xmlschema so callers can serialize the
20
20
  # ElementTree returned by validate_with_recovery() without importing xmlschema
@@ -36,7 +36,7 @@ from sdcvalidator.schema_checker import validate_sdc4_schema_compliance
36
36
  JSONRPC_VERSION = "2.0"
37
37
  MCP_PROTOCOL_VERSION = "2024-11-05"
38
38
  SERVER_NAME = "sdcvalidator"
39
- SERVER_VERSION = "4.5.1"
39
+ SERVER_VERSION = "4.5.2"
40
40
 
41
41
  # Validator cache: schema_path -> SDC4Validator
42
42
  _validators: dict[str, SDC4Validator] = {}
@@ -201,10 +201,24 @@ def _jsonrpc_error(id: Any, code: int, message: str, data: Any = None) -> dict:
201
201
  return {"jsonrpc": JSONRPC_VERSION, "id": id, "error": error}
202
202
 
203
203
 
204
- def _handle_request(request: dict) -> dict | None:
204
+ def _handle_request(request) -> dict | None:
205
+ # ★ Attacker-reachable input over stdio with no authentication. A JSON
206
+ # value that is not an object, or an object whose params is not one,
207
+ # used to raise AttributeError and kill the server (VSL rollout R9).
208
+ # Every shape the protocol does not allow is answered with -32600.
209
+ if not isinstance(request, dict):
210
+ return _jsonrpc_error(None, -32600, "Invalid Request: a JSON-RPC request is an object")
211
+ req_id = request.get("id")
212
+ if not isinstance(req_id, (str, int, type(None))):
213
+ return _jsonrpc_error(None, -32600, "Invalid Request: id must be a string, a number or null")
205
214
  method = request.get("method", "")
215
+ if not isinstance(method, str):
216
+ return _jsonrpc_error(req_id, -32600, "Invalid Request: method must be a string")
206
217
  params = request.get("params", {})
207
- req_id = request.get("id")
218
+ if params is None:
219
+ params = {}
220
+ if not isinstance(params, dict):
221
+ return _jsonrpc_error(req_id, -32600, "Invalid Request: params must be an object")
208
222
 
209
223
  if method == "initialize":
210
224
  result = {
@@ -228,6 +242,12 @@ def _handle_request(request: dict) -> dict | None:
228
242
  elif method == "tools/call":
229
243
  tool_name = params.get("name", "")
230
244
  tool_args = params.get("arguments", {})
245
+ if tool_args is None:
246
+ tool_args = {}
247
+ if not isinstance(tool_name, str) or not isinstance(tool_args, dict):
248
+ return _jsonrpc_error(
249
+ req_id, -32602, "Invalid params: name must be a string and arguments an object"
250
+ )
231
251
 
232
252
  handler = TOOL_HANDLERS.get(tool_name)
233
253
  if handler is None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sdcvalidator
3
- Version: 4.5.1
3
+ Version: 4.5.2
4
4
  Summary: SDC4 structural validator with ExceptionalValue recovery — thin wrapper over xmlschema
5
5
  Author: Axius SDC, Inc.
6
6
  License-Expression: Apache-2.0
@@ -137,3 +137,55 @@ class TestConsistentSerialization:
137
137
  assert content[0]["type"] == "text"
138
138
  parsed = json.loads(content[0]["text"])
139
139
  assert parsed is not None
140
+
141
+ class TestMalformedRequestsDoNotKillTheServer:
142
+ """
143
+ VSL rollout inventory R9. Six inputs that are valid JSON but not a
144
+ JSON-RPC request raised AttributeError in _handle_request and ended the
145
+ process. Each is now answered with -32600 (or -32602 for bad tool params)
146
+ and the loop keeps reading.
147
+ """
148
+
149
+ @pytest.mark.parametrize("payload", [[], ["tools/list"], "tools/list", 7, None, True])
150
+ def test_a_non_object_request_is_invalid_request(self, payload):
151
+ response = _handle_request(payload)
152
+ assert response["error"]["code"] == -32600
153
+ assert response["id"] is None
154
+
155
+ @pytest.mark.parametrize("params", [[], "x", 3, True])
156
+ def test_non_object_params_are_invalid_request(self, params):
157
+ response = _handle_request({"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": params})
158
+ assert response["error"]["code"] == -32600
159
+ assert response["id"] == 1
160
+
161
+ def test_null_params_are_treated_as_empty(self):
162
+ response = _handle_request({"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": None})
163
+ assert "result" in response
164
+
165
+ def test_a_non_string_method_is_invalid_request(self):
166
+ response = _handle_request({"jsonrpc": "2.0", "id": 1, "method": ["tools/list"]})
167
+ assert response["error"]["code"] == -32600
168
+
169
+ @pytest.mark.parametrize("arguments", [[], "x", 3])
170
+ def test_non_object_tool_arguments_are_invalid_params(self, arguments):
171
+ response = _handle_request({
172
+ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
173
+ "params": {"name": list(TOOL_HANDLERS)[0], "arguments": arguments},
174
+ })
175
+ assert response["error"]["code"] == -32602
176
+
177
+ def test_the_stdio_loop_survives_every_one_of_them(self, monkeypatch, capsys):
178
+ import io
179
+ import sys
180
+
181
+ from sdcvalidator.mcp_server import run_stdio
182
+
183
+ lines = ["[]", "\"tools/list\"", "7", "null",
184
+ json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": []}),
185
+ json.dumps({"jsonrpc": "2.0", "id": 2, "method": "ping"})]
186
+ monkeypatch.setattr(sys, "stdin", io.StringIO("\n".join(lines) + "\n"))
187
+ run_stdio()
188
+ out = [json.loads(l) for l in capsys.readouterr().out.strip().splitlines()]
189
+ assert len(out) == 6
190
+ assert [o.get("error", {}).get("code") for o in out[:5]] == [-32600] * 5
191
+ assert out[5] == {"jsonrpc": "2.0", "id": 2, "result": {}}
File without changes
File without changes
File without changes