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

# How It Works

> Understanding the request flow and JWT validation in Anubis

Anubis operates as a reverse proxy that sits between clients and your upstream application, evaluating every request against configured policy rules before allowing access.

## Request Flow

When a request arrives at Anubis, it follows this processing pipeline:

```mermaid theme={null}
flowchart TD
    A[Client Request] --> B[Reverse Proxy]
    B --> C{Check Cookie}
    C -->|Valid JWT| D[Validate Token]
    C -->|No Cookie| E[Evaluate Policy]
    D -->|Valid| F[Proxy to Upstream]
    D -->|Invalid| E
    E --> G{Policy Result}
    G -->|ALLOW| F
    G -->|DENY| H[Return 403]
    G -->|CHALLENGE| I[Issue Challenge]
    G -->|WEIGH| J[Adjust Weight]
    J --> K[Continue Evaluation]
    I --> L[Render Challenge Page]
    L --> M[Client Solves]
    M --> N[Validate Solution]
    N -->|Pass| O[Set JWT Cookie]
    O --> F
    N -->|Fail| E
```

### 1. Initial Request Check

Every request is evaluated in `lib/anubis.go:maybeReverseProxy()`. The system:

1. Extracts the client IP from `X-Real-Ip` header (configured by your reverse proxy)
2. Checks for an existing Anubis JWT cookie
3. If found, validates the cookie's integrity and expiration

<Note>
  Anubis requires the `X-Real-Ip` header to be set by your upstream reverse proxy (nginx, Caddy, etc). Missing this header results in a misconfiguration error.
</Note>

### 2. Cookie Validation

If a cookie exists, Anubis performs JWT validation:

```go theme={null}
// From lib/anubis.go:248
token, err := jwt.ParseWithClaims(
    ckie.Value, 
    jwt.MapClaims{}, 
    s.getTokenKeyfunc(), 
    jwt.WithExpirationRequired(), 
    jwt.WithStrictDecoding()
)
```

The JWT validation checks:

* **Signature integrity**: Using Ed25519 or HMAC-SHA512
* **Expiration**: Tokens expire based on `ANUBIS_COOKIE_EXPIRATION`
* **Policy rule hash**: Ensures the token was issued for the currently matching rule
* **Restriction header** (optional): Binds the token to specific request properties

### 3. Policy Evaluation

When no valid cookie exists, the request goes through policy evaluation in `lib/anubis.go:check()`:

```go theme={null}
// From lib/anubis.go:596
func (s *Server) check(r *http.Request, lg *slog.Logger) (
    policy.CheckResult, 
    *policy.Bot, 
    error
) {
    weight := 0
    
    // Evaluate bot rules sequentially
    for _, b := range s.policy.Bots {
        match, err := b.Rules.Check(r)
        if match {
            switch b.Action {
            case config.RuleDeny, config.RuleAllow, 
                 config.RuleBenchmark, config.RuleChallenge:
                return cr("bot/"+b.Name, b.Action, weight), &b, nil
            case config.RuleWeigh:
                weight += b.Weight.Adjust
            }
        }
    }
    
    // Evaluate thresholds
    for _, t := range s.policy.Thresholds {
        // CEL expression evaluation
        result, _, err := t.Program.ContextEval(
            r.Context(), 
            &policy.ThresholdRequest{Weight: weight}
        )
        if matches {
            return cr("threshold/"+t.Name, t.Action, weight), ...
        }
    }
    
    // Default: allow
    return cr("default/allow", config.RuleAllow, weight), ...
}
```

<CardGroup cols={2}>
  <Card title="Bot Rules" icon="list-check">
    Evaluated first. Each rule can match on IP, user agent, headers, path, ASN, or GeoIP.
  </Card>

  <Card title="Thresholds" icon="gauge">
    Evaluated after bot rules. Use accumulated weight from WEIGH actions to trigger challenges.
  </Card>
</CardGroup>

## Challenge Issuance

When a policy rule returns `CHALLENGE`, Anubis:

1. **Generates a unique challenge ID** using UUID v7
2. **Creates random data** (64 bytes) for proof-of-work
3. **Stores challenge metadata** in the configured store backend:

