
"知彼知己,百战不殆;不知彼而知己,一胜一负;不知彼,不知己,每战必殆。" ——孙子《谋攻篇》
老李盯着屏幕,有点后怕。
不是代码跑崩了,是安全团队发来的一份扫描报告。上面写着:生产环境的登录接口,存在一个SQL注入漏洞,已经上线三个月了。
漏洞很简单,就是用户名字段没有用参数化查询,直接拼接进了SQL。不是什么高明的攻击手法,是个初级问题。
小王去翻了当时的 commit 记录。那段代码,是 Claude Code 生成的,他当时看了一眼能跑,就提上去了。
老李问了一句:"你让它做安全检查了吗?"
小王沉默了几秒。
这就是2026年 AI 辅助编程的另一个真实困境——AI 生成功能的速度很快,但它不会主动告诉你它埋了什么坑。
老李后来找到了 Security Auditor 这个 skill,装上去,回头跑了一遍。
结果出来,他叹了口气。
不止一个。

Security Auditor 是一个 Claude Code skill,用于对代码做结构化安全审计,并输出带优先级排序和修复命令的报告。来自 wrsmith108/claude-skill-security-auditor,同时在 alirezarezvani/claude-skills 这套工程技能库里也有一个更完整的版本,覆盖了 OWASP Top 10 全部分类。
它做的事情:
mvn dependency-check:check)security-exceptions.json 白名单(已知风险,有审批记录)--fail-on high 可以直接卡 Pipeline它不做的:修复漏洞(它告诉你在哪,怎么改,动手的是你)。
"凡战者,以正合,以奇胜。" ——孙子《兵势篇》
这个问题我觉得值得说清楚,因为很多团队装了 AI 写代码工具之后,安全意识反而松了。
AI 写功能代码很擅长,它知道怎么实现登录、注册、JWT、分页。但它在安全这件事上有几个天然的弱点:
1. 倾向于展示功能,而不是防御性编程
AI 生成代码的目标是"让需求跑通"。一个快速实现的登录功能,它优先考虑的是流程通,不是攻击面。所以字符串拼接 SQL 会出现,因为它功能上没问题。
2. 不同上下文之间的安全知识不连贯
你在这个会话里告诉它用参数化查询,下一个会话它不记得。这是 AI 工具的架构决定的,不是 bug。每次生成都是独立的,安全规范需要反复注入。
3. 异常处理的"兜底式"写法暴露信息
// AI 常见写法
catch (Exception e) {
return ResponseEntity.status(500)
.body("系统异常: " + e.getMessage()); // 把异常信息暴露给客户端了
}把 e.getMessage() 直接返回给客户端,是信息泄露。攻击者可以从这里获取你的内部实现信息。
4. 默认配置往往不安全
JWT 的 secret 直接写在代码里,密码加密用 MD5,Spring Boot Actuator 接口没有鉴权……这些都是 AI 生成"能跑"代码时常见的默认配置。
Security Auditor 的价值就在这里。它是一个从安全视角重新过一遍的质检程序,在问题到生产之前把它挡下来。
"不战而屈人之兵,善之善者也。" ——孙子《谋攻篇》

