From 488ba642c64aed6731a94c5ef89992b5753a70bf Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Wed, 11 Feb 2026 11:08:35 -0600 Subject: [PATCH] Delete autogpt_platform/backend/test_disabled_block_bypass.py --- .../backend/test_disabled_block_bypass.py | 295 ------------------ 1 file changed, 295 deletions(-) delete mode 100644 autogpt_platform/backend/test_disabled_block_bypass.py diff --git a/autogpt_platform/backend/test_disabled_block_bypass.py b/autogpt_platform/backend/test_disabled_block_bypass.py deleted file mode 100644 index 97f024ba2e..0000000000 --- a/autogpt_platform/backend/test_disabled_block_bypass.py +++ /dev/null @@ -1,295 +0,0 @@ -""" -Test: GHSA-4crw-9p35-9x54 - Disabled block bypass via graph creation - -Validates that the fix prevents disabled blocks (specifically BlockInstallationBlock) -from being used in graphs. Tests all three attack vectors: - -1. Creating a graph with a disabled block -2. Executing a direct block call on a disabled block -3. Executing a graph containing a disabled block (if one existed pre-fix) - -Usage against a RUNNING server: - python test_disabled_block_bypass.py --base-url http://localhost:8006 --token - -Usage as a pytest unit test (no server needed): - poetry run pytest test_disabled_block_bypass.py -xvs -""" - -import argparse -import sys -import uuid - -import pytest - -# ── Block IDs of all currently disabled blocks ── -BLOCK_INSTALLATION_BLOCK_ID = "45e78db5-03e9-447f-9395-308d712f5f08" - - -# ═══════════════════════════════════════════════════════════════════════════════ -# UNIT TESTS — validate the fix at the model layer (no server needed) -# Run with: poetry run pytest test_disabled_block_bypass.py -xvs -# ═══════════════════════════════════════════════════════════════════════════════ - - -class TestDisabledBlockGraphValidation: - """Test that graph validation rejects disabled blocks.""" - - @staticmethod - def _make_graph_with_block(block_id: str, input_default: dict | None = None): - """Helper: build a GraphModel containing a single node with the given block.""" - from backend.data.graph import Graph, Node, make_graph_model - - node_id = str(uuid.uuid4()) - graph = Graph( - name=f"test-disabled-{block_id[:8]}", - description="GHSA-4crw-9p35-9x54 test", - nodes=[ - Node( - id=node_id, - block_id=block_id, - input_default=input_default or {}, - ) - ], - links=[], - ) - return make_graph_model(graph, user_id="test-user") - - def test_graph_with_block_installation_block_is_rejected(self): - """ - GHSA-4crw-9p35-9x54: The core vulnerability. - Creating a graph with BlockInstallationBlock (disabled=True) must fail validation. - """ - graph_model = self._make_graph_with_block( - BLOCK_INSTALLATION_BLOCK_ID, - input_default={"code": "print('should never run')"}, - ) - - with pytest.raises(ValueError, match="disabled"): - graph_model.validate_graph(for_run=False) - - def test_graph_with_block_installation_block_rejected_for_run(self): - """Same test but with for_run=True (execution-time validation).""" - graph_model = self._make_graph_with_block( - BLOCK_INSTALLATION_BLOCK_ID, - input_default={"code": "print('should never run')"}, - ) - - with pytest.raises(ValueError, match="disabled"): - graph_model.validate_graph(for_run=True) - - def test_all_disabled_blocks_are_rejected(self): - """Every block with disabled=True must be rejected in graph validation.""" - from backend.data.block import get_blocks - - blocks = get_blocks() - disabled_blocks = { - bid: block for bid, block in blocks.items() if block().disabled - } - - assert len(disabled_blocks) > 0, "Expected at least one disabled block" - print(f"\nFound {len(disabled_blocks)} disabled blocks to test:") - - for block_id, block_cls in disabled_blocks.items(): - block_inst = block_cls() - print(f" - {block_inst.name} ({block_id})") - - graph_model = self._make_graph_with_block(block_id) - - with pytest.raises(ValueError, match="disabled"): - graph_model.validate_graph(for_run=False) - - print(f"All {len(disabled_blocks)} disabled blocks correctly rejected!") - - def test_enabled_block_not_rejected_as_disabled(self): - """Sanity check: an enabled block should NOT be rejected for being disabled.""" - from backend.data.block import get_blocks - - blocks = get_blocks() - # Find an enabled block - enabled_entry = next( - (bid, b) for bid, b in blocks.items() if not b().disabled - ) - block_id, _ = enabled_entry - - graph_model = self._make_graph_with_block(block_id) - - # Should NOT raise "disabled" error. - # May raise other validation errors (missing inputs etc) which is fine. - try: - graph_model.validate_graph(for_run=False) - except ValueError as e: - assert "disabled" not in str(e).lower(), ( - f"Enabled block was incorrectly rejected as disabled: {e}" - ) - - def test_direct_block_execution_check(self): - """Verify BlockInstallationBlock is flagged disabled in the registry.""" - from backend.data.block import get_block - - block = get_block(BLOCK_INSTALLATION_BLOCK_ID) - assert block is not None, "BlockInstallationBlock not found in registry" - assert block.disabled is True, "BlockInstallationBlock should be disabled" - - def test_validate_graph_get_errors_returns_disabled_error(self): - """Test the structured error reporting path also catches disabled blocks.""" - graph_model = self._make_graph_with_block( - BLOCK_INSTALLATION_BLOCK_ID, - input_default={"code": "print('nope')"}, - ) - - # validate_graph_get_errors should return errors (not raise) for non-fatal issues, - # but disabled blocks raise ValueError directly in _validate_graph_get_errors - with pytest.raises(ValueError, match="disabled"): - graph_model.validate_graph_get_errors(for_run=False) - - -# ═══════════════════════════════════════════════════════════════════════════════ -# INTEGRATION TESTS — validate against a running server -# Run with: python test_disabled_block_bypass.py --base-url URL --token JWT -# ═══════════════════════════════════════════════════════════════════════════════ - - -def run_against_server(base_url: str, token: str): - """ - Hit the actual API endpoints to confirm the fix works end-to-end. - Requires a running backend. - """ - import requests - - headers = { - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - } - - results = [] - - print("=" * 70) - print("GHSA-4crw-9p35-9x54: Disabled Block Bypass - Integration Test") - print("=" * 70) - - # ── Test 1: Create graph with disabled BlockInstallationBlock ── - print("\n[TEST 1] POST /api/v1/graphs — graph with BlockInstallationBlock") - - node_id = str(uuid.uuid4()) - payload = { - "graph": { - "name": f"security-test-{uuid.uuid4().hex[:8]}", - "description": "GHSA-4crw-9p35-9x54 validation test", - "nodes": [ - { - "id": node_id, - "block_id": BLOCK_INSTALLATION_BLOCK_ID, - "input_default": { - "code": "# This should never be accepted\nprint('hello')" - }, - "metadata": {"position": {"x": 0, "y": 0}}, - } - ], - "links": [], - } - } - - resp = requests.post(f"{base_url}/api/v1/graphs", headers=headers, json=payload) - - if resp.status_code in (400, 422, 500): - print(f" PASS - Server rejected graph creation (HTTP {resp.status_code})") - try: - print(f" Detail: {resp.json().get('detail', resp.text)[:300]}") - except Exception: - print(f" Response: {resp.text[:300]}") - results.append(True) - elif resp.status_code in (200, 201): - print(f" FAIL - Server ACCEPTED the graph! (HTTP {resp.status_code})") - print(" >>> VULNERABILITY IS NOT FIXED <<<") - try: - graph_id = resp.json().get("id") - if graph_id: - requests.delete( - f"{base_url}/api/v1/graphs/{graph_id}", headers=headers - ) - print(f" (Cleaned up graph {graph_id})") - except Exception: - pass - results.append(False) - else: - print(f" WARN - Unexpected HTTP {resp.status_code}: {resp.text[:300]}") - results.append(None) - - # ── Test 2: Direct block execution of disabled block ── - print(f"\n[TEST 2] POST /api/v1/blocks/{BLOCK_INSTALLATION_BLOCK_ID}/execute") - - block_payload = { - "data": {"code": "# Should be rejected\nprint('hello')"}, - "input": {"code": "# Should be rejected\nprint('hello')"}, - } - - resp = requests.post( - f"{base_url}/api/v1/blocks/{BLOCK_INSTALLATION_BLOCK_ID}/execute", - headers=headers, - json=block_payload, - ) - - if resp.status_code == 403: - print(f" PASS - Server rejected direct execution (HTTP 403)") - try: - print(f" Detail: {resp.json().get('detail', '')}") - except Exception: - pass - results.append(True) - elif resp.status_code == 200: - print(f" FAIL - Server EXECUTED the disabled block!") - print(" >>> VULNERABILITY IS NOT FIXED <<<") - results.append(False) - else: - print(f" INFO - HTTP {resp.status_code}: {resp.text[:300]}") - results.append(None) - - # ── Test 3: External API direct block execution ── - print( - f"\n[TEST 3] POST /api/external/v1/blocks/{BLOCK_INSTALLATION_BLOCK_ID}/execute" - ) - - resp = requests.post( - f"{base_url}/api/external/v1/blocks/{BLOCK_INSTALLATION_BLOCK_ID}/execute", - headers=headers, - json=block_payload, - ) - - if resp.status_code in (403, 401): - print(f" PASS - Server rejected (HTTP {resp.status_code})") - results.append(True) - elif resp.status_code == 200: - print(f" FAIL - External API EXECUTED the disabled block!") - results.append(False) - else: - print(f" INFO - HTTP {resp.status_code} (may need API key auth for this endpoint)") - results.append(None) - - # ── Summary ── - passed = sum(1 for r in results if r is True) - failed = sum(1 for r in results if r is False) - - print("\n" + "=" * 70) - if failed > 0: - print(f"RESULT: {failed} test(s) FAILED — vulnerability may not be fixed!") - else: - print(f"RESULT: {passed}/{len(results)} tests passed — fix is working.") - print("=" * 70) - - return failed == 0 - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Test GHSA-4crw-9p35-9x54 fix (disabled block bypass)" - ) - parser.add_argument( - "--base-url", - default="http://localhost:8006", - help="Backend base URL (default: http://localhost:8006)", - ) - parser.add_argument("--token", required=True, help="JWT bearer token") - args = parser.parse_args() - - success = run_against_server(args.base_url, args.token) - sys.exit(0 if success else 1)