mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
[09d0185a] Backend: fix the roboco-api CI regression (make make quality green on slave) (#808)
* [c2c29256] Fix make quality CI regression on roboco-api slave (#804) * [c2c29256] fix: mock _chown_entry to simulate chown failure in test_marker_written_only_on_zero_failure_pass * [c2c29256] docs(changelog): add Unreleased Fixed entry for make quality slave CI regression fix --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [b5bc53ea] Fix security middleware trusted_proxies + run full make quality green (#811) * [b5bc53ea] fix(security): remove LAN ranges from trusted_proxies so forwarded LAN IPs are rejected * [b5bc53ea] refactor(task): extract private helpers from cancel() to clear xenon rank C gate * [b5bc53ea] docs(security): document trusted_proxies/whitelist lockstep invariant and the #811 LAN-IP fix --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [13193140] Strengthen security middleware IP-resolution test + document trusted_proxies invariant (#813) * [13193140] test(security): add direct IP-resolution unit tests and document trusted_proxies invariant * [13193140] docs(security): cross-reference trusted_proxies invariant proof tests and inline comment --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> --------- Co-authored-by: roboco-app[bot] <302741806+roboco-app[bot]@users.noreply.github.com> Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech>
This commit is contained in:
co-authored by
Backend Developer 1
Backend Documenter
roboco-app[bot] <302741806+roboco-app[bot]@users.noreply.github.com>
parent
628b925f40
commit
d829979b6a
@@ -333,13 +333,18 @@ def test_full_walk_when_marker_absent(
|
||||
assert str(tmp_path / "README.md") in _record_touched
|
||||
|
||||
|
||||
def test_marker_written_only_on_zero_failure_pass(tmp_path: Path) -> None:
|
||||
def test_marker_written_only_on_zero_failure_pass(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
_build_workspace(tmp_path)
|
||||
marker = tmp_path / ".git" / "roboco-owned"
|
||||
assert not marker.exists()
|
||||
|
||||
# A real pass: chown to uid 1000 fails under the test's real (non-root)
|
||||
# uid, exactly like a rootless/userns host — so no marker should land.
|
||||
# Simulate a rootless/userns host where chown is rejected — the marker
|
||||
# must NOT land when any entry's chown fails. Mocking _chown_entry avoids
|
||||
# relying on the process uid (the test runs as uid 1000 and files are
|
||||
# already owned by uid 1000, so a real chown would be a no-op success).
|
||||
monkeypatch.setattr(workspace_module, "_chown_entry", lambda _entry, _st: False)
|
||||
_ensure_agent_owned(tmp_path)
|
||||
assert not marker.exists()
|
||||
|
||||
|
||||
@@ -20,14 +20,17 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
from http import HTTPStatus
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from guard import SecurityMiddleware
|
||||
from guard.adapters import StarletteGuardRequest
|
||||
from guard.lifespan import make_lifespan
|
||||
from guard_core.utils import extract_client_ip, is_ip_allowed
|
||||
from roboco import security
|
||||
from starlette.requests import Request
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator
|
||||
@@ -218,3 +221,48 @@ class TestDecoyPaths:
|
||||
with _client(_guarded_app(passive=True)) as client:
|
||||
resp = client.get("/.git/config")
|
||||
assert resp.status_code == HTTPStatus.OK
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Direct unit tests for the IP-resolution path the stale PR-review finding
|
||||
# keeps questioning. These call guard_core's extract_client_ip / is_ip_allowed
|
||||
# directly (no running server) to make the security boundary self-evident.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_client_ip_forwarded_lan_not_peeled() -> None:
|
||||
"""With trusted_proxies narrowed to the docker-bridge/loopback mesh (no
|
||||
LAN ranges), a docker-bridge peer forwarding a LAN client's IP via
|
||||
X-Forwarded-For resolves to the real LAN IP — not peeled to the peer."""
|
||||
scope = {
|
||||
"type": "http",
|
||||
"method": "POST",
|
||||
"path": "/task",
|
||||
"query_string": b"",
|
||||
"headers": [(b"x-forwarded-for", b"192.168.1.50")],
|
||||
"client": ("172.18.0.5", 12345),
|
||||
}
|
||||
request = StarletteGuardRequest(Request(scope))
|
||||
|
||||
class _Cfg:
|
||||
trusted_proxies: ClassVar[list[str]] = ["127.0.0.1", "::1", "172.16.0.0/12"]
|
||||
trusted_proxy_depth = 1
|
||||
|
||||
assert await extract_client_ip(request, _Cfg()) == "192.168.1.50"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_is_ip_allowed_rejects_lan_ranges() -> None:
|
||||
"""The narrowed whitelist (loopback + docker-bridge only) does NOT cover
|
||||
RFC1918 LAN ranges, so a resolved LAN client IP is rejected."""
|
||||
_WHITELIST = ["127.0.0.1", "::1", "172.16.0.0/12"]
|
||||
|
||||
class _Cfg:
|
||||
whitelist: ClassVar[list[str]] = _WHITELIST
|
||||
blacklist: ClassVar[list[str]] = []
|
||||
blocked_countries: ClassVar[list[str]] = []
|
||||
block_cloud_providers: ClassVar[list[str]] = []
|
||||
|
||||
assert await is_ip_allowed("192.168.1.50", _Cfg()) is False
|
||||
assert await is_ip_allowed("10.0.0.5", _Cfg()) is False
|
||||
|
||||
Reference in New Issue
Block a user