From d6c9058f86da1b25898c35dd9ea3f3509910a860 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:59:03 +0000 Subject: [PATCH] =?UTF-8?q?URL=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=A0=95?= =?UTF-8?q?=EA=B7=9C=EC=8B=9D=20=EA=B0=95=ED=99=94=20(Path=20Traversal=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/sentinel.md | 4 ++++ scripts/ci/agent_mention_sweep.py | 4 ++-- scripts/ci/organization_commercial_readiness_loop.py | 2 +- scripts/ci/reconcile_repository_labels.py | 2 +- scripts/ci/reconcile_repository_metadata.py | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 79f94e70f3..90cab60a64 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -43,3 +43,7 @@ **Vulnerability:** Denial of Service / Availability **Learning:** Strix security scanners crashed when the backend LLM returned an 'internal server error' HTTP 500 response. This was because 'internal server error' string match was missing from the `is_llm_api_connection_error` function in the Strix retry gate. **Prevention:** Always include `internal server error` in string match conditions when handling HTTP API Connection exceptions for LLM backends to ensure proper fail-closed and retry handling. +## 2026-09-08 - Overly Permissive Regex for URL Parameters +**Vulnerability:** Permissive regex `^[A-Za-z0-9_.-]+$` used for repo and org names allowed leading/trailing/consecutive dots, creating SSRF and path traversal risks when constructing URLs. +**Learning:** Basic alphanumeric regex with dots is insufficient for URL parameter validation as it permits path traversal sequences like `..`. +**Prevention:** Use negative lookaheads `^(?!.*(?:\.\.|\.$))[A-Za-z0-9_.-]+$` to explicitly reject trailing and consecutive dots in URL parameters. diff --git a/scripts/ci/agent_mention_sweep.py b/scripts/ci/agent_mention_sweep.py index 50e0a84f17..5b56fdcf4f 100755 --- a/scripts/ci/agent_mention_sweep.py +++ b/scripts/ci/agent_mention_sweep.py @@ -22,8 +22,8 @@ ) from redact_sensitive_log import redact_text -ORG_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+$") -REPOSITORY_RE = re.compile(r"^ContextualWisdomLab/[A-Za-z0-9_.-]+$") +ORG_NAME_RE = re.compile(r"^(?!.*(?:\.\.|\.$))[A-Za-z0-9_.-]+$") +REPOSITORY_RE = re.compile(r"^ContextualWisdomLab/(?!.*(?:\.\.|\.$))[A-Za-z0-9_.-]+$") REPOSITORY_SOURCES = frozenset({"organization", "installation"}) REPOSITORY_ROTATION_SECONDS = 5 * 60 # The sweep-organization-agent-mentions job has a 900s (15-minute) GitHub diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index 9657bd2d4d..a8d5419c7e 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -26,7 +26,7 @@ DEFAULT_ORGANIZATION = "ContextualWisdomLab" -ORGANIZATION_RE = re.compile(r"^[A-Za-z0-9_.-]+$") +ORGANIZATION_RE = re.compile(r"^(?!.*(?:\.\.|\.$))[A-Za-z0-9_.-]+$") ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" CENTRAL_REPAIR_EVENT = "pr-review-fix-scheduler" diff --git a/scripts/ci/reconcile_repository_labels.py b/scripts/ci/reconcile_repository_labels.py index d4585877c6..761cfb6749 100644 --- a/scripts/ci/reconcile_repository_labels.py +++ b/scripts/ci/reconcile_repository_labels.py @@ -14,7 +14,7 @@ ORGANIZATION = "ContextualWisdomLab" -REPOSITORY_RE = re.compile(r"^[A-Za-z0-9_.-]+$") +REPOSITORY_RE = re.compile(r"^(?!.*(?:\.\.|\.$))[A-Za-z0-9_.-]+$") class TaxonomyError(ValueError): diff --git a/scripts/ci/reconcile_repository_metadata.py b/scripts/ci/reconcile_repository_metadata.py index 36a910ffa8..1570455818 100644 --- a/scripts/ci/reconcile_repository_metadata.py +++ b/scripts/ci/reconcile_repository_metadata.py @@ -21,7 +21,7 @@ ORGANIZATION = "ContextualWisdomLab" -REPOSITORY_RE = re.compile(r"^[A-Za-z0-9_.-]+$") +REPOSITORY_RE = re.compile(r"^(?!.*(?:\.\.|\.$))[A-Za-z0-9_.-]+$") TOPIC_RE = re.compile(r"^[a-z0-9][a-z0-9-]{0,49}$") MAX_DESCRIPTION_CHARS = 350 PAGES_BASE_URL = f"https://{ORGANIZATION.casefold()}.github.io"