Skip to main content

Overview

The policy package defines bot detection rules, threshold-based actions, and policy configuration parsing. Policies determine when to allow, deny, or challenge requests based on pattern matching and weighted scoring.

Types

Bot

A bot detection rule with matching conditions and an action.
type Bot struct {
	Rules     checker.Impl
	Challenge *config.ChallengeRules
	Weight    *config.Weight
	Name      string
	Action    config.Rule
}
Rules
checker.Impl
Checker implementation that determines if this rule matches a request.Can be a single checker or a checker.List combining multiple conditions.
Challenge
*config.ChallengeRules
Challenge configuration for CHALLENGE actions
Weight
*config.Weight
Weight adjustment for WEIGH actions
Name
string
Unique identifier for this rule (e.g., “googlebot”, “known-bad-bot”)
Action
config.Rule
Action to take when this rule matchesValues:
  • ALLOW: Permit request immediately
  • DENY: Block request immediately
  • CHALLENGE: Issue a challenge
  • WEIGH: Adjust weight and continue evaluation
  • DEBUG_BENCHMARK: Show benchmark page
Example
import (
	"github.com/TecharoHQ/anubis/lib/policy"
	"github.com/TecharoHQ/anubis/lib/policy/checker"
	"github.com/TecharoHQ/anubis/lib/config"
)

// Allow Googlebot
googlebot := policy.Bot{
	Name:   "googlebot",
	Action: config.RuleAllow,
	Rules:  checker.List{
		policy.NewUserAgentChecker("Googlebot"),
	},
}

// Challenge suspicious traffic
suspicious := policy.Bot{
	Name:   "suspicious-bot",
	Action: config.RuleChallenge,
	Challenge: &config.ChallengeRules{
		Algorithm:  "fast",
		Difficulty: 22,
	},
	Rules: policy.NewUserAgentChecker("curl|wget|python"),
}
See lib/policy/bot.go:11-17

Bot.Hash

Computes a deterministic hash of the bot rule configuration.
func (b Bot) Hash() string
hash
string
Hex-encoded hash of the rule name and checker configuration
Used to detect when policy rules change, invalidating existing JWT cookies. See lib/policy/bot.go:19-21

CheckResult

The result of evaluating policy rules against a request.
type CheckResult struct {
	Name   string       // Rule name that matched (e.g., "bot/googlebot")
	Rule   config.Rule  // Action to take (ALLOW, DENY, CHALLENGE, WEIGH)
	Weight int          // Accumulated weight from WEIGH rules
}
Name
string
Identifier of the matched rulePrefixes:
  • bot/: Direct bot rule match
  • threshold/: Threshold rule match
  • default/: Fell through to default action
Rule
config.Rule
Action determined by policy evaluation
Weight
int
Cumulative weight from all matched WEIGH rules
Example
result := policy.CheckResult{
	Name:   "bot/suspicious-pattern",
	Rule:   config.RuleChallenge,
	Weight: 35,
}
See lib/policy/checkresult.go:9-13

ParsedConfig

Fully parsed and validated policy configuration.
type ParsedConfig struct {
	Store             store.Interface
	orig              *config.Config
	Impressum         *config.Impressum
	OpenGraph         config.OpenGraph
	Bots              []Bot
	Thresholds        []*Threshold
	StatusCodes       config.StatusCodes
	DefaultDifficulty int
	DNSBL             bool
	DnsCache          *dns.DnsCache
	Dns               *dns.Dns
	Logger            *slog.Logger
}
Store
store.Interface
Storage backend instance
Impressum
*config.Impressum
Legal/contact information
OpenGraph
config.OpenGraph
OpenGraph tag caching configuration
Bots
[]Bot
Parsed bot detection rules (evaluated in order)
Thresholds
[]*Threshold
Weight-based threshold rules
StatusCodes
config.StatusCodes
HTTP status codes for CHALLENGE and DENY actions
DefaultDifficulty
int
Default proof-of-work difficulty (0-64)
DNSBL
bool
Enable DroneBL blocklist checking
Logger
*slog.Logger
Structured logger instance
See lib/policy/policy.go:36-49

Functions

ParseConfig

Parses a policy configuration from YAML.
func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDifficulty int, logLevel string) (*ParsedConfig, error)
ctx
context.Context
required
Context (may contain Thoth client for ASN/GeoIP features)
fin
io.Reader
required
Reader containing YAML policy configuration
fname
string
required
Filename for error messages
defaultDifficulty
int
required
Default challenge difficulty (0-64)
logLevel
string
required
Log level: “debug”, “info”, “warn”, or “error”
config
*ParsedConfig
Parsed and validated configuration
error
error
Validation or parse errors
Example
import (
	"context"
	"os"
	"github.com/TecharoHQ/anubis/lib/policy"
)

func main() {
	file, _ := os.Open("policy.yaml")
	defer file.Close()
	
	cfg, err := policy.ParseConfig(
		context.Background(),
		file,
		"policy.yaml",
		20, // default difficulty
		"info",
	)
	if err != nil {
		panic(err)
	}
	
	// Use cfg.Bots, cfg.Thresholds, etc.
}
See lib/policy/policy.go:59-248

Checker Implementations

Policy rules use checker implementations to match requests.

NewRemoteAddrChecker

