From 7f949bdb0b42327b1dc1d995edbeb500e6884ef1 Mon Sep 17 00:00:00 2001 From: Asti1982 <65121113+Asti1982@users.noreply.github.com> Date: Tue, 12 May 2026 22:05:02 +0200 Subject: [PATCH 1/2] fix: require object JSON for machine passport writes --- node/machine_passport_api.py | 60 +++++++++++-------- ..._machine_passport_event_json_validation.py | 21 +++++++ 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/node/machine_passport_api.py b/node/machine_passport_api.py index 0fdf23d64..2ddb6f26f 100644 --- a/node/machine_passport_api.py +++ b/node/machine_passport_api.py @@ -55,6 +55,24 @@ def get_optional_json_object(): return data, None +def get_required_json_object(): + """Return a required JSON object body or an error response.""" + data = request.get_json(silent=True) + if not data: + return None, (jsonify({ + 'ok': False, + 'error': 'invalid_request', + 'message': 'JSON body required', + }), 400) + if not isinstance(data, dict): + return None, (jsonify({ + 'ok': False, + 'error': 'invalid_request', + 'message': 'JSON object required', + }), 400) + return data, None + + # === Public Read Endpoints === @machine_passport_bp.route('/', methods=['GET']) @@ -210,13 +228,9 @@ def create_passport(): 'message': 'Admin key required', }), 401 - data = request.get_json() - if not data: - return jsonify({ - 'ok': False, - 'error': 'invalid_request', - 'message': 'JSON body required', - }), 400 + data, error = get_required_json_object() + if error: + return error # Validate required fields required = ['name', 'owner_miner_id'] @@ -304,13 +318,9 @@ def update_passport(machine_id: str): 'message': 'Admin key required', }), 401 - data = request.get_json() - if not data: - return jsonify({ - 'ok': False, - 'error': 'invalid_request', - 'message': 'JSON body required', - }), 400 + data, error = get_required_json_object() + if error: + return error success, msg = ledger.update_passport(machine_id, data) @@ -346,8 +356,10 @@ def add_repair_entry(machine_id: str): if not passport: return jsonify({'ok': False, 'error': 'passport_not_found'}), 404 - data = request.get_json() - if not data or 'repair_type' not in data or 'description' not in data: + data, error = get_required_json_object() + if error: + return error + if 'repair_type' not in data or 'description' not in data: return jsonify({ 'ok': False, 'error': 'missing_field', @@ -479,8 +491,10 @@ def add_lineage_note(machine_id: str): if not passport: return jsonify({'ok': False, 'error': 'passport_not_found'}), 404 - data = request.get_json() - if not data or 'event_type' not in data: + data, error = get_required_json_object() + if error: + return error + if 'event_type' not in data: return jsonify({ 'ok': False, 'error': 'missing_field', @@ -614,13 +628,9 @@ def compute_machine_id_endpoint(): Request Body: Hardware fingerprint data (same as attestation) """ - data = request.get_json() - if not data: - return jsonify({ - 'ok': False, - 'error': 'invalid_request', - 'message': 'JSON body required', - }), 400 + data, error = get_required_json_object() + if error: + return error machine_id = compute_machine_id(data) diff --git a/tests/test_machine_passport_event_json_validation.py b/tests/test_machine_passport_event_json_validation.py index e261aea08..3fd91a654 100644 --- a/tests/test_machine_passport_event_json_validation.py +++ b/tests/test_machine_passport_event_json_validation.py @@ -87,3 +87,24 @@ def test_benchmark_route_accepts_object_json(client, ledger): assert response.status_code == 200 assert ledger.benchmark_payload["compute_score"] == 1250.0 assert ledger.benchmark_payload["memory_bandwidth"] == 3200.5 + + +@pytest.mark.parametrize( + ("method", "path", "payload"), + ( + ("post", "/api/machine-passport", ["name", "owner_miner_id"]), + ("put", "/api/machine-passport/machine-1", ["name"]), + ("post", "/api/machine-passport/machine-1/repair-log", ["repair_type", "description"]), + ("post", "/api/machine-passport/machine-1/lineage", ["event_type"]), + ("post", "/api/machine-passport/compute-machine-id", ["not", "object"]), + ), +) +def test_required_machine_passport_routes_reject_non_object_json(client, method, path, payload): + response = getattr(client, method)(path, json=payload) + + assert response.status_code == 400 + assert response.get_json() == { + "ok": False, + "error": "invalid_request", + "message": "JSON object required", + } From 3cd2a63225507334425d4a84d725e2bd3e4875bb Mon Sep 17 00:00:00 2001 From: Asti1982 <65121113+Asti1982@users.noreply.github.com> Date: Tue, 12 May 2026 22:11:16 +0200 Subject: [PATCH 2/2] test: keep wallet network regression offline --- tests/test_wallet_show_regression.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/test_wallet_show_regression.py b/tests/test_wallet_show_regression.py index 4b9a7c1f9..b7d4af30a 100644 --- a/tests/test_wallet_show_regression.py +++ b/tests/test_wallet_show_regression.py @@ -46,7 +46,7 @@ def test_balance_response_parsing(self): balance = resp.get("amount_rtc", resp.get("balance_rtc", resp.get("balance", 0))) assert isinstance(balance, (int, float)) - @patch('urllib.request.urlopen') + @patch('rustchain_cli.urlopen') def test_wallet_show_handles_network_error_gracefully(self, mock_urlopen): """Test that wallet show handles network errors without crashing.""" import urllib.error @@ -54,14 +54,12 @@ def test_wallet_show_handles_network_error_gracefully(self, mock_urlopen): # Simulate network timeout mock_urlopen.side_effect = urllib.error.URLError("timeout") - # Should not raise exception, should handle gracefully - # This is the behavior we want to preserve - try: - # Test the balance fetch logic directly - result = fetch_api("/wallet/balance?miner_id=test") - except Exception as e: - # Expected to fail with network error - assert "timeout" in str(e).lower() or "network" in str(e).lower() + # The CLI reports a controlled network error and exits instead of + # leaking a urllib traceback. + with pytest.raises(SystemExit) as exc_info: + fetch_api("/wallet/balance?miner_id=test") + + assert exc_info.value.code == 1 def test_balance_endpoint_returns_valid_json(self): """Integration test: verify /wallet/balance returns valid JSON."""