Security Auditor 在 Code Simplifier 之后运行。逻辑是:先让代码可读,再做安全检查。可读的代码安全问题也更容易发现。
"故善战者,求之于势,不责于人。" ——孙子《兵势篇》
还是小王那套登录+注册+个人中心。跑 Security Auditor,指令:
security audit on the auth module
扫描报告输出(精简版):
╔══════════════════════════════════════════════╗
║ SECURITY AUDIT REPORT ║
║ Module: auth (Login/Register/Profile) ║
║ Verdict: ❌ FAIL ║
╠══════════════════════════════════════════════╣
║ 🔴 CRITICAL: 2 🟡 HIGH: 3 🟠 MEDIUM: 4 ║
╚══════════════════════════════════════════════╝
一条一条看。
// AuthRepository.java - BEFORE,原始 AI 生成
@Query(value = "SELECT * FROM users WHERE username = '" +
"?1' AND status = 'active'", nativeQuery = true)
User findActiveUserByUsername(String username);这是字符串拼接写法。输入 admin' OR '1'='1 就能绕过认证。
Security Auditor 的报告:
🔴 CRITICAL A03-INJECTION AuthRepository.java:15
Pattern: String concatenation in native SQL query
Risk: SQL injection via username parameter — authentication bypass possible
CVE Reference: CWE-89
Fix: Use parameterized queries or Spring Data JPA method naming
修复后:
// 方案一:Spring Data JPA 方法命名(最干净)
Optional<User> findByUsernameAndStatus(String username, UserStatus status);
// 方案二:@Query 参数绑定(需要 native query 的场景)
@Query(value = "SELECT * FROM users WHERE username = :username AND status = 'active'",
nativeQuery = true)
Optional<User> findActiveUserByUsername(@Param("username") String username);// JwtUtil.java - BEFORE
private static final String SECRET_KEY = "mySecretKey123456";
public String generateToken(String username, String role) {
return Jwts.builder()
.setSubject(username)
.claim("role", role)
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}Secret 写死在代码里,进了 git 仓库,就是暴露了。
Security Auditor 报告:
🔴 CRITICAL A02-CRYPTOGRAPHIC JwtUtil.java:8
Pattern: Hardcoded cryptographic secret
Risk: JWT tokens can be forged if secret is exposed via git history
Fix: Load from environment variable or secrets manager
修复后:
// JwtUtil.java - AFTER
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.expiration:86400000}")
private long expirationMs;
public String generateToken(String username, String role) {
return Jwts.builder()
.setSubject(username)
.claim("role", role)
.setExpiration(new Date(System.currentTimeMillis() + expirationMs))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}application.yml 里引用环境变量:
jwt:
secret: ${JWT_SECRET}
expiration: 86400000// AuthController.java - BEFORE
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ApiResponse(false, "系统异常: " + e.getMessage(), null));
}e.getMessage() 返回给前端。攻击者能从这里读到你的数据库连接串、表名、字段名。
Security Auditor 报告:
🟡 HIGH A09-LOGGING AuthController.java:47
Pattern: Exception message exposed in API response
Risk: Information disclosure — internal stack details visible to clients
Fix: Log exception server-side, return generic message to client
修复后:
} catch (Exception e) {
log.error("Login failed for user: {}", request.getUsername(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("服务暂时不可用,请稍后重试"));
}// RegisterRequest.java - BEFORE
public class RegisterRequest {
@NotBlank
private String username;
@NotBlank
@Size(min = 8, message = "密码至少8位")
private String password;
// 只检查了长度,没检查复杂度
}Security Auditor 报告:
🟡 HIGH A07-AUTHN RegisterRequest.java:12
Pattern: Weak password policy — length check only
Risk: Weak passwords increase brute force attack surface
Fix: Add complexity requirements (uppercase, number, special char)
修复后:
public class RegisterRequest {
@NotBlank
@Size(min = 3, max = 20)
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只允许字母、数字、下划线")
private String username;
@NotBlank
@Size(min = 8, max = 72)
@Pattern(
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$",
message = "密码需包含大小写字母、数字和特殊字符"
)
private String password;
}// ProfileController.java - BEFORE
@PutMapping("/profile/{userId}")
@PreAuthorize("#userId == authentication.principal.id")
public UserProfileResponse updateProfile(@PathVariable Long userId,
@RequestBody UpdateProfileRequest request) {
// 没有频率限制,可以无限次调用
return userService.updateProfile(userId, request);
}Security Auditor 报告:
🟠 MEDIUM A04-INSECURE-DESIGN ProfileController.java:28
Pattern: No rate limiting on authenticated endpoints
Risk: Account enumeration and abuse via high-frequency requests
Fix: Add @RateLimiter or Bucket4j rate limiting
修复后(用 Bucket4j + Spring Boot):
@PutMapping("/profile/{userId}")
@PreAuthorize("#userId == authentication.principal.id")
public UserProfileResponse updateProfile(@PathVariable Long userId,
@RequestBody @Valid UpdateProfileRequest request,
HttpServletRequest httpRequest) {
String clientIp = getClientIp(httpRequest);
rateLimiter.checkLimit("profile-update:" + userId, 10, Duration.ofMinutes(1));
return userService.updateProfile(userId, request);
}"先为不可胜,以待敌之可胜。" ——孙子《军形篇》
npm 项目有 npm audit,Java 项目对应的是 OWASP Dependency Check,Maven 插件一行配置:
<!-- pom.xml -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.0.9</version>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS> <!-- CVSS 7分以上阻断构建 -->
<formats>
<format>HTML</format>
<format>JSON</format>
</formats>
</configuration>
</plugin>CI 里加一步:
# .github/workflows/security.yml
- name: OWASP Dependency Check
run: mvn dependency-check:check
- name: Upload Security Report
uses: actions/upload-artifact@v4
if: always()
with:
name: dependency-check-report
path: target/dependency-check-report.htmlSecurity Auditor 在会话内做代码逻辑扫描,Maven Dependency Check 在 CI 里做依赖 CVE 扫描。两个层面都要守。

老李那个登录 SQL 注入,不是小王水平差,是流程里没有安全检查这一关。有了 Security Auditor,这一关是自动化的,不依赖人记不记得。

这不是夸张。一个在开发阶段五分钟修好的 SQL 注入,一旦到了生产,就要加上:事件响应、数据排查、用户通知、可能的合规报告。
所以 Security Auditor 的价值不是"找出漏洞",是"在正确的阶段找出漏洞"。

Security Auditor 做的是"安全左移"——把安全检查从测试阶段、上线前评审,往左推到开发过程里。每次写完功能,跑一遍,跟跑单元测试一样自然。
等安全问题到了上线评审会上再发现,那代价已经不一样了。
"胜兵先胜而后求战,败兵先战而后求胜。" ——孙子《军形篇》
安装(Claude Code skill 方式):
git clone https://github.com/wrsmith108/claude-skill-security-auditor.git \
~/.claude/skills/security-auditor或用 alirezarezvani 的工程技能包(OWASP Top 10 覆盖更全):
/plugin install skill-security-auditor@claude-code-skills使用口令:
security audit on the auth module
check for hardcoded secrets in the project
audit dependencies for CVEs
npx tsx scripts/index.ts --fail-on high
Java 项目配合 Maven 的完整设置:
<!-- pom.xml -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.0.9</version>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS>
</configuration>
</plugin>已知风险豁免(需要审批记录):
{
"exceptions": [
{
"id": "GHSA-xxxx-xxxx-xxxx",
"reason": "该漏洞仅在服务端渲染场景触发,我们使用 API 模式不受影响",
"expires": "2026-09-01",
"approvedBy": "security-lead@company.com"
}
]
}漏洞类型 | OWASP 分类 | Security Auditor 能发现 | 修复方向 |
|---|---|---|---|
SQL注入 | A03 | ✅ | 参数化查询 / JPA 方法命名 |
硬编码密钥 | A02 | ✅ | 环境变量 / Secrets Manager |
异常信息泄露 | A09 | ✅ | 服务端日志 + 通用错误响应 |
弱密码策略 | A07 | ✅ | Bean Validation 正则 |
缺少频率限制 | A04 | ✅ | Bucket4j + Redis |
依赖 CVE | A06 | ✅(配合 mvn) | 升级依赖版本 |
JWT 弱算法 | A02 | ✅ | 换 RS256 或强化 secret |
CORS 配置错误 | A01 | 部分 | 明确 allowedOrigins |
检查阶段 | 工具 | 覆盖范围 |
|---|---|---|
开发阶段 | Security Auditor | 代码逻辑漏洞、硬编码密钥 |
PR 阶段 | 人工 Review | 业务逻辑安全、权限设计 |
CI 阶段 | Maven dependency-check | 依赖 CVE |
生产运营 | WAF + 渗透测试 | 运行时防护 |
事故处理完之后,老李把小王叫过来,给他看了那份安全报告。
不是为了追责。是为了让他明白一件事:Security Auditor 扫出来的那些漏洞,其实有一个共同的根源——代码太乱了,乱到连 Review 的人都没看出来。
SQL 注入那一行,藏在一个五十行的方法体里;硬编码的 JWT secret,混在一堆常量声明中间;异常信息泄露,包在三层 try-catch 里面。
老李指着其中一个问小王:"你 Review 的时候看到这里了吗?"
小王盯着屏幕,沉默了几秒。
"看到了。但没觉得有问题。"
这就是可读性和安全之间的关系——代码越乱,安全问题越容易被淹没。 清理可读性,不只是让代码好看,是让问题变得看得见。
老李说:"你先去把这套系统的代码用 Code Simplifier 过一遍,然后我们再重新跑一次 Security Auditor。"
小王有点没明白:"先清理可读性,再扫安全?"
"对。顺序很重要。"
下一篇:Code Simplifier——在安全扫描之前,先让代码变得可读。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。