Skip to content

fix: security hardening and runtime bug fixes across core, platforms, and dashboard - #10201

Open
Alma1314 wants to merge 36 commits into
AstrBotDevs:masterfrom
Alma1314:master
Open

Alma1314 wants to merge 36 commits into
AstrBotDevs:masterfrom
Alma1314:master

Conversation

@Alma1314

@Alma1314 Alma1314 commented Sep 22, 2026

Copy link
Copy Markdown

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 the
    extraction directory when installing a plugin.
  • astrbot/core/backup/importer.py: validate the knowledge base id before creating
    kb_dir and writing doc.db / index.faiss. This complements fix: prevent path traversal in backup importer (CWE-22) #7681, which only
    covered the media, attachment and directory paths.
  • astrbot/core/skills/skill_manager.py and
    astrbot/dashboard/services/skills_service.py: validate skill names at both entry
    points.
  • astrbot/core/platform/sources/lark/lark_adapter.py: strip directory components and
    leading dots from attachment names.

Credential leakmisskey_api.py reused its authenticated session for URLs on
third-party media hosts, attaching Authorization: Bearer <token> to requests to
arbitrary 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 by marked). A payload would
execute in the headless browser, which has host access. The three built-in templates now
use {{ text | e }} and escape raw-HTML tokens in the marked renderer; the previous
template hashes are registered for migration.

Frontend — prototype pollution via dynamic translation keys (__proto__ /
constructor in plugin-provided i18n); the logo title rendered through v-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 when skills_like mode had no tools,
    leaving stats and run_context.messages unset and crashing later use.
  • An unknown rate limit strategy fell through the match block, spinning in a
    while True with no await.
  • A non-positive chunk_size or negative chunk_overlap hung the chunkers; parameters
    are validated up front now.
  • WeCom decoded the ciphertext before checking the encryption result, calling
    .decode() on None.
  • Telegram kept editing the previous message_id after a failed streaming send.
  • Type-narrowing fixes for the optional permission_check callable and the optional
    Lark contact client.

Data and logic

  • The Boxlite upload URL was built as http://{sb_url}/upload while sb_url already
    carried the scheme, so every upload failed.
  • The OpenAI provider dropped a response obtained on the final retry attempt, because
    the loop counter also equals max_retries - 1 on success (plain and streaming paths).
  • Platform stats migration accumulated every platform in a time bucket into one
    platform_id.
  • sync_command_configs collected only parent commands, so sub-command configs written
    to the DB were deleted as stale on restart.
  • VersionComparator used str.replace("v", ""), truncating 1.0.0-dev to 1.0.0-de.
  • WeCom: msgtype and payload key mismatched (location / miniprogram both sent as
    msgmenu); session ids were split with the wrong count, dropping the tail.
  • SQLite rejects ALTER TABLE ADD COLUMN ... STORED, so the whole DDL block failed and
    neither the generated columns nor their indexes were created — a bare
    except BaseException: pass hid it. The columns are VIRTUAL now and failures are
    logged.

Dashboard

  • ConfirmDialog overwrote its resolver when reopened before the previous confirmation
    was answered, hanging the earlier await forever.
  • beforeUnmount was defined inside methods, so Vue never called it — the scroll
    listener and two timers leaked.
  • Deleting a platform's routes used loose matching, also removing the "" / "*"
    global and wildcard routes.
  • Toggling from the null ("all capabilities") state used the selectable list as the
    baseline, silently revoking builtin tools.
  • Default values for list and object fields shared one instance across all entries.
  • Concurrent start() / resume() calls in the chunked upload composable overwrote
    session state.
  • The i18n validator never loaded locale files and reported estimated totals with
    hard-coded zeros.
  • Saving knowledge base settings omitted embedding_provider_id.
  • The d3 import in the long-term memory view was commented out while the code still
    used it, and the dependency was missing.

Docs tooling

  • docs/scripts/upload_doc_images_to_r2.py rewrote image links with str.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:

  • Fix runtime failures and incorrect behavior across agent execution, rate limiting, chunking, platform integrations, provider retries, database migrations, command synchronization, dashboard interactions, uploads, localization validation, and documentation image processing.

Enhancements:

  • Harden filesystem, credential, markup, command, and frontend handling against path traversal, token leakage, injection, prototype pollution, and unsafe HTML execution.
  • Improve dashboard lifecycle management, route matching, capability selection, upload concurrency, default value isolation, localization reporting, and knowledge base settings persistence.

Build:

  • Add the d3 dashboard dependency and update its lockfile.

Chores:

  • Register legacy T2I template hashes to support migration of existing customized templates.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread dashboard/src/i18n/validator.ts

@silvaling silvaling left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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:461marked.use({ renderer: { html() { return escapeHtml(...) } } }) 挡住了 HTML 注入,但渲染结果里 markdown 链接/图片的 href/src 协议未做显式过滤;同文件 :443marked 由 CDN 引入且未固定版本。

Questions

_resolve_skill_dir_within() 会跟随软链:若 data/skills/<name> 是指向外部目录的符号链接,现在判为非法(删除时报 Invalid skill name)。这是有意收紧,还是需要保留软链场景?

T2ITemplateEditor.vue:163 的预览 iframe 新增 sandbox="allow-scripts"(无 allow-same-origin):预览里 localStoragedocument.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 -q248 passed
  • 未跑:dashboard 构建与全量冒烟(make pr-test-full

Verdict

🔄 Request Changes —— 处理 🔴 后可合并


🤖 本评论由 AI 生成,结论已基于仓库代码核实;如有出入,以维护者判断为准。

This branch has not been deployed

No deployments
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.

2 participants