```go theme={null}
// From lib/anubis.go:119
chall := challenge.Challenge{
    ID:             id.String(),
    Method:         rule.Challenge.Algorithm,  // "fast" or "slow"
    RandomData:     fmt.Sprintf("%x", randomData),
    IssuedAt:       time.Now(),
    Difficulty:     rule.Challenge.Difficulty,  // 0-64
    PolicyRuleHash: rule.Hash(),
    Metadata: map[string]string{
        "User-Agent": r.Header.Get("User-Agent"),
        "X-Real-Ip":  r.Header.Get("X-Real-Ip"),
    },
}
```

4. **Renders the challenge page** with embedded JavaScript solver
5. **Sets a test cookie** to verify cookie support

Challenges expire after 30 minutes and can only be solved once (double-spend protection).

## JWT Token Generation

After successful challenge validation in `lib/anubis.go:PassChallenge()`, a JWT is created:

```go theme={null}
claims := jwt.MapClaims{
    "challenge":  chall.ID,
    "method":     rule.Challenge.Algorithm,
    "policyRule": rule.Hash(),  // Critical for invalidation
    "action":     string(cr.Rule),
    "iat":        time.Now().Unix(),
    "nbf":        time.Now().Add(-1 * time.Minute).Unix(),
    "exp":        time.Now().Add(cookieExpiration).Unix(),
}
```

### Policy Rule Hash

The `policyRule` claim contains a hash of the bot rule configuration:

```go theme={null}
// From lib/policy/bot.go:19
func (b Bot) Hash() string {
    return internal.FastHash(fmt.Sprintf("%s::%s", b.Name, b.Rules.Hash()))
}
```

This ensures that if you update your policy (change difficulty, add new checks, etc.), all existing JWTs become invalid and users must re-solve challenges.

## Proxying to Upstream

Once validated, the request is forwarded to your upstream application with additional headers:

```http theme={null}
X-Anubis-Rule: bot/verified-googlebot
X-Anubis-Action: ALLOW
X-Anubis-Status: PASS
```

These headers allow your application to log or make decisions based on how Anubis evaluated the request.

## Cookie Management

Anubis uses two cookies:

<AccordionGroup>
  <Accordion title="__anubis_jwt (Main JWT)">
    Contains the signed JWT proving the client passed a challenge.

    * Expires based on `ANUBIS_COOKIE_EXPIRATION` (default: 24 hours)
    * Can be scoped to a specific domain with `ANUBIS_COOKIE_DOMAIN`
    * Supports SameSite policies and Partitioned cookies
  </Accordion>

  <Accordion title="__anubis_test (Cookie Test)">
    Temporary cookie set during challenge issuance to verify cookie support.

    * Expires after 30 minutes
    * Deleted after JWT issuance
    * Used to detect clients with cookies disabled
  </Accordion>
</AccordionGroup>

## Performance Characteristics

* **Policy evaluation**: O(n) where n is the number of bot rules
* **JWT validation**: Constant time cryptographic operations
* **Challenge validation**: Constant time hash comparison with `crypto/subtle`
* **Store operations**: Depends on backend (memory: O(1), bbolt: O(log n), Valkey: network latency)

<Tip>
  Bot rules are evaluated in order. Place your most specific or most frequently matched rules first for optimal performance.
</Tip>

## DNSBL Integration

If `dnsbl: true` is configured, Anubis queries DroneBL before policy evaluation:

```go theme={null}
// From lib/anubis.go:333
if s.policy.DNSBL && ip != "" {
    resp, err := dnsbl.Lookup(ip)
    if resp != dnsbl.AllGood {
        // Deny immediately with DNSBL reason
    }
}
```

Results are cached in the store for 24 hours to minimize DNS lookups.

## Next Steps

<CardGroup cols={2}>
  <Card title="Challenges" icon="puzzle-piece" href="/concepts/challenges">
    Learn about challenge types and proof-of-work mechanisms
  </Card>

  <Card title="Policies" icon="shield" href="/concepts/policies">
    Understand bot detection rules and threshold configuration
  </Card>

  <Card title="Architecture" icon="diagram-project" href="/concepts/architecture">
    Explore component interactions and deployment patterns
  </Card>
</CardGroup>
