关于 security-defensive-programming
面向开发者的编码技能,指导在处理外部输入、错误、访问控制和敏感数据时采用防御式编程,并提供安全检查清单。
主要功能
- 01在系统边界验证输入
- 02使用安全 API 防止注入
- 03处理错误且不泄露内部信息
- 04执行身份验证与授权检查
- 05保护密钥和敏感数据
- 06使用安全清单检查代码
适用场景
- 01实现接收不可信输入的 API
- 02为受保护操作添加授权
- 03处理数据库或网络调用失败
- 04检查代码变更中的常见安全问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ xip-online-applications/ skills --skill security-defensive-programming安装后在 Claude Code 中输入/security-defensive-programming确认已加载。
详细介绍
security-defensive-programming
编写能够预见误用、无效输入和意外情况的代码——安全地失败,而不是悄无声息地破坏状态或暴露漏洞。
适用场景
在以下情况使用此技能:
- 编写或修改任何接受外部输入的函数(HTTP 请求、CLI 参数、文件内容、环境变量、数据库结果、消息队列)
- 创建 API、控制器、服务或数据处理逻辑
- 处理身份验证、授权或会话管理
- 使用文件系统、数据库或网络调用
- 构造包含动态值的查询、命令或输出
- 用户要求实现任何用于处理来自不可信或半可信来源数据的功能
核心概念
防御式编程假定所有输入在证明有效之前都具有恶意,且任何操作都可能失败。目标不是过度设计,而是在适当的边界应用一致且精简的防护措施,从而避免整类漏洞。
核心原则:
- 在边界处验证,内部可信。 在入口处(控制器、处理程序、公共 API 方法)验证输入。通过边界检查后,内部函数可以假定数据已通过验证。
- 快速、明确地失败。 立即拒绝无效状态并给出清晰的错误,而不是让损坏的数据在系统中继续传播。
- 最小权限。 仅授予操作所需的最低访问权限、范围或能力。
- 纵深防御。 不要依赖单一防护层。结合输入验证、参数化查询、输出编码和访问检查。
指南
1. 输入验证
对于每个接受外部输入的函数:
- 定义预期的数据形态。 在处理前明确类型、格式、长度、范围和允许的字符。
- 拒绝任何不符合要求的内容。 使用允许列表方法——定义哪些内容有效,而不是尝试将无效内容列入黑名单。
- 尽早验证。 在系统边界处(控制器、API 处理程序、CLI 解析器)执行验证,避免数据进入业务逻辑后才验证。
- 必要时进行清理。 如果输入必须传递到特殊字符具有特定含义的上下文(HTML、SQL、shell、正则表达式),请应用适合该上下文的编码或转义——更好的做法是使用参数化 API。
- 除非明确为可选字段,否则将缺失视为无效。 如果字段是必填项,就拒绝缺失的情况——绝不要静默地将其默认设置为不安全的值。
决策树:
- 如果语言或框架提供了架构/验证库(例如 Zod、Joi、class-validator、Pydantic、JSON Schema)→ 使用该库。
- 如果没有可用的库 → 在函数入口编写明确的守卫子句。
2. 输出编码与注入防护
绝不要通过字符串拼接用户提供的数据来构造可执行字符串(SQL、HTML、shell 命令、LDAP 查询、日志条目)。
- SQL → 始终使用参数化查询或预处理语句。绝不要插入变量值。
- HTML → 使用启用了自动转义的框架模板引擎。如果必须渲染原始 HTML,请使用经过充分测试的净化库。
- Shell 命令 → 避免使用包含用户输入的方式启动 shell 进程。如果无法避免,请使用基于数组的 API(例如,使用
execFile而非exec,使用subprocess.run([...])而非subprocess.run(shell=True))。 - 日志记录 → 在将用户输入写入日志前进行清理或编码,以防止日志注入和伪造。
- URL 和重定向 → 根据允许列表验证重定向目标的域名或路径。绝不要重定向到未经验证的用户提供的 URL。
3. 错误处理与故障安全
- 捕获特定错误。 不要编写不带至少一条日志记录或重新抛出操作的裸
catch或except代码块。只捕获预期的故障模式。 - 不要暴露内部信息。 返回给用户的错误消息不得泄露堆栈跟踪、文件路径、数据库架构或内部状态。在服务端记录详细信息;在客户端返回通用消息。
- 默认拒绝访问。 如果安全检查无法确定是否允许访问(例如,无法连接到身份验证服务),则默认拒绝访问。
- 清理资源。 使用 try-finally、
using/with代码块或 RAII 模式,确保在所有代码路径中释放文件句柄、连接和锁。 - 避免吞掉错误。 如果捕获异常,必须有意义地处理、重新抛出或记录该异常。绝不允许空的 catch 代码块。
4. 身份验证与授权
- 绝不要自行实现加密或身份验证。 使用成熟的库和平台功能。
- 对每个受保护的操作都进行授权检查。 不要只依赖 UI 层的隐藏操作;必须在服务端强制执行访问规则。
- 对机密信息使用恒定时间比较。 比较令牌、哈希或 API 密钥时,必须使用防时序攻击的比较函数。
- 妥善使会话和令牌失效。 用户登出或权限变更时,确保旧令牌无法再次使用。
5. 机密信息与敏感数据处理
- 绝不要硬编码机密信息。 API 密钥、密码、令牌和证书必须来自环境变量、机密管理器或加密配置,绝不能来自源代码。
- 绝不要记录机密信息。 记录请求/响应对象前,先移除或遮盖敏感字段(密码、令牌、信用卡号、个人身份信息)。
- 尽量减少内存中的敏感数据。 不要让密码以明文形式保存在变量中超过必要时间。在语言允许的情况下,使用后清除缓冲区。
- 所有外部通信都使用 HTTPS/TLS。 绝不要通过未加密的通道发送敏感数据。
6. Null/undefined 安全性
- 访问属性或调用方法前,先检查是否为 null/undefined——除非语言的类型系统能够明确保证值非空。
- 使用类型系统表达可选性。 优先使用
Optional<T>、可空类型或联合类型,而不是单靠运行时检查。 - 返回有意义的默认值或错误值,不要在调用方无法处理缺失值时返回 null。
7. 并发与竞态条件
- 识别共享的可变状态。 如果两个代码路径可能并发读写同一资源,请使用互斥锁、事务或原子操作等方式加以保护。
- 避免检查时与使用时(TOCTOU)漏洞。 不要将权限检查与受其保护的操作分开;尽可能将二者原子化处理。
- 对可能多次触发的操作使用幂等模式(例如重试、消息重新投递)。
8. 依赖项与供应链意识
- 锁定依赖项版本。 使用锁文件,并为生产依赖项指定精确版本。
- 添加依赖项前,检查是否存在已知漏洞(使用
npm audit、pip-audit、trivy或 Dependabot 等工具)。 - 尽量缩小依赖范围。 优先选择维护良好、使用广泛的库。不要为简单功能添加依赖项。
9. 自我验证清单
编写或修改代码后,请确认:
- 所有外部输入在使用前都经过验证。
- 未使用未经净化的用户数据为 SQL、HTML、shell 或日志上下文拼接动态字符串。
- 错误均得到显式处理,且不会泄露内部细节。
- 机密信息未被硬编码或记录。
- 受保护操作的授权在服务端得到强制执行。
- 可空值在访问前均经过检查。
- 所有代码路径中的资源(连接、句柄)均已清理。
- 未在没有明确必要性和漏洞检查的情况下引入新依赖项。
应避免的反模式
- “信任前端。” 切勿假设客户端验证就足够了——务必在服务端重新验证。
- “这只是内部服务。” 内部服务也会遭到入侵。无论网络位置如何,都应遵循同样的防御原则。
- “以后再加安全措施。” 事后补上的安全措施很脆弱。从一开始就将安全纳入设计。
- “把坏东西列入黑名单。” 黑名单总是不完整。使用允许列表来定义接受的内容。
- “捕获所有异常,然后一概忽略。” 吞掉异常会掩盖错误和安全故障。处理或传递每个错误。
- “在我的机器上能运行。” 环境差异会暴露各种假设。验证配置;缺少必需值时,应立即报错退出。
评论
支持 Markdown