mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 06:45:50 +00:00
feat: 添加 Local Vault 加密存储服务
AES-256-GCM 加密 vault,支持 OS keychain 和加密文件回退, scrypt KDF 密钥派生,64KB secret 上限。 Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
This commit is contained in:
90
src/utils/__tests__/localValidate.test.ts
Normal file
90
src/utils/__tests__/localValidate.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { isValidKey, validateKey } from '../localValidate.js'
|
||||
|
||||
describe('validateKey', () => {
|
||||
test('rejects empty', () => {
|
||||
expect(() => validateKey('')).toThrow(/empty/i)
|
||||
})
|
||||
|
||||
test('rejects too long', () => {
|
||||
expect(() => validateKey('a'.repeat(129))).toThrow(/too long/i)
|
||||
})
|
||||
|
||||
test('rejects path separators', () => {
|
||||
expect(() => validateKey('a/b')).toThrow(/invalid key chars/i)
|
||||
expect(() => validateKey('a\\b')).toThrow(/invalid key chars/i)
|
||||
})
|
||||
|
||||
test('rejects null byte', () => {
|
||||
expect(() => validateKey('a\0b')).toThrow(/invalid key chars/i)
|
||||
})
|
||||
|
||||
test('rejects spaces', () => {
|
||||
expect(() => validateKey('a b')).toThrow(/invalid key chars/i)
|
||||
})
|
||||
|
||||
test('rejects unicode', () => {
|
||||
expect(() => validateKey('键名')).toThrow(/invalid key chars/i)
|
||||
})
|
||||
|
||||
test('rejects leading dot', () => {
|
||||
expect(() => validateKey('.gitconfig')).toThrow(/leading dot/i)
|
||||
expect(() => validateKey('..parent')).toThrow(/leading dot/i)
|
||||
expect(() => validateKey('.')).toThrow(/leading dot/i)
|
||||
})
|
||||
|
||||
test('rejects Windows reserved names (case-insensitive)', () => {
|
||||
for (const name of [
|
||||
'NUL',
|
||||
'CON',
|
||||
'PRN',
|
||||
'AUX',
|
||||
'COM1',
|
||||
'COM9',
|
||||
'LPT1',
|
||||
'LPT9',
|
||||
]) {
|
||||
expect(() => validateKey(name)).toThrow(/windows reserved/i)
|
||||
expect(() => validateKey(name.toLowerCase())).toThrow(/windows reserved/i)
|
||||
}
|
||||
})
|
||||
|
||||
test('accepts valid keys', () => {
|
||||
expect(() => validateKey('a')).not.toThrow()
|
||||
expect(() => validateKey('a_b')).not.toThrow()
|
||||
expect(() => validateKey('a-b')).not.toThrow()
|
||||
expect(() => validateKey('a.b')).not.toThrow()
|
||||
expect(() => validateKey('My_Key-2026.01')).not.toThrow()
|
||||
expect(() => validateKey('a'.repeat(128))).not.toThrow()
|
||||
})
|
||||
|
||||
test('M6: Windows reserved name with extension is REJECTED', () => {
|
||||
// Windows aliases NUL.txt → NUL device regardless of extension.
|
||||
expect(() => validateKey('NUL.txt')).toThrow(/windows reserved/i)
|
||||
expect(() => validateKey('CON.foo')).toThrow(/windows reserved/i)
|
||||
expect(() => validateKey('COM1.bak')).toThrow(/windows reserved/i)
|
||||
expect(() => validateKey('lpt9.dat')).toThrow(/windows reserved/i)
|
||||
})
|
||||
|
||||
test('Names containing reserved as substring are still allowed (myCON)', () => {
|
||||
expect(() => validateKey('myCON')).not.toThrow()
|
||||
expect(() => validateKey('CONfetti')).not.toThrow()
|
||||
})
|
||||
|
||||
test('L2: bare ".." is rejected (leading-dot guard)', () => {
|
||||
expect(() => validateKey('..')).toThrow(/leading dot/i)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isValidKey', () => {
|
||||
test('returns true for valid keys', () => {
|
||||
expect(isValidKey('a_b')).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for invalid keys', () => {
|
||||
expect(isValidKey('')).toBe(false)
|
||||
expect(isValidKey('.git')).toBe(false)
|
||||
expect(isValidKey('a/b')).toBe(false)
|
||||
expect(isValidKey('NUL')).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user