Add plugin MFA Authenticator v1.3.0#143
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements the ztools-mfa-authenticator plugin, a feature-rich MFA manager supporting TOTP, HOTP, and Steam Guard protocols with capabilities for QR code recognition and Pinyin-based searching. The code review highlights significant security vulnerabilities, specifically the insecure co-location of encryption keys and data for the auto-unlock feature and the inclusion of a vulnerable version of the xlsx library. Further feedback addresses robustness and performance, noting a lack of error handling in storage parsing, inefficient Base64 encoding for large buffers, and logic errors in the CSV and TXT import implementations.
| async function save(password: string): Promise<void> { | ||
| const rawKey = crypto.getRandomValues(new Uint8Array(32)) | ||
| const aesKey = await crypto.subtle.importKey('raw', rawKey, 'AES-GCM', false, [ | ||
| 'encrypt', | ||
| ]) | ||
|
|
||
| const iv = crypto.getRandomValues(new Uint8Array(12)) | ||
| const encoded = new TextEncoder().encode(password) | ||
| const cipherBuf = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, aesKey, encoded) | ||
|
|
||
| const toB64 = (buf: ArrayBuffer) => { | ||
| const bytes = new Uint8Array(buf) | ||
| let bin = '' | ||
| for (let i = 0; i < bytes.byteLength; i++) bin += String.fromCharCode(bytes[i]) | ||
| return btoa(bin) | ||
| } | ||
|
|
||
| window.ztools.dbStorage.setItem(KEY_STORAGE, toB64(rawKey.buffer as ArrayBuffer)) | ||
| window.ztools.dbStorage.setItem( | ||
| DATA_STORAGE, | ||
| JSON.stringify({ ciphertext: toB64(cipherBuf), iv: toB64(iv.buffer as ArrayBuffer) }), | ||
| ) |
| "jsqr": "^1.4.0", | ||
| "otpauth": "^9.3.0", | ||
| "pinyin-match": "^1.2.10", | ||
| "vue": "^3.5.0", |
There was a problem hiding this comment.
xlsx (SheetJS) 0.18.5 版本较旧且存在已知安全漏洞(如 CVE-2023-30535)。建议迁移到官方维护的 @sheetjs/xlsx 最新版本以提高安全性。
| return | ||
| } | ||
|
|
||
| const ids: string[] = JSON.parse(raw) |
| export function toBase64(buffer: ArrayBuffer): string { | ||
| const bytes = new Uint8Array(buffer) | ||
| let binary = '' | ||
| for (let i = 0; i < bytes.byteLength; i++) { | ||
| binary += String.fromCharCode(bytes[i]) | ||
| } | ||
| return btoa(binary) | ||
| } |
There was a problem hiding this comment.
当前的 toBase64 实现使用字符串累加,在处理较大 buffer(如导出的 Excel 文件)时性能非常低下,且可能导致长时间阻塞主线程。建议使用分块处理(Chunking)的方式来优化性能。
| export function toBase64(buffer: ArrayBuffer): string { | |
| const bytes = new Uint8Array(buffer) | |
| let binary = '' | |
| for (let i = 0; i < bytes.byteLength; i++) { | |
| binary += String.fromCharCode(bytes[i]) | |
| } | |
| return btoa(binary) | |
| } | |
| export function toBase64(buffer: ArrayBuffer): string { | |
| const bytes = new Uint8Array(buffer) | |
| let binary = '' | |
| const len = bytes.byteLength | |
| for (let i = 0; i < len; i += 8192) { | |
| binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192) as any) | |
| } | |
| return btoa(binary) | |
| } |
| const parseCsvLine = (line: string): string[] => { | ||
| const result: string[] = [] | ||
| let current = '' | ||
| let inQuotes = false | ||
| for (const ch of line) { | ||
| if (ch === '"') { | ||
| inQuotes = !inQuotes | ||
| } else if (ch === ',' && !inQuotes) { | ||
| result.push(current.trim()) | ||
| current = '' | ||
| } else { | ||
| current += ch | ||
| } | ||
| } | ||
| result.push(current.trim()) | ||
| return result | ||
| } |
There was a problem hiding this comment.
CSV 解析逻辑存在两个问题:
- 无法正确处理转义的双引号(例如
""应解析为单个")。 - 在第 110 行预先使用
split('\n')会导致包含换行符的引用字段被错误拆分。
建议改进解析逻辑以符合 RFC 4180 标准。
const parseCsvLine = (line: string): string[] => {
const result: string[] = []
let current = ''
let inQuotes = false
for (let i = 0; i < line.length; i++) {
const ch = line[i]
if (ch === '"') {
if (inQuotes && line[i + 1] === '"') {
current += '"'
i++
} else {
inQuotes = !inQuotes
}
} else if (ch === ',' && !inQuotes) {
result.push(current.trim())
current = ''
} else {
current += ch
}
}
result.push(current.trim())
return result
}| .map((line) => line.trim()) | ||
| .filter(Boolean) | ||
| .map((line) => { | ||
| const [issuer, label, secret, algorithm, digits, period, type, counter] = line.split(':') |
|
执行npm build后dist目录构建的不是完整的插件,有些文件需要放到public中,可以参考ai-helper插件 |



插件信息
本次变更
Added
Changed
截图 / 演示
自检清单
plugins/ztools-mfa-authenticator/目录此 PR 由 ztools-plugin-cli 自动管理:每次
ztools publish在分支上追加一个 commit,PR 链接保持不变。