From 64524d390f5ba75d686e528fb877143aa40b4dc4 Mon Sep 17 00:00:00 2001 From: Lihua <1017343802@qq.com> Date: Sun, 1 Nov 2026 03:29:58 -0800 Subject: [PATCH] fix(public-safety): reject unlabeled OpenAI and Anthropic keys Signed-off-by: Lihua <1017343802@qq.com> --- loopx/contract.py | 4 ++++ loopx/public_safe_text.py | 2 ++ .../control_plane/test_presentation_public_safety.py | 10 +++++++++- tests/test_contract_credential_pattern_precision.py | 8 ++++++++ tests/test_static_site_presentation.py | 12 ++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/loopx/contract.py b/loopx/contract.py index 259655804f..ac032e5d8d 100644 --- a/loopx/contract.py +++ b/loopx/contract.py @@ -143,6 +143,10 @@ def _prefilter_fold(text: str) -> str: "sk_test_", "rk_live_", "rk_test_", + "sk-proj-", + "sk-svcacct-", + "sk-admin-", + "sk-ant-", "npm_", "pypi-", "eyj", diff --git a/loopx/public_safe_text.py b/loopx/public_safe_text.py index 90bc089695..9ec8cecc0b 100644 --- a/loopx/public_safe_text.py +++ b/loopx/public_safe_text.py @@ -301,6 +301,8 @@ r"\bxox[baprs]-[a-z0-9-]{10,}|" r"\baiza[a-z0-9_-]{20,}|" r"\b(?:sk|rk)_(?:live|test)_[a-z0-9]{12,}|" + r"\bsk-(?:proj|svcacct|admin)-[a-z0-9_-]{20,}|" + r"\bsk-ant-[a-z0-9_-]{20,}|" 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" diff --git a/tests/control_plane/test_presentation_public_safety.py b/tests/control_plane/test_presentation_public_safety.py index d0b262aadc..d3264275c4 100644 --- a/tests/control_plane/test_presentation_public_safety.py +++ b/tests/control_plane/test_presentation_public_safety.py @@ -89,6 +89,10 @@ def test_colon_prefixed_unix_paths_keep_the_existing_boundary() -> None: "sk_live_51H8" + "aBcDeFgHiJkL", "npm_" + "a1B2c3D4e5F6g7H8i9J0", "pypi-AgEIcHlwaS5vcmc" + "AAAAAAAAAA", + "sk-proj-" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", + "sk-svcacct-" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", + "sk-admin-" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", + "sk-ant-api03-" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "eyJhbGciOiJIUzI1NiJ9" + ".eyJzdWIiOiIxMjM0NTY3ODkwIn0" + ".SflKxwRJSMeKKF2QT4f", ], ) @@ -109,9 +113,13 @@ def test_unlabeled_provider_tokens_fail_the_boundary_scan(token: str) -> None: "AWS key ids start AKIA and ASIA, then sixteen more characters", "pypi- prefix alone is not a token", "sk-proj-abcdefghijklmnop is an OpenAI project key shape", + "sk-svcacct-abcdefghijklmnop is a short example", + "sk-admin-abcdefghijklmnop is a short example", + "sk-ant-abcdefghijklmnop is a short example", + "sk-custom-" + "abcdefghijklmnopqrstuvwx", ], ) -def test_ordinary_prose_and_unclaimed_shapes_stay_public(text: str) -> None: +def test_ordinary_prose_and_short_provider_lookalikes_stay_public(text: str) -> None: assert public_safety.scan_public_boundary_text(text) == { "ok": True, "warnings": [], diff --git a/tests/test_contract_credential_pattern_precision.py b/tests/test_contract_credential_pattern_precision.py index 7e229a9645..dd774c4418 100644 --- a/tests/test_contract_credential_pattern_precision.py +++ b/tests/test_contract_credential_pattern_precision.py @@ -140,6 +140,10 @@ def test_private_lark_tenant_urls_remain_blocked(tmp_path: Path, line: str) -> N # second list here: this rule gates repository publication, and a list that # recognized only some issuers certified the rest as public-safe. PROVIDER_TOKEN_SAMPLES = [ + "sk-proj-" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", + "sk-svcacct-" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", + "sk-admin-" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", + "sk-ant-api03-" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "ghp_" + "16C7e42F292c6912E7710c838347Ae178B4a", "github_pat_" + "11ABCDEFG0123456789_abcdefghijklmnopqrstuvwxyz0123456789", "xoxb-" + "123456789012-abcdefghijkl", @@ -182,6 +186,10 @@ def test_a_short_provider_lookalike_stays_clean(tmp_path: Path) -> None: PROVIDER_TOKENS_WITH_PLACEHOLDER_BODY = [ "ghp_" + "dummy" + "1234567890abcdefghijklmnop", "npm_" + "example" + "1234567890abcdefghij", + "sk-proj-" + "example" + "1234567890abcdefghij", + "sk-svcacct-" + "example" + "1234567890abcdefghij", + "sk-admin-" + "example" + "1234567890abcdefghij", + "sk-ant-api03-" + "example" + "1234567890abcdefghij", ] diff --git a/tests/test_static_site_presentation.py b/tests/test_static_site_presentation.py index 479dee10f6..84f1930040 100644 --- a/tests/test_static_site_presentation.py +++ b/tests/test_static_site_presentation.py @@ -211,6 +211,18 @@ def test_package_requires_visual_and_link_validation_receipts( _package(site, tmp_path / "publish", tmp_path / "state", **{field: value}) +@pytest.mark.parametrize("prefix", ["sk-proj-", "sk-svcacct-", "sk-admin-", "sk-ant-api03-"]) +def test_package_rejects_unlabeled_ai_provider_tokens(tmp_path: Path, prefix: str) -> None: + site = _site(tmp_path) + token = prefix + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" + (site / "notes.txt").write_text(f"provider output: {token}\n", encoding="utf-8") + destination = tmp_path / "publish" + + with pytest.raises(StaticSiteContractError, match="public boundary scan failed"): + _package(site, destination, tmp_path / "state") + assert not destination.exists() + + def test_package_rejects_reserved_paths_symlinks_and_public_boundary_leaks( tmp_path: Path, ) -> None: