guardgo

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 4, 2026 License: MIT Imports: 31 Imported by: 0

README

GuardGo

GuardGo is a Redis-backed API protection library for Go focused on high-load APIs.

Core ideas:

  • atomic critical path via Redis Lua
  • in-memory fast path (LRU + Bloom)
  • reputation scoring pipeline (rules + evaluators + behavioral entropy)
  • dynamic penalty and blacklist backoff
  • framework adapters and operational tooling (agent, cli)

Install

go get guardgo
go get github.com/redis/go-redis/v9

Quick Start (net/http)

package main

import (
	"net/http"
	"time"

	"guardgo"
	"github.com/redis/go-redis/v9"
)

func main() {
	rdb := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})

	cfg := guardgo.NewConfig(rdb, 200, 1*time.Second)
	cfg.FailOpen = true
	cfg.Bloom.Enabled = true
	cfg.Reputation.Enabled = true
	cfg.Reputation.WarningLevel = 60
	cfg.Reputation.Threshold = 100

	engine := guardgo.New(cfg)
	defer engine.Close()

	mux := http.NewServeMux()
	mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	_ = http.ListenAndServe(":8080", engine.Middleware(mux))
}

Zero-Config Mode

guard := guardgo.New(guardgo.DefaultConfig())
defer guard.Close()

Defaults:

  • Redis: 127.0.0.1:6379
  • limit: 1000 req / 1s

Integration shortcuts:

engine := guardgo.NewDefault()
engine2 := guardgo.NewWithRedisAddr("10.0.0.12:6379")
cfg := guardgo.NewConfigFromRedisAddr("127.0.0.1:6379", 200, time.Second)

Framework Adapters

Unified wrappers:

router.Use(guardgo.Gin(engine))
e.Use(guardgo.Echo(engine))
app.Use(guardgo.Fiber(engine))

Legacy adapters:

  • guardgo/adapters/gin
  • guardgo/adapters/echo
  • guardgo/adapters/fiber

Security Model

GuardGo evaluates traffic using a score pipeline:

  • static Rule checks
  • score-based Evaluator checks
  • DFA signature matching from YAML/JSON rulesets
  • behavioral entropy signals (query/header diversity)
  • fingerprinting (IP + User-Agent + Accept-Language)

Decisions:

  • score < WarningLevel: normal flow
  • score >= WarningLevel: penalty mode
  • score >= Threshold: blacklist

Dynamic Backoff

Reputation Lua applies escalating bans:

  • attack #1: BanLevel1 (default 1m)
  • attack #2: BanLevel2 (default 10m)
  • attack #3+: BanLevel3 (default 24h)

Hot Reload (Zero-Downtime)

If RulesetFile or DynamicRules is configured, GuardGo can hot-reload rules on POSIX SIGHUP:

kill -HUP <pid>

No process restart required.

Presets

Built-in baseline signatures:

rules.ApplyDefaultSecurityPresets(&cfg) // in-memory, no rules.yaml needed

Includes common SQLi/probing/traversal/scanner patterns.

Observability

  • OpenTelemetry spans (guardgo.process)
  • generic StatsCollector interface
  • Prometheus sidecar: cmd/guardgo-agent
  • live terminal dashboard: cmd/guardgo-cli

Run tools:

go run ./cmd/guardgo-agent --redis-addr 127.0.0.1:6379 --prefix guardgo --listen :9090
go run ./cmd/guardgo-cli --redis-addr 127.0.0.1:6379 --prefix guardgo --ruleset ./rules.yaml

Real Benchmark Results

Measured on:

  • CPU: 12th Gen Intel(R) Core(TM) i5-12400
  • OS: windows/amd64
  • Date: 2026-03-08
Case Latency Throughput (approx) Allocations
Clean Request (Bloom) 11.8ns ~84M ops/s 0 B/op, 0 allocs/op
DFA Match (100 rules) ~454ns ~2.2M ops/s 24 B/op, 1 alloc/op
Redis Fallback (miniredis parallel) 205-225µs ~4.4K ops/s/core-equivalent ~206KB/op, 836 allocs/op

