Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,10 @@ object HuggingFaceCodegenBase {
|
|# Defensive format check for MODEL_ID before it is interpolated into
|# HF URL paths. The base host is hardcoded so the worst case isn't
|# SSRF, but rejecting `..` traversal / query strings / fragments /
|# control chars keeps the operator's request shape predictable. The
|# leading (?!.*\.\.) rejects any `..`; the trailing /segment group is
|# optional so single-segment legacy IDs like `gpt2` are also accepted.
|_HF_MODEL_ID_PATTERN = re.compile(r"^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._-]*(/[A-Za-z0-9._-]+)*$$")
|# SSRF, but rejecting query strings / fragments / control chars keeps
|# the operator's request shape predictable. The trailing /segment group
|# is optional so single-segment legacy IDs like `gpt2` are accepted.
|_HF_MODEL_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*(/[A-Za-z0-9._-]+)*$$")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
file='common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala'

printf '%s\n' '--- outline ---'
ast-grep outline "$file" 2>/dev/null || true

printf '%s\n' '--- target file ---'
sed -n '1,180p' "$file"

printf '%s\n' '--- relevant references ---'
rg -n -C 3 'MODEL_ID|modelId|api/models|/models/|Authorization|Bearer|HF_MODEL_ID_PATTERN|http|Uri|URL' common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace

printf '%s\n' '--- repository-wide callers and definitions ---'
rg -n -C 2 'HuggingFaceCodegenBase|HF_MODEL_ID_PATTERN|modelId' common/workflow-operator/src/main common/workflow-operator/src/test 2>/dev/null || true

Repository: sshiv012/texera

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import re
from urllib.parse import urlsplit, urlunsplit

pattern = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*(/[A-Za-z0-9._-]+)*$")
values = ["a/../../whoami", "a/../../../api/x", "org/model..name", "org/model", "../whoami"]
for value in values:
    accepted = bool(pattern.fullmatch(value))
    url = f"https://huggingface.co/api/models/{value}"
    print(value, "accepted=", accepted, "raw_path=", urlsplit(url).path)
PY

Repository: sshiv012/texera

Length of output: 475


🏁 Script executed:

#!/bin/bash
set -eu
file='common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala'

printf '%s\n' '--- all pattern uses ---'
rg -n -C 5 '_HF_MODEL_ID_PATTERN|MODEL_ID' "$file"

printf '%s\n' '--- all request calls and URL expressions ---'
rg -n -C 8 'requests\.(get|post|request)|https://huggingface\.co|CHAT_ROUTES|PROVIDER_URL|providerId|MODEL_ID' "$file"

printf '%s\n' '--- generated operator body ---'
sed -n '130,430p' "$file"

Repository: sshiv012/texera

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import importlib.util
from urllib.parse import urlsplit
print("requests_available=", importlib.util.find_spec("requests") is not None)
if importlib.util.find_spec("requests"):
    import requests
    values = ["a/../../whoami", "a/../../../api/x", "org/model..name"]
    for value in values:
        for url in [
            f"https://huggingface.co/api/models/{value}",
            f"https://huggingface.co/models/{value}/v1/chat/completions",
        ]:
            req = requests.Request("GET", url).prepare()
            print("input=", value, "prepared_url=", req.url, "path=", urlsplit(req.url).path)
PY

Repository: sshiv012/texera

Length of output: 179


🏁 Script executed:

#!/bin/bash
set -eu
file='common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala'

printf '%s\n' '--- authentication headers and execution order ---'
sed -n '480,525p' "$file"
sed -n '610,665p' "$file"

printf '%s\n' '--- changed line history summary ---'
git diff --unified=8 -- "$file" | sed -n '1,100p'

printf '%s\n' '--- exact URL-bearing branches ---'
sed -n '150,250p' "$file"

Repository: sshiv012/texera

Length of output: 12675


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from urllib.parse import urljoin, urlsplit

values = ["a/../../whoami", "a/../../../api/x"]
for value in values:
    for template in (
        f"https://huggingface.co/api/models/{value}",
        f"https://router.huggingface.co/hf-inference/models/{value}",
        f"https://router.huggingface.co/hf-inference/models/{value}/v1/chat/completions",
    ):
        print("raw     =", template)
        print("urljoin =", urljoin(template, urlsplit(template).path))
PY

Repository: sshiv012/texera

Length of output: 952


🌐 Web query:

Python requests PreparedRequest dot segment normalization URL path '..' behavior

💡 Result:

The Python requests library, through its dependency urllib3, performs URL path normalization that includes the removal of dot segments (e.g., .. and .). This means that paths containing .. are resolved according to RFC 3986 standards, effectively stripping them from the URL path before the request is sent [1][2][3]. When using requests.Request or requests.Session().prepare_request(), the URL is parsed and reconstructed during the preparation phase, which triggers this normalization [4][5]. Workarounds and Behavior: - Direct Modification: Simply assigning a URL containing .. to PreparedRequest.url after calling .prepare() is a common strategy to bypass this initial normalization [6][7]. - Limitations with Proxies: While manual URL modification often works for direct connections, it may be insufficient when using proxies, as urllib3 may re-parse and normalize the URL again during the send() process [8]. - Alternative Approaches: If strict preservation of the URL path is required (e.g., for security testing or exploiting path traversal vulnerabilities), developers often recommend using lower-level libraries such as http.client or httpx, which offer greater control over the raw HTTP request and avoid the automatic normalization applied by the requests and urllib3 stack [8]. In summary, requests does not provide a native configuration flag to disable this normalization, as it is deeply integrated into the URL handling logic of the underlying urllib3 library [2][7]. The "PreparedRequest" method remains the standard, albeit sometimes limited, workaround [7][9].

Citations:


Path Traversal (CWE-22): Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Reachability: External

Reject . and .. path segments in MODEL_ID.

The current validator accepts a/../../whoami. requests/urllib3 can normalize this to an unintended Hugging Face path before sending the bearer-authenticated request. Validate repository-ID segments before constructing /api/models/{MODEL_ID} or /models/{MODEL_ID}/.... Add regression tests without real tokens.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala`
at line 78, Update the MODEL_ID validation around _HF_MODEL_ID_PATTERN to reject
any path segment equal to "." or ".." before constructing Hugging Face API or
model URLs. Add regression tests covering traversal segments such as
"a/../../whoami", using mocked requests or no real tokens, while preserving
valid repository-ID formats.

|
|class ProcessTableOperator(UDFTableOperator):
|
Expand Down