Python Urljoin Drops a Path Segment: Check Reference Resolution and Origin
By ON Lab · Sources checked November 9, 2026
Urljoin resolves a URL reference; it is not string concatenation. Compare directory-style bases, root-relative paths and absolute references before issuing a request.
Reproduce the boundary in a disposable example
Joining a relative URL removes the last base segment or changes the hostname.
from urllib.parse import urljoin, urlsplit
assert urljoin("https://example.test/api/v1", "items") == "https://example.test/api/items"
assert urljoin("https://example.test/api/v1/", "items") == "https://example.test/api/v1/items"
assert urljoin("https://example.test/api/v1/", "/items") == "https://example.test/items"
base = "https://example.test/api/v1/"
changed = urljoin(base, "//other.test/items")
assert urlsplit(changed).netloc == "other.test"
# Review the final origin before a client sends anything.
assert urlsplit(base).netloc != urlsplit(changed).netloc
print("directory, root and origin cases verified without requests")Read the result before changing application code
The tests do not make network requests. They expose the target that a later HTTP client would receive. A missing trailing slash causes the last base segment to behave like a document name. A leading slash starts at the site root. An absolute or network-path reference can select another origin.
Choose the behavior your caller needs
If the input is genuinely a relative URL reference, choose the correct base and keep URL resolution semantics. If it is a single identifier, validate and encode it as a path component rather than accepting an arbitrary URL. For a same-origin client, validate the final scheme and authority before sending, including redirects under your client’s policy. Do not fix a hostname change by attaching credentials to whichever result was produced.
Scope and limits
This is a local composition check and not a complete URL allowlist or SSRF defense. Credentials, redirects and encoded path policy require separate application-level controls.
Related: compare the adjacent data or execution boundary.
The embedded example was executed in an isolated local Python process on November 9, 2026. It is a reproducible boundary check, not a test of your full application.