Commands used:

go test ./pkg/bloom -run ^$ -bench BenchmarkBloomCleanRequest -benchmem -benchtime=5s -count=5
go test ./pkg/dfa -run ^$ -bench BenchmarkDFA100RulesMatch -benchmem -benchtime=5s -count=5
go test ./tests/load -run ^$ -bench BenchmarkEngineCheckParallel -benchmem -benchtime=10s -count=5

For real network Redis fallback (recommended for production profiling), set REDIS_ADDR and run:

go test ./tests/load -run ^$ -bench BenchmarkRedisFallbackRealRedis -benchmem -benchtime=10s -count=5

Testing

go test ./...
go test -race ./tests/integration -count=1
go test ./tests/load -bench . -benchmem -run ^$
go test -tags chaos ./tests/chaos -v -count=1

Repository Docs

  • Architecture: docs/ARCHITECTURE.md
  • Project layout: docs/PROJECT_LAYOUT.md
  • API methods: docs/API.md
  • Examples: examples/

Documentation

Overview

Package guardgo provides a Redis-backed API protection middleware with a single atomic Redis roundtrip (Lua) on the critical path and optional asynchronous pattern analysis (rules) off the critical path.

Index

Constants

This section is empty.

Variables

View Source
var ErrRulesetPathEmpty = errors.New("github.com/Zhaba1337228/GuardGo: ruleset path is empty")

Functions

func ApplyRateLimitHeaders

func ApplyRateLimitHeaders(h http.Header, d Decision)

ApplyRateLimitHeaders writes standard rate-limit headers for client visibility.

func Echo

func Echo(engine *Engine) echo.MiddlewareFunc

Echo returns an Echo middleware based on the provided Engine.

func Fiber

func Fiber(engine *Engine) fiber.Handler

Fiber returns a Fiber middleware based on the provided Engine.

func Gin

func Gin(engine *Engine) gin.HandlerFunc

Gin returns a Gin middleware based on the provided Engine.

func Guard

func Guard(cfg MiddlewareConfig) (func(http.Handler) http.Handler, error)

Guard returns net/http middleware from config.

func MustGuard

func MustGuard(cfg MiddlewareConfig) func(http.Handler) http.Handler

MustGuard returns net/http middleware from config or panics.

Types

type AdaptiveBanConfig

type AdaptiveBanConfig struct {
	Enabled bool

	// ThreatKeyWindow is a rolling window for counting "threatful" requests per IP.
	// Default: 10m.
	ThreatKeyWindow time.Duration

	// ThreatCountThreshold triggers a ban when the per-IP threat count within
	// ThreatKeyWindow reaches this value. Default: 100.
	ThreatCountThreshold int64

	// BanTTL is the ban duration once ThreatCountThreshold is reached.
	// Default: DefaultBanTTL.
	BanTTL time.Duration

	// QueueSize is the buffered channel size for background analysis. Default: 8192.
	QueueSize int

	// Workers is the number of background workers. Default: 2 * GOMAXPROCS.
	Workers int

	// CaptureHeaders lists headers to snapshot for rules. Default: []{"User-Agent"}.
	CaptureHeaders []string
}

type BehavioralConfig

type BehavioralConfig struct {
	Enabled bool

	// RiskWindow is TTL for per-IP risk score.
	RiskWindow time.Duration

	// ScoreThreshold pushes IP into penalty box.
	ScoreThreshold int64

	// PenaltyTTL is duration of penalty box mode.
	PenaltyTTL time.Duration

	// PenaltyBanTTL is ban duration after failed penalty checks.
	PenaltyBanTTL time.Duration

	// RequireUserAgent enforces User-Agent header in penalty mode.
	RequireUserAgent bool

	// RequireReferer enforces Referer header in penalty mode.
	RequireReferer bool

	// QueryDiversityThreshold adds risk when query diversity spikes.
	QueryDiversityThreshold int

	// HeaderDiversityThreshold adds risk when header diversity spikes.
	HeaderDiversityThreshold int

	// DiversityWeight contributes to risk when thresholds exceeded.
	DiversityWeight int64
}

