name: Security Specialist description: 应用安全专家,专注于认证授权、数据保护和合规性审计。当用户需要:(1) 设计安全的登录认证系统 (2) 进行安全代码审查 (3) 检查 GDPR/隐私合规 (4) 防范常见安全漏洞 (OWASP Top 10) (5) 确保无障碍设计安全使用时使用此 Skill。
⚠️ 性能提示: 此 Skill 包含完整的安全审计流程和无障碍安全指南。日常开发可只关注 OWASP Top 10 和快速安全检查清单。
Security Specialist Skills
🤖 智能体与 MCP 增强 (Agent & MCP Enhancements)
本 Skill 支持并推荐配合特定的智能体角色和 MCP 工具使用,以获得最佳效果。
推荐智能体角色
- Security Auditor: 详见 AGENTS.md。
- 该角色具备"零信任"和"攻击者视角",能够主动发现潜在风险。
- 启用后,AI 将强制执行 OWASP 安全检查清单。
推荐 MCP 工具
- Git MCP: 用于扫描历史提交中的敏感信息泄露。
- Filesystem MCP: 用于检查文件权限和
.gitignore配置。
提供全方位的安全保障,确保应用符合安全标准与法规。
包含的技能模块
1. 安全需求提取 (Security Requirements)
- 核心价值: 在设计阶段识别安全风险。
- 关键技术: 威胁建模, STRIDE 分析, 安全用户故事.
- 使用场景: 新功能安全评审、架构设计。
2. 认证实现模式 (Authentication)
- 核心价值: 实现安全可靠的用户身份验证。
- 关键技术: JWT, OAuth2, OIDC, Session 管理.
- 使用场景: 登录注册系统开发、第三方登录集成。
3. GDPR 数据处理 (GDPR Compliance)
- 核心价值: 确保数据处理符合 GDPR 等隐私法规。
- 关键技术: 数据最小化, 被遗忘权实现, 数据加密.
- 使用场景: 出海应用开发、隐私合规审计。
4. 无障碍安全设计 (Accessibility Security)
- 核心价值: 确保残障用户的安全使用体验。
- 关键技术: 安全焦点管理, ARIA 安全使用, 验证码无障碍设计.
- 使用场景: 无障碍网站安全审计、屏幕阅读器安全。
如何使用
- 登录设计: "请参考认证实现模式,帮我设计一个安全的 JWT 登录流程。"
- 合规检查: "请检查我的数据库字段设计是否符合 GDPR 要求。"
- 无障碍安全: "请检查我的登录表单是否对屏幕阅读器用户安全且可访问。"
无障碍安全设计 (Accessibility Security)
为什么无障碍与安全相关?
无障碍设计不仅仅是可用性要求,也涉及安全层面:
| 安全问题 | 无障碍风险 | 安全影响 |
|---|---|---|
| 验证码绕过 | 视觉验证码无法被屏幕阅读器访问 | 可能迫使视障用户使用不安全的替代方案 |
| 焦点管理 | 模态框焦点泄漏 | 敏感信息可能被意外暴露 |
| ARIA 滥用 | aria-hidden 滥用 | 隐藏敏感内容被屏幕阅读器读取 |
| 输入验证 | 错误提示不清晰 | 用户可能重复提交导致安全问题 |
1. 验证码无障碍设计
问题
传统验证码(CAPTCHA)对视障用户构成严重障碍:
- 视觉谜题无法被屏幕阅读器识别
- 音频验证码可能泄露信息
- 复杂的验证码增加认知负担
解决方案
<!-- ❌ 不合规:纯视觉验证码 -->
<div class="captcha">
<img src="/captcha.png" alt="验证码图片" />
<input type="text" placeholder="请输入验证码" />
</div>
<!-- ✅ 合规:提供多种验证方式 -->
<div class="captcha" role="group" aria-labelledby="captcha-label">
<span id="captcha-label" class="sr-only">安全验证</span>
<!-- 首选:隐形验证码 -->
<div data-captcha="invisible"></div>
<!-- 备选:音频验证码 -->
<button type="button" aria-describedby="audio-desc">
播放音频验证码
<span id="audio-desc" class="sr-only">
如有视觉障碍,请点击此处获取音频验证码
</span>
</button>
<label for="captcha-input" class="sr-only">请输入您听到的字符</label>
<input type="text" id="captcha-input" autocomplete="off" />
</div>
最佳实践
- 优先隐形验证码:使用行为分析(如 reCAPTCHA v3)代替视觉挑战
- 提供替代方案:始终提供非视觉验证选项
- 清晰的说明:为所有验证码提供清晰的文字说明
- 错误处理:验证码错误时提供清晰的语音提示
// ✅ 推荐:隐形验证码检查
async function validateCaptcha(token) {
try {
const response = await fetch('/api/verify-captcha', {
method: 'POST',
body: JSON.stringify({ token })
});
const result = await response.json();
if (!result.success) {
// 对屏幕阅读器用户显示语音错误
announceToScreenReader('验证码验证失败,请重试');
return false;
}
return true;
} catch (error) {
announceToScreenReader('网络错误,请稍后重试');
return false;
}
}
2. 安全焦点管理
问题
焦点管理不当可能导致敏感信息泄露:
- 模态框外的敏感内容仍可被 Tab 访问
- 焦点泄漏到隐藏内容
- 焦点丢失在复杂页面中
解决方案
<!-- ❌ 不安全:模态框焦点泄漏 -->
<div class="modal">
<h2>登录表单</h2>
<input type="text" placeholder="用户名" />
<input type="password" placeholder="密码" />
<!-- 危险:背景内容仍可被访问 -->
</div>
<div class="background-content">
<button>删除账户</button> <!-- 可被意外聚焦 -->
</div>
<!-- ✅ 安全:正确的焦点陷阱 -->
<div
class="modal"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
tabindex="-1"
>
<h2 id="modal-title">登录表单</h2>
<label for="username">用户名</label>
<input type="text" id="username" autocomplete="username" />
<label for="password">密码</label>
<input type="password" id="password" autocomplete="current-password" />
<button type="button" aria-describedby="password-requirements">
显示密码要求
</button>
<!-- aria-describedby 安全使用 -->
<p id="password-requirements" class="sr-only">
密码要求:至少8个字符,包含大小写字母和数字
</p>
</div>
// ✅ 安全的焦点陷阱实现
class FocusTrap {
constructor(modalElement) {
this.modal = modalElement;
this.firstFocusable = null;
this.lastFocusable = null;
this.previousActiveElement = null;
}
activate() {
// 保存之前的焦点元素
this.previousActiveElement = document.activeElement;
// 找到焦点元素
const focusableElements = this.modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
this.firstFocusable = focusableElements[0];
this.lastFocusable = focusableElements[focusableElements.length - 1];
// 将焦点移到模态框第一个元素
this.firstFocusable.focus();
// 添加键盘监听
this.handleKeyDown = this.handleKeyDown.bind(this);
document.addEventListener('keydown', this.handleKeyDown);
}
handleKeyDown(event) {
if (event.key !== 'Tab') return;
// Shift + Tab: 从第一个元素向前
if (event.shiftKey && document.activeElement === this.firstFocusable) {
event.preventDefault();
this.lastFocusable.focus();
}
// Tab: 从最后一个元素向后
else if (!event.shiftKey && document.activeElement === this.lastFocusable) {
event.preventDefault();
this.firstFocusable.focus();
}
}
deactivate() {
document.removeEventListener('keydown', this.handleKeyDown);
// 恢复焦点
if (this.previousActiveElement) {
this.previousActiveElement.focus();
}
}
}
3. ARIA 安全使用
问题
ARIA 属性使用不当可能导致安全信息泄露:
aria-hidden可能隐藏安全警告aria-live可能意外暴露敏感信息aria-describedby链接到错误的内容
安全 ARIA 使用规范
<!-- ❌ 不安全:隐藏安全警告 -->
<div aria-hidden="true" class="security-warning">
⚠️ 此页面包含敏感信息
</div>
<!-- ✅ 安全:保留重要安全信息 -->
<div
role="alert"
aria-live="assertive"
class="security-warning"
>
⚠️ 此页面包含敏感信息,请确认您的网络连接安全
</div>
<!-- ❌ 不安全:aria-describedby 链接到隐藏内容 -->
<span id="sensitive-info" class="sr-only">
您的账户余额为 ¥10,000
</span>
<button aria-describedby="sensitive-info">
查看详情
</button>
<!-- ✅ 安全:谨慎使用 aria-describedby -->
<button aria-describedby="button-purpose">
查看详情
</button>
<span id="button-purpose" class="sr-only">
查看账户详情和余额
</span>
ARIA 安全检查清单
- 不要使用
aria-hidden隐藏重要安全信息 -
aria-live区域设置合理的优先级(assertive/polite) -
aria-describedby只链接到相关内容 - 不要依赖 ARIA 隐藏敏感数据,使用真实 DOM 操作
- 屏幕阅读器用户应获得与视觉用户相同的安全提示
4. 表单安全与无障碍
安全的输入验证
<!-- ✅ 合规:安全的错误提示 -->
<label for="password">密码</label>
<input
type="password"
id="password"
aria-describedby="password-requirements password-hint"
aria-invalid="false"
/>
<ul id="password-requirements" class="sr-only">
<li>至少8个字符</li>
<li>包含大写字母</li>
<li>包含小写字母</li>
<li>包含数字</li>
</ul>
<!-- 安全提示,不暴露具体错误 -->
<p id="password-hint">
请参考要求创建强密码
</p>
<div
role="alert"
aria-live="polite"
id="password-error"
hidden
>
</div>
// ✅ 安全的错误提示
function validatePassword(password) {
const errors = [];
if (password.length < 8) {
errors.push('密码长度不足');
}
if (!/[A-Z]/.test(password)) {
errors.push('缺少大写字母');
}
const errorElement = document.getElementById('password-error');
if (errors.length > 0) {
// 设置 aria-invalid
document.getElementById('password').setAttribute('aria-invalid', 'true');
// 对屏幕阅读器显示通用错误
errorElement.textContent = '密码不符合要求,请重试';
errorElement.hidden = false;
} else {
document.getElementById('password').setAttribute('aria-invalid', 'false');
errorElement.hidden = true;
}
}
安全的自动完成
<!-- ✅ 合规:安全的自动完成 -->
<label for="cc-number">信用卡号</label>
<input
type="text"
id="cc-number"
inputmode="numeric"
pattern="[0-9\s]{13,19}"
autocomplete="cc-number"
aria-describedby="cc-hint"
/>
<p id="cc-hint" class="sr-only">
输入13到19位数字,可包含空格
</p>
<!-- ❌ 危险:禁用自动完成可能导致用户使用不安全的密码 -->
<input type="password" autocomplete="off" />
5. 认证流程无障碍安全
多因素认证(MFA)无障碍
<!-- ✅ 合规:MFA 无障碍设计 -->
<section aria-labelledby="mfa-title">
<h2 id="mfa-title">双因素认证</h2>
<p>为了保护您的账户安全,我们建议启用双因素认证。</p>
<!-- TOTP 应用 -->
<div>
<h3>身份验证器应用</h3>
<button type="button">
<svg aria-hidden="true"><!-- 图标 --></svg>
<span>下载 Google Authenticator</span>
</button>
<label for="totp-code">输入6位验证码</label>
<input
type="text"
id="totp-code"
inputmode="numeric"
pattern="[0-9]{6}"
maxlength="6"
autocomplete="one-time-code"
aria-describedby="totp-hint"
/>
<p id="totp-hint" class="sr-only">
请输入身份验证器应用显示的6位数字验证码
</p>
</div>
<!-- 短信验证码 -->
<div>
<h3>短信验证码</h3>
<button type="button" aria-describedby="sms-warning">
发送短信验证码
</button>
<p id="sms-warning" class="sr-only">
注意:短信验证码可能存在安全风险,建议使用身份验证器应用
</p>
</div>
</section>
安全的会话超时
<!-- ✅ 合规:会话超时无障碍通知 -->
<div
role="alertdialog"
aria-labelledby="timeout-title"
aria-describedby="timeout-desc"
aria-modal="true"
>
<h2 id="timeout-title">会话即将过期</h2>
<p id="timeout-desc">
由于您超过30分钟未操作,会话即将自动登出。
<span id="countdown" aria-live="polite" aria-atomic="true">
剩余 2 分钟
</span>
</p>
<button type="button" onclick="extendSession()">
继续操作
</button>
<button type="button" onclick="logout()">
安全登出
</button>
</div>
6. 无障碍安全测试
自动化测试
// jest-accessibility-security.test.js
describe('无障碍安全测试', () => {
test('敏感信息不被 aria-hidden 隐藏', () => {
const warnings = document.querySelectorAll('[aria-hidden="true"]');
warnings.forEach(element => {
const text = element.textContent.toLowerCase();
const isSecurityRelated = text.includes('安全') ||
text.includes('warning') ||
text.includes('敏感');
expect(isSecurityRelated).toBe(false);
});
});
test('所有表单有安全的错误提示', () => {
const inputs = document.querySelectorAll('input[aria-invalid="true"]');
const errors = document.querySelectorAll('[role="alert"], [aria-live]');
// 每个错误输入应该有对应的错误提示
inputs.forEach(input => {
const describedBy = input.getAttribute('aria-describedby');
const hasErrorConnection = errors.some(error =>
describedBy?.includes(error.id)
);
expect(hasErrorConnection).toBe(true);
});
});
test('模态框有正确的焦点陷阱', () => {
const modals = document.querySelectorAll('[role="dialog"]');
modals.forEach(modal => {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
// 模态框应该有可聚焦元素
expect(focusableElements.length).toBeGreaterThan(0);
// 第一个元素应该是可聚焦的
expect(focusableElements[0]).toBeFocusable();
});
});
});
手动测试检查清单
- 使用屏幕阅读器(NVDA/VoiceOver)测试所有安全提示是否可读
- 测试焦点管理:Tab 键是否只在模态框内循环
- 验证所有验证码选项对屏幕阅读器可用
- 检查所有错误提示是否使用 ARIA live 区域
- 测试键盘导航:是否所有安全操作都可键盘触发
7. 无障碍安全合规标准
WCAG 2.2 安全相关要求
| 标准 | 要求 | 无障碍安全实现 |
|---|---|---|
| 1.3.1 信息和关系 | 结构化信息 | 安全的表单结构和语义 |
| 2.1.1 键盘 | 完全键盘可访问 | 所有安全操作键盘可触发 |
| 2.4.3 焦点顺序 | 焦点顺序符合逻辑 | 安全的焦点管理 |
| 3.3.1 错误识别 | 错误识别 | 屏幕阅读器可感知的错误 |
| 4.1.2 名称角色值 | ARIA 正确使用 | 安全使用 ARIA 属性 |
OWASP 无障碍安全
- 身份验证: 确保验证码对所有用户可用
- 会话管理: 无障碍的会话超时提示
- 访问控制: 焦点管理防止未授权访问
- 安全日志: 屏幕阅读器可访问的安全事件
快速安全检查清单
日常开发
- 所有表单输入有正确的 label
- 错误提示使用 aria-describedby 或 aria-live
- 模态框有正确的焦点陷阱
- 没有用 aria-hidden 隐藏重要信息
- 验证码有非视觉替代方案
安全审计
- 检查所有 ARIA 使用是否安全
- 验证焦点管理不泄漏敏感信息
- 测试屏幕阅读器用户的安全流程
- 检查表单自动完成安全性
无障碍安全
- 验证屏幕阅读器可读取所有安全警告
- 测试键盘用户不会意外访问敏感内容
- 确保验证码对残障用户可用
- 检查错误消息不泄露安全细节
相关 Skill
- WebGuidelines: 无障碍设计和 WCAG 合规检查
- FrontendImplementation: 无障碍前端实现
- ReactBestPractices: React 组件的无障碍模式
- DesignSystem: 无障碍 Design Token