Security Primer
Security Primer
Section titled “Security Primer”Open this page after Stable Roots when the change still belongs to the default service path and the real question has narrowed to how the service protects itself: verifying identity, hardening HTTP headers, sanitizing input, or guarding against abuse.
security owns auth identity primitives, JWT helpers, security headers, input safety, and abuse guard primitives. It is marked risk: critical — every change here can affect the entire attack surface.
claims, err := jwt.Verify(r, secretKey)if err != nil { _ = contract.WriteError(w, r, contract.NewErrorBuilder(). Type(contract.TypeUnauthorized). Message("invalid token"). Build()) return}_ = contract.WriteResponse(w, r, http.StatusOK, map[string]string{"sub": claims.Subject}, nil)Start here when
Section titled “Start here when”- you are implementing or verifying a JWT token (
security/jwt) - you are setting or auditing HTTP security headers (
security/headers) - you are sanitizing user-supplied input or escaping values (
security/input) - you are adding rate-limit guards or abuse detection primitives (
security/abuse) - you are hashing or verifying passwords (
security/password) - you are reading or setting the auth identity on a request (
security/authn)
Do not start here when
Section titled “Do not start here when”- the change is about session lifecycle, session revocation, or tenant-session sentinel errors — that belongs in
x/tenant - the change adds circuit breakers or resilience wrappers — that belongs in
x/resilience - the change introduces app bootstrap, service construction, or persistence logic
- the work is about observability policy, structured logging, or request tracing
First files to read in the current repository
Section titled “First files to read in the current repository”security/module.yamlsecurity/authn/security/jwt/security/headers/security/input/security/abuse/security/password/
Concrete ownership examples
Section titled “Concrete ownership examples”Keep it in security when the work is about | Move out when the work becomes |
|---|---|
JWT issuance, verification, and claims extraction — security/jwt | session revocation, token refresh orchestration, or tenant-session lifecycle |
HTTP security headers: Content-Security-Policy, X-Frame-Options, HSTS — security/headers | transport middleware that observes latency, correlates logs, or counts errors |
Input sanitization, HTML escaping, or dangerous-character rejection — security/input | business-rule validation with domain-specific invariants |
Rate-limit primitives and abuse signal detection — security/abuse | full resilience orchestration like circuit breakers or adaptive backoff |
Password hashing and timing-safe comparison — security/password | user management, profile storage, or identity federation |
JWT — issue and verify tokens
Section titled “JWT — issue and verify tokens”import ( "github.com/spcent/plumego/security/jwt" kvstore "github.com/spcent/plumego/store/kv")
store, _ := kvstore.NewKVStore(kvstore.Options{DataDir: "/var/lib/myapp/jwt"})
cfg := jwt.DefaultJWTConfig() // HMAC-SHA256, 15m access, 7d refreshmanager, _ := jwt.NewJWTManager(store, cfg)
// Issue a token pairpair, _ := manager.GenerateTokenPair(ctx, jwt.IdentityClaims{Subject: userID}, jwt.AuthorizationClaims{Roles: []string{"user"}},)// pair.AccessToken — short-lived// pair.RefreshToken — long-lived
// Verify a tokenclaims, err := manager.VerifyToken(ctx, tokenString, jwt.TokenTypeAccess)For the full wiring walkthrough, see the Add JWT Auth guide.
authn — read identity from context
Section titled “authn — read identity from context”import "github.com/spcent/plumego/security/authn"
// In a handler, after auth middleware has run:principal := authn.PrincipalFromContext(r.Context())if principal == nil { // not authenticated — middleware should have blocked this}userID := principal.Subjectroles := principal.Rolesheaders — set security headers
Section titled “headers — set security headers”import ( "github.com/spcent/plumego/security/headers" secmw "github.com/spcent/plumego/middleware/security")
policy := headers.DefaultPolicy() // sensible defaults// orpolicy := headers.StrictPolicy() // stricter CSP for SPAs
mw, err := secmw.Middleware(secmw.Config{Policy: &policy})if err != nil { return err}app.Use(mw)Build a custom CSP:
csp := headers.NewCSPBuilder(). DefaultSrc("'self'"). ScriptSrc("'self'", "https://cdn.example.com"). StyleSrc("'self'", "'unsafe-inline'"). Build()
policy.ContentSecurityPolicy = cspinput — validate and sanitize user data
Section titled “input — validate and sanitize user data”import "github.com/spcent/plumego/security/input"
// Validate common formatsif !input.ValidateEmail(email) { ... }if !input.ValidateURL(rawURL) { ... }
// Sanitize before rendering or storingclean := input.BestEffortSanitizeHTML(userContent) // strips dangerous HTML tagssafe := input.StripControlChars(rawInput) // removes control characterspassword — hash and verify passwords
Section titled “password — hash and verify passwords”import "github.com/spcent/plumego/security/password"
hash, _ := password.HashPassword(plaintext)err := password.CheckPassword(hash, plaintext) // timing-safe comparisonWhy this primer exists
Section titled “Why this primer exists”Security is the one module where partial correctness causes real harm. The review checklist for security differs from all other stable roots: fail-closed is mandatory, timing-safe comparison is mandatory, secrets must never be logged. Knowing which subpackage owns a given security concern prevents the accidental blurring that leads to auth bypasses or header injection.