feat: 升级至 v0.3.16 — 工单修复 51 项 + 审查修复 30 项(安全加固/适配器/工具系统/数据层/前端)

This commit is contained in:
2026-07-22 10:31:13 +08:00
parent 516d8a728f
commit 2c2ca4a692
43 changed files with 1454 additions and 301 deletions
+43 -2
View File
@@ -16,6 +16,27 @@ import type Database from 'better-sqlite3';
import { createHash } from 'crypto';
import log from 'electron-log';
/**
* #36 修复: 稳定序列化,递归按 key 字典序排序后序列化
* JSON.stringify 对对象 key 顺序敏感({a:1,b:2} ≠ {b:2,a:1}),
* 导致相同语义的对象产生不同 hash,审计去重失效。
* stableStringify 保证相同内容始终产生相同字符串。
*/
function stableStringify(obj: unknown): string {
if (obj === null || typeof obj !== 'object') return JSON.stringify(obj);
if (Array.isArray(obj)) {
return '[' + obj.map(stableStringify).join(',') + ']';
}
const keys = Object.keys(obj as Record<string, unknown>).sort();
return (
'{' +
keys
.map((k) => JSON.stringify(k) + ':' + stableStringify((obj as Record<string, unknown>)[k]))
.join(',') +
'}'
);
}
export type AuditEventType = 'tool_call' | 'permission_check' | 'error' | 'llm_request' | 'llm_response' | 'session_start' | 'session_end' | 'config_change';
export type AuditActor = 'agent' | 'user' | 'system';
export type AuditOutcome = 'success' | 'denied' | 'error';
@@ -38,6 +59,7 @@ export class AuditService {
* 计算链式哈希
* current_hash = SHA256(prev_hash + 所有内容字段)
* v0.2.0: 纳入 iteration/outcome/durationMs 字段,使用 JSON.stringify 避免分隔符碰撞
* #36 修复: 改用 stableStringify 替代 JSON.stringify,避免对象 key 顺序不稳定导致 hash 不一致
*/
private computeHash(
prevHash: string,
@@ -47,8 +69,8 @@ export class AuditService {
outcome: string | null; durationMs: number | null; createdAt: number;
},
): string {
// 使用 JSON.stringify 避免分隔符碰撞
const content = JSON.stringify({
// #36 修复: 使用 stableStringify 避免分隔符碰撞且保证 key 顺序稳定
const content = stableStringify({
prevHash,
sessionId: entry.sessionId,
iteration: entry.iteration,
@@ -328,6 +350,25 @@ export class AuditService {
});
if (row.current_hash !== expectedHash) {
// 审查修复: #36 将 JSON.stringify 改为 stableStringify 后,旧记录的 hash 用旧算法生成
// 尝试用旧算法(JSON.stringify)重新计算,如果匹配则跳过(兼容旧数据)
const legacyContent = JSON.stringify({
prevHash,
sessionId: row.session_id,
iteration: row.iteration,
eventType: row.event_type,
actor: row.actor,
target: row.target,
details: row.details,
outcome: row.outcome,
durationMs: row.duration_ms,
createdAt: row.created_at,
});
const legacyHash = createHash('sha256').update(legacyContent, 'utf-8').digest('hex');
if (row.current_hash === legacyHash) {
prevHash = row.current_hash;
continue;
}
return { valid: false, totalRecords: rows.length, verifiedRecords: verified, tamperedId: row.id };
}