Skip to content

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)
  • 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)
  • 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”
  1. security/module.yaml
  2. security/authn/
  3. security/jwt/
  4. security/headers/
  5. security/input/
  6. security/abuse/
  7. security/password/
Keep it in security when the work is aboutMove out when the work becomes
JWT issuance, verification, and claims extraction — security/jwtsession revocation, token refresh orchestration, or tenant-session lifecycle
HTTP security headers: Content-Security-Policy, X-Frame-Options, HSTS — security/headerstransport middleware that observes latency, correlates logs, or counts errors
Input sanitization, HTML escaping, or dangerous-character rejection — security/inputbusiness-rule validation with domain-specific invariants
Rate-limit primitives and abuse signal detection — security/abusefull resilience orchestration like circuit breakers or adaptive backoff
Password hashing and timing-safe comparison — security/passworduser management, profile storage, or identity federation
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 refresh
manager, _ := jwt.NewJWTManager(store, cfg)
// Issue a token pair
pair, _ := manager.GenerateTokenPair(ctx,
jwt.IdentityClaims{Subject: userID},
jwt.AuthorizationClaims{Roles: []string{"user"}},
)
// pair.AccessToken — short-lived
// pair.RefreshToken — long-lived
// Verify a token
claims, err := manager.VerifyToken(ctx, tokenString, jwt.TokenTypeAccess)

For the full wiring walkthrough, see the Add JWT Auth guide.

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.Subject
roles := principal.Roles
import (
"github.com/spcent/plumego/security/headers"
secmw "github.com/spcent/plumego/middleware/security"
)
policy := headers.DefaultPolicy() // sensible defaults
// or
policy := 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 = csp
import "github.com/spcent/plumego/security/input"
// Validate common formats
if !input.ValidateEmail(email) { ... }
if !input.ValidateURL(rawURL) { ... }
// Sanitize before rendering or storing
clean := input.BestEffortSanitizeHTML(userContent) // strips dangerous HTML tags
safe := input.StripControlChars(rawInput) // removes control characters
import "github.com/spcent/plumego/security/password"
hash, _ := password.HashPassword(plaintext)
err := password.CheckPassword(hash, plaintext) // timing-safe comparison

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.