مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
120 lines
3.6 KiB
Python
120 lines
3.6 KiB
Python
"""Platform safety rules: subdomain validation, proxy targets, port ranges."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.domain.platform.rules import (
|
|
RuleViolation,
|
|
assert_not_protected_port,
|
|
assert_port_allocatable,
|
|
build_host,
|
|
is_port_allocatable,
|
|
validate_managed_host,
|
|
validate_proxy_target,
|
|
validate_subdomain_label,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"label", ["my-app", "app1", "a" * 40, "abc"]
|
|
)
|
|
def test_valid_labels(label: str):
|
|
assert validate_subdomain_label(label) == label
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"label",
|
|
[
|
|
"ab", # too short
|
|
"a" * 41, # too long
|
|
"-abc", # leading hyphen
|
|
"abc-", # trailing hyphen
|
|
"My-App", # uppercase is normalised, but this one has no other issue
|
|
"app_name", # underscore not allowed
|
|
"app.name", # dot not allowed
|
|
"app name", # whitespace
|
|
"app{}", # brace injection attempt
|
|
"app\nother", # newline injection attempt
|
|
"", # empty
|
|
],
|
|
)
|
|
def test_invalid_labels(label: str):
|
|
if label == "My-App":
|
|
# Documented behaviour: input is lower-cased, so this is accepted as "my-app".
|
|
assert validate_subdomain_label(label) == "my-app"
|
|
return
|
|
with pytest.raises(RuleViolation):
|
|
validate_subdomain_label(label)
|
|
|
|
|
|
def test_build_host_appends_root_domain():
|
|
assert build_host("shop", "msa.artacloud.ir") == "shop.msa.artacloud.ir"
|
|
|
|
|
|
def test_managed_host_rejects_foreign_domain():
|
|
with pytest.raises(RuleViolation):
|
|
validate_managed_host("evil.example.com", "msa.artacloud.ir")
|
|
|
|
|
|
def test_managed_host_rejects_nested_label():
|
|
with pytest.raises(RuleViolation):
|
|
validate_managed_host("a.b.msa.artacloud.ir", "msa.artacloud.ir")
|
|
|
|
|
|
def test_managed_host_rejects_injection_attempt():
|
|
with pytest.raises(RuleViolation):
|
|
validate_managed_host("app { reverse_proxy evil:80 }.msa.artacloud.ir", "msa.artacloud.ir")
|
|
|
|
|
|
def test_managed_host_accepts_valid_host():
|
|
assert (
|
|
validate_managed_host("my-app.msa.artacloud.ir", "msa.artacloud.ir")
|
|
== "my-app.msa.artacloud.ir"
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"target",
|
|
["localhost:8080", "127.0.0.1:8080", "http://localhost:8080", "http://127.0.0.1:9000"],
|
|
)
|
|
def test_valid_proxy_targets(target: str):
|
|
host, port = validate_proxy_target(target)
|
|
assert host in {"localhost", "127.0.0.1"}
|
|
assert 1 <= port <= 65535
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"target",
|
|
[
|
|
"example.com:80",
|
|
"169.254.169.254:80", # cloud metadata endpoint
|
|
"10.0.0.5:8080", # internal network
|
|
"localhost", # no port
|
|
"localhost:notaport",
|
|
"0.0.0.0:8080",
|
|
],
|
|
)
|
|
def test_ssrf_proxy_targets_rejected(target: str):
|
|
with pytest.raises(RuleViolation):
|
|
validate_proxy_target(target)
|
|
|
|
|
|
@pytest.mark.parametrize("port", [22, 53, 80, 443, 3000, 5432, 6379, 8008, 2019, 7999, 9000])
|
|
def test_reserved_and_out_of_range_ports_rejected(port: int):
|
|
assert not is_port_allocatable(port)
|
|
with pytest.raises(RuleViolation):
|
|
assert_port_allocatable(port)
|
|
|
|
|
|
@pytest.mark.parametrize("port", [8080, 8500, 8999])
|
|
def test_allocatable_ports(port: int):
|
|
assert is_port_allocatable(port)
|
|
assert assert_port_allocatable(port) == port
|
|
|
|
|
|
@pytest.mark.parametrize("port", [3000, 35555, 35556, 35557, 5432])
|
|
def test_protected_ports_cannot_be_proxied(port: int):
|
|
with pytest.raises(RuleViolation):
|
|
assert_not_protected_port(port)
|