Conversation
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="dashboard/src/i18n/validator.ts" line_range="386-388" />
<code_context>
- const totalKeys = results.length * 100; // 估算的总键数
+
+ // 汇总必须来自真实的校验输出
+ if (Object.keys(localeData).length > 0) {
+ results.push(this.validateCompleteness(localeData));
+ }
+
+ const totalKeys = this.getAllKeys(localeData[this.baseLocale] ?? {}).length;
</code_context>
<issue_to_address>
**issue (bug_risk):** `validateLocales` loads only the locales passed by the caller, but `validateCompleteness` iterates over the validator's full `supportedLocales` list. Validating a subset therefore reports every unrequested locale as missing and inflates `missingKeys` and lowers the completeness percentage.
**Triggers:** When the validator is called with a locale subset rather than every supported locale.
**Suggested fix:** Pass the requested locale list into `validateCompleteness`, or make that method iterate only over the keys present in `localeData`.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and the migration and SQLite schema changes can persist incorrect platform statistics or document metadata behavior beyond a revert, although the affected records are bounded and can be recomputed or repaired. The security and UI changes otherwise alter runtime behavior that a revert fully undoes; an incorrect hardening fix would ordinarily be corrected without leaving irreversible effects.
Blocking findings: dashboard/src/i18n/validator.ts:388
silvaling
left a comment
There was a problem hiding this comment.
PR #10201 代码审查
Summary:安全加固(路径穿越 ×5、凭证外发、T2I 标记注入、i18n 原型污染)加一批运行时修复,覆盖 core、平台适配器与 dashboard。
PR Size:Large(+471 / −174,42 文件,36 提交)
Review Time:约 50 分钟(本地还原 + 按改动面逐条核查 + 跑 lint 与相关测试)
Required Changes
🔴 [blocking] CI 的格式检查会挂
ruff format --check(仓库 pin 的 ruff==0.15.22,即 code-format.yml:30 所用版本)实测:base 检出 513 文件全部通过,head 有三个文件不通过;该 workflow 目前尚未运行。
astrbot/cli/utils/plugin.py:93—— 新增的if member_path != base_dir and not member_path.is_relative_to(折了行,ruff 要求合并为一行;:97的报错信息同理astrbot/core/computer/booters/local.py:883——if session.permission_check is None or not session.permission_check():被拆成多行,ruff 要求一行astrbot/core/platform/sources/lark/lark_adapter.py:477——safe_name = ...超出行宽,ruff 要求折行
Important Suggestions
🟡 [important] 42 文件、36 提交,未见任何测试文件改动
新增的失败分支目前均无用例覆盖:插件解包成员越界、技能名越界(SkillManager 与 Dashboard SkillsService 两处)、备份 kb_id 越界、validate_chunk_params()。后续改动放宽校验时 CI 不会报警。
Minor Suggestions
🟢 [nit] T2I 模板的转义只覆盖 raw-HTML token
astrbot/core/utils/t2i/template/base.html:461 的 marked.use({ renderer: { html() { return escapeHtml(...) } } }) 挡住了 HTML 注入,但渲染结果里 markdown 链接/图片的 href/src 协议未做显式过滤;同文件 :443 的 marked 由 CDN 引入且未固定版本。
Questions
❓ _resolve_skill_dir_within() 会跟随软链:若 data/skills/<name> 是指向外部目录的符号链接,现在判为非法(删除时报 Invalid skill name)。这是有意收紧,还是需要保留软链场景?
❓ T2ITemplateEditor.vue:163 的预览 iframe 新增 sandbox="allow-scripts"(无 allow-same-origin):预览里 localStorage、document.cookie 等会抛 SecurityError,自定义模板可能「预览正常、线上不同」。
Security Considerations
- 未见硬编码密钥或新增凭证
- 输入校验:5 处路径穿越检查均落在进入文件系统之前
-
misskey_api的同实例判定用(scheme, hostname, port)比较,第三方媒体主机不再复用带 token 的 session - 已核查范围内的 SQL 均为参数化;模板注入面已收口(残留见上)
- 未逐项复核 dashboard 全部接口的鉴权(超出本 PR 改动范围)
Test Coverage
ruff check .→ 通过;ruff format --check .→ 3 文件未过(见 🔴)pytest tests/{test_openai_source,test_rate_limit_stage,test_t2i_template_manager,test_tool_loop_agent_runner,test_lark_sender_name,test_telegram_adapter,test_skill_manager_sandbox_cache,test_backup}.py tests/unit/test_document_storage_fts.py -q→ 248 passed- 未跑:dashboard 构建与全量冒烟(
make pr-test-full)
Verdict
🔄 Request Changes —— 处理 🔴 后可合并
🤖 本评论由 AI 生成,结论已基于仓库代码核实;如有出入,以维护者判断为准。
Summary
Security hardening plus a batch of correctness fixes collected while auditing the
codebase. The 35 commits are self-contained and grouped by area below.
Security
Path traversal (CWE-22) — five places where a name from an untrusted source was
joined into a filesystem path without a containment check:
astrbot/cli/utils/plugin.py: reject archive members that resolve outside theextraction directory when installing a plugin.
astrbot/core/backup/importer.py: validate the knowledge base id before creatingkb_dirand writingdoc.db/index.faiss. This complements fix: prevent path traversal in backup importer (CWE-22) #7681, which onlycovered the media, attachment and directory paths.
astrbot/core/skills/skill_manager.pyandastrbot/dashboard/services/skills_service.py: validate skill names at both entrypoints.
astrbot/core/platform/sources/lark/lark_adapter.py: strip directory components andleading dots from attachment names.
Credential leak —
misskey_api.pyreused its authenticated session for URLs onthird-party media hosts, attaching
Authorization: Bearer <token>to requests toarbitrary servers. Only same-instance URLs reuse that session now.
Template / markup injection — the T2I templates rendered message text as live
markup (
{{ text | safe }}plus raw HTML passed through bymarked). A payload wouldexecute in the headless browser, which has host access. The three built-in templates now
use
{{ text | e }}and escape raw-HTML tokens in themarkedrenderer; the previoustemplate hashes are registered for migration.
Frontend — prototype pollution via dynamic translation keys (
__proto__/constructorin plugin-provided i18n); the logo title rendered throughv-html.Command guard — the blocked-command blacklist could be bypassed with repeated
whitespace (
rm -rf); matching now operates on whitespace-collapsed input.Stability
ToolLoopAgentRunner.__init__returned early whenskills_likemode had no tools,leaving
statsandrun_context.messagesunset and crashing later use.matchblock, spinning in awhile Truewith noawait.chunk_sizeor negativechunk_overlaphung the chunkers; parametersare validated up front now.
.decode()onNone.message_idafter a failed streaming send.permission_checkcallable and the optionalLark
contactclient.Data and logic
http://{sb_url}/uploadwhilesb_urlalreadycarried the scheme, so every upload failed.
the loop counter also equals
max_retries - 1on success (plain and streaming paths).platform_id.sync_command_configscollected only parent commands, so sub-command configs writtento the DB were deleted as stale on restart.
VersionComparatorusedstr.replace("v", ""), truncating1.0.0-devto1.0.0-de.msgtypeand payload key mismatched (location/miniprogramboth sent asmsgmenu); session ids were split with the wrong count, dropping the tail.ALTER TABLE ADD COLUMN ... STORED, so the whole DDL block failed andneither the generated columns nor their indexes were created — a bare
except BaseException: passhid it. The columns areVIRTUALnow and failures arelogged.
Dashboard
ConfirmDialogoverwrote its resolver when reopened before the previous confirmationwas answered, hanging the earlier
awaitforever.beforeUnmountwas defined insidemethods, so Vue never called it — the scrolllistener and two timers leaked.
""/"*"global and wildcard routes.
null("all capabilities") state used the selectable list as thebaseline, silently revoking builtin tools.
start()/resume()calls in the chunked upload composable overwrotesession state.
hard-coded zeros.
embedding_provider_id.d3import in the long-term memory view was commented out while the code stillused it, and the dependency was missing.
Docs tooling
docs/scripts/upload_doc_images_to_r2.pyrewrote image links withstr.replace,hitting the wrong occurrence when the alt text equals the URL; it now replaces by
match span.
Summary by Sourcery
Harden core and dashboard security while correcting reliability, data handling, platform integration, and user-interface issues across the application.
Bug Fixes:
Enhancements:
Build:
Chores: