> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/TecharoHq/Anubis/llms.txt
> Use this file to discover all available pages before exploring further.

# Policy

> Bot detection rules and policy configuration

## 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.

```go theme={null}
type Bot struct {
	Rules     checker.Impl
	Challenge *config.ChallengeRules
	Weight    *config.Weight
	Name      string
	Action    config.Rule
}
```

<ParamField path="Rules" type="checker.Impl">
  Checker implementation that determines if this rule matches a request.

  Can be a single checker or a `checker.List` combining multiple conditions.
</ParamField>

<ParamField path="Challenge" type="*config.ChallengeRules">
  Challenge configuration for CHALLENGE actions

  <Expandable title="ChallengeRules fields">
    <ParamField path="Algorithm" type="string">
      Challenge method: "fast", "preact", or "metarefresh"
    </ParamField>

    <ParamField path="Difficulty" type="int">
      Proof-of-work difficulty (0-64)
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="Weight" type="*config.Weight">
  Weight adjustment for WEIGH actions

  <Expandable title="Weight fields">
    <ParamField path="Adjust" type="int">
      Amount to add to the request weight (can be negative)
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="Name" type="string">
  Unique identifier for this rule (e.g., "googlebot", "known-bad-bot")
</ParamField>

<ParamField path="Action" type="config.Rule">
  Action to take when this rule matches

  **Values:**

  * `ALLOW`: Permit request immediately
  * `DENY`: Block request immediately
  * `CHALLENGE`: Issue a challenge
  * `WEIGH`: Adjust weight and continue evaluation
  * `DEBUG_BENCHMARK`: Show benchmark page
</ParamField>

**Example**

```go theme={null}
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.

```go theme={null}
func (b Bot) Hash() string
```

<ResponseField name="hash" type="string">
  Hex-encoded hash of the rule name and checker configuration
</ResponseField>

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.

```go theme={null}
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
}
```

<ParamField path="Name" type="string">
  Identifier of the matched rule

  **Prefixes:**

  * `bot/`: Direct bot rule match
  * `threshold/`: Threshold rule match
  * `default/`: Fell through to default action
</ParamField>

<ParamField path="Rule" type="config.Rule">
  Action determined by policy evaluation
</ParamField>

<ParamField path="Weight" type="int">
  Cumulative weight from all matched WEIGH rules
</ParamField>

**Example**

```go theme={null}
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.

```go theme={null}
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
}
```

<ParamField path="Store" type="store.Interface">
  Storage backend instance
</ParamField>

<ParamField path="Impressum" type="*config.Impressum">
  Legal/contact information
</ParamField>

<ParamField path="OpenGraph" type="config.OpenGraph">
  OpenGraph tag caching configuration
</ParamField>

<ParamField path="Bots" type="[]Bot">
  Parsed bot detection rules (evaluated in order)
</ParamField>

<ParamField path="Thresholds" type="[]*Threshold">
  Weight-based threshold rules
</ParamField>

<ParamField path="StatusCodes" type="config.StatusCodes">
  HTTP status codes for CHALLENGE and DENY actions
</ParamField>

<ParamField path="DefaultDifficulty" type="int">
  Default proof-of-work difficulty (0-64)
</ParamField>

<ParamField path="DNSBL" type="bool">
  Enable DroneBL blocklist checking
</ParamField>

<ParamField path="Logger" type="*slog.Logger">
  Structured logger instance
</ParamField>

See `lib/policy/policy.go:36-49`

***

## Functions

### ParseConfig

Parses a policy configuration from YAML.

```go theme={null}
func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDifficulty int, logLevel string) (*ParsedConfig, error)
```

<ParamField path="ctx" type="context.Context" required>
  Context (may contain Thoth client for ASN/GeoIP features)
</ParamField>

<ParamField path="fin" type="io.Reader" required>
  Reader containing YAML policy configuration
</ParamField>

<ParamField path="fname" type="string" required>
  Filename for error messages
</ParamField>

<ParamField path="defaultDifficulty" type="int" required>
  Default challenge difficulty (0-64)
</ParamField>

