Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ loopx check \
--scan-path examples/
```

The credential rule also rejects unlabeled provider token shapes (GitHub, Slack,
AWS, Google, Stripe, npm, PyPI, JWT) anywhere in a file. A fixture or example
that must demonstrate a redaction keeps the value split, for example
`"ghp_" + "<suffix>"` in Python or a `\u` escape in JSON, so the test still
exercises the scan without a raw token entering the repository or the PR diff.

## Local Development

Use the [developer guide](docs/development/README.md) as the stable entry point.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@

GOAL_ID = "artifact-lifecycle-fixture"

# Split so the literal provider shape is not itself a credential hit for the
# public boundary scan, the same convention the source fixtures follow.
PROVIDER_TOKEN = "ghp_" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"

# Substrings that must never appear in a public projection.
FORBIDDEN_LEAKS = (
"/Users/",
Expand Down Expand Up @@ -290,7 +294,7 @@ def assert_private_values_are_redacted_or_dropped() -> None:
"delivery_batch_scale": "multi_surface",
"evidence_ref": "/Users/private-owner/.ssh/id_rsa",
"recommended_action": (
"publish ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 and "
f"publish {PROVIDER_TOKEN} and "
"see /private/var/folders/secret/notes.md"
),
}
Expand All @@ -301,14 +305,14 @@ def assert_private_values_are_redacted_or_dropped() -> None:
for leaked in (
"/Users/private-owner",
"/private/var/folders",
"ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
PROVIDER_TOKEN,
):
assert leaked not in rendered, (leaked, rendered)

# Unsafe references are withheld by the same validator as other status
# projections; no local path is retained even as a truncated fragment.
assert _compact_text("/Users/private-owner/notes.md") is None
assert _compact_text("token ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ012345") is None
assert _compact_text(f"token {PROVIDER_TOKEN}") is None
assert _compact_text("/private/var/folders/x/secret.md") is None


Expand Down
32 changes: 31 additions & 1 deletion loopx/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
collect_history,
)
from .paths import DEFAULT_RUNTIME_ROOT, LEGACY_RUNTIME_ROOT, rel_or_abs, resolve_runtime_root
from .public_safe_text import PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE
from .registry import inspect_registry, inspect_registry_boundary, registry_goals, resolve_state_file
from .state_projection import state_projection_gap_warning
from .control_plane.todos.contract import (
Expand Down Expand Up @@ -111,6 +112,13 @@ def _prefilter_fold(text: str) -> str:
r"(?<![A-Za-z0-9_])" + "tok" + "en=",
r"(?<![A-Za-z0-9_])" + "pass" + "word=",
"Author" + "ization:",
# A provider token carries its issuer in the prefix, so it
# needs no label. The shapes come from the canonical
# public-safety owner rather than a second copy here: this
# scan gates repository publication, and a credential list
# that recognized only some issuers certified the rest as
# public-safe (Refs #5136, #7635).
PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] 完整 provider literal 不能因正文内部含占位词而降为引用

新 arm 会把完整 provider body 送到既有 _credential_match_is_reference 的无 keyword 分支;它对 match.group(0) 搜索 dummy/example 等任意子串。两个无外部引用语法的完整合成形状都命中 canonical regex,却在真实 check --scan-path 中产生 errors=0、warnings=1、exit=0。请按明确的引用/示例语法分类,保留 ENV、Basic 模板和已知文档例子控制,并补两组实际 CLI 回归。这里没有使用或公开真实凭据。

@JasonBuildAI JasonBuildAI Nov 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Update: This reply describes the pre-DCO-fix head. The false DCO failure was subsequently fixed by an LF-only rewrite of the commit messages; the current exact head is e17c66ac36dbab273f46e600edebc051e61aaf45. See the newer exact-head follow-up: #7713 (comment)

@loopx-agent 已按这条 P2 修复并 rebase 到 main 682080047,当前 head 为 05c8d665696c3e0f0139e8e4593c77b91392fc52。

  • 无 label 的 credential 分支现在只在完整匹配官方 AWS 文档示例 AKIAIOSFODNN7EXAMPLE 时作为示例豁免;opaque token 正文中的 dummy/example 不再降级为引用。
  • ENV、Basic 模板、labelled placeholder 等既有引用路径保持不变。
  • 新增两组合成 provider 形状的 scan API 回归和真实 loopx check --scan-path CLI 回归;两者现在均为 exit=1、ok=false,并报告 public_boundary_violation。
  • 精度套件 44 passed;全仓库 scan_public_boundary 为 ok=true、0 个未解决命中。相邻 precision/prefilter/chat 批次为 134 passed, 4 failed,四项仍是 base 上相同的 Windows symlink/CRLF 环境失败。

PR 描述已同步更新;请按新 head 复审。没有使用或公开任何真实凭据。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@loopx-agent Re-reviewed at e17c66ac36dbab273f46e600edebc051e61aaf45; the full self-review conclusion is APPROVE, published as COMMENTED because this is an author-owned PR: #7713 (review)

  • This head differs from the previously reviewed head 05c8d665696c3e0f0139e8e4593c77b91392fc52 only by an LF-only rewrite of the three commit messages, fixing the DCO false failure. git diff --quiet confirms byte-identical trees; author and commit timestamps were preserved.
  • GitHub now reports Sign-off: success. The focused precision regression was rerun at this tree: 44 passed.
  • The tested revision in the PR body now points to the current head.

]
),
re.I,
Expand All @@ -121,6 +129,23 @@ def _prefilter_fold(text: str) -> str:
"tok" + "en=",
"pass" + "word=",
"author" + "ization:",
"ghp_",
"gho_",
"ghu_",
"ghs_",
"ghr_",
"github_pat_",
"akia",
"asia",
"xox",
"aiza",
"sk_live_",
"sk_test_",
"rk_live_",
"rk_test_",
"npm_",
"pypi-",
"eyj",
),
),
# The private roots take the host's own separator, so a Windows host
Expand Down Expand Up @@ -204,6 +229,11 @@ def _prefilter_fold(text: str) -> str:
r"<[^>]{1,64}>|\{\{|example|dummy|placeholder|changeme|redacted|your[-_]",
re.I,
)
# A provider token has no keyword that separates its value from surrounding
# text, so only the exact access-key id published in the AWS documentation may
# be treated as an example. A placeholder word inside an opaque token body is
# part of the value, not an external reference (Refs #7713 review).
CREDENTIAL_DOCUMENTED_EXAMPLE_PATTERN = re.compile(r"AKIAIOSFODNN7EXAMPLE")
CREDENTIAL_HIGH_ENTROPY_VALUE_PATTERN = re.compile(r"[A-Za-z0-9._-]{20,}")
CREDENTIAL_VARIABLE_REFERENCE_PATTERN = re.compile(
r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*\s*[,)\]}]"
Expand All @@ -225,7 +255,7 @@ def _credential_match_is_reference(line: str, match: re.Match[str]) -> bool:
last_keyword = keyword.group(0)
value = value[keyword.end() :].lstrip(" \t\"'`")
if last_keyword is None:
return bool(CREDENTIAL_PLACEHOLDER_PATTERN.search(match.group(0)))
return bool(CREDENTIAL_DOCUMENTED_EXAMPLE_PATTERN.fullmatch(match.group(0)))
if not value:
return True
if CREDENTIAL_ENVIRONMENT_REFERENCE_PATTERN.match(value):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
"schema_version": "reliability_diagnostics_public_safety_counterfactuals_v0",
"unsafe_summary_tokens": [
"sk-abcdefghijklmnop0123",
"ghp_abcdefghijklmnopqrstuvwx",
"eyJabcdefghijk.abcdefghijk.abcdefghijk",
"ghp_\u0061bcdefghijklmnopqrstuvwx",
"eyJ\u0061bcdefghijk.abcdefghijk.abcdefghijk",
"C:/Users/alice/private.txt",
"path:/Users/alice/private.txt",
"file:///Users/alice/private.txt",
"file:/Users/alice/private.txt"
],
"unsafe_identity_tokens": [
"sk-abcdefghijklmnop0123",
"ghp_abcdefghijklmnopqrstuvwx",
"eyJabcdefghijk.abcdefghijk.abcdefghijk"
"ghp_\u0061bcdefghijklmnopqrstuvwx",
"eyJ\u0061bcdefghijk.abcdefghijk.abcdefghijk"
],
"invalid_source_ref_tokens": [
"C:/Users/alice/private.txt"
Expand Down
9 changes: 7 additions & 2 deletions tests/test_chat_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ def _command(command: str, actions: list[dict] | None = None, **extra) -> dict:
"commandActions": actions or [{"type": "unknown", "command": command}], **extra}


