This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-7474-557b84a770bf150bea199aa8115cd84c1d86eb46 in repository https://gitbox.apache.org/repos/asf/texera.git
commit c7dd5ae427b2fc7a09331fa311261e1ee2a7c2c2 Author: Prateek Ganigi <[email protected]> AuthorDate: Mon Aug 10 15:19:06 2026 -0700 fix(workflow-operator): accept single-segment HF model IDs and reject '..' in the model-id check (#7474) ### What changes were proposed in this PR? The generated model-ID validation regex (`_HF_MODEL_ID_PATTERN`) had two problems: 1. It let `..` path-traversal segments through: e.g. `org/..` passed, even though the comment claimed `..` was rejected (the character class allowed dots, so `..` was a valid segment). 2. It rejected legacy single-segment model IDs like `gpt2` and `bert-base-uncased`, because it required at least one `/`. This adds a `(?!.*\.\.)` lookahead to reject any `..`, and makes the trailing `/segment` group optional so single-segment IDs are accepted. The comment and the "Invalid Hugging Face model ID" error message are updated to match. ### Any related issues? Closes #7196 ### How was this PR tested? - Existing HuggingFace operator unit tests + the `PythonCodeRawInvalidTextSpec` py-compile guard (confirms the new regex is valid Python). - Extended the existing MODEL_ID spec test to assert the lookahead and the now-optional segment group are emitted. - Behavioral check of the emitted regex: `gpt2`, `bert-base-uncased`, `t5-small`, `org/model`, `org/model/revision` are accepted; `org/..`, `org/../secret`, `..` are rejected. ### Was this PR authored or co-authored using generative AI tooling? This PR was co-authored with Claude in compliance with ASF policy. --- .../operator/huggingFace/codegen/HuggingFaceCodegenBase.scala | 10 ++++++---- .../operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala index 9b59ce6d92..613a77aab3 100644 --- a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala +++ b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala @@ -72,9 +72,11 @@ 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 `..` segments / query strings / fragments / - |# control chars keeps the operator's request shape predictable. - |_HF_MODEL_ID_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*(/[A-Za-z0-9._-]+)+$$") + |# 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._-]+)*$$") | |class ProcessTableOperator(UDFTableOperator): | @@ -488,7 +490,7 @@ object HuggingFaceCodegenBase { | if not _HF_MODEL_ID_PATTERN.match(self.MODEL_ID or ""): | raise ValueError( | f"Invalid Hugging Face model ID '{self.MODEL_ID}'. " - | f"Expected format like 'org/model-name' or 'org/model-name/revision'." + | f"Expected a model ID like 'gpt2', 'org/model-name', or 'org/model-name/revision'." | ) | | # --- resolve API token --- diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala index bf6549fb80..439b111fae 100644 --- a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala +++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala @@ -141,6 +141,10 @@ class HuggingFaceInferenceOpDescSpec extends AnyFlatSpec with Matchers { code should include("if not _HF_MODEL_ID_PATTERN.match(") code should include("raise ValueError(") code should include("Invalid Hugging Face model ID") + // #7196: reject `..` path traversal (leading negative lookahead) and accept + // single-segment legacy IDs like `gpt2` (trailing /segment group optional). + code should include("(?!.*\\.\\.)") + code should include("(/[A-Za-z0-9._-]+)*$") } it should "not leak raw user-input strings into the generated Python source" in {
