From 80252379f722cb1bcf2962d8250bd047cfcf55ab Mon Sep 17 00:00:00 2001 From: JasonBuildAI Date: Fri, 30 Oct 2026 13:38:10 +0800 Subject: [PATCH 1/2] fix(presentation): reject unlabeled provider tokens at the public boundary The presentation public-boundary scan certified unlabeled provider credentials as public-safe. PRESENTATION_PUBLIC_BOUNDARY_PATTERNS carried only an absolute-local-path arm, a private-key arm and a labeled api_key=/auth_token=/access_token= assignment arm, so a GitHub PAT in a pasted CI log or git remote URL, an AWS/Slack/Google/Stripe/npm/PyPI credential or a JWT embedded in dash HTML, CLI dash output, chat detail text or a manager-channel result return returned ok=true with no warning. dash_server serves the page, the dash CLI writes the --out file, chat detail publication and the manager roundtrip accept the result under that certification. Compose the new arm from PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE, the canonical public-safety owner merged #7636 already made the outbound capability scanner consume, and keep the assignment arm for the labeled forms the issuer prefixes do not carry (Refs #5136, #7727). Signed-off-by: JasonBuildAI --- loopx/public_safe_text.py | 48 ++++++++++-------- .../test_presentation_public_safety.py | 50 +++++++++++++++++++ 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/loopx/public_safe_text.py b/loopx/public_safe_text.py index 7096a36625..90bc089695 100644 --- a/loopx/public_safe_text.py +++ b/loopx/public_safe_text.py @@ -285,6 +285,26 @@ PRESENTATION_EXTENDED_WINDOWS_PATH_PATTERN, PRESENTATION_COLON_PREFIXED_WINDOWS_PATH_PATTERN, ) +# The provider-issued credential shapes, as one alternation body shared by every +# owner that has to recognize a token with no label at all. A prefix carries the +# provider's own issuer identity, so the shape is self-describing and a caller +# never has to guess a label; the length floor keeps ordinary prose out. Case +# folding is left to the consumer's compile flags or inline `(?i)`, so this +# string stays a plain alternation instead of owning one flag decision. +# +# Kept here rather than restated per caller: a scan that recognizes only some +# issuers certifies the rest as public-safe, and copies drift (Refs #5136). +PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE = ( + r"\bgh[pousr]_[a-z0-9]{16,}\b|" + r"\bgithub_pat_[a-z0-9_]{20,}|" + r"\b(?:akia|asia)[a-z0-9]{16}\b|" + r"\bxox[baprs]-[a-z0-9-]{10,}|" + r"\baiza[a-z0-9_-]{20,}|" + r"\b(?:sk|rk)_(?:live|test)_[a-z0-9]{12,}|" + r"\bnpm_[a-z0-9]{20,}|" + r"\bpypi-[a-z0-9_-]{20,}|" + r"\beyj[a-z0-9_-]{10,}\.[a-z0-9_-]{10,}\.[a-z0-9_-]{10,}\b" +) PRESENTATION_PUBLIC_BOUNDARY_PATTERNS: tuple[ tuple[str, re.Pattern[str]], ... ] = ( @@ -311,31 +331,19 @@ re.IGNORECASE, ), ), + # An issuer prefix is its own label: a token carried in tool output or a + # pasted log rarely arrives as an assignment, so the assignment arm never + # sees it. Composed from the shared source the sibling scanners consume + # (#7636), case-folded like the presentation local-path arms. + ( + "provider token", + re.compile(r"(?i)(?:" + PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE + r")"), + ), ) # Refs #5136: one definition for "this string carries a raw remote location". # Three validators each restated the same scheme list, and the canonical # public-safety owner had no counterpart, so a fourth caller had to invent one. REMOTE_LOCATION_SURFACE_PATTERN = re.compile(r"(?i)\b(?:https?|file|s3|gs|tos|hdfs)://") -# The provider-issued credential shapes, as one alternation body shared by every -# owner that has to recognize a token with no label at all. A prefix carries the -# provider's own issuer identity, so the shape is self-describing and a caller -# never has to guess a label; the length floor keeps ordinary prose out. Case -# folding is left to the consumer's compile flags or inline `(?i)`, so this -# string stays a plain alternation instead of owning one flag decision. -# -# Kept here rather than restated per caller: a scan that recognizes only some -# issuers certifies the rest as public-safe, and copies drift (Refs #5136). -PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE = ( - r"\bgh[pousr]_[a-z0-9]{16,}\b|" - r"\bgithub_pat_[a-z0-9_]{20,}|" - r"\b(?:akia|asia)[a-z0-9]{16}\b|" - r"\bxox[baprs]-[a-z0-9-]{10,}|" - r"\baiza[a-z0-9_-]{20,}|" - r"\b(?:sk|rk)_(?:live|test)_[a-z0-9]{12,}|" - r"\bnpm_[a-z0-9]{20,}|" - r"\bpypi-[a-z0-9_-]{20,}|" - r"\beyj[a-z0-9_-]{10,}\.[a-z0-9_-]{10,}\.[a-z0-9_-]{10,}\b" -) SECRET_LIKE_SURFACE_PATTERN = re.compile( r"(?i)(?:\bbearer\s+[a-z0-9._~+/=-]{16,}|" r"\b(?:access|api|secret)[_-]?key[\"']?\s*[=:]\s*[\"']?[^\s`'\"<>]+|" diff --git a/tests/control_plane/test_presentation_public_safety.py b/tests/control_plane/test_presentation_public_safety.py index 7c30d72d94..9b79fd55f5 100644 --- a/tests/control_plane/test_presentation_public_safety.py +++ b/tests/control_plane/test_presentation_public_safety.py @@ -75,3 +75,53 @@ def test_colon_prefixed_unix_paths_keep_the_existing_boundary() -> None: "ok": False, "warnings": ["absolute local path"], } + + +@pytest.mark.parametrize( + "token", + [ + "ghp_" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4", + "github_pat_11ABCDEFG0" + "a1B2c3D4e5F6", + "AKIA" + "7Q2Z3XJ5V6N8W1PL", + "ASIA" + "7Q2Z3XJ5V6N8W1PL", + "xoxb-" + "123456789-abcdef", + "AIzaSyA" + "1234567890abcdefghij", + "sk_live_51H8" + "aBcDeFgHiJkL", + "npm_" + "a1B2c3D4e5F6g7H8i9J0", + "pypi-AgEIcHlwaS5vcmc" + "AAAAAAAAAA", + "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4f", + ], +) +def test_unlabeled_provider_tokens_fail_the_boundary_scan(token: str) -> None: + text = f"CI log fragment: pushed with {token} to the registry mirror" + + assert public_safety.scan_public_boundary_text(text) == { + "ok": False, + "warnings": ["provider token"], + } + + +@pytest.mark.parametrize( + "text", + [ + "npm run build && npm test", + "gh pr list --limit 5", + "AWS key ids start AKIA and ASIA, then sixteen more characters", + "pypi-AgEIcHlwaS5vcmc is the PyPI token prefix, not a whole token", + "sk-proj-abcdefghijklmnop is an OpenAI project key shape", + ], +) +def test_ordinary_prose_and_unclaimed_shapes_stay_public(text: str) -> None: + assert public_safety.scan_public_boundary_text(text) == { + "ok": True, + "warnings": [], + } + + +def test_labeled_credential_assignment_keeps_its_own_warning() -> None: + text = "deploy used access_token=ghp_" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4" + + result = public_safety.scan_public_boundary_text(text) + + assert result["ok"] is False + assert result["warnings"] == ["credential assignment", "provider token"] From e50e390fde41c12f1e73dd2fa5ef664e9d0e467f Mon Sep 17 00:00:00 2001 From: JasonBuildAI Date: Fri, 30 Oct 2026 13:53:50 +0800 Subject: [PATCH 2/2] test(presentation): state the provider-token boundary truthfully The PyPI negative row pinned an 18-character run and implied prefix safety that the canonical floor does not provide; use prose that is genuinely outside the shape. Split the JWT literal like the other rows so the literal-splitting convention a sibling change adds to the gate applies here too. Signed-off-by: JasonBuildAI --- tests/control_plane/test_presentation_public_safety.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/control_plane/test_presentation_public_safety.py b/tests/control_plane/test_presentation_public_safety.py index 9b79fd55f5..d0b262aadc 100644 --- a/tests/control_plane/test_presentation_public_safety.py +++ b/tests/control_plane/test_presentation_public_safety.py @@ -89,7 +89,7 @@ def test_colon_prefixed_unix_paths_keep_the_existing_boundary() -> None: "sk_live_51H8" + "aBcDeFgHiJkL", "npm_" + "a1B2c3D4e5F6g7H8i9J0", "pypi-AgEIcHlwaS5vcmc" + "AAAAAAAAAA", - "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4f", + "eyJhbGciOiJIUzI1NiJ9" + ".eyJzdWIiOiIxMjM0NTY3ODkwIn0" + ".SflKxwRJSMeKKF2QT4f", ], ) def test_unlabeled_provider_tokens_fail_the_boundary_scan(token: str) -> None: @@ -107,7 +107,7 @@ def test_unlabeled_provider_tokens_fail_the_boundary_scan(token: str) -> None: "npm run build && npm test", "gh pr list --limit 5", "AWS key ids start AKIA and ASIA, then sixteen more characters", - "pypi-AgEIcHlwaS5vcmc is the PyPI token prefix, not a whole token", + "pypi- prefix alone is not a token", "sk-proj-abcdefghijklmnop is an OpenAI project key shape", ], )