npx skills add https://github.com/samber/cc-skills-golang --skill golang-safety角色设定: 你是一位防御性的 Go 工程师。你将每一个未经测试的关于 nil、容量和数值范围的假设都视为一个潜在的、等待发生的崩溃。
防止程序员错误——在正常(非对抗性)代码中的 bug、panic 和静默数据损坏。安全处理攻击者;安全性处理我们自己。
any —— 编译器会捕获类型不匹配,而不是在运行时 panicnil —— 类型描述符使其非 nilappend 可能重用底层数组 —— 如果容量允许,两个切片会共享内存,从而静默地相互破坏数据defer 在函数退出时运行,而非循环迭代时 —— 将循环体提取到函数中int64 转 会直接回绕,不报错广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
int32math/bigsync.Once 进行惰性初始化 —— 即使在并发下也能保证仅执行一次与 nil 相关的 panic 是 Go 中最常见的崩溃原因。
接口存储(类型,值)。只有当两者都为 nil 时,接口才等于 nil。返回一个有类型的 nil 指针会设置类型描述符,使其变为非 nil:
// ✗ 危险 — interface{type: *MyHandler, value: nil} 不等于 nil
func getHandler() http.Handler {
var h *MyHandler // nil 指针
if !enabled {
return h // interface{type: *MyHandler, value: nil} != nil
}
return h
}
// ✓ 良好 — 显式返回 nil
func getHandler() http.Handler {
if !enabled {
return nil // interface{type: nil, value: nil} == nil
}
return &MyHandler{}
}
| 类型 | 从 nil 读取 | 写入 nil | nil 的 Len/Cap | 遍历 nil |
|---|---|---|---|---|
| Map | 零值 | panic | 0 | 0 次迭代 |
| Slice | panic (索引) | panic (索引) | 0 | 0 次迭代 |
| Channel | 永远阻塞 | 永远阻塞 | 0 | 永远阻塞 |
// ✗ 错误 — nil 映射在写入时 panic
var m map[string]int
m["key"] = 1
// ✓ 良好 — 初始化或在方法中惰性初始化
m := make(map[string]int)
func (r *Registry) Add(name string, val int) {
if r.items == nil { r.items = make(map[string]int) }
r.items[name] = val
}
关于 nil 接收器、泛型中的 nil 以及 nil 接口性能,请参阅 Nil 安全性深度解析。
如果容量允许,append 会重用底层数组。两个切片随后共享内存:
// ✗ 危险 — a 和 b 共享底层数组
a := make([]int, 3, 5)
b := append(a, 4)
b[0] = 99 // 同时修改了 a[0]
// ✓ 良好 — 完整切片表达式强制新分配
b := append(a[:len(a):len(a)], 4)
映射不得被并发访问 —— → 关于同步原语,请参阅 samber/cc-skills-golang@golang-concurrency。
关于遍历陷阱、子切片内存保留以及 slices.Clone/maps.Clone,请参阅 切片和映射深度解析。
// ✗ 错误 — 如果 val > math.MaxInt32,会静默回绕(30亿变成 -12.9亿)
var val int64 = 3_000_000_000
i32 := int32(val) // -1294967296 (静默回绕)
// ✓ 良好 — 转换前检查
if val > math.MaxInt32 || val < math.MinInt32 {
return fmt.Errorf("value %d overflows int32", val)
}
i32 := int32(val)
// ✗ 错误 — 浮点数运算不精确
0.1+0.2 == 0.3 // false
// ✓ 良好 — 使用 epsilon 比较
const epsilon = 1e-9
math.Abs((0.1+0.2)-0.3) < epsilon // true
整数除以零会 panic。浮点数除以零会产生 +Inf、-Inf 或 NaN。
func avg(total, count int) (int, error) {
if count == 0 {
return 0, errors.New("division by zero")
}
return total / count, nil
}
关于整数溢出作为安全漏洞,请参阅 samber/cc-skills-golang@golang-security 技能部分。
defer 在函数退出时运行,而不是在循环迭代时。资源会累积直到函数返回:
// ✗ 错误 — 所有文件保持打开状态直到函数返回
for _, path := range paths {
f, _ := os.Open(path)
defer f.Close() // 延迟到函数退出时才执行
process(f)
}
// ✓ 良好 — 提取到函数中,使 defer 在每次迭代时运行
for _, path := range paths {
if err := processOne(path); err != nil { return err }
}
func processOne(path string) error {
f, err := os.Open(path)
if err != nil { return err }
defer f.Close()
return process(f)
}
→ 关于 goroutine 生命周期和泄漏预防,请参阅 samber/cc-skills-golang@golang-concurrency。
导出函数返回切片/映射时应该返回防御性副本。
// ✗ 错误 — 导出的切片字段,任何人都可以修改
type Config struct {
Hosts []string
}
// ✓ 良好 — 未导出字段,通过访问器返回副本
type Config struct {
hosts []string
}
func (c *Config) Hosts() []string {
return slices.Clone(c.hosts)
}
设计类型时,应使 var x MyType 是安全的 —— 防止“忘记初始化”的 bug:
var mu sync.Mutex // ✓ 零值可用
var buf bytes.Buffer // ✓ 零值可用
// ✗ 错误 — nil 映射在写入时 panic
type Cache struct { data map[string]any }
type DB struct {
once sync.Once
conn *sql.DB
}
func (db *DB) connection() *sql.DB {
db.once.Do(func() {
db.conn, _ = sql.Open("postgres", connStr)
})
return db.conn
}
→ 关于为什么应避免使用 init() 而选择显式构造函数,请参阅 samber/cc-skills-golang@golang-design-patterns。
许多安全陷阱可以通过 linter 自动捕获:errcheck、forcetypeassert、nilerr、govet、staticcheck。关于配置和使用,请参阅 samber/cc-skills-golang@golang-linter 技能。
samber/cc-skills-golang@golang-concurrency 技能samber/cc-skills-golang@golang-data-structures 技能samber/cc-skills-golang@golang-error-handling 技能samber/cc-skills-golang@golang-security 技能samber/cc-skills-golang@golang-troubleshooting 技能| 错误 | 修复方法 |
|---|---|
裸类型断言 v := x.(T) | 类型不匹配时 panic,导致程序崩溃。使用 v, ok := x.(T) 来优雅处理 |
| 在接口函数中返回有类型的 nil | 接口持有 (类型, nil),这 != nil。对于 nil 情况,返回无类型的 nil |
| 向 nil 映射写入 | nil 映射没有底层存储 —— 写入会 panic。使用 make(map[K]V) 初始化或惰性初始化 |
假设 append 总是复制 | 如果容量允许,两个切片共享底层数组。使用 s[:len(s):len(s)] 强制复制 |
循环中的 defer | defer 在函数退出时运行,而非循环迭代时 —— 资源会累积。将循环体提取到单独的函数中 |
int64 转 int32 时不进行边界检查 | 值会静默回绕(30亿 → -12.9亿)。先检查是否在 math.MaxInt32/math.MinInt32 范围内 |
使用 == 比较浮点数 | IEEE 754 表示不精确(0.1+0.2 != 0.3)。使用 math.Abs(a-b) < epsilon |
| 整数除法前不检查零 | 整数除以零会 panic。除法前用 if divisor == 0 进行保护 |
| 返回内部切片/映射引用 | 调用者可以通过共享的底层数组修改你结构体的内部数据。返回防御性副本 |
多个 init() 带有顺序依赖假设 | 跨文件的 init() 执行顺序未指定。→ 参阅 samber/cc-skills-golang@golang-design-patterns —— 使用显式构造函数 |
| 在 nil 通道上永远阻塞 | nil 通道在发送和接收时都会阻塞。使用前务必初始化 |
每周安装量
94
代码仓库
GitHub Stars
184
首次出现
3 天前
安全审计
安装于
opencode77
codex76
gemini-cli76
kimi-cli75
github-copilot75
cursor75
Persona: You are a defensive Go engineer. You treat every untested assumption about nil, capacity, and numeric range as a latent crash waiting to happen.
Prevents programmer mistakes — bugs, panics, and silent data corruption in normal (non-adversarial) code. Security handles attackers; safety handles ourselves.
any when the type set is known — compiler catches mismatches instead of runtime panics== nil — the type descriptor makes it non-nilappend may reuse the backing array — both slices share memory if capacity allows, silently corrupting each otherdefer runs at function exit, not loop iteration — extract loop body to a functionint64 to int32 wraps without errormath/bigsync.Once for lazy init — guarantees exactly-once even under concurrencyNil-related panics are the most common crash in Go.
Interfaces store (type, value). An interface is nil only when both are nil. Returning a typed nil pointer sets the type descriptor, making it non-nil:
// ✗ Dangerous — interface{type: *MyHandler, value: nil} is not == nil
func getHandler() http.Handler {
var h *MyHandler // nil pointer
if !enabled {
return h // interface{type: *MyHandler, value: nil} != nil
}
return h
}
// ✓ Good — return nil explicitly
func getHandler() http.Handler {
if !enabled {
return nil // interface{type: nil, value: nil} == nil
}
return &MyHandler{}
}
| Type | Read from nil | Write to nil | Len/Cap of nil | Range over nil |
|---|---|---|---|---|
| Map | Zero value | panic | 0 | 0 iterations |
| Slice | panic (index) | panic (index) | 0 | 0 iterations |
| Channel | Blocks forever | Blocks forever | 0 | Blocks forever |
// ✗ Bad — nil map panics on write
var m map[string]int
m["key"] = 1
// ✓ Good — initialize or lazy-init in methods
m := make(map[string]int)
func (r *Registry) Add(name string, val int) {
if r.items == nil { r.items = make(map[string]int) }
r.items[name] = val
}
See Nil Safety Deep Dive for nil receivers, nil in generics, and nil interface performance.
append reuses the backing array if capacity allows. Both slices then share memory:
// ✗ Dangerous — a and b share backing array
a := make([]int, 3, 5)
b := append(a, 4)
b[0] = 99 // also modifies a[0]
// ✓ Good — full slice expression forces new allocation
b := append(a[:len(a):len(a)], 4)
Maps MUST NOT be accessed concurrently — → see samber/cc-skills-golang@golang-concurrency for sync primitives.
See Slice and Map Deep Dive for range pitfalls, subslice memory retention, and slices.Clone/maps.Clone.
// ✗ Bad — silently wraps around if val > math.MaxInt32 (3B becomes -1.29B)
var val int64 = 3_000_000_000
i32 := int32(val) // -1294967296 (silent wraparound)
// ✓ Good — check before converting
if val > math.MaxInt32 || val < math.MinInt32 {
return fmt.Errorf("value %d overflows int32", val)
}
i32 := int32(val)
// ✗ Bad — floating point arithmetic is not exact
0.1+0.2 == 0.3 // false
// ✓ Good — use epsilon comparison
const epsilon = 1e-9
math.Abs((0.1+0.2)-0.3) < epsilon // true
Integer division by zero panics. Float division by zero produces +Inf, -Inf, or NaN.
func avg(total, count int) (int, error) {
if count == 0 {
return 0, errors.New("division by zero")
}
return total / count, nil
}
For integer overflow as a security vulnerability, see the samber/cc-skills-golang@golang-security skill section.
defer runs at function exit, not loop iteration. Resources accumulate until the function returns:
// ✗ Bad — all files stay open until function returns
for _, path := range paths {
f, _ := os.Open(path)
defer f.Close() // deferred until function exits
process(f)
}
// ✓ Good — extract to function so defer runs per iteration
for _, path := range paths {
if err := processOne(path); err != nil { return err }
}
func processOne(path string) error {
f, err := os.Open(path)
if err != nil { return err }
defer f.Close()
return process(f)
}
→ See samber/cc-skills-golang@golang-concurrency for goroutine lifecycle and leak prevention.
Exported functions returning slices/maps SHOULD return defensive copies.
// ✗ Bad — exported slice field, anyone can mutate
type Config struct {
Hosts []string
}
// ✓ Good — unexported field with accessor returning a copy
type Config struct {
hosts []string
}
func (c *Config) Hosts() []string {
return slices.Clone(c.hosts)
}
Design types so var x MyType is safe — prevents "forgot to initialize" bugs:
var mu sync.Mutex // ✓ usable at zero value
var buf bytes.Buffer // ✓ usable at zero value
// ✗ Bad — nil map panics on write
type Cache struct { data map[string]any }
type DB struct {
once sync.Once
conn *sql.DB
}
func (db *DB) connection() *sql.DB {
db.once.Do(func() {
db.conn, _ = sql.Open("postgres", connStr)
})
return db.conn
}
→ See samber/cc-skills-golang@golang-design-patterns for why init() should be avoided in favor of explicit constructors.
Many safety pitfalls are caught automatically by linters: errcheck, forcetypeassert, nilerr, govet, staticcheck. See the samber/cc-skills-golang@golang-linter skill for configuration and usage.
samber/cc-skills-golang@golang-concurrency skill for concurrent access patterns and sync primitivessamber/cc-skills-golang@golang-data-structures skill for slice/map internals, capacity growth, and container/ packagessamber/cc-skills-golang@golang-error-handling skill for nil error interface trapsamber/cc-skills-golang@golang-security skill for security-relevant safety issues (memory safety, integer overflow)samber/cc-skills-golang@golang-troubleshooting skill for debugging panics and race conditions| Mistake | Fix |
|---|---|
Bare type assertion v := x.(T) | Panics on type mismatch, crashing the program. Use v, ok := x.(T) to handle gracefully |
| Returning typed nil in interface function | Interface holds (type, nil) which is != nil. Return untyped nil for the nil case |
| Writing to a nil map | Nil maps have no backing storage — write panics. Initialize with make(map[K]V) or lazy-init |
Assuming append always copies | If capacity allows, both slices share the backing array. Use s[:len(s):len(s)] to force a copy |
| in a loop |
Weekly Installs
94
Repository
GitHub Stars
184
First Seen
3 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode77
codex76
gemini-cli76
kimi-cli75
github-copilot75
cursor75
Azure PostgreSQL 无密码身份验证配置指南:Entra ID 迁移与访问管理
34,800 周安装
deferdefer runs at function exit, not loop iteration — resources accumulate. Extract body to a separate function |
int64 to int32 without bounds check | Values wrap silently (3B → -1.29B). Check against math.MaxInt32/math.MinInt32 first |
Comparing floats with == | IEEE 754 representation is not exact (0.1+0.2 != 0.3). Use math.Abs(a-b) < epsilon |
| Integer division without zero check | Integer division by zero panics. Guard with if divisor == 0 before dividing |
| Returning internal slice/map reference | Callers can mutate your struct's internals through the shared backing array. Return a defensive copy |
Multiple init() with ordering assumptions | init() execution order across files is unspecified. → See samber/cc-skills-golang@golang-design-patterns — use explicit constructors |
| Blocking forever on nil channel | Nil channels block on both send and receive. Always initialize before use |