Repository navigation
fix(presentation): reject unlabeled provider tokens at the public boundary - #7732
Conversation
…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>
…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>
Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
songoow
left a comment
There was a problem hiding this comment.
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(普通文字与原告警行为)均已实现。
关键代码讲解
loopx/public_safe_text.py:297的PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE仅调整定义顺序,原始模式正文保持一致;下方 runtime guard 继续引用同一规则,避免展示修复无意改变其他 owner。PRESENTATION_PUBLIC_BOUNDARY_PATTERNS在第340行增加provider token分支,既有路径、私钥和标签赋值告警仍按原顺序保留。带标签且符合发行方形状的输入可以产生两种告警,这是既有列表接口允许的明确诊断。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.
Importance and impact
scan_public_boundary_textis the canonical public/private boundary check for generated presentation text: the session dash server withholds HTML/JSON endpoints when it fails,loopx dash generaterefuses to write its--outfile, 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 orgit remote -vURL, an AWS/Slack/Google/Stripe/npm/PyPI credential or a JWT embedded in any of those surfaces was certifiedok: truewith 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 apypi-upload-artifact-action) and a case-foldedasiafollowed 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) predatesPROVIDER_TOKEN_SHAPE_PATTERN_SOURCEand was never composed with it. It carried three arms: absolute local path, private key material, and a labeledapi[_-]?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 checkboundary 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
provider tokenarm toPRESENTATION_PUBLIC_BOUNDARY_PATTERNScompiled as(?i)(?:" + PROVIDER_TOKEN_SHAPE_PATTERN_SOURCE + "), byte-identical to the composition merged fix(capability): detect unlabeled provider tokens in the outbound scan #7636 used for the outbound scanner.PROVIDER_TOKEN_SHAPE_PATTERN_SOURCEassignment above the presentation patterns so the tuple can reference it; the moved text is unchanged.[sensitive text omitted], rejected roundtrip).api_key=/auth_token=/access_token=forms; the issuer-family widths noted in [Bug]: the builtin public-safe-outbound scan certifies GitHub, AWS, Slack, Google, npm, PyPI and Stripe credentials as public-safe #7635's boundary (narrower AWS/Slack sets) remain open work.Evidence
Before the change, at
upstream/main2f544f969(synthetic token shapes):After the change, all ten return
ok=False warnings=['provider token'], and the new matrix intests/control_plane/test_presentation_public_safety.pypins it, together with negatives (npm run build,gh pr list, bareAKIA/pypi-prefixes and the unclaimedsk-proj-shape stay public) and a labeled-assignment case that reports both warnings.Checks
uv run --extra test python -m pyteston: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 cleanmainon this Windows host); chat manager context + slug/compact-identifier/remote-location owners — 126 passed; local-path owner + model-behavior qualification + slug owner — the 12test_actor_request_scans_decoded_scheduler_facts_without_changing_wirefailures reproduce on cleanmainon this Windows host (D:\fixture\...fixture args rejected by the local-path scan; Linux CI passes them).ruff checkon both changed files: clean. (ruff format --checkreports 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,pythonsurface, 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