Agent skill
security-review
Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, Shopify webhooks, or implementing sensitive features in Go. Provides comprehensive security checklist and Go patterns.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/security-review-tattran22-claude-code-1
SKILL.md
Security Review Skill (Go + Shopify)
This skill ensures all Go code follows security best practices and identifies potential vulnerabilities specific to Go/Fiber/Shopify applications.
When to Activate
- Implementing authentication or authorization
- Handling user input or file uploads
- Creating new API endpoints (Fiber)
- Adding Shopify webhooks or OAuth
- Working with secrets or credentials
- Implementing payment features
- Storing or transmitting sensitive data
- Integrating third-party APIs
- Database operations
Critical Security Checklist
1. Secrets Management
❌ NEVER Do This
const (
APIKey = "sk-proj-xxxxx" // Hardcoded secret
ShopifyKey = "shpat_xxxxx" // In source code
DBPassword = "password123" // NEVER!
)
✅ ALWAYS Do This
func LoadConfig() (*Config, error) {
shopifyKey := os.Getenv("SHOPIFY_API_KEY")
if shopifyKey == "" {
return nil, errors.New("SHOPIFY_API_KEY not configured")
}
return &Config{
ShopifyAPIKey: shopifyKey,
ShopifySecret: mustGetEnv("SHOPIFY_API_SECRET"),
DatabaseURL: mustGetEnv("DATABASE_URL"),
}, nil
}
func mustGetEnv(key string) string {
val := os.Getenv(key)
if val == "" {
log.Fatalf("%s environment variable not set", key)
}
return val
}
Verification Steps
- No hardcoded API keys, tokens, or passwords
- All secrets in environment variables
-
.envin .gitignore - No secrets in git history (
git log -p | grep -i "password\|api_key") - Production secrets in hosting platform
2. SQL Injection Prevention
❌ NEVER Concatenate SQL
// DANGEROUS - SQL Injection vulnerability
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", userEmail)
db.QueryRow(context.Background(), query)
✅ ALWAYS Use Parameterized Queries
// Safe - parameterized query with pgx
query := `SELECT id, email, name FROM users WHERE email = $1`
err := db.QueryRow(ctx, query, userEmail).Scan(&user.ID, &user.Email, &user.Name)
Verification Steps
- All database queries use $1, $2 placeholders
- No string concatenation in SQL
- No fmt.Sprintf for query building
- All queries reviewed for injection vulnerabilities
3. Shopify Webhook HMAC Verification (CRITICAL)
❌ NEVER Skip HMAC Verification
// DANGEROUS - Processing unverified webhook
func HandleWebhook(w http.ResponseWriter, r *http.Request) {
var order Order
json.NewDecoder(r.Body).Decode(&order) // NO VERIFICATION!
processOrder(order) // Could be forged!
}
✅ ALWAYS Verify HMAC First
func VerifyShopifyWebhook(r *http.Request, body []byte, secret string) bool {
hmacHeader := r.Header.Get("X-Shopify-Hmac-Sha256")
if hmacHeader == "" {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expectedMAC := base64.StdEncoding.EncodeToString(mac.Sum(nil))
// Use hmac.Equal for constant-time comparison (prevents timing attacks)
return hmac.Equal([]byte(hmacHeader), []byte(expectedMAC))
}
func HandleWebhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
// Verify HMAC signature
secret := os.Getenv("SHOPIFY_WEBHOOK_SECRET")
if !VerifyShopifyWebhook(r, body, secret) {
log.Warn("Invalid webhook HMAC signature")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Process webhook asynchronously
go processWebhookAsync(body)
w.WriteHeader(http.StatusOK)
}
Verification Steps
- All Shopify webhooks verify HMAC
- OAuth callbacks verify HMAC
- Constant-time comparison used (hmac.Equal)
- GDPR webhooks implemented (all 3 required)
- Webhooks processed asynchronously
4. Input Validation
❌ No Validation
func CreateMarket(w http.ResponseWriter, r *http.Request) {
var req CreateMarketRequest
json.NewDecoder(r.Body).Decode(&req)
// No validation - could be empty, malicious, etc.
market := &Market{Name: req.Name}
db.Create(market)
}
✅ Validate with go-playground/validator
import "github.com/go-playground/validator/v10"
type CreateMarketRequest struct {
Name string `json:"name" validate:"required,min=3,max=100"`
Description string `json:"description" validate:"max=500"`
Category string `json:"category" validate:"required,oneof=politics sports finance"`
}
var validate = validator.New()
func CreateMarket(w http.ResponseWriter, r *http.Request) {
var req CreateMarketRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, http.StatusBadRequest, "Invalid JSON")
return
}
// Validate input
if err := validate.Struct(req); err != nil {
respondError(w, http.StatusBadRequest, fmt.Sprintf("Validation failed: %v", err))
return
}
// Now safe to use validated input
market := &Market{
Name: req.Name,
Description: req.Description,
Category: req.Category,
}
// ...
}
Verification Steps
- All user inputs validated with go-playground/validator
- File uploads restricted (size, type, extension)
- No direct use of user input in queries
- Whitelist validation (not blacklist)
- Error messages don't leak sensitive info
5. Authentication & Authorization
JWT Token Validation
import (
"github.com/gofiber/fiber/v3"
"github.com/golang-jwt/jwt/v5"
)
func AuthMiddleware(jwtSecret string) fiber.Handler {
return func(c fiber.Ctx) error {
authHeader := c.Get("Authorization")
if authHeader == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "missing authorization header",
})
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if tokenString == authHeader {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "invalid authorization format",
})
}
// Parse and validate token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validate signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(jwtSecret), nil
})
if err != nil || !token.Valid {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "invalid token",
})
}
// Extract claims and store in Locals
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "invalid claims",
})
}
c.Locals("userID", claims["sub"])
return c.Next()
}
}
Authorization Checks
func DeleteMarket(c fiber.Ctx) error {
userID := fiber.Locals[string](c, "userID")
marketID := c.Params("id")
// Verify ownership
market, err := repo.FindByID(c.Context(), marketID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": "not found",
})
}
if market.OwnerID != userID {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "forbidden",
})
}
// Proceed with deletion
err = repo.Delete(c.Context(), marketID)
// ...
return nil
}
Verification Steps
- JWT tokens validated with golang-jwt/jwt
- Authorization checks before sensitive operations
- HTTPS enforced (TLS 1.3+)
- Session management secure
- Passwords hashed with bcrypt (cost >= 12)
6. Password Hashing
❌ NEVER Store Plaintext or Weak Hashing
// WRONG - plaintext
user.Password = password
// WRONG - weak hashing
hash := sha256.Sum256([]byte(password))
✅ Use bcrypt
import "golang.org/x/crypto/bcrypt"
func CreateUser(email, password string) error {
// Hash password with bcrypt (cost 12-14 recommended)
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
user := &User{
Email: email,
PasswordHash: string(hashedPassword),
}
return db.Create(context.Background(), user)
}
func VerifyPassword(hashedPassword, password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}
7. Race Condition Prevention
Run Race Detector
go test -race ./...
Use Mutexes for Shared State
type SafeCounter struct {
mu sync.RWMutex
count map[string]int
}
func (c *SafeCounter) Increment(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.count[key]++
}
func (c *SafeCounter) Value(key string) int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count[key]
}
8. Error Handling (Don't Leak Info)
❌ Leaking Internal Details
// WRONG - exposes internal error details to user
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
✅ Generic Error Messages
// CORRECT - log detailed error, return generic message
log.Error("Database connection failed", "error", err, "table", "markets")
http.Error(w, "Internal server error", http.StatusInternalServerError)
9. Logging Security
❌ Logging Sensitive Data
// WRONG - logs passwords, tokens
log.Printf("User login: email=%s password=%s", email, password)
log.Printf("Request: %+v", r.Header) // Contains auth tokens!
✅ Sanitized Logging
import "log/slog"
// CORRECT - structured logging without sensitive data
slog.Info("User login attempt",
"email", maskEmail(email),
"ip", r.RemoteAddr,
)
// Never log: passwords, API keys, tokens, credit cards, SSNs
Security Scanning Commands
# Check for security vulnerabilities
gosec ./...
# Check for vulnerable dependencies
go list -json -m all | nancy sleuth
# Comprehensive vulnerability scan
trivy fs --scanners vuln,secret,misconfig .
# Run security-focused linters
golangci-lint run --enable=gosec,gocritic,bodyclose,errcheck
# Check for secrets in files
grep -r "api[_-]?key\|password\|secret\|token" --include="*.go" .
# Check for Shopify secrets
grep -r "shpat_\|shpca_\|shpss_" --include="*.go" .
# Test for race conditions
go test -race ./...
Pre-Deployment Security Checklist
Before deploying to production:
Code Security
- gosec ./... passes with no critical issues
- go test -race ./... passes
- All secrets in environment variables
- No hardcoded credentials in code or git history
- Input validation on all endpoints
- Error messages don't leak sensitive info
Shopify Security
- Webhook HMAC verification implemented
- OAuth HMAC verification implemented
- All 3 GDPR webhooks implemented
- Shop domain validation
- Session tokens validated
- No Shopify secrets in frontend code
Database Security
- All queries parameterized ($1, $2, etc.)
- Database connection uses TLS
- Connection strings in environment variables
- No SQL injection vulnerabilities
Authentication/Authorization
- JWT tokens validated properly
- Passwords hashed with bcrypt (cost >= 12)
- Authorization checks on all protected routes
- HTTPS enforced
Dependencies
- nancy scan clean (no vulnerable dependencies)
- go.mod dependencies up to date
- No known CVEs in dependencies
Common Vulnerabilities (OWASP Top 10 for Go)
- SQL Injection - Use parameterized queries ($1, $2)
- Command Injection - Avoid exec.Command with user input
- Hardcoded Secrets - All secrets in environment variables
- Missing Error Checks - Always check errors (never
_) - Race Conditions - Use mutexes, test with -race
- Weak Crypto - bcrypt for passwords, not MD5/SHA256
- HMAC Verification - Verify Shopify webhooks/OAuth
- Input Validation - Use go-playground/validator
- JWT Validation - Validate signature and expiry
- Logging Sensitive Data - Sanitize logs (slog/zap)
Security Resources
- Go Security Guide: https://go.dev/doc/security/
- OWASP Go: https://owasp.org/www-project-go-secure-coding-practices-guide/
- Shopify Security: https://shopify.dev/docs/apps/launch/security
- gosec: https://github.com/securego/gosec
- CWE Top 25: https://cwe.mitre.org/top25/
Remember: Security is not optional, especially for Shopify apps handling merchant data. One vulnerability can compromise the entire app and result in app suspension.
For comprehensive security review, use the security-reviewer agent which provides detailed vulnerability analysis and remediation.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?