go-strict — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited go-strict (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Rules extracted from 2 production Go services.
// BAD: no context, impossible to trace
if err != nil {
return err
}
// GOOD: fmt.Errorf with %w for wrapping
store, err := createStore(path)
if err != nil {
return nil, fmt.Errorf("create store: %w", err)
}
// GOOD: multiple levels of context
session, err := validateToken(token)
if err != nil {
return fmt.Errorf("validate session for user %s: %w", userID, err)
}// BAD: error ignored
json.Unmarshal(data, &result)
// GOOD
if err := json.Unmarshal(data, &result); err != nil {
return fmt.Errorf("unmarshal response: %w", err)
}resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
io.Copy(io.Discard, resp.Body) // DRAIN before closing
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}type APIError struct {
Status int `json:"status"`
Message string `json:"message"`
Code string `json:"code,omitempty"`
}
func (e *APIError) Error() string {
return fmt.Sprintf("[%d] %s", e.Status, e.Message)
}
func SendError(c *gin.Context, status int, message string) {
c.JSON(status, APIError{Status: status, Message: message})
}project/
├── cmd/
│ └── server/
│ └── main.go # Entry point only: no logic
├── internal/
│ ├── handlers/ # HTTP handlers (Gin)
│ ├── middleware/ # Auth, rate limit, CORS, validation
│ ├── services/ # Business logic
│ ├── models/ # Data structures
│ ├── config/ # Env var loading
│ └── utils/ # Helpers
├── pkg/
│ ├── redis/ # Reusable Redis client
│ └── sqlite/ # Reusable SQLite wrapper
├── go.mod
└── go.suminternal/ prevents import from outside the module. Use it.
func main() {
cfg := config.Load()
redisClient := redis.NewClient(cfg.RedisURL)
handlers := handlers.NewHandlers(redisClient, cfg)
router := setupRouter(handlers, cfg)
srv := &http.Server{Addr: ":" + cfg.Port, Handler: router}
go func() { srv.ListenAndServe() }()
// Graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(ctx)
}type RateLimiter struct {
mu sync.RWMutex
entries map[string]*entry
}
func (rl *RateLimiter) GetLimiter(key string) *entry {
rl.mu.RLock()
e, ok := rl.entries[key]
rl.mu.RUnlock()
if ok { return e }
rl.mu.Lock()
defer rl.mu.Unlock()
// Double-check after acquiring write lock
if e, ok := rl.entries[key]; ok { return e }
e = &entry{/* ... */}
rl.entries[key] = e
return e
}Pattern: RLock → check → RUnlock → Lock → double-check → create.
func (rl *RateLimiter) evictLoop(interval, maxAge time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
rl.mu.Lock()
now := time.Now()
for key, e := range rl.entries {
if now.Sub(e.lastAccess) > maxAge {
delete(rl.entries, key)
}
}
rl.mu.Unlock()
}
}
// Start with: go rl.evictLoop(5*time.Minute, 10*time.Minute)ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal().Err(err).Msg("server forced to shutdown")
}type Handlers struct {
redis *redis.Client
config *config.Config
sqlite *sqlite.Client
}
func NewHandlers(r *redis.Client, cfg *config.Config, s *sqlite.Client) *Handlers {
return &Handlers{redis: r, config: cfg, sqlite: s}
}router := gin.New()
router.Use(middleware.CORS())
router.Use(middleware.InjectConfig(cfg))
// Group with shared auth
internal := router.Group("/", middleware.InternalAuth(cfg))
internal.GET("/validate-token", h.ValidateToken)
// Per-route rate limiting
router.POST("/login", middleware.RateLimit(10, time.Minute), h.Login)func ValidateLicenseKey() gin.HandlerFunc {
pattern := regexp.MustCompile(`^A-[A-Z0-9]{6}-[A-Z0-9]{8}-[A-Z0-9]{7}$`)
return func(c *gin.Context) {
key := c.Query("license_key")
if key == "" {
key = c.PostForm("license_key")
}
if !pattern.MatchString(key) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid license format"})
c.Abort()
return
}
c.Set("license_key", key)
c.Next()
}
}Compile regex once in closure, not per-request.
// Receivers: single letter matching type
func (rl *RateLimiter) GetLimiter(key string) { ... }
func (m *Manager) CreateInstance(cfg Config) { ... }
func (h *Handlers) ValidateToken(c *gin.Context) { ... }
// Constructors: NewType()
func NewHandlers(...) *Handlers { ... }
func NewRateLimiter(max int) *RateLimiter { ... }
// Getters: no Get prefix (unless disambiguating)
func (u *User) Name() string { return u.name } // Not GetName()
func (u *User) SetName(n string) { u.name = n } // Set prefix OK
// Boolean: use Is/Has/Can
func (s *Session) IsExpired() bool { ... }
func (u *User) HasPermission(p string) bool { ... }
// Packages: lowercase, no underscores
package handlers // Not package http_handlers
package middleware // Not package Middleware
// Constants: PascalCase (exported), camelCase (unexported)
const MaxRetries = 3
const defaultTimeout = 30 * time.Secondgin.H{} in hot paths// BAD: heap allocation per response
c.JSON(200, gin.H{"status": "ok", "data": result})
// GOOD: stack-allocated struct
type Response struct {
Status string `json:"status"`
Data interface{} `json:"data"`
}
c.JSON(200, Response{Status: "ok", Data: result})import "github.com/rs/zerolog"
// Setup
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// Usage
logger.Info().Str("component", "auth").Str("user", userID).Msg("login successful")
logger.Error().Err(err).Str("endpoint", path).Msg("request failed")
logger.Warn().Int("attempts", count).Msg("approaching rate limit")Never log.Printf in production: it's unstructured and hard to query.
func NewManager(logger zerolog.Logger) *Manager {
return &Manager{
log: logger.With().Str("component", "instance-mgr").Logger(),
}
}// BAD
const apiKey = "sk-abc123..."
// GOOD
apiKey := os.Getenv("API_KEY")
if apiKey == "" {
log.Fatal().Msg("API_KEY environment variable required")
}Never trust query params, POST bodies, or headers. Validate format, length, and type.
http.StatusXxx constants// BAD
c.JSON(401, gin.H{"error": "unauthorized"})
// GOOD
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})context.Context is the first argument// BAD: caller cannot cancel or propagate deadlines
func FetchUser(id string) (*User, error) { ... }
// GOOD
func FetchUser(ctx context.Context, id string) (*User, error) {
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
return doRequest(req)
}In handlers, derive from the request: ctx := c.Request.Context(). Never call context.Background() inside a request path.
errors.Is / errors.As// Sentinel error
var ErrNotFound = errors.New("not found")
if err != nil {
if errors.Is(err, ErrNotFound) {
return c.JSON(http.StatusNotFound, ...)
}
var apiErr *APIError
if errors.As(err, &apiErr) {
return c.JSON(apiErr.Status, apiErr)
}
return c.JSON(http.StatusInternalServerError, ...)
}== comparison on wrapped errors fails. Always unwrap with Is/As.
log/slog is the structured logger, not third-party libs// Old: zerolog / zap / logrus
// New (Go 1.21+): stdlib log/slog
import "log/slog"
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
slog.SetDefault(logger)
slog.Info("user signed in", "user_id", uid, "tenant", tenantID)
slog.Error("save failed", "err", err, "request_id", reqID)slog covers JSON output, log levels, attribute groups, and context propagation. New code should not pull in zerolog or zap unless there's a specific reason.
math/rand/v2 for non-cryptographic randomness// BAD: math/rand needs explicit seeding, has known bias issues
import "math/rand"
// GOOD: math/rand/v2 (Go 1.22+) seeds from a CSPRNG by default
import "math/rand/v2"
n := rand.IntN(100) // 0..99
shuffled := rand.Perm(10) // permutationFor tokens, secrets, or anything cryptographic, still use crypto/rand.
os.Root for filesystem-confined access (Go 1.24+)// Confine FS access to a directory, prevents path traversal
root, err := os.OpenRoot("/var/app/uploads")
if err != nil {
return err
}
defer root.Close()
// All operations resolve relative to root, ".." escapes are blocked
f, err := root.Open(userPath)Use os.Root for any filesystem code that touches user-supplied paths.
cloudflare/circl.new accepts an expression and returns a pointer to its initial value. Replaces x := expr; p := &x.simd/archsimd package. Useful for hot loops, still subject to change.range over functions and integers// Go 1.22+: range over int
for i := range 10 { ... }
// Go 1.23+: range over function (custom iterators)
for k, v := range myMap.All() {
fmt.Println(k, v)
}
// Define an iterator
func (m *Map[K, V]) All() iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
for k, v := range m.data {
if !yield(k, v) { return }
}
}
}Replaces hand-rolled Iterator interfaces. Pair with the iter package.
/debug/pprof publicly// BAD: pprof leaks heap, goroutine names, source paths to anyone
import _ "net/http/pprof"
http.ListenAndServe(":8080", nil)
// GOOD: separate port, bound to localhost or behind auth
go http.ListenAndServe("127.0.0.1:6060", nil)Same for expvar. Either gate behind auth middleware or bind to loopback.
fmt.Errorf("context: %w", err)io.Copy(io.Discard, resp.Body))gin.H{} in hot paths (use typed structs)c.Abort() called after error responses in middleware~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.