mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-30 00:21:34 +00:00
fix(security): 补齐审计日志脱敏缺口 + step-up 下载/取消体验修复
审计发现修复: 高危——审计日志请求体脱敏不全(audit_logs 沦为明文凭证聚合点): - 键名归一化比对(小写+去分隔符),覆盖 privateKey/apiv3key 等无分隔符与 camelCase 写法 - 程序化并入 SensitiveCredentialKeys 与 providerSensitiveConfigFields 两份权威敏感表,防清单漂移 - 补齐 proxy_key(内嵌代理密码)、custom_key(自设 API Key 明文)精确键 - Codex session 导入路由 body 整体由粘贴的 auth JSON 构成,键级脱敏无法覆盖,整体不入库 - 新增守卫测试:两份权威表的每个键必须被审计脱敏命中;provider_key 等渠道标识保留以便追责 中危——step-up 前端体验: - 备份下载改同页 anchor 导航(预签名 URL 后端强制 attachment disposition), 避免 step-up 弹窗 await 耗尽瞬态激活后 window.open 被浏览器拦截 - useStepUp.run 用户取消时抛 StepUpCancelledError sentinel, 三个调用点静默处理,不再把取消误报为红色错误 toast 低危: - 修正审计中间件挂载位置的陈旧注释(实际挂在认证之后) - 审计 body 捕获改 LimitReader 按 256KB 上限截断读取,超出部分拼接回填, 避免大体积导入请求被完整复制进内存两次 - TOTP step-up 弹窗验证成功后即时清空验证码输入
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { useStepUp, isStepUpRequired, isStepUpBlocked, stepUpBlockReason } from '../useStepUp'
|
||||
import {
|
||||
useStepUp,
|
||||
isStepUpRequired,
|
||||
isStepUpBlocked,
|
||||
isStepUpCancelled,
|
||||
stepUpBlockReason,
|
||||
StepUpCancelledError
|
||||
} from '../useStepUp'
|
||||
|
||||
describe('useStepUp error classification', () => {
|
||||
it('detects STEP_UP_REQUIRED from code field', () => {
|
||||
@@ -60,12 +67,21 @@ describe('useStepUp.run', () => {
|
||||
expect(stepUp.visible.value).toBe(false)
|
||||
})
|
||||
|
||||
it('re-throws the original error if the user cancels the prompt', async () => {
|
||||
it('throws a cancellation sentinel (not the original error) if the user cancels the prompt', async () => {
|
||||
const stepUp = useStepUp()
|
||||
const err = { status: 403, code: 'STEP_UP_REQUIRED' }
|
||||
const promise = stepUp.run(async () => { throw err })
|
||||
await vi.waitFor(() => expect(stepUp.visible.value).toBe(true))
|
||||
stepUp.onCancel()
|
||||
await expect(promise).rejects.toBe(err)
|
||||
await expect(promise).rejects.toBeInstanceOf(StepUpCancelledError)
|
||||
expect(stepUp.visible.value).toBe(false)
|
||||
})
|
||||
|
||||
it('classifies the cancellation sentinel distinctly from step-up errors', () => {
|
||||
const cancelled = new StepUpCancelledError()
|
||||
expect(isStepUpCancelled(cancelled)).toBe(true)
|
||||
expect(isStepUpRequired(cancelled)).toBe(false)
|
||||
expect(isStepUpBlocked(cancelled)).toBe(false)
|
||||
expect(isStepUpCancelled({ status: 403, code: 'STEP_UP_REQUIRED' })).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,6 +19,22 @@ const STEP_UP_REQUIRED = 'STEP_UP_REQUIRED'
|
||||
const STEP_UP_TOTP_NOT_ENABLED = 'STEP_UP_TOTP_NOT_ENABLED'
|
||||
const STEP_UP_ADMIN_API_KEY_FORBIDDEN = 'STEP_UP_ADMIN_API_KEY_FORBIDDEN'
|
||||
|
||||
/**
|
||||
* Thrown by run() when the user dismisses the TOTP dialog.
|
||||
* Callers should treat it as a silent no-op, not an error to toast.
|
||||
*/
|
||||
export class StepUpCancelledError extends Error {
|
||||
readonly code = 'STEP_UP_CANCELLED'
|
||||
constructor() {
|
||||
super('step-up verification cancelled by user')
|
||||
this.name = 'StepUpCancelledError'
|
||||
}
|
||||
}
|
||||
|
||||
export function isStepUpCancelled(err: unknown): boolean {
|
||||
return err instanceof StepUpCancelledError
|
||||
}
|
||||
|
||||
interface ApiError {
|
||||
status?: number
|
||||
code?: string | number
|
||||
@@ -76,7 +92,9 @@ export function useStepUp() {
|
||||
/**
|
||||
* Run a sensitive action. On STEP_UP_REQUIRED, prompt for a TOTP code and
|
||||
* retry once. STEP_UP_TOTP_NOT_ENABLED / admin-api-key errors are surfaced
|
||||
* to the caller (they cannot be resolved by entering a code).
|
||||
* to the caller (they cannot be resolved by entering a code). If the user
|
||||
* cancels the prompt, a StepUpCancelledError is thrown so callers can
|
||||
* distinguish "user changed their mind" from real failures.
|
||||
*/
|
||||
async function run<T>(action: () => Promise<T>): Promise<T> {
|
||||
try {
|
||||
@@ -91,7 +109,7 @@ export function useStepUp() {
|
||||
}
|
||||
const ok = await prompt()
|
||||
if (!ok) {
|
||||
throw err
|
||||
throw new StepUpCancelledError()
|
||||
}
|
||||
// Retry once now that the session holds a step-up grant.
|
||||
return await action()
|
||||
|
||||
Reference in New Issue
Block a user