233 lines
6.5 KiB
Python
233 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from opensquilla.sandbox.domain_validation import (
|
|
DomainDecision,
|
|
domain_matches,
|
|
normalize_domain,
|
|
validate_domain_pattern,
|
|
)
|
|
from opensquilla.sandbox.package_bundles import (
|
|
DEFAULT_PACKAGE_BUNDLE_IDS,
|
|
PACKAGE_BUNDLES,
|
|
expand_package_bundle,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("raw", "expected"),
|
|
[
|
|
("HTTPS://PyPI.org/simple", "pypi.org"),
|
|
("registry.npmjs.org/", "registry.npmjs.org"),
|
|
("*.PythonHosted.org", "*.pythonhosted.org"),
|
|
(".example.com", ".example.com"),
|
|
("..Example.com..", "..example.com.."),
|
|
("http://[v6.invalid]", ""),
|
|
],
|
|
)
|
|
def test_normalize_domain(raw: str, expected: str) -> None:
|
|
assert normalize_domain(raw) == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"raw",
|
|
[
|
|
"127.0.0.1",
|
|
"127.1",
|
|
"0177.0.0.1",
|
|
"0x7f.0.0.1",
|
|
"0x7f.1",
|
|
"10.0.0.2",
|
|
"169.254.169.254",
|
|
"8.8.8.8",
|
|
"[::1]",
|
|
"2606:4700:4700::1111",
|
|
"*.com",
|
|
"*.co.uk",
|
|
"*.github.io",
|
|
"*.pages.dev",
|
|
"*.appspot.com",
|
|
"*.cloudfront.net",
|
|
"*.azurewebsites.net",
|
|
"*.example.com",
|
|
"*",
|
|
"",
|
|
],
|
|
)
|
|
def test_validate_domain_pattern_blocks_unsafe_patterns(raw: str) -> None:
|
|
decision = validate_domain_pattern(raw)
|
|
assert decision.status == "blocked"
|
|
|
|
|
|
def test_validate_domain_pattern_allows_exact_and_narrow_wildcard() -> None:
|
|
assert validate_domain_pattern("pypi.org") == DomainDecision(
|
|
status="allowed",
|
|
normalized="pypi.org",
|
|
reason="exact_domain",
|
|
)
|
|
assert validate_domain_pattern("3m.com") == DomainDecision(
|
|
status="allowed",
|
|
normalized="3m.com",
|
|
reason="exact_domain",
|
|
)
|
|
assert validate_domain_pattern("example.com") == DomainDecision(
|
|
status="allowed",
|
|
normalized="example.com",
|
|
reason="exact_domain",
|
|
)
|
|
assert validate_domain_pattern("example.com:443") == DomainDecision(
|
|
status="allowed",
|
|
normalized="example.com",
|
|
reason="exact_domain",
|
|
)
|
|
assert validate_domain_pattern("*.pythonhosted.org") == DomainDecision(
|
|
status="allowed",
|
|
normalized="*.pythonhosted.org",
|
|
reason="wildcard_domain",
|
|
)
|
|
|
|
|
|
def test_package_bundles_expand_to_known_domains() -> None:
|
|
assert expand_package_bundle("python-package-install") == (
|
|
"pypi.org",
|
|
"files.pythonhosted.org",
|
|
"pypi.python.org",
|
|
"bootstrap.pypa.io",
|
|
"python-poetry.org",
|
|
"install.python-poetry.org",
|
|
)
|
|
assert expand_package_bundle("node-package-install") == (
|
|
"registry.npmjs.org",
|
|
"registry.yarnpkg.com",
|
|
"yarnpkg.com",
|
|
"nodejs.org",
|
|
"unpkg.com",
|
|
"cdn.jsdelivr.net",
|
|
)
|
|
assert expand_package_bundle("rust-package-install") == (
|
|
"crates.io",
|
|
"static.crates.io",
|
|
"index.crates.io",
|
|
"github.com",
|
|
"objects.githubusercontent.com",
|
|
)
|
|
assert expand_package_bundle("go-package-install") == (
|
|
"proxy.golang.org",
|
|
"sum.golang.org",
|
|
"go.dev",
|
|
"golang.org",
|
|
"storage.googleapis.com",
|
|
)
|
|
assert expand_package_bundle("java-package-install") == (
|
|
"repo.maven.apache.org",
|
|
"repo1.maven.org",
|
|
"plugins.gradle.org",
|
|
"services.gradle.org",
|
|
)
|
|
assert expand_package_bundle("php-package-install") == (
|
|
"packagist.org",
|
|
"repo.packagist.org",
|
|
"getcomposer.org",
|
|
)
|
|
assert "rust-package-install" in PACKAGE_BUNDLES
|
|
assert DEFAULT_PACKAGE_BUNDLE_IDS == tuple(PACKAGE_BUNDLES)
|
|
assert expand_package_bundle("unknown") == ()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"raw",
|
|
[
|
|
".example.com",
|
|
"..example.com..",
|
|
"example..com",
|
|
"-example.com",
|
|
"example.com-",
|
|
"exa_mple.com",
|
|
"*.*.example.com",
|
|
"foo.*.example.com",
|
|
"*.",
|
|
"example.com:abc",
|
|
"example.com:123",
|
|
],
|
|
)
|
|
def test_validate_domain_pattern_blocks_malformed_hostnames(raw: str) -> None:
|
|
decision = validate_domain_pattern(raw)
|
|
assert decision.status == "blocked"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"raw",
|
|
[
|
|
"https://[::1]foo",
|
|
"http://[]",
|
|
"http://[v6.invalid]",
|
|
],
|
|
)
|
|
def test_validate_domain_pattern_blocks_malformed_bracketed_url_hosts(raw: str) -> None:
|
|
try:
|
|
decision = validate_domain_pattern(raw)
|
|
except ValueError as exc:
|
|
pytest.fail(f"validate_domain_pattern raised {exc!r}")
|
|
assert decision.status == "blocked"
|
|
|
|
|
|
def test_validate_domain_pattern_blocks_huge_port_without_raising() -> None:
|
|
raw = "example.com:" + ("9" * 5000)
|
|
try:
|
|
decision = validate_domain_pattern(raw)
|
|
except ValueError as exc:
|
|
pytest.fail(f"validate_domain_pattern raised {exc!r}")
|
|
assert decision.status == "blocked"
|
|
|
|
|
|
def test_domain_matches_exact_domain() -> None:
|
|
assert domain_matches("pypi.org", "pypi.org")
|
|
assert not domain_matches("pypi.org", "files.pythonhosted.org")
|
|
assert domain_matches("example.com", "example.com:443")
|
|
|
|
|
|
def test_domain_matches_wildcard_subdomain_and_excludes_apex() -> None:
|
|
assert domain_matches("*.pythonhosted.org", "files.pythonhosted.org")
|
|
assert not domain_matches("*.pythonhosted.org", "pythonhosted.org")
|
|
|
|
|
|
def test_domain_matches_requires_label_boundary() -> None:
|
|
assert not domain_matches("*.pythonhosted.org", "notpythonhosted.org")
|
|
|
|
|
|
def test_domain_matches_returns_false_for_invalid_pattern() -> None:
|
|
assert not domain_matches("*.github.io", "project.github.io")
|
|
assert not domain_matches("*.example.com", "api.example.com")
|
|
assert not domain_matches("foo.*.example.com", "foo.api.example.com")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"host",
|
|
[
|
|
".pypi.org",
|
|
"pypi..org",
|
|
"pypi.org:abc",
|
|
"exa_mple.com",
|
|
"8.8.8.8",
|
|
"https://[::1]foo",
|
|
"http://[v6.invalid]",
|
|
"example.com:123",
|
|
],
|
|
)
|
|
def test_domain_matches_returns_false_for_invalid_host(host: str) -> None:
|
|
try:
|
|
matched = domain_matches("pypi.org", host)
|
|
except ValueError as exc:
|
|
pytest.fail(f"domain_matches raised {exc!r}")
|
|
assert not matched
|
|
|
|
|
|
def test_domain_matches_returns_false_for_huge_port_without_raising() -> None:
|
|
host = "example.com:" + ("9" * 5000)
|
|
try:
|
|
matched = domain_matches("example.com", host)
|
|
except ValueError as exc:
|
|
pytest.fail(f"domain_matches raised {exc!r}")
|
|
assert not matched
|