type BloomConfig

type BloomConfig struct {
	Enabled bool

	// Bits is bloom filter size in bits. Default: 1<<20.
	Bits uint64

	// Hashes is number of hash functions. Default: 6.
	Hashes uint8
}

type CompiledRuleset

type CompiledRuleset struct {
	Any     *dfa.Matcher
	Query   *dfa.Matcher
	Headers *dfa.Matcher
	Path    *dfa.Matcher
}

func CompileRuleset

func CompileRuleset(rules []SignatureRule) *CompiledRuleset

func LoadRulesetFile

func LoadRulesetFile(path string) (*CompiledRuleset, error)

func (*CompiledRuleset) Stats

func (r *CompiledRuleset) Stats() RulesetStats

type Decision

type Decision struct {
	Allowed    bool
	Reason     Reason
	Counter    int64
	Limit      int
	Remaining  int
	ResetAt    time.Time
	StatusCode int
}

Decision is a normalized engine output used by all middleware adapters.

func (Decision) Headers

func (d Decision) Headers() map[string]string

func (Decision) StatusCodeOr

func (d Decision) StatusCodeOr(defaultCode int) int

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine is the request-decision core. It is safe for concurrent use after NewEngine returns and until Close is called.

func GuardHTTP

func GuardHTTP(cfg MiddlewareConfig) (func(http.Handler) http.Handler, *Engine, error)

GuardHTTP is a convenience helper to construct an Engine and return a net/http middleware.

func MustNew

func MustNew(cfg MiddlewareConfig) *Engine

MustNew returns a new Engine instance or panics.

func New

func New(cfg MiddlewareConfig) *Engine

New returns a new Engine instance and panics if construction fails. This enables 1-line DX: guard := guardgo.New(guardgo.DefaultConfig()).

func NewDefault

func NewDefault() *Engine

NewDefault creates Engine with DefaultConfig.

func NewEngine

func NewEngine(cfg MiddlewareConfig) (*Engine, error)

NewEngine constructs an Engine, applying defaults and starting background workers if any reactive subsystem (rules, reputation, behavior, ruleset reload) is configured.

func NewWithError

func NewWithError(cfg MiddlewareConfig) (*Engine, error)

NewWithError returns a new Engine instance with explicit error handling.

func NewWithRedisAddr

func NewWithRedisAddr(redisAddr string) *Engine

NewWithRedisAddr creates Engine using only Redis address and defaults.

func (*Engine) Check

func (e *Engine) Check(ctx context.Context, r *http.Request) (allowed bool, reason Reason, counter int64)

Check performs critical-path checks:

  • local blacklist cache
  • Redis Lua (blacklist + rate-limit counter)

It returns allowed, reason code and current counter value.

func (*Engine) Close

func (e *Engine) Close()

Close stops background workers and releases attached resources. Safe to call multiple times.

func (*Engine) Config

func (e *Engine) Config() MiddlewareConfig

Config returns effective engine configuration with defaults applied.

func (*Engine) Middleware

func (e *Engine) Middleware(next http.Handler) http.Handler

Middleware returns a net/http middleware.

func (*Engine) Process

func (e *Engine) Process(ctx context.Context, r *http.Request) Decision

Process runs the request through the guard engine and returns a full decision payload that all middleware adapters can consume uniformly.

type Evaluator

type Evaluator interface {
	CalculateScore(r *http.Request) float64
}

Evaluator calculates request risk score. Returned value is added to reputation score for current actor.

type MatchTarget

type MatchTarget string
const (
	MatchQuery   MatchTarget = "query"
	MatchHeaders MatchTarget = "headers"
	MatchPath    MatchTarget = "path"
	MatchAny     MatchTarget = "any"
)

type Metrics

