Skip to content

fix(presentation): reject unlabeled provider tokens at the public boundary - #7732

Merged
huangruiteng merged 2 commits into
loopx-project:mainfrom
JasonBuildAI:codex/presentation-provider-tokens-m4v6
Oct 30, 2026
Merged

huangruiteng merged 2 commits into
loopx-project:mainfrom
JasonBuildAI:codex/presentation-provider-tokens-m4v6

Conversation

@JasonBuildAI

@JasonBuildAI JasonBuildAI commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Importance and impact

scan_public_boundary_text is the canonical public/private boundary check for generated presentation text: the session dash server withholds HTML/JSON endpoints when it fails, loopx dash generate refuses to write its --out file, the static-site exporter refuses to publish a bundle file, chat detail publication substitutes [sensitive text omitted], and manager-channel result return rejects the reply. Its pattern table recognized no provider-issued credential at all, so a GitHub PAT inside a pasted CI log or git remote -v URL, an AWS/Slack/Google/Stripe/npm/PyPI credential or a JWT embedded in any of those surfaces was certified ok: true with no warning and published under that certification. Credential disclosure is irreversible until the token is rotated; every dash session, dash export and multi-agent result return passes through this gate.

Fixes #7727.

The new arm is the canonical alternation unchanged, so it inherits one known over-block: a 20-character-or-longer hyphenated pypi-… slug (for example a release note naming a pypi-upload-artifact-action) and a case-folded asia followed by 16 letters can also trip the gate. Narrowing that alternation feeds six other surfaces and is the parity review #7635's boundary already names as separate work, so this PR discloses the residual rather than restating a subset.

Problem

PRESENTATION_PUBLIC_BOUNDARY_PATTERNS (loopx/public_safe_text.py) predates PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE and was never composed with it. It carried three arms: absolute local path, private key material, and a labeled api[_-]?key=/auth[_-]?token=/access[_-]?token= assignment arm. Unlabeled provider tokens — the shape tool output actually carries, since an issuer prefix is its own label — matched none of them.

This is the same owner-drift class recorded in #5136 and already repaired on the sibling surfaces: the outbound capability scanner (#7635, fixed by #7636), the loopx check boundary scan (#7712, PR #7713) and the chat activity masker (#7717, PR #7718). This change composes the last unclaimed consuming surface from the same canonical owner.

Change

Evidence

Before the change, at upstream/main 2f544f969 (synthetic token shapes):

GitHub classic PAT     ok=True warnings=[]
GitHub fine-grained    ok=True warnings=[]
AWS access key         ok=True warnings=[]
AWS session key        ok=True warnings=[]
Slack bot token        ok=True warnings=[]
Google API key         ok=True warnings=[]
Stripe live key        ok=True warnings=[]
npm token              ok=True warnings=[]
PyPI token             ok=True warnings=[]
JWT                    ok=True warnings=[]

After the change, all ten return ok=False warnings=['provider token'], and the new matrix in tests/control_plane/test_presentation_public_safety.py pins it, together with negatives (npm run build, gh pr list, bare AKIA/pypi- prefixes and the unclaimed sk-proj- shape stay public) and a labeled-assignment case that reports both warnings.

Checks

  • uv run --extra test python -m pytest on: test_presentation_public_safety.py + the public-safe text owner suites (classifier, owner parity, credential shape/caller faces, path shapes) — passed; test_chat_manager_details.py, test_chat_activity.py, test_reliability_diagnostics_dsh_provider.py, test_dash_options.py — 83 passed, 1 pre-existing local failure (test_native_output_progress_is_source_bound_and_clean_before_persisted_replay, GBK decode in the test harness, reproduces on clean main on this Windows host); chat manager context + slug/compact-identifier/remote-location owners — 126 passed; local-path owner + model-behavior qualification + slug owner — the 12 test_actor_request_scans_decoded_scheduler_facts_without_changing_wire failures reproduce on clean main on this Windows host (D:\fixture\... fixture args rejected by the local-path scan; Linux CI passes them).
  • ruff check on both changed files: clean. (ruff format --check reports pre-existing drift on lines this PR does not touch; both HEAD versions of the files fail the same check.)
  • python -m mypy loopx/public_safe_text.py: clean.
  • loopx canary premerge --from-git-diff --git-diff-base 2f544f969: passed — 2 changed files, python surface, 0 manual holds, 0 failures; both selected catalog canaries (maintainability ratchet, semantic vocabulary drift) passed. The public-boundary scan over both changed files reports only the 3 non-literal credential references the module's own pattern text has always produced (downgraded warnings, not hits).

Drafted with AI assistance at the request of the submitter, under their direction and review.

Signed-off-by: JasonBuildAI jasonbuildai@gmail.com

