fix: restore security hardening compatibility and validation

Restore authorized rule reveal, explicit full HTTP capture and retention, video task business fields, and valid payment URLs. Add opt-in credential preservation for trusted recovery, fix frontend type contracts and async races, and eliminate PostgreSQL test fixture resource leaks. Document audit coverage and successful fmt and CI-scoped Clippy checks.
This commit is contained in:
elky
2026-09-07 21:14:27 +08:00
parent a5c3699ae9
commit a90d564931
191 changed files with 6785 additions and 1643 deletions
@@ -41,7 +41,7 @@ describe('usage detail demo contracts', () => {
params: { include_bodies: true },
})
expect(response?.data?.response_body).toEqual({
expect(response?.data).toHaveProperty('response_body', {
error: {
type: 'invalid_request',
message: 'This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. To get authorized for security work, join the Trusted Access for Cyber program: https://chatgpt.com/cyber',
@@ -41,7 +41,7 @@ describe('user management demo contracts', () => {
feature_settings: null,
is_standalone: false,
})
expect(created?.data?.key).toMatch(/^sk-ae-demo-/)
expect(created?.data).toHaveProperty('key', expect.stringMatching(/^sk-ae-demo-/))
const aliceKeys = await handleMockRequest({
method: 'GET',
@@ -53,10 +53,9 @@ describe('user management demo contracts', () => {
})
expect(aliceKeys?.data).toMatchObject({ total: 1 })
expect(aliceKeys?.data?.api_keys).toHaveLength(1)
expect(aliceKeys?.data?.api_keys[0]).toMatchObject({ name: 'Alice inherited key' })
expect(aliceKeys?.data?.api_keys[0]).not.toHaveProperty('key')
expect(aliceKeys?.data?.api_keys[0]).not.toHaveProperty('fullKey')
expect(aliceKeys?.data).toHaveProperty('api_keys', [expect.objectContaining({ name: 'Alice inherited key' })])
expect(aliceKeys?.data).not.toHaveProperty('api_keys.0.key')
expect(aliceKeys?.data).not.toHaveProperty('api_keys.0.fullKey')
expect(bobKeys?.data).toEqual({ api_keys: [], total: 0 })
})
})
+28
View File
@@ -442,6 +442,10 @@ export const MOCK_PROVIDERS: ProviderWithEndpointsSummary[] = [
name: 'DuckCodingFree',
description: '',
website: 'https://duckcoding.com',
keep_priority_on_conversion: false,
enable_format_conversion: true,
global_model_ids: [],
ops_configured: false,
provider_priority: 1,
billing_type: 'free_tier',
monthly_used_usd: 0.0,
@@ -468,6 +472,10 @@ export const MOCK_PROVIDERS: ProviderWithEndpointsSummary[] = [
name: 'OpenClaudeCode',
description: '',
website: 'https://www.openclaudecode.cn',
keep_priority_on_conversion: false,
enable_format_conversion: true,
global_model_ids: [],
ops_configured: false,
provider_priority: 2,
billing_type: 'pay_as_you_go',
monthly_used_usd: 545.18,
@@ -492,6 +500,10 @@ export const MOCK_PROVIDERS: ProviderWithEndpointsSummary[] = [
name: '88Code',
description: '',
website: 'https://www.88code.org/',
keep_priority_on_conversion: false,
enable_format_conversion: true,
global_model_ids: [],
ops_configured: false,
provider_priority: 3,
billing_type: 'pay_as_you_go',
monthly_used_usd: 33.36,
@@ -517,6 +529,10 @@ export const MOCK_PROVIDERS: ProviderWithEndpointsSummary[] = [
name: 'IKunCode',
description: '',
website: 'https://api.ikuncode.cc',
keep_priority_on_conversion: false,
enable_format_conversion: true,
global_model_ids: [],
ops_configured: false,
provider_priority: 4,
billing_type: 'pay_as_you_go',
monthly_used_usd: 268.65,
@@ -543,6 +559,10 @@ export const MOCK_PROVIDERS: ProviderWithEndpointsSummary[] = [
name: 'DuckCoding',
description: '',
website: 'https://duckcoding.com',
keep_priority_on_conversion: false,
enable_format_conversion: true,
global_model_ids: [],
ops_configured: false,
provider_priority: 5,
billing_type: 'pay_as_you_go',
monthly_used_usd: 5.29,
@@ -571,6 +591,10 @@ export const MOCK_PROVIDERS: ProviderWithEndpointsSummary[] = [
name: 'Privnode',
description: '',
website: 'https://privnode.com',
keep_priority_on_conversion: false,
enable_format_conversion: true,
global_model_ids: [],
ops_configured: false,
provider_priority: 6,
billing_type: 'pay_as_you_go',
monthly_used_usd: 0.0,
@@ -593,6 +617,10 @@ export const MOCK_PROVIDERS: ProviderWithEndpointsSummary[] = [
name: 'UndyingAPI',
description: '',
website: 'https://vip.undyingapi.com',
keep_priority_on_conversion: false,
enable_format_conversion: true,
global_model_ids: [],
ops_configured: false,
provider_priority: 7,
billing_type: 'pay_as_you_go',
monthly_used_usd: 6.6,
+7 -7
View File
@@ -3,7 +3,7 @@
* 演示模式的 API 请求拦截和模拟响应
*/
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import { AxiosHeaders, type AxiosRequestConfig, type AxiosResponse } from 'axios'
import { isDemoMode, DEMO_ACCOUNTS } from '@/config/demo'
import { log } from '@/utils/logger'
import {
@@ -42,7 +42,7 @@ function createMockResponse<T>(data: T, status: number = 200): AxiosResponse<T>
status,
statusText: status === 200 ? 'OK' : 'Error',
headers: {},
config: {} as AxiosRequestConfig
config: { headers: new AxiosHeaders() }
}
}
@@ -1541,7 +1541,7 @@ const mockHandlers: Record<string, (config: AxiosRequestConfig) => Promise<Axios
await delay()
return createMockResponse(MOCK_ENDPOINTS.map(e => ({
api_format: e.api_format,
health_score: e.health_score,
health_score: 1,
is_active: e.is_active
})))
},
@@ -2142,7 +2142,7 @@ const mockHandlers: Record<string, (config: AxiosRequestConfig) => Promise<Axios
models: MOCK_GLOBAL_MODELS.map(m => ({
name: m.name,
display_name: m.display_name,
description: m.description
description: m.config?.description
}))
})
},
@@ -3054,7 +3054,7 @@ registerDynamicRoute('POST', '/api/admin/endpoints/providers/:providerId/refresh
.filter(key => !requestedKeyIds || requestedKeyIds.has(key.id))
const results = keys.map(key => ({
key_id: key.id,
key_name: key.name || key.id.slice(0, 8),
key_name: key.name || String(key.id).slice(0, 8),
status: 'success',
metadata: { updated_at: new Date().toISOString() }
}))
@@ -3198,7 +3198,7 @@ registerDynamicRoute('POST', '/api/admin/provider-oauth/providers/:providerId/ba
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
const raw = typeof body.credentials === 'string' ? body.credentials.trim() : ''
const raw: string = typeof body.credentials === 'string' ? body.credentials.trim() : ''
const lines = raw ? raw.split('\n').filter(line => line.trim() && !line.trim().startsWith('#')) : []
const total = Math.max(Math.min(lines.length, 5), 2)
const results = []
@@ -3294,7 +3294,7 @@ mockHandlers['GET /api/admin/endpoints/keys/grouped-by-format'] = async () => {
const baseUrlByFormat = Object.fromEntries(endpoints.map(e => [e.api_format, e.base_url]))
const keys = PROVIDER_KEYS_CACHE[provider.id] || []
for (const key of keys) {
const formats: string[] = key.api_formats || []
const formats = Array.isArray(key.api_formats) ? key.api_formats.filter((format): format is string => typeof format === 'string') : []
for (const fmt of formats) {
if (!grouped[fmt]) grouped[fmt] = []
grouped[fmt].push({