mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-11 08:38:24 +00:00
* test: add a manually dispatched integration trial on larger runners
Runs the providers, database and management integration groups on 16-core GitHub-hosted runners with Postgres and Redis as service containers, to measure wall time and how far each group scales with more workers
run_integration.sh now keeps a preset INTEGRATION_WORKERS, takes the service container names from the environment, and seeds test order from the GitHub run when there is no CircleCI one
* test: run the integration trial on push to this branch
* test: give integration trial workers a local redis-server
* test: run the providers trial on a 64-core runner with 32 workers
The 16-core run reached 53% of providers in 45 minutes with 16 workers, which points at CPU. Postgres gets max_connections=1000 and a 1 GB shm because one server now backs 32 worker proxies, where each CircleCI machine backs 4
* test: run every integration group in the trial on sized runners
* test: add a scan that refuses to upload integration results holding the license
* test: drop the member-name pass the archive byte scan already covers
* test: use a secret whose base64 and base64url encodings differ
* ci: run integration groups on run-ci pull requests and scheduled main runs
* ci: scope the integration license to a protected environment
Backport note: tests/integration/AGENTS.md keeps rc/1.108.0's text, since main's copy of that paragraph also carries later changes rc does not have
(cherry picked from commit 62b8ee78de)
124 lines
4.7 KiB
Python
124 lines
4.7 KiB
Python
import base64
|
|
import importlib.util
|
|
import io
|
|
import sys
|
|
import urllib.parse
|
|
import zipfile
|
|
from collections.abc import Callable, Mapping
|
|
from itertools import chain, product
|
|
from pathlib import Path
|
|
from types import MappingProxyType
|
|
from typing import Final
|
|
|
|
import pytest
|
|
|
|
_REPO_ROOT: Final = Path(__file__).resolve().parents[2]
|
|
_MODULE_PATH: Final = _REPO_ROOT / ".github" / "scripts" / "assert_secret_absent.py"
|
|
_spec: Final = importlib.util.spec_from_file_location("assert_secret_absent", _MODULE_PATH)
|
|
scanner: Final = importlib.util.module_from_spec(_spec)
|
|
sys.modules[_spec.name] = scanner
|
|
_spec.loader.exec_module(scanner)
|
|
|
|
SECRET: Final = "lic/ense+KEY:9f8e~~??>>~?7d6c5b4a3210-zz"
|
|
PREFIXES: Final = ("", "x", "xy")
|
|
ENCODERS: Final = (base64.b64encode, base64.urlsafe_b64encode)
|
|
|
|
|
|
def _zip(members: Mapping[str, bytes], compression: int = zipfile.ZIP_DEFLATED) -> bytes:
|
|
buffer: Final = io.BytesIO()
|
|
with zipfile.ZipFile(buffer, "w", compression) as archive:
|
|
for name, data in members.items():
|
|
archive.writestr(name, data)
|
|
return buffer.getvalue()
|
|
|
|
|
|
def _blob(prefix: str) -> bytes:
|
|
return f"{prefix}{SECRET}tail".encode()
|
|
|
|
|
|
def _planted_forms() -> tuple[str, ...]:
|
|
encoded: Final = tuple(encode(_blob(prefix)).decode() for encode, prefix in product(ENCODERS, PREFIXES))
|
|
return (SECRET, urllib.parse.quote(SECRET, safe=""), *encoded)
|
|
|
|
|
|
def _assert_nothing_leaked(output: str) -> None:
|
|
windows: Final = frozenset(
|
|
chain.from_iterable((form[i : i + 8] for i in range(len(form) - 7)) for form in _planted_forms())
|
|
)
|
|
assert not [window for window in windows if window in output]
|
|
|
|
|
|
def _run(root: Path, secret: str = SECRET) -> tuple[int, str]:
|
|
out: Final = io.StringIO()
|
|
code: Final = scanner.main((str(root),), {scanner.SECRET_VARIABLE: secret}, out)
|
|
return code, out.getvalue()
|
|
|
|
|
|
def _encoded_blob(prefix: str, encode: Callable[[bytes], bytes]) -> bytes:
|
|
return b"trace " + encode(_blob(prefix)) + b" end"
|
|
|
|
|
|
QUOTED: Final = urllib.parse.quote(SECRET, safe="")
|
|
PLACEMENTS: Final = MappingProxyType(
|
|
{
|
|
"raw in a log": {"proxy.log": f"boot with key {SECRET} ok".encode()},
|
|
"url-encoded in a url": {"upstream.log": f"GET /verify_license/{QUOTED}".encode()},
|
|
"base64 aligned": {"a.json": _encoded_blob("", base64.b64encode)},
|
|
"base64 one byte in": {"a.json": _encoded_blob("x", base64.b64encode)},
|
|
"base64 two bytes in": {"a.json": _encoded_blob("xy", base64.b64encode)},
|
|
"base64url two bytes in": {"a.json": _encoded_blob("xy", base64.urlsafe_b64encode)},
|
|
"inside a zip member": {"trace.zip": _zip({"network.json": f'{{"auth": "{SECRET}"}}'.encode()})},
|
|
"inside a nested zip": {"outer.zip": _zip({"inner.zip": _zip({"env.txt": SECRET.encode()})})},
|
|
"named after it in a nested zip": {
|
|
"outer.zip": _zip({"inner.zip": _zip({f"dump-{QUOTED}.txt": SECRET.encode()})})
|
|
},
|
|
"in a file name": {f"run-{QUOTED}.log": b"nothing inside"},
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("placement", PLACEMENTS)
|
|
def test_every_planted_copy_blocks_the_upload_without_printing_the_secret(tmp_path: Path, placement: str) -> None:
|
|
worker: Final = tmp_path / "gw0"
|
|
worker.mkdir()
|
|
for name, data in PLACEMENTS[placement].items():
|
|
(worker / name).write_bytes(data)
|
|
code, output = _run(tmp_path)
|
|
assert code == 1
|
|
assert "secret found:" in output
|
|
_assert_nothing_leaked(output)
|
|
|
|
|
|
def test_clean_results_pass_and_count_every_file(tmp_path: Path) -> None:
|
|
(tmp_path / "junit.xml").write_text("<testsuite/>")
|
|
(tmp_path / "gw0").mkdir()
|
|
(tmp_path / "gw0" / "proxy.log").write_text("LITELLM_LICENSE is not set")
|
|
code, output = _run(tmp_path)
|
|
assert code == 0
|
|
assert "no copy of the secret in 2 files" in output
|
|
|
|
|
|
def test_an_unset_secret_has_nothing_to_find(tmp_path: Path) -> None:
|
|
(tmp_path / "proxy.log").write_text(SECRET)
|
|
assert _run(tmp_path, secret="")[0] == 0
|
|
|
|
|
|
def test_a_symlinked_directory_fails_closed(tmp_path: Path) -> None:
|
|
outside: Final = tmp_path / "outside"
|
|
outside.mkdir()
|
|
results: Final = tmp_path / "results"
|
|
results.mkdir()
|
|
(results / "linked").symlink_to(outside, target_is_directory=True)
|
|
code, output = _run(results)
|
|
assert code == 1
|
|
assert "directory symlink" in output
|
|
|
|
|
|
def test_an_unreadable_archive_fails_without_echoing_its_contents(tmp_path: Path) -> None:
|
|
archive: Final = _zip({"env.txt": f"LITELLM_LICENSE={SECRET}".encode()}, zipfile.ZIP_STORED)
|
|
at: Final = archive.index(SECRET.encode())
|
|
(tmp_path / "trace.zip").write_bytes(archive[:at] + bytes([archive[at] ^ 0xFF]) + archive[at + 1 :])
|
|
code, output = _run(tmp_path)
|
|
assert code == 1
|
|
assert "scan failed with BadZipFile" in output
|
|
_assert_nothing_leaked(output)
|