type Metrics interface {
	IncAllowed()
	IncRateLimited()
	IncBlacklisted()
	IncRedisErrors()
	ObserveLuaLatency(d time.Duration)
}

type MiddlewareConfig

type MiddlewareConfig struct {
	// Redis is required unless you only use the local blacklist cache.
	Redis *redis.Client

	// MaxRequests is the maximum number of requests allowed per Window for a key.
	MaxRequests int
	Window      time.Duration

	// Rules are evaluated asynchronously (best-effort) to support adaptive bans.
	// Rules MUST NOT read or depend on r.Body.
	Rules []Rule

	// Evaluators calculate additive reputation score.
	Evaluators []Evaluator

	// FailOpen allows traffic when Redis is unavailable or returns an error.
	FailOpen bool

	// Prefix is used to build Redis keys. Default: "github.com/Zhaba1337228/GuardGo".
	Prefix string

	// CacheSize is the local blacklist LRU size. Default: 4096.
	CacheSize int

	// DefaultBanTTL is used by adaptive banning when no rule-specific TTL exists.
	// Default: 1h.
	DefaultBanTTL time.Duration

	// AdaptiveBan enables background adaptive banning.
	AdaptiveBan AdaptiveBanConfig

	// KeyFunc defines rate-limit key. Default: IP.
	KeyFunc func(r *http.Request) string

	// IPFunc extracts the client IP for ban keys and default KeyFunc.
	// Default: RemoteAddr host part.
	IPFunc func(r *http.Request) string

	// Metrics is optional. If nil, metrics are no-ops.
	Metrics Metrics

	// Stats is optional generic telemetry collector.
	// If nil, telemetry calls are dropped.
	Stats StatsCollector

	// OnError is optional error callback (Redis/Lua/background).
	OnError func(err error)

	// DenyStatusCode is used by middleware adapters when request is blocked.
	// Default: 429.
	DenyStatusCode int

	// Bloom enables optional in-memory bloom filter short-circuit.
	// If enabled and IP is definitely not blacklisted (negative bloom test),
	// request is allowed without Redis roundtrip.
	Bloom BloomConfig

	// Tracing enables OpenTelemetry spans for request processing.
	Tracing TracingConfig

	// SelfHealing dynamically adjusts effective request limits based on behavior.
	SelfHealing SelfHealingConfig

	// RulesetFile points to JSON/YAML signatures loaded at startup.
	RulesetFile string

	// DynamicRules allows hot-swapping signatures without recreating Engine.
	DynamicRules *RulesetManager

	// CompiledRules allows direct in-memory ruleset wiring (no file required).
	CompiledRules *CompiledRuleset

	// AutoReloadOnSIGHUP enables ruleset hot reload on POSIX SIGHUP signal.
	// Applicable when RulesetFile or DynamicRules is configured.
	AutoReloadOnSIGHUP bool

	// Behavioral enables risk scoring and penalty-box flow.
	Behavioral BehavioralConfig

	// Reputation controls score pipeline and dynamic backoff bans.
	Reputation ReputationConfig
}

func DefaultConfig

func DefaultConfig() MiddlewareConfig

DefaultConfig returns a ready-to-use configuration with sane defaults.

func NewConfig

func NewConfig(redisClient *redis.Client, maxRequests int, window time.Duration) MiddlewareConfig

NewConfig returns a minimal ready-to-use configuration.

func NewConfigFromRedisAddr

func NewConfigFromRedisAddr(redisAddr string, maxRequests int, window time.Duration) MiddlewareConfig

NewConfigFromRedisAddr creates minimal config from Redis address.

type NopMetrics

type NopMetrics struct{}

func (NopMetrics) IncAllowed

func (NopMetrics) IncAllowed()

func (NopMetrics) IncBlacklisted

func (NopMetrics) IncBlacklisted()

func (NopMetrics) IncRateLimited

func (NopMetrics) IncRateLimited()

func (NopMetrics) IncRedisErrors

func (NopMetrics) IncRedisErrors()

func (NopMetrics) ObserveLuaLatency

func (NopMetrics) ObserveLuaLatency(d time.Duration)

