diff --git a/.github/workflows/integration_e2e_tests_inference_sdk_x86.yml b/.github/workflows/integration_e2e_tests_inference_sdk_x86.yml index 4c50651b45..09a8bcea3e 100644 --- a/.github/workflows/integration_e2e_tests_inference_sdk_x86.yml +++ b/.github/workflows/integration_e2e_tests_inference_sdk_x86.yml @@ -105,7 +105,7 @@ jobs: run: | make create_isolation_wheels pip install --find-links "${{ github.workspace }}/dist" \ - "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" \ + "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" \ "${{ github.workspace }}"/dist/inference_sdk-*.whl \ "${{ github.workspace }}"/dist/inference_models-*.whl diff --git a/.github/workflows/integration_tests_inference_cli_depending_on_inference_x86.yml b/.github/workflows/integration_tests_inference_cli_depending_on_inference_x86.yml index c73b1a36e2..f83701db9a 100644 --- a/.github/workflows/integration_tests_inference_cli_depending_on_inference_x86.yml +++ b/.github/workflows/integration_tests_inference_cli_depending_on_inference_x86.yml @@ -52,6 +52,6 @@ jobs: - name: ๐Ÿ—๏ธ Build and install roboflow-workflows wheel run: | make create_workflows_wheel - pip install --find-links "${{ github.workspace }}/dist" "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" + pip install --find-links "${{ github.workspace }}/dist" "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" - name: ๐Ÿงช Integration Tests of Inference CLI run: RUN_TESTS_WITH_INFERENCE_PACKAGE=True INFERENCE_CLI_TESTS_API_KEY=${{ secrets.LOAD_TEST_PRODUCTION_API_KEY }} python -m pytest tests/inference_cli/integration_tests/test_workflows.py diff --git a/.github/workflows/integration_tests_inference_models.yml b/.github/workflows/integration_tests_inference_models.yml index 377a1ee99b..1c06c89ecf 100644 --- a/.github/workflows/integration_tests_inference_models.yml +++ b/.github/workflows/integration_tests_inference_models.yml @@ -53,7 +53,7 @@ jobs: - name: ๐Ÿ—๏ธ Build and install roboflow-workflows wheel run: | make create_workflows_wheel - pip install --find-links "${{ github.workspace }}/dist" "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl" + pip install --find-links "${{ github.workspace }}/dist" "${{ github.workspace }}"/dist/roboflow_workflows-*.whl - name: ๐Ÿงช Integration Tests of Inference models timeout-minutes: 45 run: USE_INFERENCE_MODELS=False MAX_BATCH_SIZE=6 python -m pytest tests/inference/models_predictions_tests diff --git a/.github/workflows/integration_tests_workflows_tensor_native_x86.yml b/.github/workflows/integration_tests_workflows_tensor_native_x86.yml index 9fd12db4db..bbe40d32f9 100644 --- a/.github/workflows/integration_tests_workflows_tensor_native_x86.yml +++ b/.github/workflows/integration_tests_workflows_tensor_native_x86.yml @@ -67,7 +67,7 @@ jobs: - name: ๐Ÿ—๏ธ Build and install roboflow-workflows wheel (with enterprise extra) run: | make create_workflows_wheel - pip install --find-links "${{ github.workspace }}/dist" "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" + pip install --find-links "${{ github.workspace }}/dist" "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" - name: ๐Ÿงช Integration Tests of Workflows (tensor-native) # Tensor-native path only: ENABLE_TENSOR_DATA_REPRESENTATION is AND-gated with # USE_INFERENCE_MODELS, which defaults to True on Linux runners, so it is not set here. diff --git a/.github/workflows/integration_tests_workflows_x86.yml b/.github/workflows/integration_tests_workflows_x86.yml index e4c8d53b9f..350652dc69 100644 --- a/.github/workflows/integration_tests_workflows_x86.yml +++ b/.github/workflows/integration_tests_workflows_x86.yml @@ -103,7 +103,7 @@ jobs: - name: ๐Ÿ—๏ธ Build and install roboflow-workflows wheel (with enterprise extra) run: | make create_workflows_wheel - pip install --find-links "${{ github.workspace }}/dist" "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" + pip install --find-links "${{ github.workspace }}/dist" "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" - name: ๐Ÿงช Integration Tests of Workflows env: POSTGRESQL_TEST_DSN: "host=localhost port=5432 dbname=postgres user=postgres password=postgres-test-only sslmode=disable" diff --git a/.github/workflows/test_package_install_inference.yml b/.github/workflows/test_package_install_inference.yml index 72a245a473..3ca538471b 100644 --- a/.github/workflows/test_package_install_inference.yml +++ b/.github/workflows/test_package_install_inference.yml @@ -54,7 +54,7 @@ jobs: ${{ runner.os }}-pip-${{ matrix.python-version }}- - name: ๐Ÿ“ฆ Installing `inference` package... run: | - pip install --find-links dist/ ./dist/inference-*-py3-none-any.whl ./dist/inference_sdk-*-py3-none-any.whl ./dist/inference_models-*-py3-none-any.whl "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" + pip install --find-links dist/ ./dist/inference-*-py3-none-any.whl ./dist/inference_sdk-*-py3-none-any.whl ./dist/inference_models-*-py3-none-any.whl "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" # Dependencies were resolved above; now replace any same-version # build-environment installs with the exact checkout artifacts. pip install --force-reinstall --no-deps ./dist/roboflow_workflows-*.whl ./dist/inference_sdk-*.whl ./dist/inference_models-*.whl diff --git a/.github/workflows/test_package_install_inference_gpu.yml b/.github/workflows/test_package_install_inference_gpu.yml index 36d386378d..d1fec03015 100644 --- a/.github/workflows/test_package_install_inference_gpu.yml +++ b/.github/workflows/test_package_install_inference_gpu.yml @@ -75,7 +75,7 @@ jobs: export LD_LIBRARY_PATH=/usr/local/cuda-12.5/targets/x86_64-linux/lib64:$LD_LIBRARY_PATH export CPATH=/usr/local/cuda-12.5/targets/x86_64-linux/include:$CPATH export LIBRARY_PATH=/usr/local/cuda-12.5/targets/x86_64-linux/lib64:$LIBRARY_PATH - pip install --find-links dist/ ./dist/inference_gpu-*-py3-none-any.whl ./dist/inference_sdk-*-py3-none-any.whl ./dist/inference_models-*-py3-none-any.whl "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" + pip install --find-links dist/ ./dist/inference_gpu-*-py3-none-any.whl ./dist/inference_sdk-*-py3-none-any.whl ./dist/inference_models-*-py3-none-any.whl "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" # Dependencies were resolved above; now replace any same-version # build-environment installs with the exact checkout artifacts. pip install --force-reinstall --no-deps ./dist/roboflow_workflows-*.whl ./dist/inference_sdk-*.whl ./dist/inference_models-*.whl diff --git a/.github/workflows/test_package_install_inference_gpu_with_extras.yml b/.github/workflows/test_package_install_inference_gpu_with_extras.yml index 6a2e4f180c..62719092df 100644 --- a/.github/workflows/test_package_install_inference_gpu_with_extras.yml +++ b/.github/workflows/test_package_install_inference_gpu_with_extras.yml @@ -78,7 +78,7 @@ jobs: export CPATH=/usr/local/cuda-12.5/targets/x86_64-linux/include:$CPATH export LIBRARY_PATH=/usr/local/cuda-12.5/targets/x86_64-linux/lib64:$LIBRARY_PATH wheel_name=`ls ./dist/inference_gpu-*-py3-none-any.whl | head -n 1` - pip install --find-links dist/ "${wheel_name}[clip,grounding-dino,sam,waf,yolo-world,http,hosted,transformers]" ./dist/inference_sdk-*-py3-none-any.whl ./dist/inference_models-*-py3-none-any.whl "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" + pip install --find-links dist/ "${wheel_name}[clip,grounding-dino,sam,waf,yolo-world,http,hosted,transformers]" ./dist/inference_sdk-*-py3-none-any.whl ./dist/inference_models-*-py3-none-any.whl "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" # Dependencies were resolved above; now replace any same-version # build-environment installs with the exact checkout artifacts. pip install --force-reinstall --no-deps ./dist/roboflow_workflows-*.whl ./dist/inference_sdk-*.whl ./dist/inference_models-*.whl diff --git a/.github/workflows/test_package_install_inference_with_extras.yml b/.github/workflows/test_package_install_inference_with_extras.yml index 1054078953..4d0854f6f4 100644 --- a/.github/workflows/test_package_install_inference_with_extras.yml +++ b/.github/workflows/test_package_install_inference_with_extras.yml @@ -57,7 +57,7 @@ jobs: - name: ๐Ÿ“ฆ Installing `inference` package... run: | wheel_name=`ls ./dist/inference-*-py3-none-any.whl | head -n 1` - pip install --find-links dist/ "${wheel_name}[clip,grounding-dino,sam,waf,yolo-world,http,hosted,transformers]" ./dist/inference_sdk-*-py3-none-any.whl ./dist/inference_models-*-py3-none-any.whl "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" + pip install --find-links dist/ "${wheel_name}[clip,grounding-dino,sam,waf,yolo-world,http,hosted,transformers]" ./dist/inference_sdk-*-py3-none-any.whl ./dist/inference_models-*-py3-none-any.whl "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" # Dependencies were resolved above; now replace any same-version # build-environment installs with the exact checkout artifacts. pip install --force-reinstall --no-deps ./dist/roboflow_workflows-*.whl ./dist/inference_sdk-*.whl ./dist/inference_models-*.whl diff --git a/.github/workflows/unit_tests_inference_cli_x86.yml b/.github/workflows/unit_tests_inference_cli_x86.yml index 4004f87d20..2c9bd39ebe 100644 --- a/.github/workflows/unit_tests_inference_cli_x86.yml +++ b/.github/workflows/unit_tests_inference_cli_x86.yml @@ -54,7 +54,7 @@ jobs: - name: ๐Ÿ—๏ธ Build and install roboflow-workflows wheel run: | make create_workflows_wheel - pip install --find-links "${{ github.workspace }}/dist" "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" + pip install --find-links "${{ github.workspace }}/dist" "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" - name: ๐Ÿงช Unit Tests of Inference CLI timeout-minutes: 30 run: python -m pytest tests/inference_cli/unit_tests diff --git a/.github/workflows/unit_tests_inference_x86.yml b/.github/workflows/unit_tests_inference_x86.yml index 1eade694ef..6bdd9d7ba6 100644 --- a/.github/workflows/unit_tests_inference_x86.yml +++ b/.github/workflows/unit_tests_inference_x86.yml @@ -56,7 +56,7 @@ jobs: - name: ๐Ÿ—๏ธ Build and install roboflow-workflows wheel run: | make create_workflows_wheel - pip install --find-links "${{ github.workspace }}/dist" "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[enterprise]" + pip install --find-links "${{ github.workspace }}/dist" "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[enterprise]" - name: ๐Ÿงช Unit Tests of Inference timeout-minutes: 30 run: python -m pytest tests/inference/unit_tests tests/unit/core/cache/test_air_gapped.py diff --git a/.github/workflows/unit_tests_workflows_tensor_native_x86.yml b/.github/workflows/unit_tests_workflows_tensor_native_x86.yml index 505cca14ec..1e0faf612b 100644 --- a/.github/workflows/unit_tests_workflows_tensor_native_x86.yml +++ b/.github/workflows/unit_tests_workflows_tensor_native_x86.yml @@ -56,7 +56,7 @@ jobs: - name: ๐Ÿ—๏ธ Build roboflow-workflows + SDK + inference-models wheels run: make create_isolation_wheels - name: ๐Ÿ“ฆ Install roboflow-workflows (test + enterprise extras) from local wheel - run: pip install --find-links "${{ github.workspace }}/dist" "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[test,enterprise]" "${{ github.workspace }}"/dist/inference_sdk-*.whl "${{ github.workspace }}"/dist/inference_models-*.whl + run: pip install --find-links "${{ github.workspace }}/dist" "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[test,enterprise]" "${{ github.workspace }}"/dist/inference_sdk-*.whl "${{ github.workspace }}"/dist/inference_models-*.whl - name: ๐Ÿงช Server unit tests (tensor-native) # ENABLE_TENSOR_DATA_REPRESENTATION is AND-gated with USE_INFERENCE_MODELS, # which defaults to True on Linux runners, so the tensor-native code path is exercised. @@ -92,13 +92,14 @@ jobs: - name: Install only standalone package dependencies run: | pip install --find-links "${{ github.workspace }}/dist" \ - "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[test,enterprise]" \ + "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[test,enterprise]" \ "${{ github.workspace }}"/dist/inference_sdk-*.whl \ "${{ github.workspace }}"/dist/inference_models-*.whl pip check - name: Package unit and installed-wheel isolation tests working-directory: workflows env: - WORKFLOWS_ISOLATION_WHEEL: ${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl WORKFLOWS_ISOLATION_FIND_LINKS: ${{ github.workspace }}/dist - run: python -m pytest tests/unit_tests tests/isolation + run: | + WORKFLOWS_ISOLATION_WHEEL="$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)" \ + python -m pytest tests/unit_tests tests/isolation diff --git a/.github/workflows/unit_tests_workflows_x86.yml b/.github/workflows/unit_tests_workflows_x86.yml index b16c6a8759..46510d7e02 100644 --- a/.github/workflows/unit_tests_workflows_x86.yml +++ b/.github/workflows/unit_tests_workflows_x86.yml @@ -56,7 +56,7 @@ jobs: - name: ๐Ÿ—๏ธ Build roboflow-workflows + SDK + inference-models wheels run: make create_isolation_wheels - name: ๐Ÿ“ฆ Install roboflow-workflows (test + enterprise extras) from local wheel - run: pip install --find-links "${{ github.workspace }}/dist" "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[test,enterprise]" "${{ github.workspace }}"/dist/inference_sdk-*.whl "${{ github.workspace }}"/dist/inference_models-*.whl + run: pip install --find-links "${{ github.workspace }}/dist" "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[test,enterprise]" "${{ github.workspace }}"/dist/inference_sdk-*.whl "${{ github.workspace }}"/dist/inference_models-*.whl - name: ๐Ÿงช Server unit tests timeout-minutes: 30 run: python -m pytest tests/workflows/unit_tests @@ -90,13 +90,14 @@ jobs: - name: Install only standalone package dependencies run: | pip install --find-links "${{ github.workspace }}/dist" \ - "${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl[test,enterprise]" \ + "$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)[test,enterprise]" \ "${{ github.workspace }}"/dist/inference_sdk-*.whl \ "${{ github.workspace }}"/dist/inference_models-*.whl pip check - name: Package unit and installed-wheel isolation tests working-directory: workflows env: - WORKFLOWS_ISOLATION_WHEEL: ${{ github.workspace }}/dist/roboflow_workflows-0.1.1-py3-none-any.whl WORKFLOWS_ISOLATION_FIND_LINKS: ${{ github.workspace }}/dist - run: python -m pytest tests/unit_tests tests/isolation + run: | + WORKFLOWS_ISOLATION_WHEEL="$(ls "${{ github.workspace }}"/dist/roboflow_workflows-*.whl)" \ + python -m pytest tests/unit_tests tests/isolation diff --git a/inference/core/version.py b/inference/core/version.py index 66791410b0..e91e20458e 100644 --- a/inference/core/version.py +++ b/inference/core/version.py @@ -1,4 +1,4 @@ -__version__ = "1.6.1" +__version__ = "1.6.2" if __name__ == "__main__": diff --git a/requirements/requirements.workflows.txt b/requirements/requirements.workflows.txt index 6b5387021d..28b3ebbee9 100644 --- a/requirements/requirements.workflows.txt +++ b/requirements/requirements.workflows.txt @@ -1,2 +1,2 @@ # keep in sync with workflows/pyproject.toml version -roboflow-workflows[enterprise]==0.1.1 +roboflow-workflows[enterprise]==0.1.2 diff --git a/workflows/CHANGELOG.md b/workflows/CHANGELOG.md index d6a467ecbf..61913de8ed 100644 --- a/workflows/CHANGELOG.md +++ b/workflows/CHANGELOG.md @@ -4,6 +4,14 @@ --- +## `0.1.2` + +### Fixed + +- Webhook sink: SSRF hardening (destination validation, DNS pinning, redirect and proxy refusal) now applies only when `ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES=false`. The default restores the previous plain `requests` transport, so environment `HTTP_PROXY` / `HTTPS_PROXY` work again. + +--- + ## `0.1.1` ### Fixed diff --git a/workflows/pyproject.toml b/workflows/pyproject.toml index ec8bb6ea36..52a2dad475 100644 --- a/workflows/pyproject.toml +++ b/workflows/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "roboflow-workflows" -version = "0.1.1" +version = "0.1.2" description = "Roboflow Workflows execution engine and block library" readme = "README.md" license = {file = "LICENSE"} diff --git a/workflows/roboflow_workflows/core_steps/sinks/webhook/v1.py b/workflows/roboflow_workflows/core_steps/sinks/webhook/v1.py index e9fce211c5..f6baa59e0a 100644 --- a/workflows/roboflow_workflows/core_steps/sinks/webhook/v1.py +++ b/workflows/roboflow_workflows/core_steps/sinks/webhook/v1.py @@ -64,15 +64,21 @@ ### Supported destinations +By default (`ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES=true`) requests +are sent with plain `requests` semantics: environment proxies are honoured, +redirects are followed, and any destination is allowed. This preserves existing +self-hosted private-network webhooks. + +Set `ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES=false` to enable the +hardened transport: + * Only `http://` and `https://` URLs are accepted. -* Set `ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES=false` to require - destinations to resolve exclusively to public, globally routable unicast addresses. - This rejects loopback, private (RFC1918), link-local (including cloud metadata - endpoints), CGNAT, reserved, and multicast targets. +* Destinations must resolve exclusively to public, globally routable unicast + addresses. This rejects loopback, private (RFC1918), link-local (including + cloud metadata endpoints), CGNAT, reserved, and multicast targets. * HTTP redirects are rejected and reported as a failed notification; the `Location` header is not followed. -* Non-global destinations are allowed by default to preserve existing self-hosted - private-network webhooks. +* Environment HTTP(S) proxies are refused. ### Setting Query Parameters You can easily set query parameters for your request: @@ -532,6 +538,7 @@ def execute_request( ALLOWED_METHODS = ("GET", "POST", "PUT") +METHOD_TO_HANDLER = {"GET": requests.get, "POST": requests.post, "PUT": requests.put} def _execute_request( @@ -546,6 +553,17 @@ def _execute_request( ) -> None: if method not in ALLOWED_METHODS: raise ValueError(f"Handler for HTTP method `{method}` not registered") + if ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES: + return _execute_request_legacy( + url=url, + method=method, + query_parameters=query_parameters, + headers=headers, + json_payload=json_payload, + form_data=form_data, + multi_part_encoded_files=multi_part_encoded_files, + timeout=timeout, + ) # Reject a backslash in the raw authority before Requests normalises it. # `urlsplit` accepts \ in the netloc, but urllib3 later interprets it as a # path separator, which can smuggle a target past a scheme/host review. @@ -569,9 +587,7 @@ def _execute_request( # the body (the block does not consume it), and `allow_redirects` is not # honoured by an adapter's `send()` so a 3xx surfaces here. with contextlib.closing( - SSRFProtectedHTTPAdapter( - allow_non_global_addresses=ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES - ) + SSRFProtectedHTTPAdapter(allow_non_global_addresses=False) ) as adapter: with contextlib.closing( adapter.send( @@ -584,3 +600,27 @@ def _execute_request( if 300 <= response.status_code < 400: raise requests.HTTPError("Webhook redirects are not allowed") response.raise_for_status() + + +def _execute_request_legacy( + url: str, + method: Literal["GET", "POST", "PUT"], + query_parameters: Dict[str, Any], + headers: Dict[str, Any], + json_payload: Dict[str, Any], + form_data: Dict[str, Any], + multi_part_encoded_files: Dict[str, Any], + timeout: int, +) -> None: + # Pre-SSRF-hardening transport, kept verbatim: plain `requests`, env proxies + # honoured, redirects followed, no destination validation. + response = METHOD_TO_HANDLER[method]( + url, + params=query_parameters, + headers=headers, + json=json_payload, + files=multi_part_encoded_files, + data=form_data, + timeout=timeout, + ) + response.raise_for_status() diff --git a/workflows/tests/unit_tests/core_steps/sinks/test_webhook.py b/workflows/tests/unit_tests/core_steps/sinks/test_webhook.py index dabd5daa60..3da8a463b1 100644 --- a/workflows/tests/unit_tests/core_steps/sinks/test_webhook.py +++ b/workflows/tests/unit_tests/core_steps/sinks/test_webhook.py @@ -70,6 +70,10 @@ def close(self) -> None: holder["adapter_closed"] = True monkeypatch.setattr(v1, "SSRFProtectedHTTPAdapter", _FakeAdapter) + # Adapter path only exists in hardened mode. + monkeypatch.setattr( + v1, "ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES", False + ) return holder @@ -117,9 +121,6 @@ def test_manifest_parsing_when_the_input_is_valid() -> None: def test_execute_request_forwards_payload_to_adapter(monkeypatch) -> None: response = _FakeResponse(status_code=200) holder = _install_capturing_adapter(monkeypatch, response) - monkeypatch.setattr( - v1, "ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES", True - ) result = execute_request( url="https://public.example/webhook", @@ -143,7 +144,7 @@ def test_execute_request_forwards_payload_to_adapter(monkeypatch) -> None: # steer resolution outside the validating adapter. assert holder["kwargs"]["proxies"] == {} assert holder["kwargs"]["stream"] is True - assert holder["allow_non_global_addresses"] is True + assert holder["allow_non_global_addresses"] is False assert response.closed is True assert holder["adapter_closed"] is True @@ -248,6 +249,9 @@ def _explode(*a, **k): def test_execute_request_pins_first_dns_result_against_rebinding(monkeypatch) -> None: + monkeypatch.setattr( + v1, "ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES", False + ) calls = {"n": 0} responses = [ [ @@ -381,6 +385,9 @@ def test_execute_request_ignores_environment_proxies(monkeypatch) -> None: ], ) def test_execute_request_rejects_malformed_or_non_http_urls(url, monkeypatch) -> None: + monkeypatch.setattr( + v1, "ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES", False + ) # None of these may reach a socket; if getaddrinfo or urlopen runs, fail loudly. monkeypatch.setattr( url_input.socket, @@ -696,6 +703,49 @@ def test_sending_webhook_notification_asynchronously_in_thread_pool_executor() - thread_pool_executor.submit.assert_called_once() +def test_execute_request_uses_plain_requests_when_non_global_allowed( + monkeypatch, +) -> None: + monkeypatch.setattr( + v1, "ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES", True + ) + monkeypatch.setattr( + v1, + "SSRFProtectedHTTPAdapter", + lambda **_: (_ for _ in ()).throw(AssertionError("adapter must not run")), + ) + calls = {} + + def _fake_post(url, **kwargs): + calls["url"] = url + calls["kwargs"] = kwargs + return _FakeResponse(status_code=200) + + monkeypatch.setitem(v1.METHOD_TO_HANDLER, "POST", _fake_post) + + result = execute_request( + url="http://127.0.0.1/webhook", + method="POST", + query_parameters={"a": "b"}, + headers={"c": "d"}, + json_payload={"e": "f"}, + form_data={"field": "value"}, + multi_part_encoded_files={"file": b"data"}, + timeout=3, + ) + + assert result == (False, "Notification sent successfully") + assert calls["url"] == "http://127.0.0.1/webhook" + assert calls["kwargs"] == { + "params": {"a": "b"}, + "headers": {"c": "d"}, + "json": {"e": "f"}, + "files": {"file": b"data"}, + "data": {"field": "value"}, + "timeout": 3, + } + + def test_execute_request_forwards_deny_non_global_when_flag_disabled( monkeypatch, ) -> None: diff --git a/workflows/uv.lock b/workflows/uv.lock index ef4cada3d9..b5e1a8fae3 100644 --- a/workflows/uv.lock +++ b/workflows/uv.lock @@ -3401,7 +3401,7 @@ wheels = [ [[package]] name = "roboflow-workflows" -version = "0.1.1" +version = "0.1.2" source = { editable = "." } dependencies = [ { name = "anthropic" },