别让 AI 写出“能跑但裸奔”的代码:一个 Flask 示例讲透常见安全坑
AI编程 安全漏洞分析|附源码
随着大模型能力的快速提升,越来越多开发者开始使用 AI 辅助编程:让 AI 生成接口、补全业务逻辑、编写 SQL、封装工具类、甚至直接生成一个完整项目。AI 编程确实提升了效率,但也带来了一个非常现实的问题:AI 生成的代码并不天然安全。
很多时候,AI 会根据“看起来合理”的模式生成代码,却忽略输入校验、权限控制、敏感信息保护、异常处理、安全配置等关键环节。对于经验不足的开发者来说,如果直接复制 AI 生成的代码上线,很容易引入安全漏洞。
本文将围绕 AI 编程中常见的安全问题进行分析,并通过一套简化的示例源码说明:AI 生成的代码可能如何产生漏洞,以及如何进行安全修复。
本文内容仅用于安全学习、代码审计与防御建设,不用于任何未授权攻击行为。
一、AI 编程为什么容易产生安全漏洞?
AI 编程的核心优势是“快速生成代码”,但安全开发并不只是“代码能跑”。一个接口从可用到安全,需要考虑很多方面,例如:
- 用户输入是否可信;
- 数据库查询是否防注入;
- 接口是否需要鉴权;
- 用户是否只能访问自己的数据;
- 错误信息是否泄露内部细节;
- 密码、Token、密钥是否安全存储;
- 上传文件是否校验类型与大小;
- 日志中是否包含敏感数据;
- 第三方依赖是否存在已知漏洞;
- 默认配置是否适合生产环境。
AI 生成代码时,往往会优先满足用户显式提出的功能需求。如果用户只说“帮我写一个登录接口”“写一个用户查询接口”,AI 可能会生成一个逻辑完整但安全性不足的版本。
例如,开发者可能这样提问:
帮我用 Flask 写一个用户登录接口,连接 SQLite 数据库。
AI 可能会快速生成一个可以登录的接口,但不一定会自动考虑 SQL 注入、密码哈希、错误提示、暴力破解防护等安全问题。
这就是 AI 编程中的典型风险:功能正确不等于安全正确。
二、示例项目:一个存在安全隐患的用户系统
下面我们构造一个非常简化的 Flask 示例项目,用于演示 AI 生成代码中常见的安全漏洞。
项目功能包括:
- 用户登录;
- 查询用户资料;
- 修改个人简介;
- 简单的管理接口。
假设这是 AI 根据“快速实现功能”的要求生成的代码。
目录结构
ai_security_demo/
├── app_vulnerable.py
├── app_secure.py
└── requirements.txt
requirements.txt
Flask==3.0.0
Werkzeug==3.0.1
三、存在漏洞的源码示例
下面是一个存在多处安全问题的示例代码。
注意:该代码仅用于本地学习和安全分析,不建议用于生产环境。
# app_vulnerable.py
from flask import Flask, request, jsonify
import sqlite3
import os
app = Flask(__name__)
# 问题 1:硬编码密钥
app.config["SECRET_KEY"] = "admin123"
DB_PATH = "demo.db"
def init_db():
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
password TEXT,
role TEXT,
bio TEXT
)
""")
cursor.execute("DELETE FROM users")
# 问题 2:明文密码存储
cursor.execute("""
INSERT INTO users(username, password, role, bio)
VALUES ('admin', 'admin123', 'admin', 'I am administrator')
""")
cursor.execute("""
INSERT INTO users(username, password, role, bio)
VALUES ('alice', 'alice123', 'user', 'Hello, I am Alice')
""")
conn.commit()
conn.close()
@app.route("/login", methods=["POST"])
def login():
username = request.form.get("username", "")
password = request.form.get("password", "")
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 问题 3:SQL 拼接,存在 SQL 注入风险
sql = f"SELECT id, username, role FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(sql)
user = cursor.fetchone()
conn.close()
if user:
return jsonify({
"message": "login success",
"user_id": user[0],
"username": user[1],
"role": user[2]
})
return jsonify({"message": "login failed"}), 401
@app.route("/user/", methods=["GET"])
def get_user(user_id):
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 问题 4:仍然使用字符串拼接 SQL
sql = f"SELECT id, username, role, bio FROM users WHERE id={user_id}"
cursor.execute(sql)
user = cursor.fetchone()
conn.close()
if not user:
return jsonify({"message": "user not found"}), 404
# 问题 5:未做访问控制,任何人都可以查询任意用户信息
return jsonify({
"id": user[0],
"username": user[1],
"role": user[2],
"bio": user[3]
})
@app.route("/user//bio", methods=["POST"])
def update_bio(user_id):
bio = request.form.get("bio", "")
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 问题 6:未校验身份,任何人都可以修改任意用户简介
sql = f"UPDATE users SET bio='{bio}' WHERE id={user_id}"
cursor.execute(sql)
conn.commit()
conn.close()
return jsonify({"message": "bio updated"})
@app.route("/admin/users", methods=["GET"])
def admin_users():
# 问题 7:管理接口没有鉴权
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT id, username, password, role, bio FROM users")
users = cursor.fetchall()
conn.close()
return jsonify([
{
"id": u[0],
"username": u[1],
"password": u[2],
"role": u[3],
"bio": u[4]
}
for u in users
])
if __name__ == "__main__":
if not os.path.exists(DB_PATH):
init_db()
# 问题 8:生产环境不应开启 debug
app.run(debug=True)
四、漏洞分析
上面的代码虽然可以运行,但从安全角度看存在多个典型问题。这些问题在 AI 生成代码中非常常见。
1. 硬编码密钥
app.config["SECRET_KEY"] = "admin123"
硬编码密钥是非常常见的问题。很多 AI 生成的示例代码为了方便运行,会直接把密钥写在源码中。
风险包括:
- 源码提交到 Git 仓库后密钥泄露;
- 多个环境共用同一个密钥;
- 攻击者获取源码后可伪造签名数据;
- 密钥无法做到安全轮换。
正确做法是:从环境变量或密钥管理服务中读取敏感配置。
2. 明文存储密码
VALUES ('admin', 'admin123', 'admin', 'I am administrator')
明文密码是严重安全问题。一旦数据库泄露,所有用户密码都会直接暴露。更严重的是,很多用户存在密码复用习惯,同一个密码可能用于邮箱、云服务、公司系统等多个平台。
正确做法是:
- 使用安全的密码哈希算法;
- 使用随机盐;
- 不自行设计加密算法;
- 优先使用成熟框架提供的密码处理函数。
在 Python Flask 生态中,可以使用 Werkzeug 提供的:
generate_password_hash()
check_password_hash()
3. SQL 注入风险
漏洞代码:
sql = f"SELECT id, username, role FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(sql)
这里直接把用户输入拼接进 SQL 语句,属于典型 SQL 注入风险。
AI 生成代码时经常为了简单直观使用 f-string 或字符串拼接,但数据库查询中这是一种危险写法。
正确做法是使用参数化查询:
cursor.execute(
"SELECT id, username, role FROM users WHERE username=?",
(username,)
)
参数化查询会把 SQL 结构和用户输入分离,避免输入内容被解释为 SQL 语句的一部分。
4. 缺少身份认证
示例代码中的多个接口都没有可靠的身份认证:
@app.route("/user/", methods=["GET"])
def get_user(user_id):
...
任何人只要知道用户 ID,就可以查询对应用户资料。这属于典型的未授权访问问题。
在真实业务中,接口至少应该确认:
- 请求者是谁;
- 请求者是否已经登录;
- 请求者是否有权限访问该资源。
5. 越权访问
即使系统有登录功能,也不代表权限控制正确。
例如用户 Alice 登录后,只应该访问自己的信息,而不能访问 Bob 或管理员的信息。如果后端只根据 URL 中的 user_id 查询数据,而不校验当前登录用户和目标 user_id 的关系,就会造成越权访问。
越权漏洞通常分为:
- 水平越权:普通用户访问其他普通用户的数据;
- 垂直越权:普通用户访问管理员功能。
AI 生成代码中非常容易漏掉这类业务安全逻辑,因为它不只是语法问题,而是和业务规则强相关。
6. 管理接口未鉴权
@app.route("/admin/users", methods=["GET"])
def admin_users():
...
这个接口直接返回所有用户信息,甚至包含密码字段。这是非常危险的。
管理接口至少需要:
- 登录认证;
- 管理员角色校验;
- 敏感字段脱敏;
- 操作审计日志;
- 访问频率控制。
7. Debug 模式暴露
app.run(debug=True)
Debug 模式适合本地开发,但不适合生产环境。生产环境开启 Debug 可能导致:
- 显示详细错误栈;
- 泄露路径、变量、配置;
- 在特定错误配置下引入更严重风险。
正确做法是通过环境变量控制运行模式,并在生产环境关闭 Debug。
五、安全修复版本源码
下面给出一个改进后的版本。该版本仍然是教学示例,但修复了前面提到的主要问题。
改进点包括:
- 使用环境变量读取密钥;
- 使用密码哈希;
- 使用参数化 SQL;
- 增加简单 Token 认证;
- 增加角色权限控制;
- 限制用户只能访问自己的资料;
- 不返回密码字段;
- 关闭默认 Debug。
# app_secure.py
from flask import Flask, request, jsonify, g
from werkzeug.security import generate_password_hash, check_password_hash
import sqlite3
import os
import secrets
from functools import wraps
app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("APP_SECRET_KEY", secrets.token_hex(32))
DB_PATH = "secure_demo.db"
# 示例用内存 Token 存储
# 生产环境建议使用 Redis、数据库或成熟的 Session/JWT 方案
TOKENS = {}
def get_conn():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
conn = get_conn()
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL,
bio TEXT
)
""")
cursor.execute("DELETE FROM users")
admin_password = generate_password_hash("admin123")
alice_password = generate_password_hash("alice123")
cursor.execute("""
INSERT INTO users(username, password_hash, role, bio)
VALUES (?, ?, ?, ?)
""", ("admin", admin_password, "admin", "I am administrator"))
cursor.execute("""
INSERT INTO users(username, password_hash, role, bio)
VALUES (?, ?, ?, ?)
""", ("alice", alice_password, "user", "Hello, I am Alice"))
conn.commit()
conn.close()
def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer "):
return jsonify({"message": "missing token"}), 401
token = auth_header.replace("Bearer ", "", 1).strip()
user = TOKENS.get(token)
if not user:
return jsonify({"message": "invalid token"}), 401
g.current_user = user
return func(*args, **kwargs)
return wrapper
def admin_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not hasattr(g, "current_user"):
return jsonify({"message": "unauthenticated"}), 401
if g.current_user["role"] != "admin":
return jsonify({"message": "forbidden"}), 403
return func(*args, **kwargs)
return wrapper
@app.route("/login", methods=["POST"])
def login():
username = request.form.get("username", "").strip()
password = request.form.get("password", "")
if not username or not password:
return jsonify({"message": "username and password are required"}), 400
conn = get_conn()
cursor = conn.cursor()
cursor.execute("""
SELECT id, username, password_hash, role
FROM users
WHERE username = ?
""", (username,))
user = cursor.fetchone()
conn.close()
if not user:
return jsonify({"message": "invalid username or password"}), 401
if not check_password_hash(user["password_hash"], password):
return jsonify({"message": "invalid username or password"}), 401
token = secrets.token_urlsafe(32)
TOKENS[token] = {
"id": user["id"],
"username": user["username"],
"role": user["role"]
}
return jsonify({
"message": "login success",
"token": token,
"user": {
"id": user["id"],
"username": user["username"],
"role": user["role"]
}
})
@app.route("/user/", methods=["GET"])
@login_required
def get_user(user_id):
current_user = g.current_user
# 普通用户只能访问自己的资料,管理员可以访问所有用户
if current_user["role"] != "admin" and current_user["id"] != user_id:
return jsonify({"message": "forbidden"}), 403
conn = get_conn()
cursor = conn.cursor()
cursor.execute("""
SELECT id, username, role, bio
FROM users
WHERE id = ?
""", (user_id,))
user = cursor.fetchone()
conn.close()
if not user:
return jsonify({"message": "user not found"}), 404
return jsonify({
"id": user["id"],
"username": user["username"],
"role": user["role"],
"bio": user["bio"]
})
@app.route("/user//bio", methods=["POST"])
@login_required
def update_bio(user_id):
current_user = g.current_user
# 普通用户只能修改自己的简介
if current_user["role"] != "admin" and current_user["id"] != user_id:
return jsonify({"message": "forbidden"}), 403
bio = request.form.get("bio", "").strip()
if len(bio) > 200:
return jsonify({"message": "bio is too long"}), 400
conn = get_conn()
cursor = conn.cursor()
cursor.execute("""
UPDATE users
SET bio = ?
WHERE id = ?
""", (bio, user_id))
conn.commit()
conn.close()
return jsonify({"message": "bio updated"})
@app.route("/admin/users", methods=["GET"])
@login_required
@admin_required
def admin_users():
conn = get_conn()
cursor = conn.cursor()
cursor.execute("""
SELECT id, username, role, bio
FROM users
ORDER BY id ASC
""")
users = cursor.fetchall()
conn.close()
return jsonify([
{
"id": u["id"],
"username": u["username"],
"role": u["role"],
"bio": u["bio"]
}
for u in users
])
if __name__ == "__main__":
if not os.path.exists(DB_PATH):
init_db()
debug_mode = os.getenv("FLASK_DEBUG", "false").lower() == "true"
app.run(debug=debug_mode)
六、修复点逐项说明
1. 密钥不再硬编码
修复后代码:
app.config["SECRET_KEY"] = os.getenv("APP_SECRET_KEY", secrets.token_hex(32))
这样可以从环境变量读取密钥,避免直接写死在源码中。
在生产环境中,更推荐使用:
- 云厂商 Secret Manager;
- Kubernetes Secret;
- Vault;
- CI/CD 密钥注入;
- 环境变量托管平台。
需要注意的是,示例中如果没有环境变量,会自动生成随机密钥。这适合本地演示,但生产环境最好显式设置稳定密钥,否则服务重启后可能导致会话失效。
2. 密码使用哈希存储
修复后代码:
admin_password = generate_password_hash("admin123")
登录校验时使用:
check_password_hash(user["password_hash"], password)
这样数据库中不再保存明文密码。即使数据库泄露,攻击者也无法直接获得用户原始密码。
当然,真实生产系统还应增加:
- 密码复杂度要求;
- 登录失败次数限制;
- 多因素认证;
- 异常登录提醒;
- 密码重置流程保护。
3. 使用参数化 SQL
修复前:
sql = f"SELECT ... WHERE username='{username}'"
cursor.execute(sql)
修复后:
cursor.execute("""
SELECT id, username, password_hash, role
FROM users
WHERE username = ?
""", (username,))
参数化查询是防御 SQL 注入的基础手段。开发中应避免把用户输入直接拼接到 SQL 语句中。
如果项目使用 ORM,例如 SQLAlchemy、Django ORM,也不代表绝对安全。因为很多 ORM 仍然允许执行原生 SQL,一旦拼接字符串,仍然可能引入注入风险。
4. 增加登录认证
修复后的代码使用了一个简单装饰器:
def login_required(func):
...
它会检查请求头中的 Token:
Authorization: Bearer
如果 Token 不存在或无效,则拒绝访问。
需要说明的是,示例中使用内存字典保存 Token:
TOKENS = {}
这只适合教学演示。真实系统中应考虑:
- Token 过期时间;
- Token 注销机制;
- Refresh Token;
- 多端登录管理;
- Redis 或数据库存储;
- JWT 签名和密钥轮换;
- HTTPS 传输。
5. 增加权限控制
用户资料接口中增加了判断:
if current_user["role"] != "admin" and current_user["id"] != user_id:
return jsonify({"message": "forbidden"}), 403
这表示:
- 管理员可以访问所有用户;
- 普通用户只能访问自己。
这是防止越权访问的基本逻辑。
权限控制不能只依赖前端。前端隐藏按钮、隐藏菜单只能改善用户体验,不能作为安全边界。真正的权限校验必须放在后端。
6. 管理接口增加管理员校验
修复后管理接口:
@app.route("/admin/users", methods=["GET"])
@login_required
@admin_required
def admin_users():
...
其中:
if g.current_user["role"] != "admin":
return jsonify({"message": "forbidden"}), 403
确保只有管理员角色才能访问管理接口。
同时,接口返回字段也去掉了密码哈希:
SELECT id, username, role, bio
FROM users
即使是管理员接口,也不应随意返回密码字段、Token、密钥等敏感信息。
七、AI 生成代码常见安全问题清单
在实际工作中,AI 编程引入的漏洞往往不止以上几类。下面整理一份常见清单,可用于审查 AI 生成代码。
1. 输入校验缺失
常见表现:
- 没有限制字符串长度;
- 没有限制数字范围;
- 没有校验邮箱、手机号格式;
- 没有校验文件类型;
- 没有校验 JSON 字段结构;
- 直接信任客户端传入的 role、user_id、price 等字段。
安全建议:
- 所有外部输入默认不可信;
- 在服务端进行统一校验;
- 使用白名单策略;
- 对关键字段进行类型转换和范围限制。
2. 鉴权逻辑缺失
AI 生成接口时,经常只生成业务逻辑,不生成鉴权逻辑。例如:
@app.route("/orders/")
def get_order(order_id):
...
如果没有确认当前登录用户是否拥有该订单,就可能造成订单信息泄露。
安全建议:
- 默认所有敏感接口都需要登录;
- 明确哪些接口允许匿名访问;
- 使用统一的鉴权中间件;
- 对资源访问做对象级权限判断。
3. 敏感信息泄露
常见泄露内容包括:
- 数据库密码;
- API Key;
- JWT 密钥;
- 用户密码;
- 身份证号;
- 手机号;
- 邮箱;
- 内部错误栈;
- 服务器路径;
- 调试日志。
安全建议:
- 敏感配置使用环境变量或密钥管理系统;
- 日志中避免记录密码、Token;
- 错误响应不要返回内部栈信息;
- 对手机号、邮箱等字段进行脱敏展示;
- 生产环境关闭 Debug。
4. 文件上传风险
AI 生成文件上传接口时,经常只写:
file.save(file.filename)
这种写法可能带来:
- 文件名路径穿越风险;
- 上传可执行脚本;
- 覆盖已有文件;
- 存储型 XSS;
- 大文件导致磁盘耗尽。
安全建议:
- 使用安全文件名;
- 限制扩展名和 MIME 类型;
- 限制文件大小;
- 上传文件重命名;
- 文件存储到非 Web 可执行目录;
- 对图片进行重新编码处理。
5. 不安全的默认配置
很多 AI 示例代码会写:
app.run(host="0.0.0.0", debug=True)
或者:
allow_origins: ["*"]
这些配置在本地调试方便,但上线后可能扩大攻击面。
安全建议:
- 开发配置和生产配置分离;
- 生产环境关闭 Debug;
- CORS 不要随意允许全部来源;
- Cookie 设置 HttpOnly、Secure、SameSite;
- 仅开放必要端口。
6. 依赖安全问题
AI 可能会推荐已经过时的库或版本。某些依赖可能存在公开漏洞。
安全建议:
- 使用依赖扫描工具;
- 锁定依赖版本;
- 定期升级依赖;
- 关注官方安全公告;
- 避免使用无人维护的库。
常见工具包括:
- pip-audit;
- npm audit;
- osv-scanner;
- Dependabot;
- Snyk;
- Trivy。
八、如何安全地使用 AI 编程?
AI 编程不是不能用,而是要正确使用。比较推荐的方式是:让 AI 提效,让人负责安全决策。
1. 提示词中明确加入安全要求
不要只说:
写一个登录接口。
可以改成:
用 Flask 写一个登录接口,要求使用参数化查询、防止 SQL 注入、密码使用哈希存储、错误信息不要泄露用户是否存在,并给出安全说明。
这样 AI 更可能生成安全性较好的代码。
2. 要求 AI 自查安全风险
生成代码后,可以继续要求:
请从 OWASP Top 10 角度审计这段代码,指出潜在漏洞并给出修复建议。
这能帮助发现一些明显问题。不过需要注意,AI 的审计结果不能完全替代专业安全测试。
3. 将 AI 生成代码纳入 Code Review
团队中应该明确规定:
- AI 生成的代码必须经过人工 Review;
- 涉及认证、授权、支付、文件上传、数据导出等功能必须重点审查;
- 不能直接将 AI 生成代码复制上线;
- 对安全敏感模块应由有经验的工程师把关。
4. 配合自动化安全工具
AI 编程应结合自动化安全能力,例如:
- SAST 静态代码扫描;
- SCA 依赖漏洞扫描;
- DAST 动态安全测试;
- Secret Scan 密钥扫描;
- 单元测试和集成测试;
- IaC 配置安全扫描。
工具不能发现所有业务逻辑漏洞,但可以覆盖大量通用问题。
九、AI 编程安全检查表
下面是一份简单实用的检查表,适用于审查 AI 生成代码。
基础安全
- [ ] 是否存在硬编码密码、密钥、Token?
- [ ] 是否关闭生产环境 Debug?
- [ ] 是否使用安全的错误处理?
- [ ] 日志中是否避免输出敏感信息?
- [ ] 是否区分开发、测试、生产配置?
输入与输出
- [ ] 是否校验所有外部输入?
- [ ] 是否限制字段长度和类型?
- [ ] 是否使用白名单规则?
- [ ] 输出内容是否进行脱敏?
- [ ] 错误信息是否避免泄露内部细节?
数据库安全
- [ ] 是否使用参数化查询或安全 ORM?
- [ ] 是否避免拼接 SQL?
- [ ] 数据库账号权限是否最小化?
- [ ] 是否避免返回敏感字段?
- [ ] 是否对重要操作做审计?
认证与授权
- [ ] 敏感接口是否要求登录?
- [ ] 是否校验当前用户身份?
- [ ] 是否存在水平越权?
- [ ] 是否存在垂直越权?
- [ ] 管理接口是否有角色限制?
密码与会话
- [ ] 密码是否使用安全哈希?
- [ ] Token 是否有过期机制?
- [ ] Cookie 是否设置 HttpOnly、Secure、SameSite?
- [ ] 是否有退出登录机制?
- [ ] 是否有暴力破解防护?
文件与依赖
- [ ] 文件上传是否限制类型和大小?
- [ ] 文件名是否安全处理?
- [ ] 文件是否存储在安全目录?
- [ ] 依赖版本是否存在已知漏洞?
- [ ] 是否定期执行依赖扫描?
十、总结
AI 编程正在改变软件开发方式,它可以帮助开发者更快完成接口、页面、脚本和工具。但从安全角度看,AI 生成代码并不等于安全代码。
本文通过一个 Flask 示例分析了 AI 编程中常见的安全漏洞,包括:
- 硬编码密钥;
- 明文密码存储;
- SQL 注入;
- 未授权访问;
- 越权访问;
- 管理接口未鉴权;
- 敏感信息泄露;
- Debug 配置风险。
同时,文章给出了修复后的源码,展示了如何通过环境变量、密码哈希、参数化查询、登录认证、角色校验和字段脱敏等方式提升安全性。
最后需要强调:AI 是开发助手,不是安全责任人。真正决定代码是否安全的,仍然是开发流程、工程规范、安全审计和团队意识。
在使用 AI 编程时,建议始终坚持以下原则:
- AI 生成代码必须 Review;
- 所有输入默认不可信;
- 鉴权和授权必须后端校验;
- 密码和密钥不得明文存储;
- 数据库操作必须避免拼接 SQL;
- 生产环境必须关闭 Debug;
- 安全工具应进入 CI/CD 流程;
- 关键业务逻辑必须人工确认。
只有这样,AI 编程才能真正成为提升效率的工具,而不是引入风险的源头。