type NopStatsCollector

type NopStatsCollector struct{}

func (NopStatsCollector) Record

func (NopStatsCollector) Record(name string, value float64, labels map[string]string)

type Reason

type Reason int

Reason describes the decision made by Engine.Check / Engine.Process.

const (
	ReasonAllowed Reason = iota
	ReasonRateLimited
	ReasonBlacklisted
	ReasonRedisError
	ReasonFailOpen
	ReasonPenaltyForbidden
)

func (Reason) String

func (r Reason) String() string

type ReputationConfig

type ReputationConfig struct {
	Enabled bool

	// WarningLevel moves actor into penalty box.
	WarningLevel float64

	// Threshold triggers blacklist via reputation Lua script.
	Threshold float64

	// ScoreWindow controls reputation hash TTL.
	ScoreWindow time.Duration

	// PenaltyTTL is strict-mode duration.
	PenaltyTTL time.Duration

	// BackoffWindow controls how long attack attempt counter lives.
	BackoffWindow time.Duration

	// BanLevel1 is ban duration for first serious attack.
	BanLevel1 time.Duration

	// BanLevel2 is ban duration for second serious attack.
	BanLevel2 time.Duration

	// BanLevel3 is ban duration for third and next attack.
	BanLevel3 time.Duration
}

type Rule

type Rule interface {
	Evaluate(r *http.Request) int
}

Rule evaluates a request and returns an integer threat weight. Higher values represent higher confidence/more dangerous patterns.

Rules MUST NOT read or depend on r.Body. They may read URL, Method, Host and headers.

type RulesetManager

type RulesetManager struct {
	// contains filtered or unexported fields
}

func NewRulesetManager

func NewRulesetManager(path string) (*RulesetManager, error)

func (*RulesetManager) Current

func (m *RulesetManager) Current() *CompiledRuleset

func (*RulesetManager) Reload

func (m *RulesetManager) Reload() error

func (*RulesetManager) ReloadOnSignal

func (m *RulesetManager) ReloadOnSignal(ctx context.Context, ch <-chan os.Signal, onError func(error))

type RulesetStats

type RulesetStats struct {
	AnyRules    int
	QueryRules  int
	HeaderRules int
	PathRules   int
	AnyNodes    int
	QueryNodes  int
	HeaderNodes int
	PathNodes   int
	TotalRules  int
	TotalNodes  int
}

type SelfHealingConfig

type SelfHealingConfig struct {
	Enabled bool

	// Window is a rolling analysis period.
	Window time.Duration

	// MinSamples controls the minimum traffic volume before adaptation.
	MinSamples int

	// CleanTrafficThreshold is the minimum clean ratio to activate spike detection.
	CleanTrafficThreshold float64

	// SpikeFactor triggers stricter limits when a single IP exceeds max*SpikeFactor.
	SpikeFactor float64

	// MaxSensitivity is upper bound of adaptive multiplier.
	MaxSensitivity float64

	// DecayStep controls how quickly sensitivity returns to normal per window.
	DecayStep float64
}

type SignatureRule

type SignatureRule struct {
	Name           string      `json:"name" yaml:"name"`
	Match          MatchTarget `json:"match" yaml:"match"`
	Pattern        string      `json:"pattern" yaml:"pattern"`
	Weight         int64       `json:"weight" yaml:"weight"`
	CountThreshold int         `json:"count_threshold" yaml:"count_threshold"`
}

type StatsCollector

type StatsCollector interface {
	Record(name string, value float64, labels map[string]string)
}

StatsCollector is a vendor-neutral contract for custom telemetry export. Implement this interface to forward metrics to Prometheus, Datadog, OTEL, etc.

type TracingConfig

type TracingConfig struct {
	Enabled bool
	Tracer  trace.Tracer
}

Directories

Path Synopsis
adapters
gin
cmd
guardgo-agent command
guardgo-cli command
examples
gin-basic command
nethttp-basic command
presets-basic command
internal
pkg
dfa

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL