victorzhuk/skills

z-go-safety

Defensive Go against panics and silent data corruption — nil panics, append aliasing, map concurrent-write panics, float comparison, numeric overflow, defer-in-loop leaks.

Zobacz źródło

Jak używać

  1. Skopiuj polecenie instalacji i sprawdź źródła przed instalacją we własnym środowisku agenta.
  2. W oryginale sprawdź wymagane narzędzia, dane dostępowe i obsługiwane środowiska.
  3. Opisz dane wejściowe i oczekiwany rezultat; porównaj wynik ze źródłami.
npx skills add https://github.com/victorzhuk/skills --skill z-go-safety

Przed rozpoczęciem

  • Instalacja nie obejmuje subskrypcji modelu, limitu API ani uprawnień do plików.
  • Gwiazdki i instalacje pokazują aktywność, a nie niezależny test jakości lub bezpieczeństwa.
Oryginalny dokument Skill

Treść z repozytorium z zachowaniem nagłówków, przykładów, kodu, tabel, linków i obrazów.

Go Safety

Quick Reference

RiskFix
Typed nil in interfaceReturn untyped nil, never a typed nil pointer
Nil map writemake or lazy-init in the method
append aliasingCap-limit before growing; clone for independent elements; both are shallow
Bare type assertionAlways comma-ok: v, ok := x.(T)
defer in loopExtract body to a function
Integer narrowingBounds-check against math.MaxInt32 first
Float comparisonchoose domain-specific absolute/relative tolerances; handle NaN/infinity explicitly
Iota enumPlain iota from 0 with a real state at zero (default); iota + 1 only if zero-as-invalid genuinely matters
Mutable globalInject func() time.Time
Crypto keysrand.Read() + hex.EncodeToString (or rand.Int(rand.Reader, ...)); rand.Text() (Go 1.24+) is a shorthand alt

Nil Interface Trap

A typed nil pointer sets the interface type descriptor — the interface is not nil:

go
// bad: returns non-nil interface wrapping a nil *Handler
func getHandler() http.Handler { var h *Handler; return h }

// good
func getHandler() http.Handler {
    if !enabled { return nil }
    return &Handler{}
}

Lazy-init maps to keep zero value usable:

go
func (r *Registry) Add(name string, v int) {
    if r.items == nil { r.items = make(map[string]int) }
    r.items[name] = v
}

Append Aliasing

append reuses backing array when cap > len — both slices share memory:

go
b := append(a, 4)        // bad: b[0] = 99 also mutates a[0]
b := append(a[:len(a):len(a)], 4) // good: forces new allocation

Exported getters return copies: func (c *Config) Hosts() []string { return slices.Clone(c.hosts) }

A full slice expression (or slices.Clip) limits capacity; it does not copy existing elements or release the backing array. Appending at least one element to a slice with len == cap obtains new backing storage. Direct writes before that still affect aliases.

copy, slices.Clone, and maps.Clone are shallow: nested slices, maps, and pointers still share data. At an API boundary, copy the mutable depth the contract promises to isolate, or document immutable sharing/ownership transfer. Sending the value through a channel does not change this; see [[z-go-concurrency]].

Range and map mutation

  • for _, v := range s copies each element into v; &v is not &s[i], even with Go 1.22 loop semantics. Use indices when mutating elements or retaining their addresses.
  • Slice range saves the initial slice header and length. Appending does not extend that iteration; writes to shared backing storage can affect later values.
  • Map iteration order is unspecified. Deleting an unreached entry skips it; an entry inserted during iteration may be visited or skipped. Sort keys when order matters; use a separate pass when visiting every new entry matters.
  • Concurrent map access involving a write requires synchronization. Runtime detection is not a safety guarantee; see [[z-go-concurrency]].

Type Assertions

go
// bad — panics on mismatch
s := x.(string)

// good
s, ok := x.(string)
if !ok { ... }

Defer in Loops

defer runs at function exit, not loop iteration — resources accumulate. Extract body to a function:

go
// bad
for _, path := range paths {
    f, _ := os.Open(path)
    defer f.Close() // leaks until function returns
}

// good
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)
}

Deferred call arguments and method receivers are evaluated when registering the defer. A closure instead reads captured variables when it runs; choose deliberately for cleanup targets, named results, and elapsed-time measurements. In defer record(time.Since(start)), the duration is computed immediately; defer a closure to measure at return. Defers run in reverse registration order.


Numeric Conversions

go
// bad — silently wraps: 3_000_000_000 becomes -1294967296
i32 := int32(val)