# Split so the literal provider shape is not itself a credential hit for the
# public boundary scan, the same convention the source fixtures follow.
GITHUB_TOKEN = "ghp_" + "abcdefghijklmnop123"


def test_command_step_names_the_intent_and_keeps_the_full_command_behind_details():
steps = CodexActivitySteps(protected_paths=["/work/project"])
item = _command(
Expand Down Expand Up @@ -67,9 +72,9 @@ def test_failed_items_are_failed_steps_not_completions():
def test_credentials_in_commands_are_masked_before_replay():
title = CodexActivitySteps().started(_command(
"curl -H 'Authorization: Bearer abc.def' --api-key=k123 https://x.test?q=1 "
"&& GH_TOKEN=ghp_abcdefghijklmnop123 gh pr list --password hunter2 sk-proj-abcdefghijklmnop"
f"&& GH_TOKEN={GITHUB_TOKEN} gh pr list --password hunter2 sk-proj-abcdefghijklmnop"
))["title"]
for secret in ("abc.def", "k123", "ghp_abcdefghijklmnop123", "hunter2", "sk-proj-abcdefghijklmnop"):
for secret in ("abc.def", "k123", GITHUB_TOKEN, "hunter2", "sk-proj-abcdefghijklmnop"):
assert secret not in title
assert "gh pr list" in title and "https://x.test" in title

Expand Down
113 changes: 112 additions & 1 deletion tests/test_contract_credential_pattern_precision.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from __future__ import annotations

import json
import os
import subprocess
import sys
from pathlib import Path

import pytest
Expand All @@ -14,6 +18,7 @@
PASSWORD_ASSIGN = "pass" + "word="
REAL_BEARER_VALUE = "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90"
PRIVATE_LARK_TENANT_HOST = "tenant.lark" + "office.com"
ROOT = Path(__file__).resolve().parents[1]


def _scan_line(tmp_path: Path, line: str) -> dict[str, list[str]]:
Expand Down Expand Up @@ -76,7 +81,7 @@ def test_literal_credential_hits_stay_errors(tmp_path: Path, line: str) -> None:


def test_a_literal_on_a_line_with_references_still_fails(tmp_path: Path) -> None:
line = f'{PASSWORD_ASSIGN}$SERVICE_SECRET {TOKEN_ASSIGN}{REAL_BEARER_VALUE}'
line = f"{PASSWORD_ASSIGN}$SERVICE_SECRET {TOKEN_ASSIGN}{REAL_BEARER_VALUE}"

payload = _scan_line(tmp_path, line)

Expand Down Expand Up @@ -128,3 +133,109 @@ def test_private_lark_tenant_urls_remain_blocked(tmp_path: Path, line: str) -> N

assert len(payload["hits"]) == 1
assert payload["hits"][0].endswith(": private_doc_url")


# A provider token carries its issuer in the prefix, so no label says what it
# is. The shapes come from the canonical public-safety owner instead of a
# 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 = [
"ghp_" + "16C7e42F292c6912E7710c838347Ae178B4a",
"github_pat_" + "11ABCDEFG0123456789_abcdefghijklmnopqrstuvwxyz0123456789",
"xoxb-" + "123456789012-abcdefghijkl",
"sk_live_" + "abcdefghijklmnopqrst",
"npm_" + "abcdefghijklmnopqrstuvwxyz",
"pypi-" + "abcdefghijklmnopqrst",
"AIza" + "SyD-1234567890abcdefghijklmnopqrstu",
"eyJ" + "hbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.dozjgNryP4J3jVmNHl0w5N_XgL0n3",
]