Creates a checker that matches IP addresses against CIDR ranges.
func NewRemoteAddrChecker(cidrs []string) (checker.Impl, error)
cidrs
[]string
required
List of CIDR ranges (e.g., [“192.168.1.0/24”, “10.0.0.0/8”])
checker
checker.Impl
IP address matcher using efficient prefix tree
error
error
Error if CIDR parsing fails
Example
// Block Tor exit nodes
torChecker, err := policy.NewRemoteAddrChecker([]string{
	"104.244.72.0/21",
	"185.220.100.0/22",
)
if err != nil {
	log.Fatal(err)
}

bot := policy.Bot{
	Name:   "tor-exit-node",
	Action: config.RuleDeny,
	Rules:  torChecker,
}
See lib/policy/checker.go:25-41

NewUserAgentChecker

Creates a checker that matches the User-Agent header against a regex.
func NewUserAgentChecker(rexStr string) (checker.Impl, error)
rexStr
string
required
Regular expression pattern
checker
checker.Impl
User-Agent matcher
error
error
Error if regex compilation fails
Example
// Allow search engine bots
searchBots, _ := policy.NewUserAgentChecker(
	"Googlebot|bingbot|Slurp|DuckDuckBot",
)

bot := policy.Bot{
	Name:   "search-engines",
	Action: config.RuleAllow,
	Rules:  searchBots,
}
See lib/policy/checker.go:72-74

NewPathChecker

Creates a checker that matches the request path against a regex.
func NewPathChecker(rexStr string) (checker.Impl, error)
rexStr
string
required
Regular expression pattern for path matching
checker
checker.Impl
Path matcher
error
error
Error if regex compilation fails
Example
// Allow unrestricted access to /api/health
healthCheck, _ := policy.NewPathChecker("^/api/health$")

bot := policy.Bot{
	Name:   "health-endpoint",
	Action: config.RuleAllow,
	Rules:  healthCheck,
}
See lib/policy/checker.go:101-107

NewHeadersChecker

Creates a checker that matches multiple HTTP headers.
func NewHeadersChecker(headermap map[string]string) (checker.Impl, error)
headermap
map[string]string
required
Map of header names to regex patternsSpecial value: Use ".*" to check for header existence without pattern matching
checker
checker.Impl
Multi-header matcher (all headers must match)
error
error
Error if any regex compilation fails
Example
// Require specific headers for API access
apiHeaders, _ := policy.NewHeadersChecker(map[string]string{
	"X-API-Key":     ".*",              // Must exist
	"Authorization": "^Bearer .+$",     // Must match pattern
	"Content-Type":  "application/json",
)

bot := policy.Bot{
	Name:   "api-client",
	Action: config.RuleAllow,
	Rules:  apiHeaders,
}
See lib/policy/checker.go:148-172

NewCELChecker

Creates a checker using Common Expression Language (CEL).
func NewCELChecker(expr *config.ExpressionOrList, dns *dns.Dns) (checker.Impl, error)
expr
*config.ExpressionOrList
required
CEL expression or list of expressions
dns
*dns.Dns
required
DNS resolver for reverse DNS lookups in expressions
checker
checker.Impl
CEL expression evaluator
error
error
Error if expression compilation fails
Available CEL variables:
  • request.method: HTTP method
  • request.path: Request path
  • request.headers: Header map
  • request.query: Query parameters
  • request.remote_addr: Client IP
  • env: Environment variables
  • dns.reverse(ip): Reverse DNS lookup
Example
// Advanced rule using CEL
expr := &config.ExpressionOrList{
	Expression: `request.path.startsWith("/admin") && 
	             !dns.reverse(request.remote_addr).endsWith(".company.com")`,
}

celChecker, _ := policy.NewCELChecker(expr, dnsResolver)

bot := policy.Bot{
	Name:   "admin-external",
	Action: config.RuleChallenge,
	Rules:  celChecker,
}
See lib/policy/celchecker.go

Checker Interface

type Impl interface {
	Check(*http.Request) (bool, error)
	Hash() string
}

Check

Evaluates if a request matches this checker’s conditions.
Check(*http.Request) (bool, error)
request
*http.Request
required
HTTP request to evaluate
match
bool
True if the request matches this checker
error
error
Error if check evaluation fails

Hash

Returns a deterministic hash of the checker configuration.
Hash() string
hash
string
Hex-encoded hash string

Checker.List

Combines multiple checkers with AND semantics.
type List []Impl
Behavior:
  • Returns true only if ALL checkers return true
  • Short-circuits on first false
  • Returns error if any checker errors
Example
import "github.com/TecharoHQ/anubis/lib/policy/checker"

// Match specific user agent AND path
combined := checker.List{
	policy.NewUserAgentChecker("curl"),
	policy.NewPathChecker("/api"),
}

bot := policy.Bot{
	Name:   "curl-api-access",
	Action: config.RuleDeny,
	Rules:  combined,
}
See lib/policy/checker/checker.go:25-55

YAML Configuration

Example policy.yaml:
store:
  backend: memory

status_codes:
  CHALLENGE: 200
  DENY: 403

bots:
  # Allow known good bots
  - name: googlebot
    user_agent_regex: Googlebot
    action: ALLOW

  # Challenge suspicious patterns
  - name: scraper-pattern
    user_agent_regex: curl|wget|python-requests
    action: CHALLENGE
    challenge:
      algorithm: fast
      difficulty: 22

  # Weigh unusual headers
  - name: missing-accept
    expression: '!has(request.headers.Accept)'
    action: WEIGH
    weight:
      adjust: 20

thresholds:
  - name: high-weight
    expression: 'weight >= 50'
    action: CHALLENGE
    challenge:
      algorithm: fast
      difficulty: 24

  - name: default
    expression: 'true'
    action: ALLOW

Build docs developers (and LLMs) love