<ParamField path="logLevel" type="string" required>
  Log level: "debug", "info", "warn", or "error"
</ParamField>

<ResponseField name="config" type="*ParsedConfig">
  Parsed and validated configuration
</ResponseField>

<ResponseField name="error" type="error">
  Validation or parse errors
</ResponseField>

**Example**

```go theme={null}
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.

```go theme={null}
func NewRemoteAddrChecker(cidrs []string) (checker.Impl, error)
```

<ParamField path="cidrs" type="[]string" required>
  List of CIDR ranges (e.g., \["192.168.1.0/24", "10.0.0.0/8"])
</ParamField>

<ResponseField name="checker" type="checker.Impl">
  IP address matcher using efficient prefix tree
</ResponseField>

<ResponseField name="error" type="error">
  Error if CIDR parsing fails
</ResponseField>

**Example**

```go theme={null}
// 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.

```go theme={null}
func NewUserAgentChecker(rexStr string) (checker.Impl, error)
```

<ParamField path="rexStr" type="string" required>
  Regular expression pattern
</ParamField>

<ResponseField name="checker" type="checker.Impl">
  User-Agent matcher
</ResponseField>

<ResponseField name="error" type="error">
  Error if regex compilation fails
</ResponseField>

**Example**

```go theme={null}
// 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.

```go theme={null}
func NewPathChecker(rexStr string) (checker.Impl, error)
```

<ParamField path="rexStr" type="string" required>
  Regular expression pattern for path matching
</ParamField>

<ResponseField name="checker" type="checker.Impl">
  Path matcher
</ResponseField>

<ResponseField name="error" type="error">
  Error if regex compilation fails
</ResponseField>

**Example**

```go theme={null}
// 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.

```go theme={null}
func NewHeadersChecker(headermap map[string]string) (checker.Impl, error)
```

<ParamField path="headermap" type="map[string]string" required>
  Map of header names to regex patterns

  **Special value:** Use `".*"` to check for header existence without pattern matching
</ParamField>

<ResponseField name="checker" type="checker.Impl">
  Multi-header matcher (all headers must match)
</ResponseField>

<ResponseField name="error" type="error">
  Error if any regex compilation fails
</ResponseField>

**Example**

```go theme={null}
// 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).

```go theme={null}
func NewCELChecker(expr *config.ExpressionOrList, dns *dns.Dns) (checker.Impl, error)
```

<ParamField path="expr" type="*config.ExpressionOrList" required>
  CEL expression or list of expressions
</ParamField>

<ParamField path="dns" type="*dns.Dns" required>
  DNS resolver for reverse DNS lookups in expressions
</ParamField>

<ResponseField name="checker" type="checker.Impl">
  CEL expression evaluator
</ResponseField>

<ResponseField name="error" type="error">
  Error if expression compilation fails
</ResponseField>

**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**

```go theme={null}
// 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

```go theme={null}
type Impl interface {
	Check(*http.Request) (bool, error)
	Hash() string
}
```

### Check

Evaluates if a request matches this checker's conditions.

```go theme={null}
Check(*http.Request) (bool, error)
```

<ParamField path="request" type="*http.Request" required>
  HTTP request to evaluate
</ParamField>

<ResponseField name="match" type="bool">
  True if the request matches this checker
</ResponseField>

<ResponseField name="error" type="error">
  Error if check evaluation fails
</ResponseField>

***

### Hash

Returns a deterministic hash of the checker configuration.

```go theme={null}
Hash() string
```

<ResponseField name="hash" type="string">
  Hex-encoded hash string
</ResponseField>

***

## Checker.List

Combines multiple checkers with AND semantics.

```go theme={null}
type List []Impl
```

**Behavior:**

* Returns `true` only if ALL checkers return `true`
* Short-circuits on first `false`
* Returns error if any checker errors

**Example**

```go theme={null}
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:**

```yaml theme={null}
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
```

***

## Related Types

* [Anubis Server](/api/anubis) - Policy enforcement
* [Challenge](/api/challenge) - Challenge implementation
* [Store](/api/store) - Policy data persistence