@pytest.mark.parametrize("token", PROVIDER_TOKEN_SAMPLES)
def test_unlabeled_provider_tokens_are_credential_errors(
tmp_path: Path,
token: str,
) -> None:
payload = _scan_line(tmp_path, f"token {token}")

assert len(payload["hits"]) == 1
assert payload["hits"][0].endswith(": credential")
assert payload["credential_reference_hits"] == []


def test_credential_rule_reuses_the_canonical_provider_shape_owner() -> None:
from loopx.public_safe_text import PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE

assert (
PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE
in contract.LEAK_RULES["credential"].pattern.pattern
)


def test_a_short_provider_lookalike_stays_clean(tmp_path: Path) -> None:
payload = _scan_line(tmp_path, "token gh_preview, not a credential")

assert payload["hits"] == []
assert payload["credential_reference_hits"] == []


PROVIDER_TOKENS_WITH_PLACEHOLDER_BODY = [
"ghp_" + "dummy" + "1234567890abcdefghijklmnop",
"npm_" + "example" + "1234567890abcdefghij",
]


@pytest.mark.parametrize("token", PROVIDER_TOKENS_WITH_PLACEHOLDER_BODY)
def test_provider_placeholder_words_inside_the_body_stay_literal_errors(
tmp_path: Path,
token: str,
) -> None:
payload = _scan_line(tmp_path, f"token {token}")

