en un clic
input-validation
处理外部输入时使用。在边界统一校验,防脏数据与注入。
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
处理外部输入时使用。在边界统一校验,防脏数据与注入。
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Basé sur la classification professionnelle SOC
做 Cocos Creator 多机型/多分辨率适配时使用。Canvas、Widget、安全区。
Cocos Creator 用 AssetBundle 做分包/远程资源时使用。加载、释放、依赖、缓存。
优化 Cocos Creator 渲染性能时使用。合批、图集、动静分离、Label。
给 Cocos Creator 原生包做热更新时使用。version manifest、增量、校验、回滚。
写 Cocos Creator 动效/动画时使用。tween、Animation、Spine、性能与清理。
做 Cocos Creator 大量条目列表时使用。虚拟列表、节点复用。
| name | input-validation |
| description | 处理外部输入时使用。在边界统一校验,防脏数据与注入。 |
| category | security |
| tags | ["校验","输入","边界"] |
规则: 在 API 入口(controller/route handler)对所有外部输入做统一校验,覆盖类型是否正确、数值是否在合法范围内、字符串格式是否符合预期、必填字段是否存在;校验通过后的数据才传入业务逻辑层。
为什么: AI 生成 API 接口时最常见的模式是直接把 request.json 传进业务函数,让业务函数自己处理字段缺失或类型错误。这导致两个问题:一是 AttributeError/KeyError 在内层崩溃,堆栈信息可能暴露内部结构;二是相同的校验逻辑散落在各处,维护困难,容易漏掉。AI 还经常只校验"快乐路径",完全忽略 age=-1、quantity=99999999、email=""等边界值。
怎么做:
from pydantic import BaseModel, Field, field_validator
from typing import Literal
class CreateOrderRequest(BaseModel):
product_id: int = Field(gt=0) # ✅ 类型 + 范围
quantity: int = Field(ge=1, le=100) # ✅ 1~100 之间
email: str = Field(pattern=r'^[\w.+-]+@[\w-]+\.[a-z]{2,}$') # ✅ 格式
channel: Literal["web", "app", "api"] # ✅ 枚举白名单
@app.post("/orders")
def create_order(body: CreateOrderRequest): # ✅ 入口即校验
return order_service.create(body) # 业务层拿到的已是合法数据
规则: 对枚举值用白名单(只接受已知合法值),不用黑名单(拒绝已知危险值);文件名和路径做规范化后校验,防止路径穿越(../../../etc/passwd);用户可控的文件名只允许 [a-zA-Z0-9._-]。
为什么: AI 实现文件操作时惯用黑名单:if ".." in filename: reject。这类黑名单极容易被绕过——URL 编码 %2e%2e、双重编码 %252e%252e、Unicode 等价字符都能轻易规避。路径穿越漏洞至今仍是 OWASP Top 10 常见漏洞之一,很大程度上因为开发者(包括 AI)低估了绕过黑名单的攻击面。
怎么做:
import os
import re
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".pdf"}
UPLOAD_BASE = "/var/uploads"
def safe_filename(user_filename: str) -> str:
# ✅ 白名单字符集:只保留安全字符
name = re.sub(r'[^a-zA-Z0-9._-]', '_', os.path.basename(user_filename))
ext = os.path.splitext(name)[1].lower()
# ✅ 扩展名白名单
if ext not in ALLOWED_EXTENSIONS:
raise ValueError(f"不支持的文件类型: {ext}")
return name
def safe_path(base_dir: str, user_path: str) -> str:
# ✅ 规范化后确认仍在 base_dir 内,防路径穿越
full = os.path.realpath(os.path.join(base_dir, user_path))
if not full.startswith(os.path.realpath(base_dir) + os.sep):
raise ValueError("路径穿越攻击")
return full
规则: 校验阶段只做是/否判断,不合法就拒绝并返回错误;净化(escaping/sanitization)是针对通过校验的合法值,根据输出目标做的格式适配,是两个独立步骤,不能互相替代。
为什么: AI 经常把"净化"当作万能解决方案:sanitize(input) 之后就不管三七二十一地拿去用。这有两个问题:①净化函数因目标环境不同而不同(HTML、SQL、Shell、JSON 各有各的转义规则),用错了等于没转义;②净化改变了数据,可能让原本合法的业务值被意外篡改(用户名里的 < 被转成 < 存进数据库,展示时又被双重转义)。校验和净化分开能让每一步的职责和错误来源都清晰。
怎么做:
from markupsafe import escape as html_escape
def process_comment(raw_text: str) -> dict:
# 第一步:校验(是否合法)
if not raw_text or not raw_text.strip():
raise ValueError("评论内容不能为空")
if len(raw_text) > 2000:
raise ValueError("评论不得超过 2000 字")
# 第二步:规范化(去掉首尾空白,统一换行符)
normalized = raw_text.strip().replace("\r\n", "\n")
# 存库:存原始净化后的文本(不 HTML 转义,数据库不是 HTML 上下文)
saved_text = normalized
# 返回前端:针对 HTML 上下文做转义(渲染时才转义,不提前)
return {
"text": saved_text,
"html": str(html_escape(saved_text)).replace("\n", "<br>"),
}
规则: 前端的输入校验只是用户体验优化(即时反馈),不是安全控制;后端 API 对每个接收到的参数必须独立校验,不能假设"前端已经校验过了"。
为什么: AI 生成全栈应用时,有时只在前端加 required、maxlength、pattern 等 HTML 属性,然后后端直接信任前端送来的数据。任何人用 curl、Postman 或浏览器开发者工具都能绕过前端校验,直接发送任意数据到后端。这类漏洞在 AI 代码审查中极为常见,因为 AI 看到前端"已有校验"就不再在后端重复。
怎么做:
// 前端:用户体验,即时反馈(不是安全控制)
<input type="number" min="1" max="100" required />
// 后端:安全边界,独立校验(不依赖前端传来的值)
app.post("/api/cart/add", async (req, res) => {
const schema = z.object({
productId: z.number().int().positive(),
quantity: z.number().int().min(1).max(100), // ✅ 后端独立校验
});
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.format() });
}
// 使用 result.data,不直接用 req.body
await cartService.add(req.user.id, result.data.productId, result.data.quantity);
res.json({ ok: true });
});
规则: 校验失败时返回结构化错误,告知调用方哪个字段出了什么问题("field": "email", "message": "格式不合法");但不返回数据库表结构、SQL 语句、文件路径、堆栈信息等内部细节。
为什么: AI 生成的错误处理有两个极端:一是直接返回 Python 异常信息(含 SQL 语句、文件路径),帮助攻击者了解系统内部;二是统一返回 "请求失败",开发者无法调试,合法调用方也不知道如何修正请求。恰当的错误信息是安全性与可用性的平衡点。
怎么做:
# 反例:内部细节泄露
except Exception as e:
return jsonify({"error": str(e)}), 400
# 可能输出:UNIQUE constraint failed: users.email (table structure leaked)
# 正例:结构化错误,不暴露内部
from pydantic import ValidationError
@app.errorhandler(ValidationError)
def handle_validation_error(e: ValidationError):
errors = [
{"field": ".".join(str(loc) for loc in err["loc"]), "message": err["msg"]}
for err in e.errors()
]
return jsonify({"code": "VALIDATION_ERROR", "errors": errors}), 422
# 输出:{"code": "VALIDATION_ERROR", "errors": [{"field": "email", "message": "value is not a valid email address"}]}
{"code": "INTERNAL_ERROR", "message": "服务暂时不可用"},详细堆栈只写进服务端日志,不下发给调用方。# 反例
@app.route("/upload", methods=["POST"])
def upload():
filename = request.form["filename"] # ❌ 未校验,可含路径穿越
content = request.files["file"].read()
# ❌ 直接拼接用户提供的文件名
path = f"/var/uploads/{filename}"
with open(path, "wb") as f:
f.write(content)
return jsonify({"path": path}) # ❌ 返回服务器内部路径
# 正例
import os, re
from pathlib import Path
from flask import request, jsonify
UPLOAD_DIR = Path("/var/uploads").resolve()
ALLOWED_EXT = {".jpg", ".png", ".pdf"}
MAX_SIZE = 10 * 1024 * 1024 # 10 MB
@app.route("/upload", methods=["POST"])
@login_required
def upload():
if "file" not in request.files:
return jsonify({"code": "MISSING_FIELD", "field": "file"}), 422
f = request.files["file"]
raw_name = f.filename or ""
# ✅ 白名单字符集清洁文件名
safe_name = re.sub(r'[^a-zA-Z0-9._-]', '_', os.path.basename(raw_name))
ext = Path(safe_name).suffix.lower()
if ext not in ALLOWED_EXT:
return jsonify({"code": "INVALID_FILE_TYPE",
"message": f"只支持 {', '.join(ALLOWED_EXT)}"}), 422
content = f.read()
if len(content) > MAX_SIZE:
return jsonify({"code": "FILE_TOO_LARGE",
"message": "文件不得超过 10 MB"}), 422
# ✅ 路径穿越防护:规范化后确认仍在 UPLOAD_DIR 内
dest = (UPLOAD_DIR / safe_name).resolve()
if not str(dest).startswith(str(UPLOAD_DIR) + os.sep):
return jsonify({"code": "INVALID_PATH"}), 422
dest.write_bytes(content)
return jsonify({"id": safe_name}), 201 # ✅ 不返回服务器内部路径