// good
if val > math.MaxInt32 || val < math.MinInt32 {
    return fmt.Errorf("value %d overflows int32", val)
}

Float comparisons need scale-appropriate absolute/relative tolerances; no universal epsilon works for every domain. Decide how NaN, infinity and exact representable values should behave. Guard division by zero where the domain forbids it.


API Boundary Checklist

  1. Establish ownership before storing incoming slices/maps; clone mutable data when callers retain access
  2. Return copies when promising isolation; shallow clones do not isolate nested mutable values
  3. Compile-time interface check: var _ I = (*T)(nil)
  4. defer cleanup immediately after resource open
  5. iota enums start at 0 with a real state there; reserve iota + 1 for when zero-as-invalid genuinely matters
  6. Inject time.Now as func() time.Time; never call it directly in logic
  7. crypto/rand for keys, never math/rand

Zero-Value & Initialization

Design zero values deliberately. Initialize fallible dependencies explicitly and return errors. If an existing API uses sync.Once, store both the value and initialization error; sync.Once will not retry a failed call. Do not discard sql.Open errors or assume opening validates connectivity; use a bounded PingContext when startup readiness requires it.

Enums — plain iota from 0, with a real state at zero (observed default): const ( Add Operation = iota; Subtract; Multiply ). Use iota + 1 only when zero-as-invalid genuinely matters and an unset value must be caught.


Mutable Globals

Inject instead of mutating package-level vars: type signer struct{ now func() time.Time }


Panic / Must

Must helpers are acceptable at package init only. Return errors for expected failures. Do not add blanket recovery at package or server-goroutine boundaries: an unexpected panic may indicate corrupted shared state.

go
var re = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`)

Recover only an owned internal panic protocol or at a documented containment boundary with state isolation, cleanup, an explicit failure result, and stack diagnostics. Re-panic unexpected values; see [[z-go-errors]]. Recovery must be called directly by a deferred function in the panicking goroutine. A caller's defer cannot recover a child goroutine's panic.


Crypto

math/rand and math/rand/v2 are deterministic from a seed — never safe for keys, tokens, or anything else that must be unpredictable. crypto/rand is the only safe source; rand.Int(rand.Reader, max) covers a bounded integer, rand.Text() (Go 1.24+) a random string.

See [[z-go-security]] for the token-generation and encryption snippets.


Inbound server hardening

An unbounded inbound http.Server can be held open by a slow client (slowloris). The gosec G112 fix is setting all four timeouts plus a header cap at construction:

go
srv := &http.Server{
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       10 * time.Second,
    WriteTimeout:      15 * time.Second,
    IdleTimeout:       60 * time.Second,
    MaxHeaderBytes:    1 << 20,
}

The values are defaults; tune per route (long uploads or SSE need larger WriteTimeout or per-route overrides). Drain-on-shutdown wiring lives in [[z-go-entrypoint]].

Verify

sh
golangci-lint run --enable=forcetypeassert,nilerr,errcheck
go vet ./...

Sources

z tego samego repozytorium

Więcej Skills

Wszystkie Skills
victorzhuk
Społeczność

z-accessibility

Audit and improve web accessibility following WCAG 2.2 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible".

instalacje
1
GitHub Stars
0
Aktualizacja
22 paź
victorzhuk
Społeczność

z-algo-core

DS/algorithm selection judgment for production code, not competitive tricks — hash map for membership/dedup, slice+sort beats a tree until proven, heap for top-k, two-pointer/sliding-window for scans, BFS/DFS/topo-sort for dependency shapes. Triggers on Big-O, hash map vs tree. Does not cover profiling/tuning; see [[z-go-performance]].

instalacje
1
GitHub Stars
0
Aktualizacja
22 paź
victorzhuk
Społeczność

z-bdd

BDD acceptance contracts for humans and agents across UI, API, and CLI products. Use for Gherkin, Given-When-Then, E2E acceptance, or scenario evidence. Define repeatable scenarios with explicit assertions and required outcomes. Test selection belongs to [[z-testing-strategy]]. Go harness wiring belongs to [[z-go-bdd]].

instalacje
1
GitHub Stars
0
Aktualizacja
22 paź
victorzhuk
Społeczność

z-codegraph

Use an existing CodeGraph integration for call graphs, impact analysis, affected tests, or route discovery. Discover installed tools and index freshness; follow host LSP precedence and verify current source. Do not initialize indexes without user authorization.

instalacje
1
GitHub Stars
0
Aktualizacja
22 paź