Skip to content

Commit 9e2dbf3

Browse files
committed
fix(session-policy): 修复 Dashboard XSS 风险并标注 client_category 为预留字段;
- 新增 escapeHtml() 工具函数,对所有用户可控数据(session_key、models、vendors、categories)插入 innerHTML 前进行 HTML 实体转义 - title 属性中的 session_key 同样使用转义后的值 - 在 SessionPolicyMatch.client_category 字段描述和 config.default.yaml 中明确标注该条件为预留功能,当前版本暂未生效 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
1 parent d41ad50 commit 9e2dbf3

3 files changed

Lines changed: 15 additions & 7 deletions

File tree

‎src/coding/proxy/config/config.default.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ native_api:
652652
#
653653
# 支持的匹配条件(OR 语义,满足任一即匹配):
654654
# session_keys: 精确匹配的 session key 列表
655-
# client_category: 按客户端类别匹配("cc" = Claude Code,"api" = 原生 API 透传)
655+
# client_category: 按客户端类别匹配(⚠️ 预留字段,当前版本暂未生效,后续版本支持)
656656
#
657657
# tiers: 覆盖全局 tier 顺序的供应商优先级列表(未提及的 vendor 保持在末尾)
658658
#

‎src/coding/proxy/config/session_policy.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ class SessionPolicyMatch(BaseModel):
1414
)
1515
client_category: str | None = Field(
1616
default=None,
17-
description="按客户端类别匹配('cc' 或 'api')",
17+
description=(
18+
"按客户端类别匹配('cc' 或 'api')。"
19+
"⚠️ 预留字段,当前路由执行链路未传入 client_category,"
20+
"配置此条件不会生效。后续版本将支持。"
21+
),
1822
)
1923

2024

‎src/coding/proxy/server/dashboard.py‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,9 +1323,13 @@ def _build_favicon() -> bytes:
13231323
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
13241324
return Math.floor(diff / 86400) + 'd ago';
13251325
}
1326+
function escapeHtml(s) {
1327+
if (!s) return '';
1328+
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
1329+
}
13261330
function truncateKey(key, maxLen) {
1327-
if (!key || key.length <= maxLen) return key || '–';
1328-
return key.slice(0, maxLen - 3) + '…';
1331+
if (!key || key.length <= maxLen) return escapeHtml(key) || '–';
1332+
return escapeHtml(key.slice(0, maxLen - 3)) + '…';
13291333
}
13301334
function successBarHtml(pct) {
13311335
if (pct == null) return '–';
@@ -1338,7 +1342,7 @@ def _build_favicon() -> bytes:
13381342
if (!str) return '–';
13391343
var list = str.split(',');
13401344
var html = list.slice(0, max).map(function(c) {
1341-
return '<span class="session-tag">' + c.trim() + '</span>';
1345+
return '<span class="session-tag">' + escapeHtml(c.trim()) + '</span>';
13421346
}).join('');
13431347
if (list.length > max) html += '<span class="session-tag">+' + (list.length - max) + '</span>';
13441348
return html;
@@ -1347,7 +1351,7 @@ def _build_favicon() -> bytes:
13471351
if (!cats) return '–';
13481352
return cats.split(',').map(function(c) {
13491353
var t = c.trim();
1350-
var label = t === 'cc' ? 'Claude Code' : (t === 'api' ? 'API' : t);
1354+
var label = t === 'cc' ? 'Claude Code' : (t === 'api' ? 'API' : escapeHtml(t));
13511355
return '<span class="session-tag">' + label + '</span>';
13521356
}).join('');
13531357
}
@@ -1370,7 +1374,7 @@ def _build_favicon() -> bytes:
13701374
}
13711375
tbody.innerHTML = sessions.map(function(s) {
13721376
return '<tr>' +
1373-
'<td class="session-key" title="' + (s.session_key || '') + '">' + truncateKey(s.session_key, 22) + '</td>' +
1377+
'<td class="session-key" title="' + escapeHtml(s.session_key) + '">' + truncateKey(s.session_key, 22) + '</td>' +
13741378
'<td>' + relativeTime(s.last_active_ts) + '</td>' +
13751379
'<td style="font-family:JetBrains Mono,monospace">' + fmtNum(s.total_requests) + '</td>' +
13761380
'<td style="font-family:JetBrains Mono,monospace">' + fmtTokens(s.total_tokens) + '</td>' +

0 commit comments

Comments
 (0)