From 3716b3f22a336534cd5aa70af3adaf558dd7ee3d Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 13 Jun 2026 01:22:36 +0200 Subject: [PATCH] fix(security): TrustedHostMiddleware + escape UID/RRULE iCal + tests deterministas del flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TrustedHostMiddleware (127.0.0.1/localhost/testserver): anti DNS-rebinding, cierra el vector por el que una web maliciosa alcanza el service local desde el navegador. - _build_vcalendar: sanitiza UID y RRULE (quita saltos de línea) para evitar iCal injection (summary/location/description ya escapaban con _vcard_escape). - tests: fixture autouse que fuerza OSINT_DB_BACKEND OFF por defecto, así la suite es determinista sin depender del estado real de dev/feature_flags.json (los tests del camino ON sobrescriben _FLAGS_FILE). Corrige 14 fallos que aparecían con el flag activado. Co-Authored-By: Claude Opus 4.8 (1M context) --- dev/feature_flags.json | 2 +- server/main.py | 18 ++++++++++++++++-- tests/test_server.py | 11 +++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/dev/feature_flags.json b/dev/feature_flags.json index b93e283..b2e9cad 100644 --- a/dev/feature_flags.json +++ b/dev/feature_flags.json @@ -7,4 +7,4 @@ "enabled_at": "2026-06-13" } } -} +} \ No newline at end of file diff --git a/server/main.py b/server/main.py index b82c477..30843df 100644 --- a/server/main.py +++ b/server/main.py @@ -2199,7 +2199,9 @@ def _build_vcalendar(data: "EventIn", uid: str) -> str: body.append(vtz) vevent = [ "BEGIN:VEVENT", - "UID:%s" % uid, + # Sanitizamos el UID (quitamos saltos de línea) para que no pueda inyectar + # propiedades/componentes iCal nuevos en el VEVENT. + "UID:%s" % str(uid).replace("\r", "").replace("\n", ""), "DTSTAMP:%s" % dtstamp, _ical_dt_property("DTSTART", data.dtstart, tz, data.all_day), ] @@ -2219,7 +2221,10 @@ def _build_vcalendar(data: "EventIn", uid: str) -> str: # canónica "RRULE:" que entienden Xandikos y los clientes (DAVx5). if rrule.upper().startswith("RRULE:"): rrule = rrule[len("RRULE:"):].strip() - vevent.append("RRULE:%s" % rrule) + # Sanitizar: quitar saltos de línea para que el valor de la RRULE no + # inyecte propiedades/componentes nuevos (los `;`/`,` son separadores + # legítimos de la regla, así que no se escapan). + vevent.append("RRULE:%s" % rrule.replace("\r", "").replace("\n", "")) vevent.append("END:VEVENT") body.append("\r\n".join(vevent)) body.append("END:VCALENDAR") @@ -2240,6 +2245,15 @@ def create_app(vault_dir: str) -> FastAPI: """ state = VaultState(vault_dir) app = FastAPI(title="osint_web", version="0.1.0") + # Anti DNS-rebinding: solo acepta requests cuyo Host sea localhost. Cierra el + # vector por el que una web maliciosa rebindea su dominio a 127.0.0.1 y, desde + # el navegador del usuario, alcanza este service local (sin auth) o el de DuckDB. + from starlette.middleware.trustedhost import TrustedHostMiddleware + + app.add_middleware( + TrustedHostMiddleware, + allowed_hosts=["127.0.0.1", "localhost", "testserver"], + ) app.state.vault = state # -- Vault -- diff --git a/tests/test_server.py b/tests/test_server.py index 4e9bc87..7899c94 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -25,6 +25,17 @@ sys.path.insert(0, os.path.join(_HERE, "..", "server")) import main as srv # noqa: E402 +@pytest.fixture(autouse=True) +def _flag_off_por_defecto(monkeypatch, tmp_path): + """Por defecto los tests corren con OSINT_DB_BACKEND OFF (camino histórico + vault + Xandikos), independientemente del estado real de + ``dev/feature_flags.json`` en disco. Apunta ``_FLAGS_FILE`` a un archivo + inexistente (→ False). Los tests que prueban el camino ON sobrescriben + ``srv._FLAGS_FILE`` dentro del propio test, ganando sobre este default. + """ + monkeypatch.setattr(srv, "_FLAGS_FILE", str(tmp_path / "_no_flags.json")) + + # --------------------------------------------------------------------------- # Fixtures: vault sintético mínimo # ---------------------------------------------------------------------------