The 180 s query cap was enforced independently by the backend clamp, the agent limits, and the container supervisor. All three now follow CONFLUENCE_WEB_MAX_DEADLINE_SECONDS (default 900, allowed 60-3600): the backend passes it into the container at launch, the supervisor reads it and forwards it to the bridge, and both fall back to 900 s on invalid input. The query timeout must not exceed it (startup fails otherwise). The supervisor keeps a separate 180 s guard for a container that never receives a start frame. Add assets/book.svg as the tab icon: the backend serves assets/ and the CSP allows same-origin images (the sanitizer still never emits <img>).
92 lines
3.9 KiB
Python
92 lines
3.9 KiB
Python
import pytest
|
|
from backend.errors import DestinationDeniedError, InvalidInputError
|
|
from backend.settings import Settings, canonicalize_url, validate_confluence_url
|
|
|
|
|
|
def test_canonicalize_url_valid():
|
|
assert canonicalize_url("https://approved.example.com") == "https://approved.example.com"
|
|
assert canonicalize_url("https://approved.example.com/") == "https://approved.example.com"
|
|
assert canonicalize_url("https://approved.example.com:443") == "https://approved.example.com"
|
|
assert canonicalize_url("http://approved.example.com:80") == "http://approved.example.com"
|
|
assert canonicalize_url("https://approved.example.com:8443/wiki/") == "https://approved.example.com:8443/wiki"
|
|
assert canonicalize_url("HTTPS://EXAMPLE.COM/Wiki") == "https://example.com/Wiki"
|
|
|
|
|
|
def test_canonicalize_url_rejects_invalids():
|
|
with pytest.raises(InvalidInputError, match="userinfo"):
|
|
canonicalize_url("https://user:pass@example.com")
|
|
|
|
with pytest.raises(InvalidInputError, match="fragment"):
|
|
canonicalize_url("https://example.com/#frag")
|
|
|
|
with pytest.raises(InvalidInputError, match="query"):
|
|
canonicalize_url("https://example.com/?query=1")
|
|
|
|
with pytest.raises(InvalidInputError, match="traversal"):
|
|
canonicalize_url("https://example.com/foo/../bar")
|
|
|
|
with pytest.raises(InvalidInputError, match="traversal"):
|
|
canonicalize_url("https://example.com/foo/%2e%2e/bar")
|
|
|
|
with pytest.raises(InvalidInputError, match="traversal"):
|
|
canonicalize_url("https://example.com/%2fetc/passwd")
|
|
|
|
with pytest.raises(InvalidInputError, match="scheme"):
|
|
canonicalize_url("ftp://example.com")
|
|
|
|
with pytest.raises(InvalidInputError, match="empty"):
|
|
canonicalize_url(" ")
|
|
|
|
# 8 KiB limit
|
|
with pytest.raises(InvalidInputError, match="limit"):
|
|
canonicalize_url("https://example.com/" + "a" * 8200)
|
|
|
|
|
|
def test_validate_confluence_url():
|
|
approved = [
|
|
"https://approved.example.com",
|
|
"https://corp.example.com/wiki",
|
|
]
|
|
|
|
# Exact match
|
|
assert validate_confluence_url("https://approved.example.com", approved) == "https://approved.example.com"
|
|
assert validate_confluence_url("https://approved.example.com/", approved) == "https://approved.example.com"
|
|
assert validate_confluence_url("https://corp.example.com/wiki", approved) == "https://corp.example.com/wiki"
|
|
|
|
# Context subpath
|
|
assert validate_confluence_url("https://corp.example.com/wiki/sub", approved) == "https://corp.example.com/wiki/sub"
|
|
|
|
# Reject prefix lookalike: /wikileaks must NOT match /wiki
|
|
with pytest.raises(DestinationDeniedError, match="denied"):
|
|
validate_confluence_url("https://corp.example.com/wikileaks", approved)
|
|
|
|
# Reject different host
|
|
with pytest.raises(DestinationDeniedError, match="denied"):
|
|
validate_confluence_url("https://malicious.example.com", approved)
|
|
|
|
# Reject different scheme
|
|
with pytest.raises(DestinationDeniedError, match="denied"):
|
|
validate_confluence_url("http://approved.example.com", approved)
|
|
|
|
|
|
def test_max_deadline_seconds_defaults_and_bounds():
|
|
assert Settings().max_deadline_seconds == 900.0
|
|
Settings(max_deadline_seconds=60.0, query_timeout_seconds=60.0)
|
|
Settings(max_deadline_seconds=3600.0)
|
|
with pytest.raises(ValidationError):
|
|
Settings(max_deadline_seconds=59.0, query_timeout_seconds=59.0)
|
|
with pytest.raises(ValidationError):
|
|
Settings(max_deadline_seconds=3601.0)
|
|
|
|
|
|
def test_query_timeout_must_not_exceed_max_deadline():
|
|
Settings(query_timeout_seconds=900.0, max_deadline_seconds=900.0)
|
|
with pytest.raises(ValidationError, match="must not exceed"):
|
|
Settings(query_timeout_seconds=901.0, max_deadline_seconds=900.0)
|
|
|
|
|
|
def test_max_deadline_seconds_from_env(monkeypatch):
|
|
monkeypatch.setenv("CONFLUENCE_WEB_MAX_DEADLINE_SECONDS", "1200")
|
|
monkeypatch.setenv("CONFLUENCE_WEB_QUERY_TIMEOUT_SECONDS", "1000")
|
|
assert Settings.from_env().max_deadline_seconds == 1200.0
|