mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-30 00:21:34 +00:00
feat(security-audit): add narrow blocking audit scope
This commit is contained in:
@@ -95,6 +95,7 @@
|
||||
<div class="flex flex-wrap items-center gap-x-5 gap-y-2">
|
||||
<SaveToggle :label="t('admin.promptAudit.saveBar.enabled')" :model-value="draft.enabled" data-test="enabled-toggle" @update:model-value="setEnabled" />
|
||||
<SaveToggle :label="t('admin.promptAudit.saveBar.blocking')" :model-value="draft.blocking_enabled" :disabled="!draft.enabled" data-test="blocking-toggle" @update:model-value="setBlocking" />
|
||||
<SaveToggle :label="t('admin.promptAudit.saveBar.blockingLatestTurnOnly')" :model-value="draft.blocking_latest_turn_only" :disabled="!draft.enabled || !draft.blocking_enabled" data-test="blocking-latest-turn-only-toggle" @update:model-value="replaceDraft({ ...draft!, blocking_latest_turn_only: $event })" />
|
||||
<SaveToggle :label="t('admin.promptAudit.saveBar.storePass')" :model-value="draft.store_pass_events" data-test="store-pass-toggle" @update:model-value="replaceDraft({ ...draft!, store_pass_events: $event })" />
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
|
||||
@@ -19,7 +19,7 @@ vi.mock('vue-i18n', async () => {
|
||||
})
|
||||
|
||||
const baseConfig = (): PromptAuditConfig => ({
|
||||
enabled: true, blocking_enabled: false, store_pass_events: false, effective_mode: 'async_audit', strategy: 'priority',
|
||||
enabled: true, blocking_enabled: false, blocking_latest_turn_only: false, store_pass_events: false, effective_mode: 'async_audit', strategy: 'priority',
|
||||
worker_count: 4, queue_capacity: 100, scanners: SCANNER_CATALOG.map((item) => item.id), all_groups: true, group_ids: [],
|
||||
endpoints: [{ id: 'guard-1', name: 'Guard One', protocol: 'openai_compatible', base_url: 'http://127.0.0.1:8000', model: 'guard-model', timeout_ms: 3000, input_limit: 4000, enabled: true, has_token: true, token_status: 'configured' }],
|
||||
config_version: 7, updated_at: '2026-07-16T00:00:00Z', updated_by: 1, change_summary: '{}',
|
||||
@@ -126,10 +126,13 @@ describe('PromptAuditView', () => {
|
||||
expect(wrapper.find('[data-test="confirm"]').exists()).toBe(true)
|
||||
await wrapper.get('[data-test="confirm-action"]').trigger('click')
|
||||
expect(wrapper.get('[data-test="blocking-toggle"]').attributes('aria-checked')).toBe('true')
|
||||
await wrapper.get('[data-test="blocking-latest-turn-only-toggle"]').trigger('click')
|
||||
expect(wrapper.get('[data-test="blocking-latest-turn-only-toggle"]').attributes('aria-checked')).toBe('true')
|
||||
await wrapper.get('[data-test="enabled-toggle"]').trigger('click')
|
||||
expect(wrapper.get('[data-test="enabled-toggle"]').attributes('aria-checked')).toBe('false')
|
||||
expect(wrapper.get('[data-test="blocking-toggle"]').attributes('aria-checked')).toBe('false')
|
||||
expect(wrapper.get('[data-test="blocking-toggle"]').attributes()).toHaveProperty('disabled')
|
||||
expect(wrapper.get('[data-test="blocking-latest-turn-only-toggle"]').attributes()).toHaveProperty('disabled')
|
||||
})
|
||||
|
||||
it('clears plaintext token state after a successful save', async () => {
|
||||
@@ -175,7 +178,7 @@ describe('PromptAuditView', () => {
|
||||
await flushPromises()
|
||||
await wrapper.get('[data-test="tab-config"]').trigger('click')
|
||||
const switches = wrapper.findAll('[role="switch"]')
|
||||
expect(switches).toHaveLength(3)
|
||||
expect(switches).toHaveLength(4)
|
||||
expect(switches.every((item) => Boolean(item.attributes('aria-label')))).toBe(true)
|
||||
expect(wrapper.html()).toContain('fixed inset-x-0 bottom-0')
|
||||
expect(wrapper.html()).toContain('flex-wrap')
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('Prompt Audit components', () => {
|
||||
|
||||
it('supports group search, stale configured groups, nine scanners, and bounded worker inputs', async () => {
|
||||
const draft: PromptAuditDraft = {
|
||||
enabled: true, blocking_enabled: false, store_pass_events: false, effective_mode: 'async_audit', strategy: 'priority',
|
||||
enabled: true, blocking_enabled: false, blocking_latest_turn_only: false, store_pass_events: false, effective_mode: 'async_audit', strategy: 'priority',
|
||||
worker_count: 4, queue_capacity: 100, scanners: SCANNER_CATALOG.map((item) => item.id), all_groups: false, group_ids: [1, 99],
|
||||
endpoints: [endpoint()], config_version: 1, updated_at: '', updated_by: 0, change_summary: '',
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
const config = (): PromptAuditConfig => ({
|
||||
enabled: true,
|
||||
blocking_enabled: false,
|
||||
blocking_latest_turn_only: false,
|
||||
store_pass_events: false,
|
||||
effective_mode: 'async_audit',
|
||||
strategy: 'priority',
|
||||
@@ -56,6 +57,12 @@ describe('Prompt Audit view model', () => {
|
||||
expect(buildUpdateRequest(draft).endpoints[0]).toMatchObject({ token: undefined, clear_token: true })
|
||||
})
|
||||
|
||||
it('includes the optional narrow blocking scope in the update payload', () => {
|
||||
const draft = configToDraft(config())
|
||||
draft.blocking_latest_turn_only = true
|
||||
expect(buildUpdateRequest(draft)).toMatchObject({ blocking_latest_turn_only: true })
|
||||
})
|
||||
|
||||
it('tracks dirty state from the full normalized save payload', () => {
|
||||
const original = configToDraft(config())
|
||||
const changed = configToDraft(config())
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface PromptAuditEndpointDraft extends PromptAuditEndpoint {
|
||||
export interface PromptAuditConfig {
|
||||
enabled: boolean
|
||||
blocking_enabled: boolean
|
||||
blocking_latest_turn_only: boolean
|
||||
store_pass_events: boolean
|
||||
effective_mode: PromptAuditMode
|
||||
strategy: 'priority'
|
||||
@@ -46,6 +47,7 @@ export interface PromptAuditUpdateRequest {
|
||||
expected_config_version: number
|
||||
enabled: boolean
|
||||
blocking_enabled: boolean
|
||||
blocking_latest_turn_only: boolean
|
||||
store_pass_events: boolean
|
||||
strategy: 'priority'
|
||||
worker_count: number
|
||||
|
||||
@@ -62,6 +62,7 @@ export function buildUpdateRequest(draft: PromptAuditDraft): PromptAuditUpdateRe
|
||||
expected_config_version: draft.config_version,
|
||||
enabled: draft.enabled,
|
||||
blocking_enabled: draft.enabled && draft.blocking_enabled,
|
||||
blocking_latest_turn_only: draft.blocking_latest_turn_only,
|
||||
store_pass_events: draft.store_pass_events,
|
||||
strategy: 'priority',
|
||||
worker_count: Number(draft.worker_count),
|
||||
|
||||
@@ -55,7 +55,7 @@ export default {
|
||||
searchGroups: 'Search groups', noGroups: 'No matching groups', missingGroups: 'Configured IDs for groups that no longer exist', selectedCount: '{count} groups selected',
|
||||
scanners: 'Qwen3Guard input-risk categories', workerCount: 'Worker count', queueCapacity: 'Persistent queue capacity', strategy: 'Node strategy', strategyHint: 'Try nodes in configuration order and fail over when allowed.',
|
||||
},
|
||||
saveBar: { enabled: 'Enable prompt audit', blocking: 'Synchronous blocking', storePass: 'Store safe events', dirty: 'Unsaved changes', synced: 'Configuration synced' },
|
||||
saveBar: { enabled: 'Enable prompt audit', blocking: 'Synchronous blocking', blockingLatestTurnOnly: 'Only latest input and prior output', storePass: 'Store safe events', dirty: 'Unsaved changes', synced: 'Configuration synced' },
|
||||
blockingConfirm: {
|
||||
title: 'Enable synchronous blocking?',
|
||||
message: 'Applicable requests wait for Guard before account selection, billing, or upstream access. Block, unavailable Guard, and invalid responses all prevent upstream access.',
|
||||
|
||||
@@ -55,7 +55,7 @@ export default {
|
||||
searchGroups: '搜索分组', noGroups: '没有匹配分组', missingGroups: '配置中包含已删除的分组 ID', selectedCount: '已选择 {count} 个分组',
|
||||
scanners: 'Qwen3Guard 输入风险分类', workerCount: 'Worker 数量', queueCapacity: '持久队列容量', strategy: '节点策略', strategyHint: '按配置顺序优先尝试,必要时故障切换。',
|
||||
},
|
||||
saveBar: { enabled: '启用提示词审计', blocking: '同步阻止', storePass: '保存安全事件', dirty: '有未保存的更改', synced: '配置已同步' },
|
||||
saveBar: { enabled: '启用提示词审计', blocking: '同步阻止', blockingLatestTurnOnly: '仅审最新输入和上一轮输出', storePass: '保存安全事件', dirty: '有未保存的更改', synced: '配置已同步' },
|
||||
blockingConfirm: {
|
||||
title: '开启同步阻止?',
|
||||
message: '适用请求会在账号选择、计费和访问上游之前等待 Guard。命中 Block、Guard 不可用或响应非法时,请求都不会访问上游。',
|
||||
|
||||
Reference in New Issue
Block a user