assert payload["hits"] == ["sample.md:1: credential"]
assert payload["credential_reference_hits"] == []


def _run_check_cli(tmp_path: Path, line: str) -> tuple[int, dict[str, object]]:
outgoing = tmp_path / "outgoing.md"
outgoing.write_text(f"{line}\n", encoding="utf-8")
result = subprocess.run(
[
sys.executable,
"-m",
"loopx.cli",
"--format",
"json",
"--registry",
str(tmp_path / "registry.json"),
"--runtime-root",
str(tmp_path / "runtime"),
"check",
"--scan-path",
str(outgoing),
],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
check=False,
)
assert result.stdout, result.stderr
return result.returncode, json.loads(result.stdout)


@pytest.mark.parametrize("token", PROVIDER_TOKENS_WITH_PLACEHOLDER_BODY)
def test_real_check_cli_rejects_provider_placeholder_words_inside_the_body(
tmp_path: Path,
token: str,
) -> None:
returncode, payload = _run_check_cli(tmp_path, f"token {token}")

assert returncode == 1
assert payload["ok"] is False
assert payload["public_boundary_scan"]["ok"] is False
assert any(
row["code"] == "public_boundary_violation"
for row in payload["error_diagnostics"]
)
16 changes: 16 additions & 0 deletions tests/test_contract_public_boundary_prefilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ def test_explicit_scan_file_keeps_unsupported_suffix(tmp_path: Path) -> None:
("credential", "tok" + "en=literal"),
("credential", "pass" + "word=literal"),
("credential", "Author" + "ization: literal"),
# Provider tokens carry their issuer in the prefix instead of a label.
# Every shape the canonical owner matches needs a prefilter literal, or
# the rule is silently skipped for that whole issuer family.
("credential", "ghp_" + "16C7e42F292c6912E7710c838347Ae178B4a"),
("credential", "ghs_" + "16C7e42F292c6912E7710c838347Ae178B4a"),
("credential", "github_pat_" + "11ABCDEFG0123456789_abcdefghijklmnopqrstuvwxyz0123456789"),
("credential", "AS" + "IA1234567890ABCDEF"),
("credential", "xoxb-" + "123456789012-abcdefghijkl"),
("credential", "AIza" + "SyD-1234567890abcdefghijklmnopqrstu"),
("credential", "sk_live_" + "abcdefghijklmnopqrst"),
("credential", "sk_test_" + "abcdefghijklmnopqrst"),
("credential", "rk_live_" + "abcdefghijklmnopqrst"),
("credential", "rk_test_" + "abcdefghijklmnopqrst"),
("credential", "npm_" + "abcdefghijklmnopqrstuvwxyz"),
("credential", "pypi-" + "abcdefghijklmnopqrst"),
("credential", "eyJ" + "hbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.dozjgNryP4J3jVmNHl0w5N_XgL0n3"),
("local_private_path", "/" + "Users/alice/Documents/example.md"),
("local_private_path", "/" + "Users/alice/code-reading/example.md"),
("local_private_path", "/ext" + "_data/example.md"),
Expand Down
Loading