diff --git a/AGENTS.md b/AGENTS.md index 79ee2d84..8de62500 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -121,6 +121,16 @@ Strong success criteria let you loop independently. Weak criteria ("make it work - 管理员代码推荐使用 `db.DB(ctx)`(`internal/infra/persistence`,包名 `db`)保证 Trace 链路透传。 - 禁止在 Handler 写复杂 SQL;迁移文件位于 `internal/infra/persistence/migrator/goose/`(禁止 GORM AutoMigrate)。 - 不创建物理外键(显式建索引);Go 模型零值需与数据库默认值匹配。 +- **SQL LIKE 查询防注入与转义**:所有含用户输入的模糊查询必须调用 `pkg/util.EscapeLike` 转义通配符,并显式指定 `ESCAPE '\\'` 语法(如 `Where("username LIKE ? ESCAPE '\\'", util.EscapeLike(keyword)+"%")`),同时兼容 PostgreSQL 与 SQLite 方言并杜绝通配符注入攻击。 + +### 并发与安全防护规范 +- **Goroutine 安全**:禁止直接使用裸 `go func()`;统一使用 `pkg/util.Go`,确保具备未捕获 panic 恢复和调用栈日志记录能力。 +- **Pub/Sub 监听并发安全**:启动 Redis Pub/Sub 订阅监听前,必须捕获局部客户端实例(如 `redisClient := db.Redis`),禁止在 goroutine 闭包中直读可变全局 `db.Redis`;提供 `Stop*Listener` 时必须维护 `done` 通道等待 goroutine 完整退出后再重置状态,消除测试或重连时的数据竞争。 +- **Session 固定攻击防御**:用户登录/授权成功后,必须调用 `oauth.SetLoginSession`(内部执行 Session ID 轮换),防止 Session 固定攻击。 +- **防账户枚举与时序攻击**: + - 登录失败统一返回模糊报错;当查询用户不存在时,必须调用 `pkg/util.DummyCheckPassword` 执行同等开销的 bcrypt 哈希计算,彻底消除时序侧信道攻击。 + - 验证码、签名 Token 等敏感字符串比对必须使用 `crypto/subtle.ConstantTimeCompare` 常量时间比对。 +- **敏感端点限流**:登录尝试、OAuth 授权发起等敏感接口必须接入基于 Redis 的滑动窗口限流机制,防止暴力破解与缓存资源耗尽。 ## 前端开发规范 @@ -130,6 +140,10 @@ Strong success criteria let you loop independently. Weak criteria ("make it work - 标题容器统一 `flex items-center gap-2`(带操作按钮用 `justify-between`)。 - 图标直接使用 Lucide 组件(`size-5 text-primary`),禁止包裹背景小卡片或装饰边框。 - 标题文字统一使用 `

`。 +- **无障碍语义与色彩规范 (a11y & WCAG)**: + - **标题层级规范 (Heading Hierarchy)**:页面中非顶级结构化标题(如空状态提示、加载提示、卡片眉题/卡片标题、抽屉区块名)严禁滥用 `

`/`

`,统一使用 `

` 配合样式,保证屏幕阅读器感知的标题层级连续。 + - **无文本控件无障碍**:所有仅包含图标的按钮(如仅有 Icon 的 Button、Switch、无文本的 SelectTrigger)必须显式添加 `aria-label`。 + - **色彩对比度**:正文、提示、徽章等小字颜色在亮色/暗色模式下必须满足 WCAG AA(对比度 ≥ 4.5:1)。 - **组件拆分与维护**: - 物理路由页面 `page.tsx` 仅维护高级骨架与布局。 - 单文件超过 600 行或含多 Tab/大复杂区块时,必须按就近原则拆分为子组件存放在路由同级的 `components/` 局部目录中(参考 `/admin/database` 的模块化拆分结构)。 diff --git a/docs/docs.go b/docs/docs.go index 658e7fad..3157b639 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -5798,7 +5798,7 @@ const docTemplate = `{ } }, "400": { - "description": "用户名或密码错误、帐号已禁用等", + "description": "用户名或密码错误", "schema": { "$ref": "#/definitions/response.Any" } diff --git a/docs/swagger.json b/docs/swagger.json index 6e49f134..3c73a47a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -5791,7 +5791,7 @@ } }, "400": { - "description": "用户名或密码错误、帐号已禁用等", + "description": "用户名或密码错误", "schema": { "$ref": "#/definitions/response.Any" } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 2398208e..d8c83c93 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -5011,7 +5011,7 @@ paths: $ref: '#/definitions/oauth.BasicUserInfo' type: object "400": - description: 用户名或密码错误、帐号已禁用等 + description: 用户名或密码错误 schema: $ref: '#/definitions/response.Any' "500": diff --git a/frontend/app/(main)/admin/database/components/cache-manager.tsx b/frontend/app/(main)/admin/database/components/cache-manager.tsx index 5f2d5650..0e0b4a4a 100644 --- a/frontend/app/(main)/admin/database/components/cache-manager.tsx +++ b/frontend/app/(main)/admin/database/components/cache-manager.tsx @@ -189,9 +189,9 @@ export function CacheManager({ refreshTrigger }: CacheManagerProps) {

{/* 左边:状态区 (2/5 cols) */}
-

+

{t('runtimeStatus')} -

+

{/* 已占空间 */}
@@ -240,9 +240,9 @@ export function CacheManager({ refreshTrigger }: CacheManagerProps) { {/* 右边:配置区 (3/5 cols) */}
-

+

{t('policyConfig')} -

+

diff --git a/frontend/app/(main)/admin/database/components/sql-console.tsx b/frontend/app/(main)/admin/database/components/sql-console.tsx index 7aa2f728..57a4a5d5 100644 --- a/frontend/app/(main)/admin/database/components/sql-console.tsx +++ b/frontend/app/(main)/admin/database/components/sql-console.tsx @@ -165,7 +165,10 @@ export function SQLConsole({ dbType, onClose }: SQLConsoleProps) { {t('quickTemplates')} - + diff --git a/frontend/app/(main)/admin/logs/components/access-analytics.tsx b/frontend/app/(main)/admin/logs/components/access-analytics.tsx index ebd58ed2..34c015a0 100644 --- a/frontend/app/(main)/admin/logs/components/access-analytics.tsx +++ b/frontend/app/(main)/admin/logs/components/access-analytics.tsx @@ -227,6 +227,7 @@ export function AccessAnalytics() { variant='ghost' size='icon' className='size-8' + aria-label={t('refresh')} onClick={fetchAnalytics} disabled={loading} > diff --git a/frontend/app/(main)/admin/push/components/events-tab.tsx b/frontend/app/(main)/admin/push/components/events-tab.tsx index f2fb9e3e..aabcfdc2 100644 --- a/frontend/app/(main)/admin/push/components/events-tab.tsx +++ b/frontend/app/(main)/admin/push/components/events-tab.tsx @@ -435,6 +435,7 @@ export function EventsTab() { onCheckedChange={() => toggleEventMutation.mutate(event.id) } + aria-label={t('colStatus')} className='scale-75' /> @@ -449,6 +450,7 @@ export function EventsTab() { + ))} +
} columns={[ { diff --git a/frontend/app/(main)/admin/tasks/components/task-manager.tsx b/frontend/app/(main)/admin/tasks/components/task-manager.tsx index 7fe0587b..1be39ab6 100644 --- a/frontend/app/(main)/admin/tasks/components/task-manager.tsx +++ b/frontend/app/(main)/admin/tasks/components/task-manager.tsx @@ -372,9 +372,9 @@ export function TaskManager() {
-

+

{task.name} -

+

{task.description}

diff --git a/frontend/app/(main)/admin/users/components/user-detail-sheet.tsx b/frontend/app/(main)/admin/users/components/user-detail-sheet.tsx index 6b61f3d3..083f8e1f 100644 --- a/frontend/app/(main)/admin/users/components/user-detail-sheet.tsx +++ b/frontend/app/(main)/admin/users/components/user-detail-sheet.tsx @@ -104,9 +104,9 @@ export function UserDetailSheet({
-

+

{t('personalInfo')} -

+

@@ -167,9 +167,9 @@ export function UserDetailSheet({
-

+

{t('systemRecords')} -

+

diff --git a/frontend/app/(main)/admin/users/components/user-filter-bar.tsx b/frontend/app/(main)/admin/users/components/user-filter-bar.tsx index 06a511fd..5a0f4007 100644 --- a/frontend/app/(main)/admin/users/components/user-filter-bar.tsx +++ b/frontend/app/(main)/admin/users/components/user-filter-bar.tsx @@ -26,6 +26,7 @@ import { cn } from '@/lib/utils'; export function UserFilterBar() { const t = useTranslations('admin.users'); + const tCommon = useTranslations('common'); const { total, loading, @@ -223,6 +224,7 @@ export function UserFilterBar() {
{/* 安全警告提示 */} -
+
{ta('securityTitle')} diff --git a/frontend/components/common/settings/notifications.tsx b/frontend/components/common/settings/notifications.tsx index 34559c5a..17effa57 100644 --- a/frontend/components/common/settings/notifications.tsx +++ b/frontend/components/common/settings/notifications.tsx @@ -23,6 +23,7 @@ export function NotificationsMain() { return (
+

{tn('breadcrumb')}

diff --git a/frontend/components/common/user/file-manager.tsx b/frontend/components/common/user/file-manager.tsx index 40ba7a8e..e923aebd 100644 --- a/frontend/components/common/user/file-manager.tsx +++ b/frontend/components/common/user/file-manager.tsx @@ -238,7 +238,7 @@ export function UserFileManager() {
-

{t('noFiles')}

+

{t('noFiles')}

{debouncedKeyword ? t('noMatchingFiles') : t('uploadFirstFile')}

diff --git a/frontend/components/layout/empty.tsx b/frontend/components/layout/empty.tsx index a37d7ff3..4ddb7f47 100644 --- a/frontend/components/layout/empty.tsx +++ b/frontend/components/layout/empty.tsx @@ -81,7 +81,7 @@ export function EmptyState({
{displayTitle && ( -

{displayTitle}

+

{displayTitle}

)} {displayDescription && ( diff --git a/frontend/components/layout/error.tsx b/frontend/components/layout/error.tsx index 4b2a0381..4a4ad5e8 100644 --- a/frontend/components/layout/error.tsx +++ b/frontend/components/layout/error.tsx @@ -74,7 +74,7 @@ export function ErrorDisplay({
-

{displayTitle}

+

{displayTitle}

{errorMessage} diff --git a/frontend/components/layout/header.tsx b/frontend/components/layout/header.tsx index 03a4e4cd..276e6478 100644 --- a/frontend/components/layout/header.tsx +++ b/frontend/components/layout/header.tsx @@ -160,7 +160,7 @@ export function SiteHeader({

{t('search')} - + {metaKey} K diff --git a/frontend/components/layout/loading.tsx b/frontend/components/layout/loading.tsx index 8b615c3c..a980a43b 100644 --- a/frontend/components/layout/loading.tsx +++ b/frontend/components/layout/loading.tsx @@ -120,9 +120,7 @@ export function LoadingState({
{displayTitle && ( -

- {displayTitle} -

+

{displayTitle}

)} {displayDescription && ( diff --git a/frontend/components/layout/sidebar.tsx b/frontend/components/layout/sidebar.tsx index 7badfb77..4bd51429 100644 --- a/frontend/components/layout/sidebar.tsx +++ b/frontend/components/layout/sidebar.tsx @@ -217,12 +217,15 @@ export function AppSidebar({ ...props }: React.ComponentProps) {