…ndary

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 loopx-project#7636 already made the outbound
capability scanner consume, and keep the assignment arm for the labeled
forms the issuer prefixes do not carry (Refs loopx-project#5136, loopx-project#7727).

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
JasonBuildAI added a commit to JasonBuildAI/loopx that referenced this pull request Oct 30, 2026
…t#7732

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
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 <jasonbuildai@gmail.com>
JasonBuildAI added a commit to JasonBuildAI/loopx that referenced this pull request Oct 30, 2026
Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>

@songoow songoow left a comment

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.

Reviewer: model_agent · GPT-6 Astra Pro · OpenAI · self_reported

动机 / Motivation

把 Agent 工作摘要发布到工作看板、静态导出或共享管理会话的操作者会遇到这个问题。例如摘要带有一个没有 api_key 标签的 npm 凭据,旧扫描器会认证为安全并把原值送到页面;修改后输出被拒绝,管理详情替换敏感内容,修正后的普通文字仍能正常显示。本次独立 HTTP 与管理详情探针验证了七类此前漏过的合成凭据被拦截,并验证同一服务随后接受修正内容。范围是既有展示发布边界的漏检修复;不建立通用秘密发现系统,不改变私有状态权限,也不扩大执行或调度权限。

改动思路 / Approach

共享 owner 负责识别凭据形状,既有展示消费者继续分别负责拒绝发布或省略内容,这与维护者要求的统一识别、按目的地制定策略一致。当前 PR 保留为共享模式表的一处增量及其针对性回归;不再复制一个扫描器,也不把所有目的地的拒绝策略合并。我比较了不修改、分别修五个消费者和扩充共享表三种做法:前者保留已复现的泄漏,第二种增加规则漂移,本次改动已经是最小的既有 owner 修复。识别结果仍由消费者解释:公开看板拒绝响应,管理详情省略敏感内容;没有新增持久标记、权限、工作义务或人工配置步骤。

具体改动 / Changes

完整审阅了两个变更文件(+78/-20),其中主要生产代码变化是常量前移和一个匹配分支,新增测试为50行。依据为 https://github.com/loopx-project/loopx/issues/7727,文本版本 body-sha256:b191afeea62deca2751ce053a97e9c2557d0cf0d66ee349731daf502e871c349,并按维护者在 #5136 确立的目的地策略边界判断。CRED-7727-1(无标签发行方凭据)、CRED-7727-2(复用共享识别规则)和 CRED-7727-3(普通文字与原告警行为)均已实现。

关键代码讲解

  1. loopx/public_safe_text.py:297 的 PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE 仅调整定义顺序,原始模式正文保持一致;下方 runtime guard 继续引用同一规则,避免展示修复无意改变其他 owner。
  2. PRESENTATION_PUBLIC_BOUNDARY_PATTERNS 在第340行增加 provider token 分支,既有路径、私钥和标签赋值告警仍按原顺序保留。带标签且符合发行方形状的输入可以产生两种告警,这是既有列表接口允许的明确诊断。
  3. DashRequestHandler._serve_json_with_boundary(loopx/dash_server.py:115)是实际发布端:序列化后先扫描,再决定拒绝或写响应。我用真实本地 HTTP handler 和嵌套 manager sanitizer 验证,七类合成值在基线均为200/原文,在本 head 均为500/无原值;随后安全文字恢复为200。这个观察验证了消费路径,未使用真实凭据。

对主干的风险 / Risks

未发现阻断性代码问题。最大残余风险是共享识别规则本身的精度,例如足够长的 PyPI 形状普通字符串也可能被拒绝;该限制已在原问题与 PR 中说明,本次没有额外扩大 canonical 模式。不能据此宣称所有秘密格式都能发现。普通 npm 文档、Asia 地区和单独 password/Bearer 字样保留可读;被拒绝后修正内容可以立即重读,未引入持续阻塞状态。

PASS:presentation/manager 定向组 41 passed;既有 owner/caller parity 独立组 290 passed;static-site packaging 独立组 11 passed;base/head 真实 HTTP、嵌套详情和纠正后恢复探针通过。把预期无泄漏的 oracle 放到基线会实际失败,避免测试只证明新 helper 自洽。完整扫描规则和各消费端已检查;没有运行 live model、安装或发布。

语义与 CI 对齐

这是复用现有识别语义,目的地从漏检变为拒绝的有意变更;没有新增状态枚举或第二规则 owner。我同时检查了 #7713、#7718、#7733 的共享模式使用,未发现需要按合并顺序同步另一份 Python 模式的依赖。当前快照有完成的 checks、kernel、TypeScript、dashboard/browser 检查成功,Python shards 尚在运行/等待,另有取消项;未宣称全绿,也未等待或轮询。代码判断使用本地直接覆盖;合并仍需独立完成仓库 CI 门槛。

我的整体评价 / Verdict

认可 exact head e50e390fde41c12f1e73dd2fa5ef664e9d0e467f 的这项有用且适度的修复:长期重复发布风险降低,普通使用和纠正后的恢复路径保留,兼容成本局限于公开文本的新告警。已检查 loopx/presentation/public_safety.py、dash_server.py 与 chat_manager_details.py;此改动只收紧发布前的文字检查,未修改页面结构、样式或交互控件,视觉布局复验不适用。未重新运行可视化布局测试;本 PR 无布局变更。现有证据支持代码审查 APPROVE,不等同于合并资格已满足;未完成或取消的远端检查由合并流程另行处理。

English verdict: APPROVE — e50e390fde41c12f1e73dd2fa5ef664e9d0e467f; the existing presentation boundary now consumes the canonical provider-token detector, with independently verified baseline/head publication and recovery behavior. Remote merge readiness remains separate.

@huangruiteng
huangruiteng merged commit be1158c into loopx-project:main Oct 30, 2026
32 of 41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: the presentation public-boundary scan certifies unlabeled GitHub, AWS, Slack, Google, Stripe, npm, PyPI and JWT credentials as public-safe

3 participants